Model/datatypes/epc
Khalim Conn-Kowlessar 962b66d8b0 Slice S0380.94: RIR insulation "400+ mm PUR or PIR" extractor + mapper + cascade (RdSAP 10 Table 17 col 3b)
RdSAP 10 §5.11.3 + Table 17 (PDF p.42-43) "Roof room U-values when
insulation thickness is known". Column (3b) "Stud wall — PUR or PIR
optional" 400 mm row → 0.10 W/m²K. Cert 000565 Summary §8.1 BP[2] Ext2
(Detailed) lodges:

  Stud Wall 2  2.00 × 2.00   400+ mm   PUR or PIR   Default U=0.10

Pre-slice three coupled bugs silently dropped the lodgement, routing
the cascade through the uninsulated Table 17 row 0 (U=2.30) — over-
counting Stud Wall 2 by (2.30 − 0.10) × 4 m² = +8.80 W/K on roof:

1. **Extractor regex** `_RIR_INSULATION_THICKNESS_RE = ^\d+\s*mm$`
   failed to match the "400+ mm" bucket-cap form (Table 17's largest
   tabulated row is annotated with a trailing "+" in the Summary).
2. **Extractor insulation_type allow-list** `("Mineral or EPS",
   "PUR", "PIR")` failed to match the disjunction "PUR or PIR" — the
   actual Summary form when the assessor doesn't distinguish PUR from
   PIR. (Both columns Table 17 column (b) anyway.)
3. **Mapper thickness parser** `_elmhurst_rir_insulation_thickness_mm`
   used the same `^\d+\s*mm$` regex — also failed on "400+ mm".

Plus a fourth coupled fix: the cascade's `_is_rigid_foam` checked a
frozenset `{"pur", "pir", "rigid"}` that didn't include the canonical
mapper-side code "rigid_foam" — even if the mapper translated "PUR or
PIR" → "rigid_foam", the cascade would route to column (a) mineral-
wool instead of column (b) rigid-foam.

Slice span (4 layers):
1. **Extractor regex** — `^\d+\+?\s*mm$` matches both "100 mm" and
   "400+ mm".
2. **Extractor allow-list** — add "PUR or PIR" alongside individual
   "PUR" / "PIR" + "Mineral or EPS".
3. **Mapper** — `_RIR_INSULATION_TYPE_TO_SAP10` canonicalises all
   rigid-foam strings to "rigid_foam"; thickness parser regex matches
   "400+ mm" → 400 mm int.
4. **Cascade** — `_RR_RIGID_FOAM_INSULATION_TYPES` adds "rigid_foam"
   alongside the legacy "pur"/"pir"/"rigid" aliases.

Cert 000565 movement (HEAD `23aaa4fa` → this slice):
  - cascade BP[2] Ext2 Stud Wall 2 U:  2.30 → 0.10 ✓ EXACT vs ws 0.10
  - cascade roof_w_per_k:              43.44 → 34.64 (Δ−7.94 → Δ−16.74)
  - sap_score:                         29 ✓ EXACT unchanged
  - sap_score_continuous:              28.81 → 29.02 (Δ+0.26 → Δ+0.51)
  - space_heating_kwh:                 −427 → −685
  - main_heating_fuel:                 −251 → −403
  - hot_water_kwh:                     ✓ 0 EXACT unchanged

Closing one spec-correct sub-component while others remain non-spec-
correct drifts continuous SAP further; per user direction temporary
drift is acceptable as long as we're fixing true intermediate-value
problems — once every sub-component is spec-correct, the continuous
SAP error closes to zero by construction. The remaining −16.74 W/K
roof gap localises to:
  - BP[0/1/3] missing RR residual area for Detailed-RR mode (§3.10.1
    spec — cascade only handles Simplified mode today); +27.85 W/K
    closure when wired.
  - BP[4] Flat Ceiling 1 lodges "Unknown thickness, PUR or PIR" → ws
    U=0.15; cascade over-counts at 2.30 (uninsulated). Elmhurst's
    "Unknown PUR or PIR" → 200 mm convention is non-spec; the spec-
    correct path falls back to Table 18 col 4 default (`u_rr_default
    _all_elements`). Separate diagnostic slice.

Cohort safety: 21 other Elmhurst Summary fixtures lodge no RIR detailed
surfaces with "400+ mm" or "PUR or PIR" (modal cohort uses As Built /
None / no detailed surfaces). Existing "Mineral or EPS" tests at
`test_u_rr_stud_wall_table17_col3a_mineral_wool_100mm_returns_0_36`
remain green — the new aliases extend rather than replace.

Test baseline: 585 pass + 8 expected `000565` fails (was 583 + 8; +2
new tests). Pyright net-zero per touched file (0/32/1/65/13 preserved).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 16:28:47 +00:00
..
domain Slice S0380.94: RIR insulation "400+ mm PUR or PIR" extractor + mapper + cascade (RdSAP 10 Table 17 col 3b) 2026-06-01 16:28:47 +00:00
loaders demo generated for use in address2uprn 2026-05-08 14:48:15 +00:00
schema Slice S0380.48: surface real-API pv_batteries[].battery_capacity (5 kWh) 2026-06-01 16:28:47 +00:00
search bolstering testing 2026-04-28 13:46:09 +00:00
surveys Slice S0380.92: AP4 + MEV decentralised plumbing (SAP 10.2 §2 (17a)/(18)/(23a)/(24c)) 2026-06-01 16:28:47 +00:00
__init__.py testing out rebaselining 2026-02-12 22:25:03 +00:00
construction_age_band.py testing out rebaselining 2026-02-12 22:25:03 +00:00
efficiency.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
floor.py preparing partiy class 2026-02-05 08:54:27 +00:00
fuel.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
heating_controls.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
hotwater.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
main_heating.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
property_type_built_form.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
roof.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
walls.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
windows.py testing out rebaselining 2026-02-12 22:25:03 +00:00