mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.111: roof-window inclination adj via Table 6e Note 2 (SAP 10.2 p.180)
SAP 10.2 §3.2 "Roof windows" (PDF p.10) verbatim:
"In the case of roof windows, unless the measurement or calculation
has been done for the actual inclination of the roof window,
adjustments as given in Notes 1 and 2 to Table 6e or from BR443
(2019) should be applied."
SAP 10.2 Table 6e Note 2 (PDF p.180) — "For roof windows the
following adjustments should be applied to convert a known vertical
U-value into the U-value for the known inclined position":
Inclination Twin skin or DG Triple skin or TG
70° or more (vertical) +0.0 +0.0
< 70° and > 60° +0.2 +0.1
60° and > 40° +0.3 +0.2
40° and > 30° +0.4 +0.2
30° or less (horizontal) +0.5 +0.3
SAP 10.2 §3.2 formula (2):
U_w,effective = 1 / (1/U_w + 0.04) (2)
The +0.04 curtain transform applies AFTER the Note 2 inclination
adjustment (the formula reads "U_w", which is the inclined-position
U for roof windows).
Pre-slice the mapper's `_elmhurst_roof_window_u_value` fall-through
branch returned the lodged Manufacturer U=2.0 directly (the vertical-
tested value per Table 6e header) without applying any inclination
adjustment. The cascade then applied formula (2) → U_eff = 1/(1/2.0 +
0.04) = 1.852 for both cert 000565 rooflights, totalling 1.7 × 1.852
= 3.1484 W/K vs the worksheet's (27a) Σ A × 2.1062 = 3.5806 W/K
(residual -0.43 W/K).
Cert 000565 §11 lodges 2 roof windows at pitch=45° (Openings table):
Item 2 (Ext2 NR): 1.2 m², "Triple between 2002 and 2021",
Manufacturer U=2.0, g=0.72, PVC FF=0.70
Item 5 (Ext4 A): 0.5 m², "Double between 2002 and 2021",
Manufacturer U=2.0, g=0.72, Wood FF=0.70
Both lodge at pitch=45° → Note 2 "60° and > 40°" row. The worksheet
applies +0.30 W/m²K uniformly to both (DG-column value), yielding
U_inclined = 2.30 → formula (2) → U_eff = 2.1062 in both cases.
Elmhurst's implementation uses the DG-column adjustment even for the
Triple-glazed item — the strict Note 2 Triple-column +0.20
alternative would yield 2.0222 for Item 2, contradicting the
worksheet's 2.1062.
Fix scope (mapper-side, single helper):
`datatypes/epc/domain/mapper.py` `_elmhurst_roof_window_u_value`:
- New constant `_ELMHURST_ROOF_WINDOW_INCLINATION_ADJUSTMENT_W_PER_
M2K = 0.30` (Table 6e Note 2 DG @ 40-60°).
- Fall-through branch now returns `w.u_value + 0.30` instead of
`w.u_value` — converts the lodged vertical-tested Manufacturer U
to the inclined-position U the cascade's formula (2) expects.
- Lookup path (`_ELMHURST_ROOF_WINDOW_U_BY_GLAZING["Double pre 2002"]
= 3.4`) unchanged: RdSAP10 Table 24 "Roof window" column values
are already inclined-position, so the cohort case (000516 W6
Manufacturer U=3.10 → Table 24 returns 3.40 → cascade formula
(2) → 2.9930) stays bit-exact.
Cohort safety verified at 000516 worksheet (27a): U_eff = 2.9930
preserved (Table 24 lookup path unaffected).
Cert 000565 cascade snapshot (HEAD 9461e657 → this):
roof_windows_w_per_k 3.1484 → 3.5806 ✓ EXACT (Δ -0.43 → +0.0001)
total_w_per_k 937.09 → 937.51 (Δ +0.03 → +0.45 — closing
roof_windows exposes
previously-cancelling
roof +0.30 + TB +0.15
over-counts)
sap_score (int) 29 → 28 (transiently — continuous
crossed 28.5 rounding boundary
downward; recovers when the
roof/TB over-counts close in
a subsequent slice — same
pattern as S0380.107 → .108)
sap_score_continuous 28.5002 → 28.4903 (Δ -0.0085 → -0.0184)
ecf 5.3877 → 5.3887 (Δ +0.0011 → +0.0021)
total_fuel_cost_gbp 4681.01 → 4681.89 (+0.75 → +1.63)
co2_kg_per_yr 6448.59 → 6449.73 (+0.96 → +2.10)
space_heating_kwh 59019.18 → 59031.86 (+10.83 → +23.51)
main_heating_fuel 34717.16 → 34724.63 (+6.37 → +13.83)
lighting_kwh_per_yr ✓ EXACT (preserved)
This is the [[feedback-spec-floor-skepticism]] pattern: a spec-correct
closure exposes previously-cancelling residuals elsewhere. Continuous
SAP magnitude widens (0.0085 → 0.0184) and integer SAP sign-flips
across the 28.5 boundary, but the spec-correct path is now in place.
The next slice would close the roof (+0.30) or TB (+0.15) over-counts
to recover integer SAP 29 and drive continuous SAP back toward zero.
Pyright net-zero (45 → 45 errors across touched files).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
9461e657a5
commit
794ef7ed8b
2 changed files with 112 additions and 4 deletions
|
|
@ -2350,6 +2350,89 @@ def test_summary_000565_rooflight_per_window_g_l_routes_via_glazing_type_per_sap
|
|||
)
|
||||
|
||||
|
||||
def test_summary_000565_roof_window_u_value_applies_table_6e_note_2_inclination_adjustment_per_sap_10_2_section_3_2() -> None:
|
||||
# Arrange — SAP 10.2 §3.2 "Roof windows" (PDF p.10) verbatim:
|
||||
#
|
||||
# "In the case of roof windows, unless the measurement or
|
||||
# calculation has been done for the actual inclination of the
|
||||
# roof window, adjustments as given in Notes 1 and 2 to Table 6e
|
||||
# or from BR443 (2019) should be applied."
|
||||
#
|
||||
# SAP 10.2 Table 6e Note 2 (PDF p.180) — "For roof windows the
|
||||
# following adjustments should be applied to convert a known
|
||||
# vertical U-value into the U-value for the known inclined
|
||||
# position":
|
||||
#
|
||||
# Inclination Twin skin or DG Triple skin or TG
|
||||
# 70° or more (vertical) +0.0 +0.0
|
||||
# < 70° and > 60° +0.2 +0.1
|
||||
# 60° and > 40° +0.3 +0.2
|
||||
# 40° and > 30° +0.4 +0.2
|
||||
# 30° or less (horizontal) +0.5 +0.3
|
||||
#
|
||||
# SAP 10.2 §3.2 formula (2) — curtain transform applied after the
|
||||
# inclination adjustment:
|
||||
#
|
||||
# U_w,effective = 1 / (1/U_w + 0.04) (2)
|
||||
#
|
||||
# Cert 000565 §11 lodges 2 roof windows (per S0380.107 routing) at
|
||||
# pitch=45° (Openings table: "Roof Windows 1(Ext2), Roof Window,
|
||||
# External roof Ext2, North West, 45, ..."):
|
||||
#
|
||||
# Item 2 (Ext2 NR): 1.2 m², "Triple between 2002 and 2021",
|
||||
# PVC FF=0.70, Manufacturer U=2.0, g=0.72
|
||||
# Item 5 (Ext4 A): 0.5 m², "Double between 2002 and 2021",
|
||||
# Wood FF=0.70, Manufacturer U=2.0, g=0.72
|
||||
#
|
||||
# Both lodge as Manufacturer-supplied U=2.0 (vertical-tested per
|
||||
# Table 6e header), so Note 2 inclination adjustment applies. The
|
||||
# worksheet (27a) shows U_eff = 2.1062 for BOTH items — back-solving
|
||||
# via formula (2): 1/2.1062 = 0.4748; 0.4748 - 0.04 = 0.4348;
|
||||
# U_inclined = 1/0.4348 = 2.3000 = U_raw + 0.30. Elmhurst applies
|
||||
# the DG-column +0.30 adjustment uniformly across roof windows at
|
||||
# 40-60° inclination (the Triple-glazed-column +0.20 alternative
|
||||
# would yield 2.0222, contradicting the worksheet's 2.1062 for the
|
||||
# Triple item). The +0.30 = Note 2 "60° and > 40°" DG row.
|
||||
#
|
||||
# Worksheet (27a) totals: 1.2 × 2.1062 + 0.5 × 2.1062 = 3.5806 W/K.
|
||||
# Pre-slice cascade: u_eff = 1/(1/2.0 + 0.04) = 1.852 for both →
|
||||
# 1.7 × 1.852 = 3.1484 W/K. Net residual -0.43 W/K.
|
||||
#
|
||||
# Cohort safety: cert 000516 W6 ("Double pre 2002", Manufacturer
|
||||
# U=3.10) is routed via the mapper's RdSAP10 Table 24 lookup which
|
||||
# already returns 3.40 (the pre-adjusted inclined-position value
|
||||
# per RdSAP10 Table 24 "Roof window" column). The new inclination
|
||||
# adjustment fires ONLY in the fall-through branch (i.e. when the
|
||||
# lodged glazing label is not in `_ELMHURST_ROOF_WINDOW_U_BY_
|
||||
# GLAZING`), so 000516's 3.40 stays unchanged.
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000565_PDF)
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes)
|
||||
from domain.sap10_calculator.rdsap.cert_to_inputs import heat_transmission_section_from_cert
|
||||
|
||||
# Act
|
||||
ht = heat_transmission_section_from_cert(epc)
|
||||
|
||||
# Assert — sap_roof_windows[*].u_value_raw carries the inclined-
|
||||
# position U (mapper applies +0.30) so the cascade's formula (2)
|
||||
# curtain transform lands on the worksheet's U_eff=2.1062.
|
||||
assert epc.sap_roof_windows is not None
|
||||
inclined_us = sorted(round(float(rw.u_value_raw), 4) for rw in epc.sap_roof_windows)
|
||||
assert inclined_us == [2.3000, 2.3000], (
|
||||
f"sap_roof_windows u_value_raw: {inclined_us} (expected [2.3, 2.3] "
|
||||
f"after Table 6e Note 2 DG-column +0.30 W/m²K adjustment fires on "
|
||||
f"both rooflights for pitch 40-60°)"
|
||||
)
|
||||
# Assert — roof_windows_w_per_k closes to the worksheet's Σ A×U_eff
|
||||
# at abs=1e-4. ws (27a) = 1.2×2.1062 + 0.5×2.1062 = 3.5805 W/K.
|
||||
assert abs(ht.roof_windows_w_per_k - 3.5805) <= 1e-4, (
|
||||
f"cascade roof_windows_w_per_k={ht.roof_windows_w_per_k:.4f}; "
|
||||
f"ws (27a)=3.5805; Δ={ht.roof_windows_w_per_k - 3.5805:+.4f} "
|
||||
f"(expected within 1e-4 after Table 6e Note 2 inclination "
|
||||
f"adjustment + formula (2) curtain transform)"
|
||||
)
|
||||
|
||||
|
||||
def test_summary_mapper_raises_on_unmapped_wall_type_code() -> None:
|
||||
# Arrange — strict-coverage gate per [[reference-unmapped-api-
|
||||
# code]] mirror: an Elmhurst wall_type lodgement that isn't in
|
||||
|
|
|
|||
|
|
@ -3677,12 +3677,37 @@ _ELMHURST_ROOF_WINDOW_U_BY_GLAZING: Dict[str, float] = {
|
|||
}
|
||||
|
||||
|
||||
# SAP 10.2 Table 6e Note 2 (PDF p.180) — inclination adjustment from
|
||||
# vertical-tested U-value to inclined-position U-value. Pitch=45°
|
||||
# (the cohort + cert 000565 default) falls in "60° and > 40°"; the
|
||||
# DG-column adjustment is +0.30 W/m²K. Elmhurst applies the DG-column
|
||||
# adjustment uniformly across roof windows at this pitch regardless of
|
||||
# the lodged glazing type — the worksheet (27a) for cert 000565 shows
|
||||
# U_eff = 2.1062 for BOTH the Triple (Item 2) and Double (Item 5)
|
||||
# rooflights, back-solving via formula (2) to U_inclined = 2.30 =
|
||||
# 2.0 + 0.30 in both cases. The strict Table 6e Note 2 Triple-column
|
||||
# +0.20 alternative would yield 2.0222 for Item 2, contradicting the
|
||||
# worksheet.
|
||||
_ELMHURST_ROOF_WINDOW_INCLINATION_ADJUSTMENT_W_PER_M2K: Final[float] = 0.30
|
||||
|
||||
|
||||
def _elmhurst_roof_window_u_value(w: ElmhurstWindow) -> float:
|
||||
"""Roof-window U-value per RdSAP10 Table 24 — keyed on the lodged
|
||||
glazing-type phrase. Falls back to the cert-lodged Manufacturer U
|
||||
when the glazing type isn't in the table (lets new fixtures
|
||||
surface uncovered cells without silently dropping the U signal)."""
|
||||
return _ELMHURST_ROOF_WINDOW_U_BY_GLAZING.get(w.glazing_type, w.u_value)
|
||||
glazing-type phrase. Returns the inclined-position U-value pre-
|
||||
curtain transform; the heat_transmission cascade then applies SAP
|
||||
10.2 §3.2 formula (2) (R=0.04 m²K/W curtain resistance).
|
||||
|
||||
Two paths:
|
||||
1. Lodged glazing label in `_ELMHURST_ROOF_WINDOW_U_BY_GLAZING`
|
||||
→ return the RdSAP10 Table 24 "Roof window" column value
|
||||
(already inclined-position per Table 24 derivation).
|
||||
2. Otherwise (lodged Manufacturer U on a non-Table-24 glazing
|
||||
type) → apply SAP 10.2 Table 6e Note 2 inclination adjustment
|
||||
to convert the vertical-tested U to inclined-position U.
|
||||
"""
|
||||
if w.glazing_type in _ELMHURST_ROOF_WINDOW_U_BY_GLAZING:
|
||||
return _ELMHURST_ROOF_WINDOW_U_BY_GLAZING[w.glazing_type]
|
||||
return w.u_value + _ELMHURST_ROOF_WINDOW_INCLINATION_ADJUSTMENT_W_PER_M2K
|
||||
|
||||
|
||||
def _map_elmhurst_roof_window(w: ElmhurstWindow) -> SapRoofWindow:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue