diff --git a/domain/sap10_calculator/tables/table_12a.py b/domain/sap10_calculator/tables/table_12a.py index f98e1c27..2ee7c331 100644 --- a/domain/sap10_calculator/tables/table_12a.py +++ b/domain/sap10_calculator/tables/table_12a.py @@ -250,13 +250,16 @@ _RULE_2_STORAGE_CODES: Final[frozenset[int]] = frozenset( # Rule 3: direct-acting electric + heat pumps + electric room heaters # → 10-hour. §12 lists "heat pump (211 to 224, 521 to 524, or # database)" — the "database" branch fires when the cert lodges a -# PCDB Table 362 heat-pump index regardless of SAP code. +# PCDB Table 362 heat-pump index regardless of SAP code. §12 also +# names "electric room heaters" verbatim (RdSAP 10 PDF p.62) — Table 4a +# electric room-heater codes 691 (panel/convector/radiant), 692 (fan), +# 693 (portable), 694 (water-/oil-filled), 699 (assumed). Without these +# a Dual-meter room-heater cert fell through to Rule 4 (7-hour default). _RULE_3_TEN_HOUR_CODES: Final[frozenset[int]] = frozenset( [191] # direct-acting electric boiler + list(range(211, 225)) # heat pumps 211-224 + list(range(521, 525)) # warm-air heat pumps 521-524 - # TODO: electric room heater codes (SAP Table 4a row 6xx for - # electric panel / radiant heaters) when a fixture surfaces them. + + [691, 692, 693, 694, 699] # electric room heaters (Table 4a) ) diff --git a/tests/domain/sap10_calculator/test_table_12a.py b/tests/domain/sap10_calculator/test_table_12a.py index 2b294ca2..a15cb2ed 100644 --- a/tests/domain/sap10_calculator/test_table_12a.py +++ b/tests/domain/sap10_calculator/test_table_12a.py @@ -17,12 +17,35 @@ from domain.sap10_calculator.tables.table_12a import ( Table12aSystem, Tariff, other_use_high_rate_fraction, + rdsap_tariff_for_cert, space_heating_high_rate_fraction, tariff_from_meter_type, water_heating_high_rate_fraction, ) +def test_dual_meter_electric_room_heater_resolves_to_ten_hour_tariff() -> None: + # Arrange — RdSAP 10 §12 (PDF p.62) Dual-meter tariff dispatch: for a + # Dual meter the choice between 7-hour and 10-hour is made by the main + # heating type. Rule 3 verbatim: "if the main system ... is a direct- + # acting electric boiler (191), or electric room heaters ... it is + # 10-hour tariff." The electric room-heater codes are Table 4a 691 + # (panel/convector/radiant), 692 (fan), 693 (portable), 694 (water-/ + # oil-filled), 699 (assumed). Pre-slice these fell through to Rule 4 + # (7-hour default), so a Dual-meter room-heater cert was billed at the + # 7-hour rates with Table 12a high-rate fraction 1.00 instead of the + # 10-hour rates with fraction 0.50 — over-charging direct-acting heat + # once S0380.230 routed it to OTHER_DIRECT_ACTING_ELECTRIC. + + # Act / Assert — every electric room-heater code on a Dual meter → 10-hour. + for code in (691, 692, 693, 694, 699): + assert rdsap_tariff_for_cert(1, main_1_sap_code=code) is Tariff.TEN_HOUR + # Storage heaters (Rule 2) stay 7-hour; a gas room heater (non-Rule-3 + # code) keeps the Rule 4 Dual default of 7-hour. + assert rdsap_tariff_for_cert(1, main_1_sap_code=401) is Tariff.SEVEN_HOUR + assert rdsap_tariff_for_cert(1, main_1_sap_code=601) is Tariff.SEVEN_HOUR + + def test_tariff_enum_has_five_members() -> None: """Table 12a columns: standard (no off-peak split), 7-hour, 10-hour, 18-hour, 24-hour. Worksheet-shape fidelity: TEN_HOUR is included for