Resolve a landlord double-glazing override to its glazing code 🟥

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-06-19 13:18:45 +00:00
parent fd922a26c2
commit f1c6825cae
3 changed files with 66 additions and 0 deletions

View file

@ -0,0 +1,23 @@
"""Map a Landlord-Override glazing value to a glazing Simulation Overlay.
A glazing value is one canonical glazing description carrying type + era
("Double glazing, 2002 or later", "Single glazing", "Triple glazing, 2002 or
later"). The calculator derives each window's U-value from its SAP10
`glazing_type` code via the RdSAP Table 24 cascade, so the overlay decomposes
the value into that code and emits a whole-dwelling `GlazingOverlay` (a landlord
describes the dwelling's glazing as a whole, with no per-window geometry, so
`building_part` is ignored). `_fold_glazing` expands it across every window.
Unresolvable values produce no overlay.
"""
from __future__ import annotations
from typing import Optional
from domain.modelling.simulation import EpcSimulation
def glazing_overlay_for(
glazing_value: str, building_part: int
) -> Optional[EpcSimulation]:
raise NotImplementedError

View file

@ -73,6 +73,28 @@ class WindowOverlay:
solar_transmittance: Optional[float] = None
@dataclass(frozen=True)
class GlazingOverlay:
"""All-optional partial of the dwelling's whole-glazing state — the
correction a Landlord Override makes when the lodged glazing is wrong.
Unlike a per-window `WindowOverlay` (keyed by `sap_windows` index), this
targets no single window: a landlord describes the dwelling's glazing as a
whole ("Double glazing, 2002 or later") with no per-window geometry, so the
overlay builder (which never sees the baseline window list) emits one of
these and `_fold_glazing` expands it across every `sap_windows` entry.
`glazing_type` is the SAP10 glazing-type code (Table 24 / `u_window`
cascade: 1=single, 2=double 2002-2021, 3=double pre-2002, 9=triple 2002+,
). The fold sets it on every window AND clears each window's lodged
transmission U-value, so the Table-24 cascade re-derives the corrected U
from the new type (the lodged U was for the OLD, mis-recorded glazing).
A `None` field means "leave the baseline value unchanged".
"""
glazing_type: Optional[int] = None
@dataclass(frozen=True)
class LightingOverlay:
"""All-optional partial of the dwelling's fixed-lighting bulb counts — the
@ -220,6 +242,7 @@ class EpcSimulation:
windows: Mapping[int, WindowOverlay] = field(default_factory=_no_windows)
ventilation: Optional[VentilationOverlay] = None
lighting: Optional[LightingOverlay] = None
glazing: Optional[GlazingOverlay] = None
heating: Optional[HeatingOverlay] = None
secondary_heating: Optional[SecondaryHeatingOverlay] = None
solar: Optional[SolarOverlay] = None

View file

@ -0,0 +1,20 @@
"""The Landlord-Override glazing → glazing Simulation Overlay mapping.
A glazing value resolves to the SAP10 `glazing_type` code the calculator's
Table-24 cascade reads; the overlay is whole-dwelling (expanded across every
window by `_fold_glazing`).
"""
from __future__ import annotations
from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for
def test_double_glazing_post_2002_overlays_its_glazing_code() -> None:
# Act
simulation = glazing_overlay_for("Double glazing, 2002 or later", 0)
# Assert — double glazing 2002-2021 is SAP10 glazing_type code 2.
assert simulation is not None
assert simulation.glazing is not None
assert simulation.glazing.glazing_type == 2