From 6d812d99c5fdc989cc72782e8903ed5bc9ca1b38 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 2 Jul 2026 11:20:55 +0000 Subject: [PATCH] =?UTF-8?q?An=20unlodged=20roof=20type=20with=20ND=20thick?= =?UTF-8?q?ness=20gets=20no=20roof=20recommendation=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- ...y-resolves-sentinel-thickness-by-age-band.md | 10 ++++++++++ .../modelling/generators/roof_recommendation.py | 17 ++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/adr/0047-roof-eligibility-resolves-sentinel-thickness-by-age-band.md b/docs/adr/0047-roof-eligibility-resolves-sentinel-thickness-by-age-band.md index d4c1721ce..b582f99c5 100644 --- a/docs/adr/0047-roof-eligibility-resolves-sentinel-thickness-by-age-band.md +++ b/docs/adr/0047-roof-eligibility-resolves-sentinel-thickness-by-age-band.md @@ -46,6 +46,16 @@ extends the codebase's existing precedent (`_PRE_1950_AGE_CODES = {A,B,C,D}` in the mapper's code-8 rule; pinned by cert 001479 Ext2 at U=2.30) from the scoring path to eligibility. +**Sentinel resolution requires a KNOWN pitched/thatched roof type.** An +unlodged `roof_construction_type` reaching the loft fallback can be a **party +ceiling** — gov-API codes 6/7 ("(another dwelling above)" / thatch with +additional insulation) map the type to `None`, and real code-7 certs lodge +`"ND"` — with zero roof heat loss (RdSAP 10 Table 18), so insulating it +contradicts the dwelling. An unlodged type therefore keeps the explicit-0-only +trigger (the pre-ADR behaviour for that shape); only a type containing +`pitched`/`thatch` (or the sloping/vaulted branch, known by construction) +resolves sentinels by age band. + **Coherence rule (load-bearing):** sentinel resolution must never make an undefined cert *more* eligible than an explicitly-lodged one. An explicit 50 mm lodgement is not eligible today, so `"ND"` on a band-E/F ("limited diff --git a/domain/modelling/generators/roof_recommendation.py b/domain/modelling/generators/roof_recommendation.py index 5d2c1cd0c..5b234a41d 100644 --- a/domain/modelling/generators/roof_recommendation.py +++ b/domain/modelling/generators/roof_recommendation.py @@ -129,9 +129,20 @@ def recommend_roof_insulation( if "no access" in roof_type: return None # the roof void can't be reached to insulate it - if not _pitched_roof_is_uninsulated( - main.roof_insulation_thickness, main.construction_age_band - ): + # Sentinel resolution needs a KNOWN pitched/thatched roof (ADR-0047). An + # unlodged type reaching this fallback can be a party ceiling — gov-API + # codes 6/7 ("(another dwelling above)" / insulated thatch) map the type + # to None and real code-7 certs lodge "ND" — with zero roof heat loss + # (RdSAP 10 Table 18), so only an explicit 0 may trigger it. + known_pitched: bool = "pitched" in roof_type or "thatch" in roof_type + uninsulated: bool = ( + _pitched_roof_is_uninsulated( + main.roof_insulation_thickness, main.construction_age_band + ) + if known_pitched + else main.roof_insulation_thickness == 0 + ) + if not uninsulated: return None return _roof_recommendation( epc,