test(modelling): HHRSH before/after cascade pins (001431) at 1e-4

The same absolute-target HHR overlay reproduces the common relodged after from
two different base systems (existing electric storage; "no system present"
electric) — proving the bundle is a true whole-system end-state. Closes one
named gap the pin surfaced: the relodged HHR cylinder lodges
cylinder_thermostat='Y', so HeatingOverlay + _fold_heating + the HHRSH overlay
gain cylinder_thermostat (ΔSAP 0.065 -> <1e-4). ADR-0024.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-05 19:09:23 +00:00
parent b883e75da8
commit b3badc9b10
8 changed files with 49 additions and 1 deletions

View file

@ -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

View file

@ -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")

View file

@ -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

View file

@ -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)

View file

@ -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",
)