diff --git a/packages/domain/src/domain/ml/tests/_fixtures.py b/packages/domain/src/domain/ml/tests/_fixtures.py index bce05fda..ca4c906b 100644 --- a/packages/domain/src/domain/ml/tests/_fixtures.py +++ b/packages/domain/src/domain/ml/tests/_fixtures.py @@ -98,6 +98,7 @@ def make_sap_heating( cylinder_insulation_thickness_mm: Optional[int] = None, secondary_fuel_type: Optional[int] = None, secondary_heating_type: Optional[int] = None, + number_baths: Optional[int] = None, ) -> SapHeating: """Build a SapHeating with SAP10 API defaults.""" return SapHeating( @@ -112,6 +113,7 @@ def make_sap_heating( cylinder_insulation_thickness_mm=cylinder_insulation_thickness_mm, secondary_fuel_type=secondary_fuel_type, secondary_heating_type=secondary_heating_type, + number_baths=number_baths, ) diff --git a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000477.py b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000477.py index f1e6db87..146b3972 100644 --- a/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000477.py +++ b/packages/domain/src/domain/sap/worksheet/tests/_elmhurst_worksheet_000477.py @@ -26,7 +26,12 @@ from datatypes.epc.domain.epc_property_data import ( SapVentilation, SapWindow, ) -from domain.ml.tests._fixtures import make_minimal_sap10_epc, make_window +from domain.ml.tests._fixtures import ( + make_main_heating_detail, + make_minimal_sap10_epc, + make_sap_heating, + make_window, +) from domain.sap.worksheet.solar_gains import RoofWindowInput, RooflightInput from domain.sap.worksheet.ventilation import MechanicalVentilationKind from domain.sap.worksheet.water_heating import TABLE_J1_TCOLD_FROM_MAINS_C @@ -76,6 +81,8 @@ def build_epc() -> EpcPropertyData: heated_rooms_count=4, door_count=2, percent_draughtproofed=100, + low_energy_fixed_lighting_bulbs_count=SECTION_5_BULB_COUNT_LEL, + sap_windows=list(SECTION_6_VERTICAL_WINDOWS), sap_ventilation=SapVentilation( extract_fans_count=2, sheltered_sides=2, @@ -83,6 +90,16 @@ def build_epc() -> EpcPropertyData: suspended_timber_floor_sealed=False, has_draught_lobby=False, ), + sap_heating=make_sap_heating( + main_heating_details=[ + make_main_heating_detail( + main_heating_index_number=18118, + main_heating_data_source=1, + ), + ], + secondary_heating_type=691, + number_baths=0, # PDF: Total number of baths in property = 0 + ), ) diff --git a/packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py b/packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py index c184fa92..9748a66f 100644 --- a/packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py +++ b/packages/domain/src/domain/sap/worksheet/tests/test_e2e_elmhurst_sap_score.py @@ -24,6 +24,7 @@ from domain.sap.calculator import Sap10Calculator from domain.sap.rdsap.cert_to_inputs import cert_to_inputs from domain.sap.worksheet.tests import ( _elmhurst_worksheet_000474 as _w000474, + _elmhurst_worksheet_000477 as _w000477, _elmhurst_worksheet_000490 as _w000490, ) from domain.sap.worksheet.tests._elmhurst_fixtures import ( @@ -53,6 +54,14 @@ _ELMHURST_000490_EXPECTED: Final[ElmhurstExpectedSap] = ElmhurstExpectedSap( total_energy_cost_gbp=807.5421, ecf=3.0539, ) +_ELMHURST_000477_EXPECTED: Final[ElmhurstExpectedSap] = ElmhurstExpectedSap( + sap_rating=65, + sap_score_continuous=65.0050, + space_heating_kwh=10111.2019, + hot_water_kwh=2116.0365, + total_energy_cost_gbp=732.1396, + ecf=2.5086, +) _ELMHURST_000474_EXPECTED: Final[ElmhurstExpectedSap] = ElmhurstExpectedSap( sap_rating=62, sap_score_continuous=62.2584, @@ -63,6 +72,44 @@ _ELMHURST_000474_EXPECTED: Final[ElmhurstExpectedSap] = ElmhurstExpectedSap( ) +@pytest.mark.xfail( + reason=( + "Table 3c two-profile combi-loss override not yet implemented. PCDB " + "18118 (Vaillant ecoTEC sustain 24) lodges separate_dhw_tests=2 → " + "spec routes to Table 3c, which uses both Profile M (F1, R1) and " + "Profile L (F2, R2) loss factors. Our override gate (`_pcdb_table_" + "3b_combi_loss_override`) only accepts separate_dhw_tests==1 (Table " + "3b row 1, single-profile) → falls back to Table 3a keep-hot time-" + "clock 600 kWh/yr default = 25x overshoot on combi loss → +712 HW " + "kWh → continuous SAP +0.83 over PDF (66 vs 65). Re-enable when " + "Table 3c lands per the next ticket (see project memory)." + ), + strict=True, +) +def test_elmhurst_000477_end_to_end_sap_score_matches_pdf() -> None: + """Cohort closure pin for 000477. Mid-terrace combi-gas with PCDF + Vaillant ecoTEC sustain 24 (index 18118) + Electricity Electric + Panel secondary heater (SAP code 691). PDF SAP rating 65.""" + # Arrange + epc = _w000477.build_epc() + + # Act + result = Sap10Calculator().calculate(epc) + + # Assert — integer match (the rdsap engine integration gate). + delta = abs(result.sap_score - _ELMHURST_000477_EXPECTED.sap_rating) + assert delta == 0, ( + f"SAP rating delta {delta} — expected 0 (integer match with PDF). " + f"Actual={result.sap_score}, expected={_ELMHURST_000477_EXPECTED.sap_rating}." + ) + continuous_delta = abs( + result.sap_score_continuous - _ELMHURST_000477_EXPECTED.sap_score_continuous + ) + assert continuous_delta <= 0.5, ( + f"Continuous SAP delta {continuous_delta:.2f} exceeds ceiling 0.5" + ) + + def test_elmhurst_000490_end_to_end_sap_score_currently_within_3_points() -> None: """Mid-terrace combi-gas dwelling with time-clock keep-hot. After the PCDB Table 105 integration the fixture lodges `main_heating_index_