diff --git a/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py b/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py index 8af56e93..83bd83a2 100644 --- a/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py +++ b/packages/domain/src/domain/sap/rdsap/tests/test_golden_fixtures.py @@ -39,9 +39,8 @@ from datatypes.epc.domain.mapper import EpcPropertyDataMapper from domain.sap.calculator import calculate_sap_from_inputs from domain.sap.rdsap.cert_to_inputs import ( SAP_10_2_SPEC_PRICES, + cert_to_demand_inputs, cert_to_inputs, - environmental_section_from_cert, - local_climate_for_cert, ) _FIXTURES_DIR = Path(__file__).parent / "fixtures" / "golden" @@ -76,7 +75,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( cert_number="0240-0200-5706-2365-8010", actual_sap=73, expected_sap_resid=-12, - expected_pe_resid_kwh_per_m2=+0.74, + expected_pe_resid_kwh_per_m2=+5.5809, expected_co2_resid_tonnes_per_yr=+0.3436, notes=( "Detached house, TFA 202, age J, oil boiler, Table 4b code 130. " @@ -96,7 +95,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( cert_number="0300-2747-7640-2526-2135", actual_sap=78, expected_sap_resid=-9, - expected_pe_resid_kwh_per_m2=+17.3417, + expected_pe_resid_kwh_per_m2=+4.4546, expected_co2_resid_tonnes_per_yr=-0.5359, notes=( "Large semi-detached, TFA 526, age D, gas boiler PCDB-listed " @@ -108,7 +107,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( cert_number="0390-2954-3640-2196-4175", actual_sap=60, expected_sap_resid=-7, - expected_pe_resid_kwh_per_m2=-27.6371, + expected_pe_resid_kwh_per_m2=-26.6760, expected_co2_resid_tonnes_per_yr=-2.5816, notes="Large detached, TFA 360, age F, oil PCDB-listed. Cert lodges has_draught_lobby=true.", ), @@ -116,7 +115,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( cert_number="6035-7729-2309-0879-2296", actual_sap=70, expected_sap_resid=-6, - expected_pe_resid_kwh_per_m2=+34.62, + expected_pe_resid_kwh_per_m2=+45.0454, expected_co2_resid_tonnes_per_yr=+1.0245, notes="Mid-terrace, TFA 128, age A, gas combi Table 4b code 104.", ), @@ -124,7 +123,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( cert_number="7536-3827-0600-0600-0276", actual_sap=68, expected_sap_resid=+3, - expected_pe_resid_kwh_per_m2=-27.45, + expected_pe_resid_kwh_per_m2=-17.9833, expected_co2_resid_tonnes_per_yr=-0.4781, notes="Detached + 2 extensions, TFA 152, age D, gas PCDB.", ), @@ -132,7 +131,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( cert_number="8135-1728-8500-0511-3296", actual_sap=72, expected_sap_resid=+1, - expected_pe_resid_kwh_per_m2=-14.3709, + expected_pe_resid_kwh_per_m2=-9.5017, expected_co2_resid_tonnes_per_yr=-0.1537, notes="Semi-detached, TFA 102, age C, gas PCDB-listed. Cert lodges blocked_chimneys_count=1.", ), @@ -140,7 +139,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( cert_number="2130-1033-4050-5007-8395", actual_sap=82, expected_sap_resid=+8, - expected_pe_resid_kwh_per_m2=-61.2533, + expected_pe_resid_kwh_per_m2=-65.8945, expected_co2_resid_tonnes_per_yr=+0.1856, notes=( "End-terrace + 1 extension, TFA 64, gas combi PCDB index 17505, " @@ -160,7 +159,7 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = ( cert_number="0390-2254-6420-2126-5561", actual_sap=65, expected_sap_resid=0, - expected_pe_resid_kwh_per_m2=-3.1420, + expected_pe_resid_kwh_per_m2=+0.1797, expected_co2_resid_tonnes_per_yr=+0.0413, notes=( "End-terrace + 1 extension, TFA 80, gas combi PCDB index 18119, " @@ -198,24 +197,28 @@ def _load_cert(cert_number: str) -> dict[str, Any]: ) def test_golden_cert_residual_matches_pin(expectation: _GoldenExpectation) -> None: # Arrange — load the frozen cert JSON, map to EpcPropertyData, run - # the rating cascade (UK-avg climate, SAP 10.2 spec prices per - # ADR-0010) for SAP + PE; run the demand-cascade environmental - # section (postcode climate via PCDB Table 172) for CO2 — that's - # what the EPC publishes as `co2_emissions_current`. + # the calculator end-to-end via two cascades: + # - cert_to_inputs → UK-average climate → SAP rating (per SAP10.2 + # Appendix U: only SAP + EI use UK-avg); + # - cert_to_demand_inputs → postcode climate (PCDB Table 172) → + # PE + CO2 (per the same Appendix U: everything the EPC publishes + # as "Current X" uses postcode-specific weather). + # The single public interface `calculate_sap_from_inputs` surfaces + # all three outputs on SapResult; no section helpers required. doc = _load_cert(expectation.cert_number) epc = EpcPropertyDataMapper.from_api_response(doc) - inputs = cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES) # Act - result = calculate_sap_from_inputs(inputs) - pc = local_climate_for_cert(epc) - env = environmental_section_from_cert(epc, postcode_climate=pc) - assert env is not None, "demand-cascade environmental section must compute" - co2_calc_tonnes = env.total_co2_kg_per_yr / 1000 + rating = calculate_sap_from_inputs( + cert_to_inputs(epc, prices=SAP_10_2_SPEC_PRICES) + ) + demand = calculate_sap_from_inputs( + cert_to_demand_inputs(epc, prices=SAP_10_2_SPEC_PRICES) + ) - sap_resid = result.sap_score - expectation.actual_sap - pe_resid = result.primary_energy_kwh_per_m2 - doc["energy_consumption_current"] - co2_resid = co2_calc_tonnes - doc["co2_emissions_current"] + sap_resid = rating.sap_score - expectation.actual_sap + pe_resid = demand.primary_energy_kwh_per_m2 - doc["energy_consumption_current"] + co2_resid = demand.co2_kg_per_yr / 1000 - doc["co2_emissions_current"] # Assert — each residual sits within an absolute tolerance of the # recorded pin. Shifts beyond tolerance fire loudly: tighten the pin