Slice S0380.3: surface wall_insulation_type=6 for 'FE Filled Cavity + External'

Extends `_ELMHURST_INSULATION_CODE_TO_SAP10` in
`datatypes/epc/domain/mapper.py` with the two-letter dual codes
documented on Elmhurst Summary PDFs:

  "FE" → 6  (Filled cavity + External insulation; cohort fixture)
  "FI" → 7  (Filled cavity + Internal insulation; mirror, no fixture)

The cascade `wall_insulation_type` enum (per
`domain/sap10_ml/rdsap_uvalues.py` lines 120-131) treats codes 6 and
7 as composite-resistance walls (filled cavity in series with an
external/internal insulation layer), routing through a different
U-value calc than the plain filled-cavity default. Cert 0380's
Summary lodges `walls.insulation = "FE Filled Cavity + External"`
which until this slice fell through `_leading_code` to a missing
dict entry and the mapper produced `wall_insulation_type=None`,
defaulting the cascade to the as-built path and overstating walls
heat loss by +58 W/K.

Forcing function (Slice S0380.1): cert 0380 Summary cascade SAP
moves from 81.7528 (Δ -6.7576 — i.e. after Slice S0380.2 only) to
86.8671 (Δ -1.6433) — closes ~76% of the remaining gap. `walls_w_per_k`
drops from 69.6900 to 24.6238. Residual ~13 W/K wall gap vs API's
11.6150 is the next workstream: `wall_insulation_thickness` is still
None on the Summary EPC (API lodges '100mm'). Without the thickness
the cascade applies the composite U-value at the dual-code's default
thickness rather than the lodged 100 mm.

Added focused unit test
`test_summary_0380_filled_cavity_plus_external_insulation_routes_to_code_6`
that pins both `wall_construction == 4` and `wall_insulation_type == 6`
on the mapper boundary, so future debuggers can localise regressions
in the dual-code lookup before walking the full chain.

Pyright baseline preserved:
  datatypes/epc/domain/mapper.py: 32 errors (no new errors introduced)
  backend/documents_parser/tests/test_summary_pdf_mapper_chain.py: 0 errors

Regression suite: 671 pass + 11 fail (vs handover baseline 669 + 10 —
net +2 pass for the two new GREEN unit tests across Slices S0380.2-3,
+1 fail still being the S0380.1 chain test that this slice continues
to close but does not yet fully resolve).

Spec refs:
- SAP 10.2 §3.7 / Table S5 (U-values for masonry walls — composite
  filled-cavity-plus-insulation calc)
- `domain/sap10_ml/rdsap_uvalues.py:120` (RdSAP schema
  `wall_insulation_type` enum: 6 = filled cavity + external)
- Cert 0380 worksheet `dr87-0001-000899.pdf` (lodges Mitsubishi
  PUZ-WM50VHA ASHP on a cavity wall with subsequent external
  insulation — the composite-wall fixture)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-27 18:06:02 +00:00
parent b1a1bb8dbb
commit 575cdd539a
2 changed files with 33 additions and 0 deletions

View file

@ -514,6 +514,30 @@ def test_summary_0380_main_heating_category_is_heat_pump() -> None:
assert main.main_heating_category == 4
def test_summary_0380_filled_cavity_plus_external_insulation_routes_to_code_6() -> None:
# Arrange — cert 0380's Summary lodges main walls as
# `wall_type = "CA Cavity"` and `insulation = "FE Filled Cavity +
# External"` (a cavity wall with subsequent external-insulation
# upgrade). The cascade enum `wall_insulation_type=6` is
# "filled cavity + external insulation" (per
# `domain.sap10_ml.rdsap_uvalues` lines 120-131); without it the
# cascade defaults to the as-built routing and overstates walls
# heat loss by +58 W/K on cert 0380 (Summary 69.69 vs API 11.62
# at HEAD before this slice). API path EPC for cert 0380 surfaces
# `wall_insulation_type=6` and is the ground-truth pin here.
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000899_PDF)
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
# Act
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
# Assert
assert epc.sap_building_parts, "no building parts surfaced"
main = epc.sap_building_parts[0]
assert main.wall_construction == 4 # 4 = Cavity ('CA')
assert main.wall_insulation_type == 6 # 6 = filled cavity + external
def test_summary_0380_full_chain_sap_matches_worksheet_pdf_exactly() -> None:
# Arrange — cert 0380-2471-3250-2596-8761 (Summary_000899.pdf /
# dr87-0001-000899.pdf) is the first heat-pump cert under per-cert

View file

@ -2040,12 +2040,21 @@ _ELMHURST_WALL_CODE_TO_SAP10: Dict[str, int] = {
# Elmhurst wall-insulation-type codes mapped to the SAP10 integer enum
# documented at domain.sap10_ml.rdsap_uvalues.WALL_INSULATION_FILLED_CAVITY.
# Two-letter dual codes ("FE", "FI") encode a cavity-wall that received a
# subsequent external- or internal-insulation upgrade — these map to the
# SAP10 composite codes 6 and 7, which the cascade routes through the
# series-resistance composite-U calc rather than the filled-cavity table
# default. Cert 0380's `walls.insulation = "FE Filled Cavity + External"`
# is the cohort fixture pinning the FE branch.
_ELMHURST_INSULATION_CODE_TO_SAP10: Dict[str, int] = {
"E": 1, # External wall insulation
"F": 2, # Filled cavity
"I": 3, # Internal wall insulation
"A": 4, # As built / assumed (default cascade)
"N": 5, # None specified
"FE": 6, # Filled cavity + external insulation (cohort: cert 0380)
"FI": 7, # Filled cavity + internal insulation (no fixture yet —
# mirror of FE for the internal-upgrade variant)
}