"""The main-heating reclassify maps the garbage-drawer archetypes (old storage, Gas CPSU) onto the faithful ones the guard resolves, leaves correctly-classified rows alone, and is idempotent (#1376 / ADR-0044, ADR-0045).""" from __future__ import annotations from domain.epc.property_overlays.main_heating_system_overlay import ( main_heating_overlay_for, ) from domain.epc.property_overrides.main_heating_guard import main_heating_guard from domain.epc.property_overrides.main_heating_system_type import ( MainHeatingSystemType, ) from scripts.reclassify_main_heating import ( electric_cpsu_for, main_heating_corrections, ) def test_garbage_drawer_rows_are_remapped_to_faithful_archetypes() -> None: # Arrange — HHRSH funnelled into old storage, and the Gas CPSU dumping ground # (solid-fuel room heaters + electric "NA" boiler); plus correctly-classified # rows (genuine old storage, a gas combi) that must be left untouched. stored = [ ( "electric storage systems: high heat retention storage heaters", "Electric storage heaters, old", ), ("solid fuel room heaters: open fire in grate", "Gas CPSU"), ( "solid fuel room heaters: open fire with back boiler (no radiators)", "Gas CPSU", ), ( "solid fuel room heaters: closed room heater with boiler (no radiators)", "Gas CPSU", ), ("boiler: a rated na", "Gas CPSU"), # Correctly classified — the guard does not claim these. ( "electric storage systems: old (large volume) storage heaters", "Electric storage heaters, old", ), ("boiler: a rated combi", "Gas boiler, combi"), ] # Act corrections = main_heating_corrections(stored) # Assert — every mis-classified row moves to its faithful archetype. assert corrections == { "electric storage systems: high heat retention storage heaters": ( "Electric storage heaters, high heat retention" ), "solid fuel room heaters: open fire in grate": ( "Solid fuel room heater, open fire" ), "solid fuel room heaters: open fire with back boiler (no radiators)": ( "Solid fuel room heater, open fire with back boiler" ), "solid fuel room heaters: closed room heater with boiler (no radiators)": ( "Solid fuel room heater, closed with boiler" ), "boiler: a rated na": "Electric boiler", } def test_electric_underfloor_rows_are_remapped_off_direct_acting_and_old_storage() -> None: # Underfloor dumped into Direct-acting (191) / old storage (401) → its own codes # (ADR-0046). Description-determined, so the guard-based reclassify handles it. stored = [ ( "electric underfloor heating: in concrete slab (off-peak only)", "Direct-acting electric", ), ( "electric underfloor heating: integrated (storage+direct-acting) (off peak)", "Electric storage heaters, old", ), ( "electric underfloor heating: in screed above insulation (standard or off peak)", "Direct-acting electric", ), ] # Act corrections = main_heating_corrections(stored) # Assert assert corrections == { "electric underfloor heating: in concrete slab (off-peak only)": ( "Electric underfloor, in concrete slab (off-peak)" ), "electric underfloor heating: integrated (storage+direct-acting) (off peak)": ( "Electric underfloor, integrated storage and direct-acting" ), "electric underfloor heating: in screed above insulation (standard or off peak)": ( "Electric underfloor, in screed above insulation" ), } def test_every_guard_target_round_trips_to_a_resolvable_archetype() -> None: # Every archetype the guard emits for the rescued descriptions must decode to a # real SAP heating code — a guard/overlay drift can't silently write an # unmodellable value (ADR-0044, ADR-0045, ADR-0046). descriptions = [ "Electric Storage Systems: High heat retention storage heaters", "Electric Underfloor Heating: In concrete slab (off-peak only)", "Electric Underfloor Heating: Integrated (storage+direct-acting) (off peak)", "Electric Underfloor Heating: In screed above insulation (standard or off peak)", "Solid fuel room heaters: Open fire in grate", "Solid fuel room heaters: Open fire with back boiler (no radiators)", "Solid fuel room heaters: Closed room heater with boiler (no radiators)", "Boiler: A rated NA", ] for description in descriptions: member = main_heating_guard(description) assert member is not None, description assert member in MainHeatingSystemType, description simulation = main_heating_overlay_for(member.value, 0) assert simulation is not None, description assert simulation.heating is not None, description assert simulation.heating.sap_main_heating_code is not None, description def test_electric_cpsu_is_resolved_from_the_fuel_not_the_description() -> None: # "Boiler: A/C rated CPSU" is identical bar the band — the electric/gas split is # fuel-determined (the pure-string guard can't see it), so the reclassify keys # on the property's main_fuel override: electricity → Electric CPSU (192); # mains gas / unknown → keep Gas CPSU (120). A non-CPSU boiler is ignored here # (the guard handles "NA" electric boilers) — ADR-0045. assert electric_cpsu_for("boiler: a rated cpsu", "electricity") == "Electric CPSU" assert electric_cpsu_for("boiler: c rated cpsu", "mains gas") is None assert electric_cpsu_for("boiler: c rated cpsu", None) is None assert electric_cpsu_for("boiler: a rated na", "electricity") is None def test_already_corrected_rows_need_no_change() -> None: # Act / Assert — idempotent. assert ( main_heating_corrections( [ ( "electric storage systems: high heat retention storage heaters", "Electric storage heaters, high heat retention", ), ("boiler: a rated na", "Electric boiler"), ] ) == {} )