From c01f48d4fda0000348e95d2ebe5fb27cb457f70c Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 23 Jul 2026 13:38:00 +0000 Subject: [PATCH] =?UTF-8?q?Carry=20the=20sloping-ceiling=20insulation=20de?= =?UTF-8?q?pth=20into=20the=20overlay=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../property_overlays/roof_type_overlay.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/domain/epc/property_overlays/roof_type_overlay.py b/domain/epc/property_overlays/roof_type_overlay.py index 9fa8f3024..e9b85f175 100644 --- a/domain/epc/property_overlays/roof_type_overlay.py +++ b/domain/epc/property_overlays/roof_type_overlay.py @@ -1,6 +1,6 @@ """Map a Landlord-Override `RoofType` value to a roof Simulation Overlay (ADR-0032/0033). -Two resolvable families: +Three resolvable families: * `"Pitched, N mm loft insulation"` — the loft depth N maps to `BuildingPartOverlay.roof_insulation_thickness` (mm); the calculator scores the @@ -9,9 +9,13 @@ Two resolvable families: age-band default (`_FLAT_ROOF_BY_AGE`, ADR-0033). The calculator's flat path keys on the `roof_construction_type` *string* (`"flat" in …`), so the overlay sets that to `"Flat"` and leaves thickness `None` for the (separately overlaid) - construction age band to drive the U-value. No flat `RoofType` value carries an - explicit mm depth, confirmed by the Elmhurst sweep (As Built / Unknown ≡ - age-band default). + construction age band to drive the U-value. A flat `RoofType` value may also + carry an explicit mm depth (Table 16 col (1), ADR-0041). +* `"Pitched, sloping ceiling, …"` — a slope-following ceiling (no loft void, + ADR-0066). The overlay sets `roof_construction_type` to `"Pitched, sloping + ceiling"` so the calculator's `is_pitched_sloping_ceiling` fires: "as built" + leaves thickness `None` for the Table 18 col (3) age-band default, while the + `N mm` ladder carries the depth (Table 17 col (1a) "insulated slope"). Everything else (room-in-roof, "Unknown loft insulation", party-ceiling adjacency markers like "Another Premises Above") has no clean depth or shape correction, so @@ -27,11 +31,10 @@ from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier from domain.modelling.simulation import BuildingPartOverlay, EpcSimulation _LOFT_MM = re.compile(r"(\d+)\+?\s*mm loft insulation") -# A flat roof carries no loft, so its depth reads "N mm insulation" (no "loft"). -_FLAT_MM = re.compile(r"(\d+)\+?\s*mm insulation") -# A sloping ceiling's depth reads the same "N mm insulation" shape (no "loft"); -# the "sloping ceiling" prefix, not the suffix, distinguishes it from a flat roof. -_SLOPING_MM = re.compile(r"(\d+)\+?\s*mm insulation") +# A flat roof or sloping ceiling carries no loft, so its depth reads +# "N mm insulation" (no "loft"); the leading shape ("Flat," / "Pitched, sloping +# ceiling"), not the suffix, distinguishes the two. +_MM_INSULATION = re.compile(r"(\d+)\+?\s*mm insulation") def roof_overlay_for( @@ -60,7 +63,7 @@ def _overlay_for(roof_type_value: str) -> Optional[BuildingPartOverlay]: # A known depth ("N mm insulation") rides into the overlay and scores on # Table 17 col (1a) "insulated slope"; "as built" leaves thickness None so # the Table 18 col (3) age-band default drives it. Never the loft ladder. - sloping_depth = _SLOPING_MM.search(roof_type_value) + sloping_depth = _MM_INSULATION.search(roof_type_value) return BuildingPartOverlay( roof_construction_type="Pitched, sloping ceiling", roof_insulation_thickness=( @@ -68,7 +71,7 @@ def _overlay_for(roof_type_value: str) -> Optional[BuildingPartOverlay]: ), ) if roof_type_value.startswith("Flat,"): - flat_depth = _FLAT_MM.search(roof_type_value) + flat_depth = _MM_INSULATION.search(roof_type_value) if flat_depth is not None: # A flat roof with a known depth: RdSAP 10 Table 16 col (1) ("joists at # ceiling level and flat roof") scores it by thickness, so carry the