mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(uvalues): thread glazing gap into pre-2002 window U fallback (RdSAP 10 Table 24, PDF p.50)
`u_window` hard-coded the 12 mm gap row for pre-2002 double/triple glazing (double 2.8, triple 2.1), ignoring the lodged glazing gap. Table 24 splits the pre-2002 rows by gap: double 6mm=3.1 / 12mm=2.8 / 16mm+=2.7; triple 6mm=2.4 / 12mm=2.1 / 16mm+=2.0 (PVC/wooden), with a metal-frame column (+0.5/+0.5/+0.5 ish). Added a `glazing_gap` parameter + `_glazing_gap_row` helper and wired `w.glazing_gap` through the synthesised-window caller in heat_transmission. Corpus impact nil by design: the gov-API mapper already resolves per-window U gap-aware via `_API_GLAZING_TYPE_GAP_TO_TRANSMISSION` (e.g. code 3 + gap "16+" → 2.7), so corpus certs use that lodged per-window U, not this fallback. This aligns the reduced-field / worksheet fallback path with the mapper and Table 24. Unknown gap still defaults to the 12 mm row. (Metal frames are not distinguishable on the gov-API path — only a `pvc_frame` boolean exists and Table 24 groups PVC+wooden — so the PVC/wooden U stands there; the metal column applies only where frame material is lodged.) Spec-pinned: pre-2002 double + triple gap-row tests. pyright not installed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
600684f5df
commit
d7a60efcdf
3 changed files with 75 additions and 6 deletions
|
|
@ -171,7 +171,12 @@ def _synthesised_window_u_raw(windows: Optional[Sequence[SapWindow]]) -> float:
|
|||
if isinstance(code, int)
|
||||
else ("double", None)
|
||||
)
|
||||
return u_window(installed_year=year, glazing_type=glaze, frame_type=w.frame_material)
|
||||
return u_window(
|
||||
installed_year=year,
|
||||
glazing_type=glaze,
|
||||
frame_type=w.frame_material,
|
||||
glazing_gap=w.glazing_gap,
|
||||
)
|
||||
# RdSAP10 §15 "Rounding of data" (p.66): "All element areas (gross)
|
||||
# including window areas and conservatory wall area: 2 d.p." plus
|
||||
# "U-values: 2 d.p.". This is the data-passed-to-SAP-calculator
|
||||
|
|
|
|||
|
|
@ -1401,12 +1401,51 @@ def u_exposed_floor(
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
# RdSAP 10 Table 24 (PDF p.50-51) — pre-2002 (Scotland pre-2003 / NI pre-2006)
|
||||
# double and triple glazing split by glazing gap between panes: 6 mm, 12 mm,
|
||||
# and 16 mm or more, each with a PVC/wooden and a metal-frame U-value. The
|
||||
# 2002+ and 2022+ rows are gap-independent ("any" gap). (pvc, metal) per gap:
|
||||
_PRE_2002_DOUBLE_U_BY_GAP: Final[dict[str, tuple[float, float]]] = {
|
||||
"6": (3.1, 3.7), "12": (2.8, 3.4), "16+": (2.7, 3.3),
|
||||
}
|
||||
_PRE_2002_TRIPLE_U_BY_GAP: Final[dict[str, tuple[float, float]]] = {
|
||||
"6": (2.4, 2.9), "12": (2.1, 2.6), "16+": (2.0, 2.5),
|
||||
}
|
||||
|
||||
|
||||
def _glazing_gap_row(glazing_gap: "str | int | None") -> str:
|
||||
"""Map a lodged glazing gap to its Table 24 row key ("6" / "12" / "16+").
|
||||
|
||||
The cert lodges discrete gaps as the int 6 or 12 or the string "16+"
|
||||
(RdSAP-Schema `glazing_gap`). Unknown gap (None) defaults to the 12 mm
|
||||
row — the spec's typical pre-2002 sealed unit. Robust to intermediate
|
||||
integers: <=8 → 6 mm, >=15 → 16 mm-or-more, else 12 mm."""
|
||||
if glazing_gap is None:
|
||||
return "12"
|
||||
if isinstance(glazing_gap, str):
|
||||
s = glazing_gap.strip().lower()
|
||||
if "16" in s or "+" in s:
|
||||
return "16+"
|
||||
try:
|
||||
g = int(float(s))
|
||||
except ValueError:
|
||||
return "12"
|
||||
else:
|
||||
g = int(glazing_gap)
|
||||
if g <= 8:
|
||||
return "6"
|
||||
if g >= 15:
|
||||
return "16+"
|
||||
return "12"
|
||||
|
||||
|
||||
def u_window(
|
||||
installed_year: Optional[int],
|
||||
glazing_type: Optional[str],
|
||||
frame_type: Optional[str],
|
||||
glazing_gap: "str | int | None" = None,
|
||||
) -> float:
|
||||
"""RdSAP10 window U-value in W/m^2K, never null."""
|
||||
"""RdSAP10 window U-value in W/m^2K, never null (RdSAP 10 Table 24)."""
|
||||
if glazing_type is None and installed_year is None and frame_type is None:
|
||||
return 2.5
|
||||
glaze = (glazing_type or "double").lower()
|
||||
|
|
@ -1423,10 +1462,11 @@ def u_window(
|
|||
return 1.6 if metal else 1.4
|
||||
if installed_year is not None and installed_year >= 2002:
|
||||
return 2.2 if metal else 2.0
|
||||
# pre-2002 double/triple default to 12mm gap row.
|
||||
if glaze == "triple":
|
||||
return 2.6 if metal else 2.1
|
||||
return 3.4 if metal else 2.8
|
||||
# pre-2002 double/triple — Table 24 splits by glazing gap (6/12/16+ mm).
|
||||
gap_row = _glazing_gap_row(glazing_gap)
|
||||
table = _PRE_2002_TRIPLE_U_BY_GAP if glaze == "triple" else _PRE_2002_DOUBLE_U_BY_GAP
|
||||
pvc_u, metal_u = table[gap_row]
|
||||
return metal_u if metal else pvc_u
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1760,6 +1760,30 @@ def test_u_window_post_2022_metal_returns_table24_1_6_not_pvc_1_4() -> None:
|
|||
assert result == pytest.approx(1.6, abs=0.001)
|
||||
|
||||
|
||||
def test_u_window_pre_2002_double_glazing_gap_selects_table24_row() -> None:
|
||||
# Arrange — RdSAP 10 Table 24 (PDF p.50) pre-2002 double glazing splits
|
||||
# by glazing gap (PVC/wooden frame): 6 mm → 3.1, 12 mm → 2.8, 16 mm or
|
||||
# more → 2.7. The cert lodges the gap as the int 6/12 or the string
|
||||
# "16+"; unknown gap defaults to the 12 mm row.
|
||||
|
||||
# Act / Assert
|
||||
assert u_window(installed_year=None, glazing_type="double", frame_type="pvc", glazing_gap=6) == pytest.approx(3.1, abs=0.001)
|
||||
assert u_window(installed_year=None, glazing_type="double", frame_type="pvc", glazing_gap=12) == pytest.approx(2.8, abs=0.001)
|
||||
assert u_window(installed_year=None, glazing_type="double", frame_type="pvc", glazing_gap="16+") == pytest.approx(2.7, abs=0.001)
|
||||
assert u_window(installed_year=None, glazing_type="double", frame_type="pvc", glazing_gap=None) == pytest.approx(2.8, abs=0.001)
|
||||
|
||||
|
||||
def test_u_window_pre_2002_triple_glazing_gap_and_metal_frame_select_table24_row() -> None:
|
||||
# Arrange — Table 24 pre-2002 triple glazing: 6 mm → 2.4, 12 mm → 2.1,
|
||||
# 16 mm+ → 2.0 (PVC); metal frame adds +0.5 per the metal column
|
||||
# (6 → 2.9, 12 → 2.6, 16+ → 2.5).
|
||||
|
||||
# Act / Assert
|
||||
assert u_window(installed_year=None, glazing_type="triple", frame_type="pvc", glazing_gap="16+") == pytest.approx(2.0, abs=0.001)
|
||||
assert u_window(installed_year=None, glazing_type="triple", frame_type="metal", glazing_gap=6) == pytest.approx(2.9, abs=0.001)
|
||||
assert u_window(installed_year=None, glazing_type="triple", frame_type="metal", glazing_gap="16+") == pytest.approx(2.5, abs=0.001)
|
||||
|
||||
|
||||
def test_u_window_falls_back_to_mid_range_when_unknown() -> None:
|
||||
# Arrange — nothing known.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue