mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Slice 93: API mapper window_transmission_details from glazing_type
The API schema lodges `glazing_type` (int code) per window but `window_transmission_details=None` and `frame_factor=None`. Without per-window U lodgement the cascade falls back to a single global `u_window(None,None,None)=2.5` × total area, which over-shot cert 001479's window W/K by +2.63 (cascade 46.23 vs worksheet 43.60). Fix: `_API_GLAZING_TYPE_TO_TRANSMISSION` lookup translates `glazing_type` → (u_value, solar_transmittance, frame_factor) and the mapper populates `WindowTransmissionDetails` + `frame_factor` per window so the cascade uses its per-window U fast path (each window contributes A × U_eff_individual rather than total_area × U_eff_global). Two codes mapped now: 3 → DG pre-2002 U=2.8 g=0.76 FF=0.70 13 → DG post-2022 Argon U=1.4 g=0.72 FF=0.70 Cert 001479 lodges 8 Main windows at glazing_type=3 + 1 Ext1 window at glazing_type=13 — exactly the manufacturer-lodged worksheet values. The cascade now matches the worksheet's `Windows 1: 13.96 × 2.518 = 35.15 W/K` and `Windows 2: 6.37 × 1.3258 = 8.45 W/K` → **windows W/K EXACT 43.5962**. **Cert 001479 API path: fabric heat loss is now COMPLETELY EXACT across all 6 components** (walls/party/roof/floor/windows/doors all match worksheet at the worksheet's 4 d.p. precision). Total fabric: 139.4957 W/K ✓ (was 122.6130 before Slice 87) walls: 39.7652 ✓ party walls: 17.0700 ✓ roof: 10.3438 ✓ floor: 23.1705 ✓ windows: 43.5962 ✓ doors: 5.5500 ✓ API SAP delta progression through Slices 87-93: Slice 87 baseline: +3.0752 After Slice 90: +1.5298 (party walls) After Slice 91: +1.0970 (descriptive strings + roof desc) After Slice 92: +1.0022 (floor dims) After Slice 93: +1.1846 (windows — fabric now EXACT) The +1.18 SAP gap is now PURELY non-fabric: candidates are internal gains, solar gains, ventilation, MIT, or hot water cascade — to diagnose in the next slice. Golden cert residuals updated for the cascade improvements. Pyright net-zero on mapper.py (33 → 33). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
8e752e5720
commit
7281b7b300
2 changed files with 58 additions and 16 deletions
|
|
@ -1524,7 +1524,11 @@ class EpcPropertyDataMapper:
|
||||||
glazing_gap=w.glazing_gap,
|
glazing_gap=w.glazing_gap,
|
||||||
orientation=w.orientation,
|
orientation=w.orientation,
|
||||||
window_type=w.window_type,
|
window_type=w.window_type,
|
||||||
frame_factor=w.frame_factor,
|
frame_factor=(
|
||||||
|
w.frame_factor
|
||||||
|
if w.frame_factor is not None
|
||||||
|
else _API_GLAZING_TYPE_TO_TRANSMISSION.get(w.glazing_type, (None, None, None))[2]
|
||||||
|
),
|
||||||
glazing_type=w.glazing_type,
|
glazing_type=w.glazing_type,
|
||||||
window_width=_measurement_value(w.window_width),
|
window_width=_measurement_value(w.window_width),
|
||||||
window_height=_measurement_value(w.window_height),
|
window_height=_measurement_value(w.window_height),
|
||||||
|
|
@ -1532,6 +1536,16 @@ class EpcPropertyDataMapper:
|
||||||
window_location=w.window_location,
|
window_location=w.window_location,
|
||||||
window_wall_type=w.window_wall_type,
|
window_wall_type=w.window_wall_type,
|
||||||
permanent_shutters_present=w.permanent_shutters_present == "Y",
|
permanent_shutters_present=w.permanent_shutters_present == "Y",
|
||||||
|
# When the API lodgement carries explicit
|
||||||
|
# `window_transmission_details`, pass through verbatim
|
||||||
|
# (Manufacturer-lodged U + solar takes precedence over
|
||||||
|
# the cascade default). Otherwise derive from the
|
||||||
|
# `glazing_type` integer code via the SAP10 lookup —
|
||||||
|
# gives the cascade per-window U-values for the
|
||||||
|
# `windows_have_per_window_u` fast path in
|
||||||
|
# `heat_transmission.py`, matching the cohort
|
||||||
|
# Elmhurst behaviour (which sets these per-window via
|
||||||
|
# `make_window`).
|
||||||
window_transmission_details=(
|
window_transmission_details=(
|
||||||
WindowTransmissionDetails(
|
WindowTransmissionDetails(
|
||||||
u_value=w.window_transmission_details.u_value,
|
u_value=w.window_transmission_details.u_value,
|
||||||
|
|
@ -1539,7 +1553,15 @@ class EpcPropertyDataMapper:
|
||||||
solar_transmittance=w.window_transmission_details.solar_transmittance,
|
solar_transmittance=w.window_transmission_details.solar_transmittance,
|
||||||
)
|
)
|
||||||
if w.window_transmission_details is not None
|
if w.window_transmission_details is not None
|
||||||
else None
|
else (
|
||||||
|
WindowTransmissionDetails(
|
||||||
|
u_value=_API_GLAZING_TYPE_TO_TRANSMISSION[w.glazing_type][0],
|
||||||
|
data_source="SAP10 lookup (glazing_type)",
|
||||||
|
solar_transmittance=_API_GLAZING_TYPE_TO_TRANSMISSION[w.glazing_type][1],
|
||||||
|
)
|
||||||
|
if w.glazing_type in _API_GLAZING_TYPE_TO_TRANSMISSION
|
||||||
|
else None
|
||||||
|
)
|
||||||
),
|
),
|
||||||
permanent_shutters_insulated=w.permanent_shutters_insulated,
|
permanent_shutters_insulated=w.permanent_shutters_insulated,
|
||||||
)
|
)
|
||||||
|
|
@ -2070,6 +2092,26 @@ def _api_roof_construction_str(value: Optional[int]) -> Optional[str]:
|
||||||
_API_FLOOR_HEAT_LOSS_EXPOSED: Final[int] = 1
|
_API_FLOOR_HEAT_LOSS_EXPOSED: Final[int] = 1
|
||||||
|
|
||||||
|
|
||||||
|
# GOV.UK API `glazing_type` integer → (u_value W/m²K, g_perpendicular,
|
||||||
|
# frame_factor) lookup the cascade reads via `window_transmission_
|
||||||
|
# details` for per-window cascade fidelity. The cascade defaults to a
|
||||||
|
# single global `u_window(None,None,None)=2.5` and `_G_PERPENDICULAR_
|
||||||
|
# DEFAULT=0.76` when these are unset — close to right for typical
|
||||||
|
# DG-pre-2002 dwellings but wildly off for newer DG (e.g. cert 001479
|
||||||
|
# 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: 3 (Main DG pre-2002)
|
||||||
|
# and 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.
|
||||||
|
_API_GLAZING_TYPE_TO_TRANSMISSION: Dict[int, tuple[float, float, float]] = {
|
||||||
|
# (u_value, solar_transmittance/g_⊥, frame_factor)
|
||||||
|
3: (2.8, 0.76, 0.70), # Double glazed, pre-2002
|
||||||
|
13: (1.4, 0.72, 0.70), # Double glazed, Argon-filled post-2022
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _api_build_sap_floor_dimensions(
|
def _api_build_sap_floor_dimensions(
|
||||||
fds: List[Any],
|
fds: List[Any],
|
||||||
floor_heat_loss: Optional[int],
|
floor_heat_loss: Optional[int],
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
||||||
cert_number="0300-2747-7640-2526-2135",
|
cert_number="0300-2747-7640-2526-2135",
|
||||||
actual_sap=78,
|
actual_sap=78,
|
||||||
expected_sap_resid=+2,
|
expected_sap_resid=+2,
|
||||||
expected_pe_resid_kwh_per_m2=-0.9139,
|
expected_pe_resid_kwh_per_m2=-0.2955,
|
||||||
expected_co2_resid_tonnes_per_yr=-0.9974,
|
expected_co2_resid_tonnes_per_yr=-0.9443,
|
||||||
notes=(
|
notes=(
|
||||||
"Large semi-detached, TFA 526, age D, gas boiler PCDB-listed "
|
"Large semi-detached, TFA 526, age D, gas boiler PCDB-listed "
|
||||||
"(no Table 4b code). Cert lodges open_flues_count=1 + "
|
"(no Table 4b code). Cert lodges open_flues_count=1 + "
|
||||||
|
|
@ -119,8 +119,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
||||||
cert_number="6035-7729-2309-0879-2296",
|
cert_number="6035-7729-2309-0879-2296",
|
||||||
actual_sap=70,
|
actual_sap=70,
|
||||||
expected_sap_resid=-5,
|
expected_sap_resid=-5,
|
||||||
expected_pe_resid_kwh_per_m2=+37.7305,
|
expected_pe_resid_kwh_per_m2=+39.1452,
|
||||||
expected_co2_resid_tonnes_per_yr=+0.8510,
|
expected_co2_resid_tonnes_per_yr=+0.8845,
|
||||||
notes=(
|
notes=(
|
||||||
"Mid-terrace, TFA 128, age A, gas combi Table 4b code 104. "
|
"Mid-terrace, TFA 128, age A, gas combi Table 4b code 104. "
|
||||||
"Slice 59 per-bp window apportionment tightens all 3 "
|
"Slice 59 per-bp window apportionment tightens all 3 "
|
||||||
|
|
@ -132,9 +132,9 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
||||||
_GoldenExpectation(
|
_GoldenExpectation(
|
||||||
cert_number="7536-3827-0600-0600-0276",
|
cert_number="7536-3827-0600-0600-0276",
|
||||||
actual_sap=68,
|
actual_sap=68,
|
||||||
expected_sap_resid=+2,
|
expected_sap_resid=+1,
|
||||||
expected_pe_resid_kwh_per_m2=-11.7633,
|
expected_pe_resid_kwh_per_m2=-8.8124,
|
||||||
expected_co2_resid_tonnes_per_yr=-0.3124,
|
expected_co2_resid_tonnes_per_yr=-0.2337,
|
||||||
notes=(
|
notes=(
|
||||||
"Detached + 2 extensions, TFA 152. Multi-age bps (Main=D, "
|
"Detached + 2 extensions, TFA 152. Multi-age bps (Main=D, "
|
||||||
"Ext1=L, Ext2=F). Slice 59 (per-bp window apportionment) and "
|
"Ext1=L, Ext2=F). Slice 59 (per-bp window apportionment) and "
|
||||||
|
|
@ -147,8 +147,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
||||||
cert_number="8135-1728-8500-0511-3296",
|
cert_number="8135-1728-8500-0511-3296",
|
||||||
actual_sap=72,
|
actual_sap=72,
|
||||||
expected_sap_resid=+1,
|
expected_sap_resid=+1,
|
||||||
expected_pe_resid_kwh_per_m2=-13.0069,
|
expected_pe_resid_kwh_per_m2=-10.0737,
|
||||||
expected_co2_resid_tonnes_per_yr=-0.2200,
|
expected_co2_resid_tonnes_per_yr=-0.1645,
|
||||||
notes=(
|
notes=(
|
||||||
"Semi-detached, TFA 102, age C, gas PCDB-listed. Cert lodges "
|
"Semi-detached, TFA 102, age C, gas PCDB-listed. Cert lodges "
|
||||||
"blocked_chimneys_count=1. Slice 59 per-bp window apportionment "
|
"blocked_chimneys_count=1. Slice 59 per-bp window apportionment "
|
||||||
|
|
@ -160,8 +160,8 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
||||||
cert_number="2130-1033-4050-5007-8395",
|
cert_number="2130-1033-4050-5007-8395",
|
||||||
actual_sap=82,
|
actual_sap=82,
|
||||||
expected_sap_resid=+2,
|
expected_sap_resid=+2,
|
||||||
expected_pe_resid_kwh_per_m2=-44.8941,
|
expected_pe_resid_kwh_per_m2=-43.5103,
|
||||||
expected_co2_resid_tonnes_per_yr=+0.2250,
|
expected_co2_resid_tonnes_per_yr=+0.2414,
|
||||||
notes=(
|
notes=(
|
||||||
"End-terrace + 1 extension, TFA 64, gas combi PCDB index 17505, "
|
"End-terrace + 1 extension, TFA 64, gas combi PCDB index 17505, "
|
||||||
"postcode DE22 (PCDB Table 172 match), PV: 2× 2.04 kWp arrays "
|
"postcode DE22 (PCDB Table 172 match), PV: 2× 2.04 kWp arrays "
|
||||||
|
|
@ -179,9 +179,9 @@ _EXPECTATIONS: tuple[_GoldenExpectation, ...] = (
|
||||||
_GoldenExpectation(
|
_GoldenExpectation(
|
||||||
cert_number="0390-2254-6420-2126-5561",
|
cert_number="0390-2254-6420-2126-5561",
|
||||||
actual_sap=65,
|
actual_sap=65,
|
||||||
expected_sap_resid=+1,
|
expected_sap_resid=+0,
|
||||||
expected_pe_resid_kwh_per_m2=-3.5091,
|
expected_pe_resid_kwh_per_m2=-1.9117,
|
||||||
expected_co2_resid_tonnes_per_yr=-0.0136,
|
expected_co2_resid_tonnes_per_yr=+0.0102,
|
||||||
notes=(
|
notes=(
|
||||||
"End-terrace + 1 extension, TFA 80, gas combi PCDB index 18119, "
|
"End-terrace + 1 extension, TFA 80, gas combi PCDB index 18119, "
|
||||||
"no PV, no secondary, postcode LN12 (PCDB Table 172 match). "
|
"no PV, no secondary, postcode LN12 (PCDB Table 172 match). "
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue