From 04484edc6785dcdfa93736d56f4b5dd63107dec5 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 1 Jul 2026 14:33:24 +0000 Subject: [PATCH] =?UTF-8?q?Re-map=20old-storage=20HHRSH=20overrides=20onto?= =?UTF-8?q?=20the=20high-heat-retention=20archetype=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/reclassify_hhrsh_storage.py | 12 ++++ .../scripts/test_reclassify_hhrsh_storage.py | 70 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 scripts/reclassify_hhrsh_storage.py create mode 100644 tests/scripts/test_reclassify_hhrsh_storage.py diff --git a/scripts/reclassify_hhrsh_storage.py b/scripts/reclassify_hhrsh_storage.py new file mode 100644 index 000000000..da4fbce82 --- /dev/null +++ b/scripts/reclassify_hhrsh_storage.py @@ -0,0 +1,12 @@ +"""One-time re-classification of HHRSH overrides funnelled into old storage. + +Placeholder — decision function stubbed; filled in GREEN. +""" + +from __future__ import annotations + +from collections.abc import Iterable + + +def hhrsh_storage_corrections(stored: Iterable[tuple[str, str]]) -> dict[str, str]: + raise NotImplementedError diff --git a/tests/scripts/test_reclassify_hhrsh_storage.py b/tests/scripts/test_reclassify_hhrsh_storage.py new file mode 100644 index 000000000..c3b025c53 --- /dev/null +++ b/tests/scripts/test_reclassify_hhrsh_storage.py @@ -0,0 +1,70 @@ +"""The HHRSH reclassify maps the "old storage" dumping ground onto the high-heat- +retention archetype, leaves other storage subtypes alone, and is idempotent +(#1376 / ADR-0044).""" + +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_hhrsh_storage import hhrsh_storage_corrections + + +def test_hhrsh_rows_are_remapped_off_old_storage() -> None: + # Arrange — HHRSH funnelled into old storage (the bug), plus a genuinely-old + # storage row and a gas row that must be left untouched. + stored = [ + ( + "electric storage systems: high heat retention storage heaters", + "Electric storage heaters, old", + ), + ( + "electric storage systems: old (large volume) storage heaters", + "Electric storage heaters, old", + ), + ("gas boiler", "Gas boiler, regular"), + ] + + # Act + corrections = hhrsh_storage_corrections(stored) + + # Assert — only the HHRSH row moves, to its own archetype. + assert corrections == { + "electric storage systems: high heat retention storage heaters": ( + "Electric storage heaters, high heat retention" + ) + } + + +def test_every_guard_target_round_trips_to_a_resolvable_archetype() -> None: + # The HHRSH archetype the guard emits must decode to a real SAP heating code — + # a guard/overlay drift can't silently write an unmodellable value (ADR-0044). + member = main_heating_guard( + "Electric Storage Systems: High heat retention storage heaters" + ) + + assert member is not None + assert member in MainHeatingSystemType + simulation = main_heating_overlay_for(member.value, 0) + assert simulation is not None + assert simulation.heating is not None + assert simulation.heating.sap_main_heating_code is not None + + +def test_already_corrected_rows_need_no_change() -> None: + # Act / Assert — idempotent. + assert ( + hhrsh_storage_corrections( + [ + ( + "electric storage systems: high heat retention storage heaters", + "Electric storage heaters, high heat retention", + ) + ] + ) + == {} + )