fix(modelling): HHR overlay sets off-peak immersion type so HW Table 13 applies

The HHR-storage HeatingOverlay (ADR-0024) added an off-peak electric
immersion cylinder but never set `immersion_heating_type`, so the overlaid
cert left it None. The calculator then could not resolve `immersion_single`
for the SAP 10.2 Table 13 HW high-rate split and billed hot water 100% at
the off-peak low rate — £127.41 vs the relodged after-cert's £169.39,
overstating the overlay's SAP by +1.26 (CO2/PE matched, isolating it to the
HW cost path).

Add `immersion_heating_type` to HeatingOverlay, route it through
`_fold_heating` (it lives on `sap_heating`), and set it to 1 (single
off-peak immersion) on the HHR overlay to match the relodged reference.
Closes both `test_hhr_storage_overlay_reproduces_the_relodged_after_*`
cascade pins (electric-storage and no-system befores share the after).

Pre-existing failure (present before this branch's recent commits), outside
the handover regression gate. Full modelling suite 220 pass, pyright net-
zero.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-15 06:53:14 +00:00
parent 361abc1202
commit c11eb46b8a
4 changed files with 11 additions and 0 deletions

View file

@ -64,6 +64,10 @@ _HHR_STORAGE_OVERLAY = HeatingOverlay(
cylinder_insulation_type=1,
cylinder_insulation_thickness_mm=120,
cylinder_thermostat="Y",
# Single off-peak electric immersion — drives the SAP 10.2 Table 13 HW
# high-rate split (matches the relodged after-cert; without it the HW
# bills 100% at the low rate, +1.26 SAP over the reference).
immersion_heating_type=1,
has_hot_water_cylinder=True,
meter_type="Dual",
)

View file

@ -98,6 +98,7 @@ _SAP_HEATING_FIELDS: tuple[str, ...] = (
"cylinder_insulation_type",
"cylinder_insulation_thickness_mm",
"cylinder_thermostat",
"immersion_heating_type",
)
_ENERGY_SOURCE_FIELDS: tuple[str, ...] = ("meter_type", "mains_gas")

View file

@ -133,6 +133,11 @@ class HeatingOverlay:
cylinder_insulation_type: Optional[Union[int, str]] = None
cylinder_insulation_thickness_mm: Optional[int] = None
cylinder_thermostat: Optional[str] = None
# The cylinder's immersion-heater arrangement (e.g. single off-peak
# immersion = 1). Drives the SAP 10.2 Table 13 HW high-rate fraction on
# off-peak tariffs — without it the calculator cannot resolve
# `immersion_single` and bills HW 100% at the low rate.
immersion_heating_type: Optional[Union[int, str]] = None
# EpcPropertyData (top-level)
has_hot_water_cylinder: Optional[bool] = None
# sap_energy_source

View file

@ -67,6 +67,7 @@ def test_electric_storage_dwelling_yields_an_hhr_storage_bundle() -> None:
cylinder_insulation_type=1,
cylinder_insulation_thickness_mm=120,
cylinder_thermostat="Y",
immersion_heating_type=1,
has_hot_water_cylinder=True,
meter_type="Dual",
)