mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
docs: roof-insulation eligibility design (ADR-0021 + CONTEXT)
Grill-with-docs outcome for the roof-insulation generator. One dispatching recommend_roof_insulation, one Measure per roof by type (loft 300mm incl. thatch / sloping-ceiling 100mm / flat-roof 200mm; no-access → none), MAIN-only, room-in-roof deferred. Detection keys on the roof_construction_type string (populated on both paths; the calculator already dispatches on it) with sloping→flat→no-access→loft ordering; the roof_construction-int cross-mapper parity is the follow-up Hestia-Homes/Model#1178. Thatch is not excluded — it takes loft insulation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3af6c21ff0
commit
0c7ce634db
2 changed files with 43 additions and 0 deletions
|
|
@ -242,6 +242,10 @@ _Avoid_: solid wall insulation (that names the pair, not one Option), cladding,
|
|||
The rule fixing which wall Option(s) the main-wall **Recommendation** offers, by wall construction then planning status. By construction: **cavity** → cavity fill only (never solid insulation); **solid brick** / **system-built** → IWI + EWI; **timber-frame** → IWI only (EWI is not constructable); **cob** / **granite or whinstone** / **sandstone or limestone** → none (breathable fabric — standard insulation risks trapping moisture). Then, as planning gates: a **conservation area** or **flat** removes EWI (external-appearance / whole-block constraints), and a **listed** or **heritage** building removes **both** EWI and IWI (protected fabric).
|
||||
_Avoid_: restricted measures (legacy collapsed conservation/listed/heritage into one boolean — they now gate different Options, so keep them distinct)
|
||||
|
||||
**Roof Insulation Eligibility**:
|
||||
The rule fixing which single roof Measure the main-roof **Recommendation** offers, by roof type. One Measure per roof — never a menu the Optimiser chooses between (ADR-0021): **pitched with an accessible loft** (incl. **thatch** — the covering doesn't block insulating the loft floor) → **loft insulation** (laid flat at the ceiling joists, 300 mm); **pitched with a sloping ceiling** → **sloping-ceiling insulation** (at the rafters, 100 mm); **flat roof** → **flat-roof insulation** (200 mm); **pitched, no access** → none (can't reach the void). A **room-in-roof** takes neither loft nor sloping-ceiling insulation — it is insulated at its own slopes/stud-walls (rafter-area, not floor-area, quantity) as a distinct **room-in-roof insulation** Measure, currently **deferred** (pending retrofit-specialist examples). A Measure is offered only when the roof is genuinely uninsulated ("As Built" / "None" / 0 mm).
|
||||
_Avoid_: "roof insulation" (name the specific Measure — loft / sloping-ceiling / flat-roof / room-in-roof); "joist insulation" (use **loft insulation**, the established Measure Type)
|
||||
|
||||
### Valuation
|
||||
|
||||
**Property Valuation**:
|
||||
|
|
|
|||
39
docs/adr/0021-roof-insulation-eligibility.md
Normal file
39
docs/adr/0021-roof-insulation-eligibility.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Roof Insulation Eligibility (loft vs sloping-ceiling vs flat-roof vs room-in-roof)
|
||||
|
||||
The roof Recommendation Generator must decide, per Property, which roof-insulation Measure to offer. Unlike walls (ADR-0019), where External and Internal are two *competing* Options under one Recommendation, a roof of a given type takes **exactly one** applicable Measure — there is no menu to choose between. We fix eligibility by **roof type**, detected from the roof's construction description.
|
||||
|
||||
## Decision
|
||||
|
||||
**One dispatching generator, one Measure per roof.** `recommend_roof_insulation` inspects the MAIN building part's roof and emits a single "Roof" Recommendation carrying the one applicable Measure Option — folding the existing `recommend_loft_insulation` in as the loft branch. The measures are mutually exclusive *by roof type*, so the dispatch makes that exclusivity structural (no cross-guards, unlike the legacy `is_sloping_ceiling_appropriate` / `is_loft_insulation_appropriate` tangle).
|
||||
|
||||
| Roof type (`roof_construction_type` substring) | Measure | Uninsulated trigger | Overlay `roof_insulation_thickness` |
|
||||
|---|---|---|---|
|
||||
| `sloping ceiling` | `sloping_ceiling_insulation` | `roof_insulation_thickness` 0 | → **100 mm** |
|
||||
| `flat` | `flat_roof_insulation` | `roof_insulation_thickness` None | → **200 mm** |
|
||||
| `loft` / `thatch` | `loft_insulation` *(existing)* | `roof_insulation_thickness` 0 | → **300 mm** |
|
||||
| `no access` | none (can't reach the void) | — | — |
|
||||
| room-in-roof (`sap_room_in_roof` present) | **deferred** | — | — |
|
||||
|
||||
**All three measures write the same overlay field — `roof_insulation_thickness`** (verified against the Elmhurst before→after certs: sloping 0→100, flat None→200, thatch-loft 0→300). They differ only by detected roof type and recommended depth. The uninsulated trigger is therefore `roof_insulation_thickness ∈ {0, None}` (the Elmhurst mapper resolves "As Built" to 0 for pitched/sloping but None for flat). Note: `flat_roof_insulation_thickness` is **not** used on the Elmhurst path (it stays None even after a flat-roof measure) — it is an API-path field; reconciling that is part of the #1178 parity work. `roof_insulation_location` carries the human label (`Sloping ceiling insulation` / `Flat roof insulation` / `Joists`) as a corroborating signal.
|
||||
|
||||
**Detection keys on `roof_construction_type` (the string), not the `roof_construction` int.** This *inverts* the wall decision (ADR-0019 keys walls on the int because the Elmhurst wall *description* is empty) for a concrete reason: for roofs it is the **int** that the Elmhurst path leaves `None` (it only sets `roof_construction_type` via `_strip_code(roof.roof_type)`), while the string is populated on **both** paths — and the calculator already dispatches roof type the same way (`"sloping ceiling" in roof_construction_type.lower()`, `heat_transmission.py:811`). Keying the generator on the same field keeps generator and calculator reading one source. Populating `roof_construction` on the Elmhurst path for full cross-mapper parity is tracked separately in **Hestia-Homes/Model#1178** (incl. the unknown gov code for thatch); the generator is not blocked on it.
|
||||
|
||||
**Dispatch order matters** (substring matching): **sloping → flat → no-access → loft/thatch**. `"no access to loft"` contains both `"loft"` and `"access to loft"`, so the no-access branch must be tested before the loft branch, else a no-access roof would wrongly match loft.
|
||||
|
||||
**Thatch is not excluded.** A `Pitched (thatch)` roof takes **loft (joist) insulation** — the before→after Elmhurst cert shows `Insulation: None → Joists, 300 mm`. The thatch *covering* doesn't block insulating the loft floor beneath it; a breathability exclusion would only bite if we tried to insulate a thatched roof *at the rafters*, which is not in scope.
|
||||
|
||||
**Room-in-roof is a distinct, deferred Measure.** An RR (`sap_room_in_roof` present) is a habitable room in the roof space: its ceiling joists are the room's floor, so loft/joist insulation is physically impossible — it is insulated at its own slopes / stud walls / flat ceiling, and the **quantity is the estimated rafter area, not the part's floor area** (`roof_area`). Dispatch checks RR first and, finding one, emits nothing for now. Deferred pending retrofit-specialist before/after examples — soon to close.
|
||||
|
||||
**MAIN building part only.** Like every other rebuild generator (wall / loft / floor), the roof generator operates on the MAIN part. Multi-roof dwellings (MAIN + extensions) are out of scope here and land when extensions are tackled across all generators.
|
||||
|
||||
## Considered options
|
||||
|
||||
- **Separate generators per measure** (`recommend_sloping_ceiling`, `recommend_flat_roof`, alongside `recommend_loft_insulation`). Rejected: each would need cross-guards so only one fires on a given roof — reintroducing the interacting-conditions complexity the legacy `RoofRecommendations` carries. A single dispatcher makes one-measure-per-roof structural.
|
||||
- **Key detection on the `roof_construction` int** (consistent with walls). Rejected for now: the Elmhurst path leaves the int `None`, so it can't drive detection for the very fixtures we pin. Fixing that is #1178; until then the string is the only cross-path signal.
|
||||
- **Exclude thatch** (as we exclude cob/stone walls). Rejected: the cert evidence shows thatched roofs take loft insulation; the breathability concern is about rafter-level insulation of the covering, which we don't offer.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Substring detection is looser than an enum; the match terms and their order are load-bearing and pinned against real fixtures. Closing #1178 lets a later revision switch to the int if desired.
|
||||
- A room-in-roof property gets **no** roof Measure until the deferred RR work lands — a known temporary gap, not a silent drop.
|
||||
- Recommended depths differ by Measure (loft 300 mm, flat 200 mm, sloping 100 mm), each pinned to the Elmhurst re-lodged after-cert at 1e-4.
|
||||
Loading…
Add table
Reference in a new issue