diff --git a/domain/sap10_calculator/tables/table_12a.py b/domain/sap10_calculator/tables/table_12a.py index d79d9fbe..72ea8f7b 100644 --- a/domain/sap10_calculator/tables/table_12a.py +++ b/domain/sap10_calculator/tables/table_12a.py @@ -262,6 +262,19 @@ _RULE_3_TEN_HOUR_CODES: Final[frozenset[int]] = frozenset( + [691, 692, 693, 694, 699] # electric room heaters (Table 4a) ) +# §12 Unknown-meter exception: "main heating is ground source or water source +# heat pump" makes the dwelling dual even though the same heat pump on a Single +# meter stays standard. This is the GROUND/WATER-source subset of Table 4a +# (SAP 10.2 PDF p.176-177): ground 211/215/221/225 + warm-air 521/525, water +# 213/216/223/226 + warm-air 523/526. AIR-source (214/217/224/227, 524/527) is +# DELIBERATELY EXCLUDED — the spec names only ground/water source. A heat pump +# lodged via a PCDB database index without one of these SAP codes can't have +# its source type read from the code alone (coverage gap: rare, 0 corpus certs). +_GROUND_OR_WATER_SOURCE_HEAT_PUMP_CODES: Final[frozenset[int]] = frozenset( + {211, 215, 221, 225, 521, 525} # ground source (radiator + warm-air) + | {213, 216, 223, 226, 523, 526} # water source (radiator + warm-air) +) + def _meter_is_unknown(meter_type: object) -> bool: """True when the meter is the RdSAP "Unknown" sentinel (code 3 / the @@ -367,11 +380,18 @@ def rdsap_tariff_for_cert( # immersion); every other Unknown+dual-immersion cert already has a # storage main (Rule 2). Single-immersion 691 certs (Flat 7, Flat 2) and # whc-909 instantaneous certs correctly STAY standard — they carry no - # text-box off-peak system. (GSHP/WSHP-main trigger: separate follow-up.) + # text-box off-peak system. The §12 third exception bullet ("main heating + # is ground source or water source heat pump") is the + # `_GROUND_OR_WATER_SOURCE_HEAT_PUMP_CODES` check — AIR-source heat pumps + # (214/224 etc.) are NOT a trigger and stay standard (e.g. corpus certs + # "3/10 Bedford House", main 214 ASHP on Unknown meters — verified 2026-06- + # 23 they keep STANDARD). No corpus cert carries a GSHP/WSHP on an Unknown + # meter, so this trigger is a spec-completeness forward guard (0 impact). if _meter_is_unknown(meter_type): off_peak_evidence = ( bool(main_codes & _RULE_1_CPSU_CODES) or bool(main_codes & _RULE_2_STORAGE_CODES) + or bool(main_codes & _GROUND_OR_WATER_SOURCE_HEAT_PUMP_CODES) or water_is_off_peak_dual_immersion ) if off_peak_evidence: diff --git a/tests/domain/sap10_calculator/test_table_12a.py b/tests/domain/sap10_calculator/test_table_12a.py index 8ce42b09..9a70dbfa 100644 --- a/tests/domain/sap10_calculator/test_table_12a.py +++ b/tests/domain/sap10_calculator/test_table_12a.py @@ -106,6 +106,25 @@ def test_unknown_meter_dual_electric_immersion_triggers_off_peak_via_rules() -> ) is Tariff.STANDARD +def test_unknown_meter_ground_or_water_source_heat_pump_triggers_off_peak() -> None: + # Arrange — RdSAP 10 §12 (PDF p.62) third Unknown-meter exception bullet: + # "main heating is ground source or water source heat pump." Per SAP 10.2 + # Table 4a (PDF p.176-177) ground source = 211/215/221/225 (+ warm-air + # 521/525), water source = 213/216/223/226 (+ warm-air 523/526); AIR + # source (214/224 etc.) is NOT named, so it stays standard. Once the GSHP/ + # WSHP makes the meter dual, Rules 1-4 (Rule 3, heat pump 211-224) → 10h. + # 0 corpus certs carry a GSHP/WSHP on an Unknown meter (the only Unknown- + # meter HPs are 3/10 Bedford House, main 214 = AIR source) — this is a + # spec-completeness forward guard. + + # Act / Assert — ground/water source → 10-hour; air source → STANDARD. + assert rdsap_tariff_for_cert(3, main_1_sap_code=211) is Tariff.TEN_HOUR + assert rdsap_tariff_for_cert(3, main_1_sap_code=213) is Tariff.TEN_HOUR + assert rdsap_tariff_for_cert(3, main_1_sap_code=521) is Tariff.TEN_HOUR + assert rdsap_tariff_for_cert(3, main_1_sap_code=214) is Tariff.STANDARD + assert rdsap_tariff_for_cert(3, main_1_sap_code=224) is Tariff.STANDARD + + def test_unknown_meter_with_non_electric_main_stays_standard() -> None: # Arrange — an "Unknown" meter on a GAS-heated dwelling (SAP 102) has # no off-peak evidence, so it must NOT pick up the Rule-4 Dual default