diff --git a/domain/modelling/generators/heating_recommendation.py b/domain/modelling/generators/heating_recommendation.py index b13068b8..4c018635 100644 --- a/domain/modelling/generators/heating_recommendation.py +++ b/domain/modelling/generators/heating_recommendation.py @@ -46,6 +46,7 @@ _HHR_STORAGE_OVERLAY = HeatingOverlay( cylinder_size=2, cylinder_insulation_type=1, cylinder_insulation_thickness_mm=120, + cylinder_thermostat="Y", has_hot_water_cylinder=True, meter_type="Dual", ) @@ -98,6 +99,6 @@ def _hhr_storage_eligible(epc: EpcPropertyData) -> bool: return False if main.main_heating_category == _HEAT_PUMP_CATEGORY: return False - off_gas: bool = epc.sap_energy_source is None or not epc.sap_energy_source.mains_gas + off_gas: bool = not epc.sap_energy_source.mains_gas electric_main: bool = main.main_fuel_type == _ELECTRICITY_FUEL return electric_main or off_gas diff --git a/domain/modelling/scoring/overlay_applicator.py b/domain/modelling/scoring/overlay_applicator.py index e2860465..73934779 100644 --- a/domain/modelling/scoring/overlay_applicator.py +++ b/domain/modelling/scoring/overlay_applicator.py @@ -74,6 +74,7 @@ _SAP_HEATING_FIELDS: tuple[str, ...] = ( "cylinder_size", "cylinder_insulation_type", "cylinder_insulation_thickness_mm", + "cylinder_thermostat", ) _ENERGY_SOURCE_FIELDS: tuple[str, ...] = ("meter_type", "mains_gas") diff --git a/domain/modelling/simulation.py b/domain/modelling/simulation.py index 085ad30b..2e9757e8 100644 --- a/domain/modelling/simulation.py +++ b/domain/modelling/simulation.py @@ -120,6 +120,7 @@ class HeatingOverlay: cylinder_size: Optional[Union[int, str]] = None cylinder_insulation_type: Optional[Union[int, str]] = None cylinder_insulation_thickness_mm: Optional[int] = None + cylinder_thermostat: Optional[str] = None # EpcPropertyData (top-level) has_hot_water_cylinder: Optional[bool] = None # sap_energy_source diff --git a/tests/domain/modelling/fixtures/hhr_storage_001431_after.pdf b/tests/domain/modelling/fixtures/hhr_storage_001431_after.pdf new file mode 100644 index 00000000..d23b2024 Binary files /dev/null and b/tests/domain/modelling/fixtures/hhr_storage_001431_after.pdf differ diff --git a/tests/domain/modelling/fixtures/hhr_storage_from_electric_storage_001431_before.pdf b/tests/domain/modelling/fixtures/hhr_storage_from_electric_storage_001431_before.pdf new file mode 100644 index 00000000..f70e0568 Binary files /dev/null and b/tests/domain/modelling/fixtures/hhr_storage_from_electric_storage_001431_before.pdf differ diff --git a/tests/domain/modelling/fixtures/hhr_storage_from_no_system_001431_before.pdf b/tests/domain/modelling/fixtures/hhr_storage_from_no_system_001431_before.pdf new file mode 100644 index 00000000..797e9a6b Binary files /dev/null and b/tests/domain/modelling/fixtures/hhr_storage_from_no_system_001431_before.pdf differ diff --git a/tests/domain/modelling/test_elmhurst_cascade_pins.py b/tests/domain/modelling/test_elmhurst_cascade_pins.py index 6bba36ef..7410d969 100644 --- a/tests/domain/modelling/test_elmhurst_cascade_pins.py +++ b/tests/domain/modelling/test_elmhurst_cascade_pins.py @@ -38,6 +38,7 @@ from domain.modelling.generators.solid_wall_recommendation import ( ) from domain.modelling.generators.glazing_recommendation import recommend_glazing from domain.modelling.generators.lighting_recommendation import recommend_lighting +from domain.modelling.generators.heating_recommendation import recommend_heating from domain.modelling.scoring.overlay_applicator import apply_simulations from domain.modelling.recommendation import MeasureOption from domain.sap10_calculator.calculator import Sap10Calculator, SapResult @@ -576,3 +577,46 @@ def test_lighting_overlay_reproduces_the_relodged_after_some_existing_leds() -> _assert_overlay_reproduces_after( before, after, recommendation.options[0].overlay ) + + +def test_hhr_storage_overlay_reproduces_the_relodged_after_from_electric_storage() -> None: + # Arrange — an existing electric storage system re-lodged as high-heat- + # retention storage (Table 4a 402 -> 409, control 2401 -> 2404), gaining an + # off-peak immersion cylinder and a dual meter (ADR-0024). + before: EpcPropertyData = parse_recommendation_summary( + "hhr_storage_from_electric_storage_001431_before.pdf" + ) + after: EpcPropertyData = parse_recommendation_summary( + "hhr_storage_001431_after.pdf" + ) + recommendation: Recommendation | None = recommend_heating(before, _AnyProduct()) + assert recommendation is not None + option = next( + o + for o in recommendation.options + if o.measure_type == "high_heat_retention_storage_heaters" + ) + + # Act / Assert + _assert_overlay_reproduces_after(before, after, option.overlay) + + +def test_hhr_storage_overlay_reproduces_the_relodged_after_from_no_system() -> None: + # Arrange — a "no system present" electric dwelling re-lodged as HHR storage; + # the same absolute-target overlay must reproduce the common after. + before: EpcPropertyData = parse_recommendation_summary( + "hhr_storage_from_no_system_001431_before.pdf" + ) + after: EpcPropertyData = parse_recommendation_summary( + "hhr_storage_001431_after.pdf" + ) + recommendation: Recommendation | None = recommend_heating(before, _AnyProduct()) + assert recommendation is not None + option = next( + o + for o in recommendation.options + if o.measure_type == "high_heat_retention_storage_heaters" + ) + + # Act / Assert + _assert_overlay_reproduces_after(before, after, option.overlay) diff --git a/tests/domain/modelling/test_heating_recommendation.py b/tests/domain/modelling/test_heating_recommendation.py index aa27ae04..5ecf8c44 100644 --- a/tests/domain/modelling/test_heating_recommendation.py +++ b/tests/domain/modelling/test_heating_recommendation.py @@ -62,6 +62,7 @@ def test_electric_storage_dwelling_yields_an_hhr_storage_bundle() -> None: cylinder_size=2, cylinder_insulation_type=1, cylinder_insulation_thickness_mm=120, + cylinder_thermostat="Y", has_hot_water_cylinder=True, meter_type="Dual", )