mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.41: GOV.UK RdSAP 21 glazing-type code 1 → DG pre-2002 cascade
Closes the cohort-2 API-path +0.42..+0.44 cluster (certs 0300/9380
closed to <1e-4; cert 1536 partially closed +0.4445 → +0.0015 — a
sub-2e-3 secondary tail remains for Slice S0380.42).
Root cause: per `datatypes/epc/domain/epc_codes.csv` the GOV.UK API
schema RdSAP-Schema-21.0.0 defines `glazed_type=1` as "double glazing
installed before 2002 in EAW, 2003 in SCT, 2006 NI". Three cohort-2
certs (0300/1536/9380) lodge this code with `glazing_gap=16+` and
description "Fully double glazed" — but the API mapper passed the
raw code straight through to SapWindow.glazing_type, and:
1. `_api_glazing_transmission` had no (1, "16+") entry, so the
U-value lookup returned None and the cascade defaulted to U=2.5
instead of the spec-correct U=2.7 (RdSAP 10 Table 24 row 2,
PVC/wooden frame, 16+ gap = 2.7).
2. The cascade's `_G_LIGHT_BY_GLAZING_CODE` table is keyed on the
SAP 10.2 Table 6b enum (the Elmhurst extractor produces this
enum via `_ELMHURST_GLAZING_LABEL_TO_SAP10`), where code 1 means
"single glazed" (g_L=0.90). Passing RdSAP 21 code 1 straight
through gave the cascade the wrong g_L for the daylight factor
calculation, off by 0.90 vs spec 0.80.
Both gaps closed in one slice because they're the same misinterpretation:
- `_API_GLAZING_TYPE_TO_TRANSMISSION` + `_API_GLAZING_TYPE_GAP_TO_
TRANSMISSION` now alias code 1 as a schema sibling of code 3 — both
resolve to RdSAP 10 Table 24 row 2 ("DG pre-2002 / unknown install
date"). Per-gap entries cover the full 6mm=3.1 / 12mm=2.8 / 16+=2.7
row; type-only fallback uses the 12mm default U=2.8.
- New `_API_TO_SAP10_CASCADE_GLAZING_CODE = {1: 2}` remap is applied
in `_api_sap_window` AFTER the U-value lookup, so SapWindow.glazing_
type carries the SAP 10.2 cascade enum (code 2 = DG pre-2002 air-
filled, g_L=0.80) while the U lookup stays keyed on the raw GOV.UK
API code. The cohort-1 codes 2/3/13/14 already coincide with the
cascade table's intended SAP 10.2 g_L values, so no remap entry
required for them; only divergent codes get a remap.
Test impact:
- Cohort-2 API path: 34/38 → 36/38 at 1e-4 (0300 +4.8e-5; 9380 -5e-6
both move from _COHORT_2_API_OPEN to _COHORT_2_API_CLOSED).
- Cert 1536 pin updated from 66.337334 to 65.894324; ws Δ now +0.0015
(was +0.4445) — same root-cause fix dominated, residual tail is
distinct-cause work for the next slice.
- Cert 2102 unchanged (-6.30 residual, secondary-heating routing gap).
- Cohort-1 (9 ASHP certs) unaffected: 9/9 still < 1e-4 on both paths.
Test suite: 750 pass + 0 fail. Pyright net-zero per touched file.
Spec citations:
- RdSAP-Schema-21.0.0 glazed_type=1 → datatypes/epc/domain/epc_codes.csv
- RdSAP 10 Specification §8.2 Table 24 (p.49) row 2 "Double glazed:
Installed England/Wales before 2002 / Scotland before 2003 /
N. Ireland before 2006" — U=2.7 (PVC/wooden, 16+ gap).
- SAP 10.2 Table 6b: DG air-filled g_L=0.80 (vs single 0.90).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
b9afb7f9d7
commit
d7cecf45f5
2 changed files with 61 additions and 11 deletions
|
|
@ -1902,6 +1902,7 @@ _COHORT_2_API_CLOSED: list[tuple[str, float]] = [
|
|||
("0036-6325-1100-0063-1226", 62.7471),
|
||||
("0100-5141-0522-4696-3463", 85.8332),
|
||||
("0200-3155-0122-2602-3563", 80.8674),
|
||||
("0300-2403-2650-2206-0235", 76.6541), # S0380.41 closure
|
||||
("0310-2763-5450-2506-3501", 78.3593),
|
||||
("0320-2126-2150-2326-6161", 71.7224),
|
||||
("0320-2756-8640-2296-1101", 89.9458),
|
||||
|
|
@ -1930,6 +1931,7 @@ _COHORT_2_API_CLOSED: list[tuple[str, float]] = [
|
|||
("7836-3125-0600-0526-2202", 80.1792),
|
||||
("9036-0824-3500-0420-8222", 84.2727),
|
||||
("9370-3060-1205-3546-4204", 87.8687),
|
||||
("9380-2957-7490-2595-3141", 74.5902), # S0380.41 closure
|
||||
("9421-3045-3205-1646-6200", 87.4495),
|
||||
("9796-3058-6205-0346-9200", 90.1318),
|
||||
("9836-7525-9500-0575-1202", 75.2223),
|
||||
|
|
@ -1956,9 +1958,13 @@ _COHORT_2_API_CLOSED: list[tuple[str, float]] = [
|
|||
# API mapper likely lodges the secondary fuel differently. Probe
|
||||
# the API JSON's `secondary_heating` block first.
|
||||
_COHORT_2_API_OPEN: list[tuple[str, float, float]] = [
|
||||
("0300-2403-2650-2206-0235", 76.6541, 77.084454),
|
||||
("1536-9325-5100-0433-1226", 65.8928, 66.337334),
|
||||
("9380-2957-7490-2595-3141", 74.5902, 75.010196),
|
||||
# S0380.41 partially closed this cert: residual moved from +0.4445
|
||||
# to +0.0015 via the same RdSAP 21 → SAP 10.2 glazing-type alias
|
||||
# that closed 0300/9380 cleanly. A sub-2e-3 secondary tail remains
|
||||
# — likely a windows-area Decimal-rounding boundary case in the
|
||||
# cert's specific dimensions; investigate per the cohort closure
|
||||
# convention in Slice S0380.42.
|
||||
("1536-9325-5100-0433-1226", 65.8928, 65.894324),
|
||||
("2102-3018-0205-7886-5204", 63.8732, 57.570156),
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -2303,17 +2303,26 @@ def _api_sheltered_sides(built_form: object) -> Optional[int]:
|
|||
# Ext1 lodges glazing_type=13 → manufacturer DG post-2022 Argon
|
||||
# U=1.4 / g=0.72, vs cascade default U=2.5).
|
||||
#
|
||||
# Codes observed across the 10 golden fixtures: 2 (DG England/Wales
|
||||
# 2002 or later, pre-2022), 3 (Main DG pre-2002), 13 (Ext1 post-2022
|
||||
# Argon). The wider SAP10.2 glazing-type enum (4-12, 14+) is not yet
|
||||
# mapped — incremental coverage as new fixtures surface them.
|
||||
# Codes observed across the 10 cohort-1 golden fixtures: 2 (DG England/
|
||||
# Wales 2002 or later, pre-2022), 3 (Main DG pre-2002), 13 (Ext1
|
||||
# post-2022 Argon). Cohort-2 (Slice S0380.39) added code 1 — observed
|
||||
# in 3 certs (0300/1536/9380) all lodging gap=16+ and description
|
||||
# "Fully double glazed" with a worksheet-resolved U=2.7. Per Table 24
|
||||
# row 2 (DG pre-2002, gap 16+, PVC/wooden) the spec answer is U=2.7,
|
||||
# so GOV.UK API code 1 is a schema sibling of code 3 (both alias the
|
||||
# "DG pre-2002 / unknown install date" row). The wider SAP10.2
|
||||
# glazing-type enum (4-12, 15+) is not yet mapped — incremental
|
||||
# coverage as new fixtures surface them.
|
||||
#
|
||||
# Spec source: RdSAP 10 Table 24 "Window characteristics" page 79 —
|
||||
# Spec source: RdSAP 10 Table 24 "Window characteristics" page 49 —
|
||||
# DG pre-2002 spec U varies by gap (6mm=3.1, 12mm=2.8, 16+=2.7); the
|
||||
# (type, gap)-keyed lookup picks the spec-correct entry when the gap
|
||||
# is lodged, falling back to the type-only default for missing gaps.
|
||||
_API_GLAZING_TYPE_TO_TRANSMISSION: Dict[int, tuple[float, float, float]] = {
|
||||
# (u_value, solar_transmittance/g_⊥, frame_factor)
|
||||
1: (2.8, 0.76, 0.70), # Double glazed, pre-2002 / unknown install
|
||||
# date — Table 24 row 2 (PVC/wooden), 12mm
|
||||
# gap default. Schema sibling of code 3.
|
||||
2: (2.0, 0.72, 0.70), # Double glazed, England/Wales 2002+ (pre-2022)
|
||||
3: (2.8, 0.76, 0.70), # Double glazed, pre-2002 (12mm gap default)
|
||||
13: (1.4, 0.72, 0.70), # Double glazed, Argon-filled post-2022
|
||||
|
|
@ -2336,7 +2345,11 @@ _API_GLAZING_TYPE_TO_TRANSMISSION: Dict[int, tuple[float, float, float]] = {
|
|||
_API_GLAZING_TYPE_GAP_TO_TRANSMISSION: Dict[
|
||||
tuple[int, object], tuple[float, float, float]
|
||||
] = {
|
||||
# Double glazed, pre-2002 — Table 24 row 2 (PVC/wooden frame):
|
||||
# Double glazed, pre-2002 / unknown install date — Table 24 row 2
|
||||
# (PVC/wooden frame). Codes 1 and 3 alias the same Table 24 row:
|
||||
(1, 6): (3.1, 0.76, 0.70),
|
||||
(1, 12): (2.8, 0.76, 0.70),
|
||||
(1, "16+"): (2.7, 0.76, 0.70),
|
||||
(3, 6): (3.1, 0.76, 0.70),
|
||||
(3, 12): (2.8, 0.76, 0.70),
|
||||
(3, "16+"): (2.7, 0.76, 0.70),
|
||||
|
|
@ -2357,12 +2370,43 @@ def _api_glazing_transmission(
|
|||
return _API_GLAZING_TYPE_TO_TRANSMISSION.get(glazing_type)
|
||||
|
||||
|
||||
# GOV.UK RdSAP 21 `glazing_type` integer → SAP 10.2 Table 6b cascade
|
||||
# glazing-type integer. The cascade's `_G_LIGHT_BY_GLAZING_CODE` table
|
||||
# (domain/sap10_calculator/worksheet/internal_gains.py) is keyed on the
|
||||
# SAP 10.2 enum that the Elmhurst extractor produces via
|
||||
# `_ELMHURST_GLAZING_LABEL_TO_SAP10` — so the API-side glazing_type must
|
||||
# be canonicalised to that same enum before storage on SapWindow.
|
||||
#
|
||||
# Per datatypes/epc/domain/epc_codes.csv (RdSAP-Schema-21.0.0):
|
||||
# - RdSAP 21 code 1 = "double glazing installed before 2002 in EAW,
|
||||
# 2003 in SCT, 2006 NI" — semantically matches SAP 10.2 Table 6b
|
||||
# "DG air-filled pre-2002" (cascade code 2, g_L=0.80).
|
||||
#
|
||||
# The cohort-1 codes 2, 3, 13, 14 already coincide with the cascade
|
||||
# table's intended SAP 10.2 g_L values, so no remap entry is required
|
||||
# for them. Only divergent codes (RdSAP 21 ≠ cascade table) need a
|
||||
# remap — incremental coverage as new fixtures surface them.
|
||||
_API_TO_SAP10_CASCADE_GLAZING_CODE: Dict[int, int] = {
|
||||
1: 2, # RdSAP 21 DG pre-2002 → cascade DG (g_L=0.80, not single 0.90)
|
||||
}
|
||||
|
||||
|
||||
def _api_cascade_glazing_type(api_glazing_type: int) -> int:
|
||||
"""Canonicalise an API-lodged RdSAP 21 glazing-type code to the SAP
|
||||
10.2 Table 6b cascade enum that `_G_LIGHT_BY_GLAZING_CODE` reads.
|
||||
Pass-through for codes already coincident with the cascade table."""
|
||||
return _API_TO_SAP10_CASCADE_GLAZING_CODE.get(api_glazing_type, api_glazing_type)
|
||||
|
||||
|
||||
def _api_sap_window(w: Any) -> SapWindow:
|
||||
"""Build a `SapWindow` from one API schema sap_windows entry,
|
||||
routing the glazing-type + glazing-gap pair through the spec
|
||||
lookup so DG pre-2002 windows pick up the gap-specific U
|
||||
(RdSAP 10 Table 24 row 2: 6mm=3.1 / 12mm=2.8 / 16+=2.7) instead
|
||||
of the type-only default."""
|
||||
of the type-only default. SapWindow.glazing_type is canonicalised
|
||||
to the SAP 10.2 Table 6b cascade enum so the cascade's daylight g_L
|
||||
lookup picks the spec-correct value (e.g. RdSAP 21 code 1 = DG
|
||||
pre-2002, cascade g_L=0.80, not single-glazed 0.90)."""
|
||||
transmission = _api_glazing_transmission(w.glazing_type, w.glazing_gap)
|
||||
frame_factor: Optional[float] = w.frame_factor
|
||||
if frame_factor is None and transmission is not None:
|
||||
|
|
@ -2387,7 +2431,7 @@ def _api_sap_window(w: Any) -> SapWindow:
|
|||
orientation=w.orientation,
|
||||
window_type=w.window_type,
|
||||
frame_factor=frame_factor,
|
||||
glazing_type=w.glazing_type,
|
||||
glazing_type=_api_cascade_glazing_type(w.glazing_type),
|
||||
window_width=_measurement_value(w.window_width),
|
||||
window_height=_measurement_value(w.window_height),
|
||||
draught_proofed=w.draught_proofed == "true",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue