mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.96: RIR insulation "Unknown" thickness extractor + mapper (RdSAP 10 §3.10.1)
RdSAP 10 Specification §3.10.1 (PDF p.24) "Default U-values of the
roof rooms":
"Where the details of insulation are not available, the default
U-values are those for the appropriate age band for the
construction of the roof rooms (see Table 18 : Assumed roof
U-values when Table 16 or Table 17 do not apply). The default
U-values apply when the roof room insulation is 'as built' or
'unknown'."
Cert 000565 Summary §8.1 BP[4] Ext4 lodges:
Flat Ceiling 1 5.00 1.00 Unknown PUR or PIR 0.15 No
Worksheet line (30): `Roof room Ext4 Flat Ceiling 1: 5 × 0.15 =
0.75 W/K` (U985-0001-000565 line 333).
Pre-slice the extractor allow-list `_RIR_INSULATION_THICKNESS_RE
| ("As Built", "None")` did NOT include the "Unknown" thickness
token, so the cell was dropped (`insulation = ""`). The mapper
translated `""` to `insulation_thickness_mm=0`, and the cascade
hit Table 17 row 0 → U=2.30 vs worksheet 0.15 (over-counting
BP[4] FC1 by +10.75 W/K on a 5 m² ceiling).
Two-layer fix:
1. Extractor (`elmhurst_extractor.py:_parse_rir_surface_row`) — add
"Unknown" as the third spec-valid thickness token alongside
"As Built" and "None".
2. Mapper (`mapper.py:_elmhurst_rir_insulation_thickness_mm`) —
return `Optional[int]`; "Unknown" → None. The cascade's existing
`_u_rr_table_17` already falls back to `u_rr_default_all_elements`
(Table 18 col 4) when thickness is None — for cert 000565 BP[4]
age band M, returns 0.15 W/m²K ✓.
Cascade no-op: the existing None → Table 18 col 4 fallback IS the
spec-correct path per §3.10.1; no calculator changes needed.
Movement at HEAD (cert 000565):
- BP[4] FC1 cascade U: 2.30 → 0.15 ✓ EXACT vs ws 0.15
- roof_w_per_k: 63.72 → 52.97 (Δ +12.34 → +1.59, closed -10.75)
- sap_score_continuous: 28.07 → 28.31 (Δ -0.44 → -0.20)
- sap_score (int): 28 (continuous still below 28.5 threshold;
remaining residual + BP[1] residual + BP[2] floor)
- SH: +533 → +218 kWh
Test count: 585 → 587 pass (+2 new AAA tests) + 9 expected 000565
fails unchanged.
Cohort safety: "Unknown" RIR insulation appears only in cert 000565
across the Summary fixture set (grep audit); cohort certs lodge
concrete thickness or "None"/"As Built". Pyright net-zero per
touched file.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
25f3af9eba
commit
32a4cf2080
3 changed files with 75 additions and 7 deletions
|
|
@ -530,7 +530,13 @@ class ElmhurstSiteNotesExtractor:
|
|||
insulation_type: Optional[str] = None
|
||||
gable_type: Optional[str] = None
|
||||
for t in middle:
|
||||
if self._RIR_INSULATION_THICKNESS_RE.match(t) or t in ("As Built", "None"):
|
||||
if self._RIR_INSULATION_THICKNESS_RE.match(t) or t in ("As Built", "None", "Unknown"):
|
||||
# "Unknown" is the third spec-valid thickness token
|
||||
# (RdSAP 10 §3.10.1 PDF p.24: "default U-values apply
|
||||
# when the roof room insulation is 'as built' or
|
||||
# 'unknown'"). Mapper routes "Unknown" to
|
||||
# insulation_thickness_mm=None so the cascade falls
|
||||
# back to Table 18 col 4 default.
|
||||
if not insulation:
|
||||
insulation = t
|
||||
elif t in ("Mineral or EPS", "PUR", "PIR", "PUR or PIR"):
|
||||
|
|
|
|||
|
|
@ -1738,6 +1738,60 @@ def test_summary_000565_ext2_stud_wall_2_routes_to_400mm_rigid_foam_via_mapper()
|
|||
assert sw_2.insulation_type == "rigid_foam"
|
||||
|
||||
|
||||
def test_summary_000565_ext4_flat_ceiling_1_extracts_unknown_thickness_pur_or_pir_lodgement() -> None:
|
||||
# Arrange — cert 000565 Summary §8.1 BP[4] Ext4 lodges:
|
||||
# "Flat Ceiling 1 5.00 1.00 Unknown PUR or PIR 0.15 No"
|
||||
# Worksheet line (30): `Roof room Ext4 Flat Ceiling 1: 5 × 0.15
|
||||
# = 0.75 W/K` (U985-0001-000565 line 333). Pre-slice the extractor
|
||||
# allow-list `_RIR_INSULATION_THICKNESS_RE | ("As Built", "None")`
|
||||
# did NOT include the "Unknown" thickness token, so the cell was
|
||||
# dropped (`insulation = ""`). Mapper translated `""` to
|
||||
# `insulation_thickness_mm=0`, cascade hit Table 17 row 0 → U=2.30
|
||||
# vs worksheet 0.15 (over by +10.75 W/K on a 5 m² ceiling).
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000565_PDF)
|
||||
|
||||
# Act
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
|
||||
# Assert
|
||||
ext4_rir = site_notes.extensions[3].room_in_roof
|
||||
assert ext4_rir is not None
|
||||
flat_ceiling_1 = next(s for s in ext4_rir.surfaces if s.name == "Flat Ceiling 1")
|
||||
assert flat_ceiling_1.insulation == "Unknown"
|
||||
assert flat_ceiling_1.insulation_type == "PUR or PIR"
|
||||
|
||||
|
||||
def test_summary_000565_ext4_flat_ceiling_1_maps_unknown_to_none_thickness_per_rdsap_10_section_3_10_1() -> None:
|
||||
# Arrange — RdSAP 10 §3.10.1 (PDF p.24) "Default U-values of the
|
||||
# roof rooms":
|
||||
# "Where the details of insulation are not available, the default
|
||||
# U-values are those for the appropriate age band for the
|
||||
# construction of the roof rooms (see Table 18 : Assumed roof
|
||||
# U-values when Table 16 or Table 17 do not apply). The default
|
||||
# U-values apply when the roof room insulation is 'as built' or
|
||||
# 'unknown'."
|
||||
# Translation: when Summary lodges "Unknown" thickness (regardless
|
||||
# of named insulation material), the mapper must set
|
||||
# `insulation_thickness_mm=None` (not 0). The cascade's existing
|
||||
# `_u_rr_table_17` falls back to `u_rr_default_all_elements`
|
||||
# (Table 18 col 4) → for cert 000565 BP[4] age band M, returns
|
||||
# 0.15 W/m²K ✓ matching the worksheet.
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000565_PDF)
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
|
||||
# Act
|
||||
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||
|
||||
# Assert
|
||||
ext4_rir = epc.sap_building_parts[4].sap_room_in_roof
|
||||
assert ext4_rir is not None
|
||||
detailed = ext4_rir.detailed_surfaces or []
|
||||
flat_ceilings = [s for s in detailed if s.kind == "flat_ceiling"]
|
||||
fc_1 = next(s for s in flat_ceilings if s.area_m2 == 5.0)
|
||||
assert fc_1.insulation_thickness_mm is None
|
||||
assert fc_1.insulation_type == "rigid_foam"
|
||||
|
||||
|
||||
def test_summary_000565_ext1_floor_above_partially_heated_routes_to_u_value_0p7_per_rdsap_10_section_5_14() -> None:
|
||||
# Arrange — RdSAP 10 §5.14 (PDF p.47) "U-value of floor above a
|
||||
# partially heated space":
|
||||
|
|
|
|||
|
|
@ -3269,13 +3269,21 @@ def _round_half_up_2dp(*operands: float) -> float:
|
|||
return float(product.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
|
||||
|
||||
|
||||
def _elmhurst_rir_insulation_thickness_mm(insulation_text: str) -> int:
|
||||
def _elmhurst_rir_insulation_thickness_mm(insulation_text: str) -> Optional[int]:
|
||||
"""Translate the Insulation cell ("100 mm", "400+ mm", "None", "As
|
||||
Built", "") into a thickness integer. The Elmhurst cohort uses "As
|
||||
Built" only on surfaces whose Default U-value is the uninsulated
|
||||
2.30 row, so treating it as 0 mm is consistent with the Table 17
|
||||
'none' column. The "400+ mm" bucket-cap (Table 17's largest
|
||||
tabulated row) is read as 400."""
|
||||
Built", "Unknown", "") into a thickness value. The Elmhurst cohort
|
||||
uses "As Built" only on surfaces whose Default U-value is the
|
||||
uninsulated 2.30 row, so treating it as 0 mm is consistent with
|
||||
the Table 17 'none' column. The "400+ mm" bucket-cap (Table 17's
|
||||
largest tabulated row) is read as 400.
|
||||
|
||||
"Unknown" returns None per RdSAP 10 §3.10.1 (PDF p.24): "default
|
||||
U-values apply when the roof room insulation is 'as built' or
|
||||
'unknown'". The cascade's `_u_rr_table_17` falls back to
|
||||
`u_rr_default_all_elements` (Table 18 col 4) when the thickness
|
||||
is None — so the spec's age-band default applies."""
|
||||
if insulation_text == "Unknown":
|
||||
return None
|
||||
if not insulation_text or insulation_text in ("None", "As Built"):
|
||||
return 0
|
||||
m = re.match(r"^(\d+)\+?\s*mm$", insulation_text)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue