Model/datatypes
Khalim Conn-Kowlessar 4cfec00f22 Slice S0380.17: map Elmhurst §11 glazing-type labels to SAP10 codes
Closes a systematic +0.02..+0.07 SAP over-prediction on every triple-
glazed cert in cohort 2 (13 of 38) and removes a silent-default
failure mode flagged via cert 3336-2825-9400-0512-8292 (+0.0674 Δ).

Root cause: `_map_elmhurst_window` (datatypes/epc/domain/mapper.py)
was passing the Elmhurst-lodged glazing-type string verbatim into
`SapWindow.glazing_type` (declared `Union[int, str]`). The §5 (66)..
(67) daylight-factor cascade at
`domain/sap10_calculator/worksheet/internal_gains.py:512` requires
`isinstance(w.glazing_type, int)` to look up Table 6b col light g_L —
string lodgings silently fell through to the `_G_LIGHT_DEFAULT = 0.80`
(double-glazed) branch. Cert 3336 (Triple glazed, worksheet "Window,
Triple glazed") got g_L = 0.80 instead of the correct 0.70, inflating
C_daylight from 1.072 to 1.041 → lighting kWh under-predicted by
−4.53 kWh/yr → total fuel cost under by −1.17 GBP → ECF Δ −0.0049 →
SAP continuous over by +0.0674.

Fix: `_ELMHURST_GLAZING_LABEL_TO_SAP10` dict + `_elmhurst_glazing_
type_code` helper translate the Elmhurst Summary §11 lodged strings
to the SAP 10.2 Table U2 integer codes the cascade keys on:

  "Single"                                          → 1
  "Double pre 2002"                                 → 2
  "Double between 2002 and 2021"                    → 3
  "Double with unknown install date"                → 3
  "Double with unknown 16 mm or install date more"  → 3
  "Double post or during 2022"                      → 5
  "Triple post or during 2022"                      → 6
  "Triple post or during"                           → 6  (year-trunc.)
  "Secondary"                                       → 7

Two regex passes strip the layout noise the extractor sometimes folds
into the glazing-type token: a `(?:Part )?value value Proofed Shutters`
prefix (from adjacent column headers) and a ` Summary Information` /
` Alternative wall…` suffix. Verified against the union of cohort-1
(7 certs) + cohort-2 (38 certs) + test-fixture (9 PDFs) glazing
labels: 18 distinct surface forms, all closed by the dict + noise
patterns; one window in cert 2636's Summary_000898.pdf lodged the
year-truncated "Triple post or during" — added as an alias for code 6
per worksheet "Triple glazed" lodging.

Strict-enum gate: `_elmhurst_glazing_type_code` raises
`UnmappedElmhurstLabel("glazing_type", label)` (Slice S0380.15
pattern, extended to the new helper) when the label is None or not
in the dict — surfaces mapper-coverage gaps at extraction time rather
than masking them as a SAP precision floor.

Cohort-2 Summary-path delta progression (38 certs):
  bucket          before slice 2    after slice 2
  exact (<1e-4)   11                11
  <0.005          0                 5     ← 9421 +0.0012, 2536 +0.0016, 9370 +0.0017, 0100 +0.0028, 2800 +0.0044
  0.005-0.07      15                10    ← all triple-glazed
  0.07-0.5        5                 5
  0.5-1           4                 4
  1-5             1                 1
  5+              2                 2
  RAISES          0                 0

3336 (user's flag) closes from +0.0674 → +0.0400 — the residual is
the remaining systematic offset the next slice will investigate.

Tests added (3):
- `test_summary_3336_triple_glazed_windows_route_to_code_6` — pins
  the mapper output for the user's flagged cert.
- `test_summary_000474_double_glazed_windows_route_to_code_3` —
  exercises the DG branch + the year-unknown alias mapping.
- `test_summary_mapper_raises_on_unmapped_glazing_type_label` —
  strict-enum coverage gate via mutated site notes.

Tests updated (1):
- `test_first_window_glazing_type` (test_elmhurst_end_to_end.py):
  asserts int code 5 (DG low-E argon — "Double post or during 2022")
  not the string verbatim. The string-passthrough behaviour was
  always a latent bug; this test was the only direct pin on it.

Pyright net-zero per file:
  - datatypes/epc/domain/mapper.py: 32 (baseline 32)
  - backend/documents_parser/tests/test_summary_pdf_mapper_chain.py: 0
  - backend/documents_parser/tests/test_elmhurst_end_to_end.py: 0

Regression baseline: 694 pass + 10 fail (= prior 691 + 10 + 3 new).
Triple-glazed original-cohort certs are now closer to worksheet too;
the ±0.07 chain tests on the original cohort still hold, and a future
slice tightens them once the next-largest residual is closed.

Spec refs:
- SAP 10.2 Table U2 — glazing-type integer enum.
- SAP 10.2 Table 6b col light — light-transmission g_L by glazing
  type (triple 0.70, double-glazed variants 0.80, single 0.90).
- RdSAP 10 §11 Windows — Summary lodging of glazing type as a
  type+install-date phrase.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 16:28:46 +00:00
..
epc Slice S0380.17: map Elmhurst §11 glazing-type labels to SAP10 codes 2026-06-01 16:28:46 +00:00
magicplan typing and renaming 🟪 2026-05-07 13:26:49 +00:00
__init__.py further breaking up code 2023-07-20 12:24:34 +01:00
datatypes.py further breaking up code 2023-07-20 12:24:34 +01:00
enums.py completed build of new demo portfolio - some fixed required still 2023-11-20 14:42:16 +00:00