diff --git a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py index e387c73c9..109dabaed 100644 --- a/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py +++ b/tests/domain/sap10_calculator/rdsap/test_cert_to_inputs.py @@ -1079,6 +1079,68 @@ def test_electric_boilers_191_195_map_to_distinct_table_12a_grid1_rows() -> None assert space_heating_high_rate_fraction(storage, Tariff.SEVEN_HOUR) == 0.00 +def test_electric_underfloor_codes_map_to_their_table_12a_grid1_rows() -> None: + # Arrange — SAP 10.2 Table 12a Grid 1 (PDF p.184/191) names the underfloor + # codes explicitly: "Integrated (storage+direct-acting) systems (applies to + # storage heaters 408 and underfloor heating 422 and 423)" → 7-hour 0.20; + # "Underfloor heating (in screed above insulation, in timber floor or + # immediately below floor covering)" (424/425) → 7-hour 0.90 / 10-hour + # 0.50. Code 421 ("in concrete slab, off-peak only") is named in NEITHER + # underfloor row — the slab is the storage medium, so it takes "Other + # storage heaters" (0.00 at 7-hour or 24-hour), consistent with §12 Rule 2 + # listing 421 among the off-peak-implying storage codes. Before this + # resolver branch every underfloor code fell to the None fallback and + # billed space heat 100% at the night rate — on portfolio 796, 22 of the + # 90 underfloor-override dwellings sit on a 7-hour Dual meter and were + # over-credited (e.g. screed 424 at 5.50p/kWh instead of the blended + # ~14.3p). + from domain.sap10_calculator.tables.table_12a import ( + Table12aSystem, + Tariff, + space_heating_high_rate_fraction, + ) + + def _underfloor_main(code: int) -> MainHeatingDetail: + return MainHeatingDetail( + has_fghrs=False, + main_fuel_type=29, # electricity + heat_emitter_type=0, + emitter_temperature=1, + main_heating_control=2701, + main_heating_category=8, # electric underfloor + sap_main_heating_code=code, + ) + + # Act + slab = _table_12a_system_for_main(_underfloor_main(421)) + integrated = [_table_12a_system_for_main(_underfloor_main(c)) for c in (422, 423)] + screed = [_table_12a_system_for_main(_underfloor_main(c)) for c in (424, 425)] + + # Assert — the three spec rows, with their published fractions. + assert slab is Table12aSystem.OTHER_STORAGE_HEATERS + assert all(s is Table12aSystem.INTEGRATED_STORAGE_DIRECT for s in integrated) + assert all(s is Table12aSystem.UNDERFLOOR_HEATING for s in screed) + assert space_heating_high_rate_fraction(slab, Tariff.SEVEN_HOUR) == 0.00 + assert ( + space_heating_high_rate_fraction( + Table12aSystem.INTEGRATED_STORAGE_DIRECT, Tariff.SEVEN_HOUR + ) + == 0.20 + ) + assert ( + space_heating_high_rate_fraction( + Table12aSystem.UNDERFLOOR_HEATING, Tariff.SEVEN_HOUR + ) + == 0.90 + ) + assert ( + space_heating_high_rate_fraction( + Table12aSystem.UNDERFLOOR_HEATING, Tariff.TEN_HOUR + ) + == 0.50 + ) + + def test_storage_heater_classified_by_sap_code_over_stale_category() -> None: # Regression: electric STORAGE heaters must resolve to a storage Table 12a # Grid 1 row from their Table 4a SAP code (401-409) even when the RdSAP