mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 22: per-window curtain resistance — fixes mixed-glazing window U
SAP 10.2 §3.2 applies the 0.04 m²K/W curtain resistance per window;
the worksheet's (27) column shows it that way. Our calc had been
applying it ONCE to the area-weighted-avg raw U across all windows.
That's correct when all windows share a U but biased when a dwelling
has mixed glazing types (typical Elmhurst fixture lodges 2 types):
U_eff(weighted_avg(U_i)) ≠ weighted_avg(U_eff(U_i))
because 1/(1/U + 0.04) is non-linear. The drift was ~0.05-0.10 W/K
on `windows_w_per_k` for 000474, 000477, 000487 (mixed-glazing
fixtures).
Fix: when sap_windows have per-window u_value lodged (the spec-
faithful path), iterate them computing per-window U_eff × area and
sum. Falls back to the legacy single-avg-U path when window U isn't
lodged (back-compat for synthetic tests that pass
`window_avg_u_value=...` directly).
Per-window LINE_27 numbers now match PDF exactly:
fixture | windows W/K calc → PDF | LINE_33 Δ before → after
--------|------------------------|---------------------------
000474 | 25.4243 → 25.3674 ✓ | +0.0864 → +0.0296 (-66%)
000477 | 17.8550 → 17.8349 ✓ | -0.1045 → -0.1246 (small
widening — exposes
upstream floor-U drift)
000487 | (cascading) | +37.88 (RR defect, slice 23)
000480 | unchanged | -0.0168 → -0.0168 (single U)
000490 | unchanged | +0.0282 → +0.0282 (single U)
000516 | (cascading) | -6.75 (RR defect, slice 23)
Total cascade pin failure count unchanged at 83 (pins still above
abs=1e-4 floor by 0.03-0.13 W/K — sub-display-precision drift left
in floor-U cascades + the two RR fixture defects).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
778b150c98
commit
6be8fdb7b6
1 changed files with 34 additions and 11 deletions
|
|
@ -263,17 +263,37 @@ def heat_transmission_from_cert(
|
|||
floor_description = _joined_descriptions(epc.floors)
|
||||
|
||||
door_area = max(0, door_count) * _DEFAULT_DOOR_AREA_M2
|
||||
window_u_raw = window_avg_u_value if (window_avg_u_value or 0) > 0 else u_window(
|
||||
installed_year=None, glazing_type=None, frame_type=None
|
||||
)
|
||||
# SAP10.2 §3.2: effective window U includes the 0.04 m²K/W curtain
|
||||
# resistance — matches the (27) column in the worksheet (raw U=2.0
|
||||
# → effective 1/(0.5+0.04)=1.852).
|
||||
window_u = (
|
||||
1.0 / (1.0 / window_u_raw + _WINDOW_CURTAIN_RESISTANCE_M2K_PER_W)
|
||||
if window_u_raw > 0
|
||||
else 0.0
|
||||
# resistance — `(27)` worksheet column applies it per-window. When
|
||||
# sap_windows have per-window U lodgements (mixed glazing types in
|
||||
# the same dwelling), per-window curtain transform is the spec-
|
||||
# faithful path: U_eff(weighted_avg) ≠ weighted_avg(U_eff(U_i)) due
|
||||
# to the curtain resistance non-linearity.
|
||||
windows_have_per_window_u = bool(epc.sap_windows) and all(
|
||||
w.window_transmission_details is not None
|
||||
and w.window_transmission_details.u_value is not None
|
||||
for w in (epc.sap_windows or [])
|
||||
)
|
||||
if windows_have_per_window_u:
|
||||
windows_w_per_k_total = 0.0
|
||||
for w in epc.sap_windows or []:
|
||||
a_w = float(w.window_width) * float(w.window_height)
|
||||
u_raw_w = float(w.window_transmission_details.u_value) # type: ignore[union-attr]
|
||||
u_eff_w = (
|
||||
1.0 / (1.0 / u_raw_w + _WINDOW_CURTAIN_RESISTANCE_M2K_PER_W)
|
||||
if u_raw_w > 0 else 0.0
|
||||
)
|
||||
windows_w_per_k_total += a_w * u_eff_w
|
||||
else:
|
||||
window_u_raw = window_avg_u_value if (window_avg_u_value or 0) > 0 else u_window(
|
||||
installed_year=None, glazing_type=None, frame_type=None
|
||||
)
|
||||
window_u = (
|
||||
1.0 / (1.0 / window_u_raw + _WINDOW_CURTAIN_RESISTANCE_M2K_PER_W)
|
||||
if window_u_raw > 0
|
||||
else 0.0
|
||||
)
|
||||
windows_w_per_k_total = window_u * window_total_area_m2
|
||||
primary_age = parts[0].construction_age_band
|
||||
door_uninsulated_u = u_door(country=country, age_band=primary_age, insulated=False, insulated_u_value=None)
|
||||
door_insulated_u = (
|
||||
|
|
@ -288,7 +308,9 @@ def heat_transmission_from_cert(
|
|||
roof = 0.0
|
||||
floor = 0.0
|
||||
party = 0.0
|
||||
windows = 0.0
|
||||
windows = windows_w_per_k_total # SAP10.2 §3.2 — total computed
|
||||
# pre-loop with correct per-window
|
||||
# curtain transform.
|
||||
doors = 0.0
|
||||
bridging = 0.0
|
||||
total_external_area = 0.0
|
||||
|
|
@ -449,7 +471,8 @@ def heat_transmission_from_cert(
|
|||
party += 0.25 * area
|
||||
floor += uf * floor_area_total
|
||||
party += upw * party_area
|
||||
windows += window_u * w_area
|
||||
# windows: total computed pre-loop (`windows_w_per_k_total`).
|
||||
# w_area still drives the net-wall opening subtraction below.
|
||||
doors += door_u * d_area
|
||||
# (31) — total external element area used by both the worksheet
|
||||
# readout and the (36) thermal-bridging multiplier. Excludes the
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue