diff --git a/packages/domain/src/domain/sap/calculator.py b/packages/domain/src/domain/sap/calculator.py index 50e3be02..4218c120 100644 --- a/packages/domain/src/domain/sap/calculator.py +++ b/packages/domain/src/domain/sap/calculator.py @@ -375,6 +375,7 @@ def calculate_sap_from_inputs(inputs: CalculatorInputs) -> SapResult: "hot_water_cost_gbp": hot_water_cost, "pumps_fans_cost_gbp": pumps_fans_cost, "lighting_cost_gbp": lighting_cost, + "pv_export_credit_gbp": pv_credit, "ecf": ecf, "deflator": ENERGY_COST_DEFLATOR, "delivered_fuel_kwh_per_yr": delivered_fuel_kwh, diff --git a/packages/domain/src/domain/sap/tests/test_calculator.py b/packages/domain/src/domain/sap/tests/test_calculator.py index 967fa361..56a4bdaa 100644 --- a/packages/domain/src/domain/sap/tests/test_calculator.py +++ b/packages/domain/src/domain/sap/tests/test_calculator.py @@ -348,6 +348,39 @@ def test_calculate_exposes_primary_energy_breakdown() -> None: assert result.primary_energy_kwh_per_yr == pytest.approx(expected_total, rel=1e-9) +def test_calculate_exposes_pv_export_credit() -> None: + # Arrange — P5 trace mode: total_fuel_cost_gbp = sum(per-end-use + # costs) − pv_export_credit, floored at 0. The PV credit is the only + # missing term linking the P5.6 per-end-use cost breakdown to the + # top-level total. Set non-zero PV values so the credit is meaningful. + inputs = replace( + _baseline_inputs(), + pv_generation_kwh_per_yr=1000.0, + pv_export_credit_gbp_per_kwh=0.05, + ) + + # Act + result = calculate_sap_from_inputs(inputs) + + # Assert + expected_credit = ( + inputs.pv_generation_kwh_per_yr * inputs.pv_export_credit_gbp_per_kwh + ) + assert result.intermediate["pv_export_credit_gbp"] == pytest.approx( + expected_credit, rel=1e-12 + ) + gross_cost = ( + result.intermediate["main_heating_cost_gbp"] + + result.intermediate["secondary_heating_cost_gbp"] + + result.intermediate["hot_water_cost_gbp"] + + result.intermediate["pumps_fans_cost_gbp"] + + result.intermediate["lighting_cost_gbp"] + ) + assert max(0.0, gross_cost - expected_credit) == pytest.approx( + result.total_fuel_cost_gbp, rel=1e-9 + ) + + def test_calculate_exposes_rating_equation_spec_constants() -> None: # Arrange — P5 trace mode: the §13 ECF denominator carries a 45 m² # floor-area offset (Table 12) and the SAP rating splits between a