mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(uvalues): cap as-built stone U at age E only, not A-E; drop insulation-state gate (RdSAP 10 Tables 6-7 footnote a, PDF p.33-35)
Commit 034d4b7c corrected two stone cases but via the wrong mechanism: it
removed the 1.7 cap for ALL age bands and gated the §5.6 formula on
`wall_insulation_type is not None`. Both diverge from RdSAP 10 Tables 6-10:
- The stone rows read "According to 5.6" for bands A-D (uncapped formula)
and "1.7 a" at band E, where footnote (a) = "Or from equations in 5.6 if
the calculated U-value is less than 1.7" → U_E = min(formula, 1.7). The
cap belongs ONLY at age E. Removing it for E let an as-built 50 mm granite
age-E wall return the formula's 6.09 instead of 1.70.
- Scotland sandstone/limestone age E defaults to 1.5 (Table 7, PDF p.35);
granite/whinstone stays 1.7.
- The insulation-state gate is not a spec rule. It sent age-A-D stone with
"insulation Unknown" (wall_insulation_type None) to the flat-1.7 table
instead of the §5.6 formula; it only "worked" for fixture 000565 Ext1
because that wall is age E, where 1.7 is correct anyway. Removed: the
age-E cap, not the gate, is what produces 000565 Ext1's worksheet 1.70.
Bands A-D stay uncapped (sandstone 400 mm → 1.90, granite 120 mm → 3.89).
Unknown-thickness handling unchanged in this commit (still flat-table; the
§3.5 Table-3 default follows separately).
pyright not installed in this container — strict type gate not run locally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
034d4b7cd5
commit
7e187078b9
2 changed files with 126 additions and 26 deletions
|
|
@ -567,30 +567,43 @@ def u_wall(
|
||||||
ctry = country if country is not None else Country.ENG
|
ctry = country if country is not None else Country.ENG
|
||||||
age_idx = _age_index(age_band)
|
age_idx = _age_index(age_band)
|
||||||
band = _AGE_BANDS[age_idx]
|
band = _AGE_BANDS[age_idx]
|
||||||
# RdSAP 10 §5.6 (PDF p.40) — uninsulated stone wall formula, age
|
# RdSAP 10 Tables 6-10 stone rows + footnote (a) (PDF p.33-39), §5.6
|
||||||
# bands A-E. Fires only when (a) a documentary wall thickness is lodged
|
# formula (PDF p.40), §5.8 + Table 14 (PDF p.41-42). A documentary wall
|
||||||
# (per §5.3 documentary-evidence rule) AND (b) the insulation STATE is
|
# thickness (per §5.3) routes stone in age bands A-E off the §5.6 formula,
|
||||||
# known (`wall_insulation_type` not None — As Built / external / internal).
|
# NOT the flat Table-6 typical-thickness default:
|
||||||
# When either is absent the cascade falls through to the Table-6
|
# - Bands A-D: pure §5.6 formula, UNCAPPED. The stone rows read
|
||||||
# typical-thickness default (1.7) below: an "insulation Unknown" lodgement
|
# "According to 5.6" with NO 1.7 entry, because a thin/standard solid
|
||||||
# is NOT treated as bare stone of the lodged thickness (cert 000565 Ext1:
|
# stone wall genuinely loses more than the typical default (sandstone
|
||||||
# granite 50 mm + insulation Unknown → Table-6 1.70 in the worksheet, NOT
|
# 400 mm → 1.90, granite 120 mm → 3.89).
|
||||||
# the §5.6 formula's 6.09), and the footnote (a) "use §5.6 if the
|
# - Band E: the stone row reads "1.7 a"; footnote (a) = "Or from
|
||||||
# calculated U-value is less than 1.7" clause governs the unknown path. When the thickness IS
|
# equations in 5.6 if the calculated U-value is less than 1.7" →
|
||||||
# lodged the raw §5.6 U is the spec target — it is NOT capped at 1.7,
|
# U_E = min(formula, 1.7). Scotland sandstone/limestone age E defaults
|
||||||
# because a thin/standard solid stone wall genuinely loses more than the
|
# to 1.5 (Table 7), granite/whinstone stays 1.7. The 1.7 (1.5) cap
|
||||||
# typical-thickness default (sandstone 400 mm → 1.90, granite 120 mm →
|
# belongs ONLY at age E, never A-D.
|
||||||
# 3.89). §5.8 + Table 14 insulation / dry-line adjustments apply on top
|
# The insulation STATE is NOT a gate: an "as built / insulation Unknown"
|
||||||
# of the raw §5.6 U₀ (cert 000565 Main alt_wall_1: granite W=120 mm
|
# lodgement (`wall_insulation_type` None or 4) takes the formula too. Cert
|
||||||
# dry-lined → U₀=3.88 + dry-line → 2.34, matches worksheet).
|
# 000565 Ext1 (granite 50 mm, age E, insulation Unknown) → min(6.09, 1.7)
|
||||||
|
# = 1.70, matching the U985 worksheet WITHOUT a flat-table detour — the
|
||||||
|
# age-E cap, not an insulation gate, is what produces the 1.70.
|
||||||
if (
|
if (
|
||||||
wall_thickness_mm is not None
|
wall_thickness_mm is not None
|
||||||
and wall_insulation_type is not None
|
|
||||||
and band in _STONE_AGE_A_TO_E
|
and band in _STONE_AGE_A_TO_E
|
||||||
and construction in (WALL_STONE_GRANITE, WALL_STONE_SANDSTONE)
|
and construction in (WALL_STONE_GRANITE, WALL_STONE_SANDSTONE)
|
||||||
):
|
):
|
||||||
u0 = _u_stone_thin_wall_age_a_to_e(construction, wall_thickness_mm)
|
u0 = _u_stone_thin_wall_age_a_to_e(construction, wall_thickness_mm)
|
||||||
if u0 is not None:
|
if u0 is not None:
|
||||||
|
# Footnote (a) cap is age-E only: clamp the as-built U to the
|
||||||
|
# Table-6/7 age-E default (1.7, or 1.5 for Scotland sandstone/
|
||||||
|
# limestone) when the §5.6 formula exceeds it. A-D stay uncapped.
|
||||||
|
if band == "E":
|
||||||
|
e_default = (
|
||||||
|
1.5
|
||||||
|
if ctry == Country.SCT
|
||||||
|
and construction == WALL_STONE_SANDSTONE
|
||||||
|
else 1.7
|
||||||
|
)
|
||||||
|
if u0 >= e_default:
|
||||||
|
u0 = e_default
|
||||||
# RdSAP 10 §5.8 + Table 14 (PDF p.41-42) — added External/Internal
|
# RdSAP 10 §5.8 + Table 14 (PDF p.41-42) — added External/Internal
|
||||||
# insulation on a stone wall: U = 1/(1/U₀ + R_ins), with U₀ the
|
# insulation on a stone wall: U = 1/(1/U₀ + R_ins), with U₀ the
|
||||||
# RAW §5.6 stone result (the Table-6 footnote (a) 1.7 cap does NOT
|
# RAW §5.6 stone result (the Table-6 footnote (a) 1.7 cap does NOT
|
||||||
|
|
@ -630,15 +643,12 @@ def u_wall(
|
||||||
Decimal("0.01"), rounding=ROUND_HALF_UP
|
Decimal("0.01"), rounding=ROUND_HALF_UP
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# As-built (uninsulated, not dry-lined) stone wall of KNOWN
|
# As-built (uninsulated, not dry-lined) stone wall, age A-E:
|
||||||
# thickness, age A-E: return the raw §5.6 result. The Table-6
|
# return the §5.6 result — uncapped for A-D, age-E-capped above.
|
||||||
# footnote (a) "< 1.7" clause governs the UNKNOWN-thickness path
|
# A thin solid stone wall genuinely has U > 1.7 (sandstone 400 mm
|
||||||
# (which falls through to the Table-6 typical 1.7 default below) —
|
# = 1.90, granite 120 mm = 3.89); capping A-D to 1.7 under-counts
|
||||||
# NOT a documentary-thickness lodgement. A thin solid stone wall
|
# fabric loss and over-rates. Confirmed against Elmhurst (age-B
|
||||||
# genuinely has U > 1.7 (e.g. sandstone 400 mm = 1.90, granite
|
# sandstone 400 mm → 1.90) and the §5.6 Table-12 formula tests.
|
||||||
# 120 mm = 3.89); capping it to 1.7 under-counts fabric loss and
|
|
||||||
# over-rates. Confirmed against Elmhurst (age-B sandstone 400 mm
|
|
||||||
# → 1.9) and the §5.6 Table-12 formula tests.
|
|
||||||
return u0
|
return u0
|
||||||
known_types = {
|
known_types = {
|
||||||
WALL_STONE_GRANITE, WALL_STONE_SANDSTONE, WALL_SOLID_BRICK, WALL_CAVITY,
|
WALL_STONE_GRANITE, WALL_STONE_SANDSTONE, WALL_SOLID_BRICK, WALL_CAVITY,
|
||||||
|
|
|
||||||
|
|
@ -849,6 +849,96 @@ def test_u_wall_stone_granite_age_a_without_wall_thickness_returns_table_6_age_a
|
||||||
assert abs(result - 1.7) <= 1e-3
|
assert abs(result - 1.7) <= 1e-3
|
||||||
|
|
||||||
|
|
||||||
|
def test_u_wall_stone_granite_age_e_50mm_caps_at_table6_default_1_7() -> None:
|
||||||
|
# Arrange — RdSAP 10 Table 6 (England, PDF p.33-34) stone row reads
|
||||||
|
# "1.7 a" at age E, footnote (a) = "Or from equations in 5.6 if the
|
||||||
|
# calculated U-value is less than 1.7". A 50 mm granite wall's §5.6
|
||||||
|
# formula gives U = 45.315 × 50^(-0.513) = 6.09 (> 1.7), so the age-E
|
||||||
|
# default 1.7 stands → min(6.09, 1.7) = 1.70. The cap is age-E ONLY:
|
||||||
|
# bands A-D are uncapped (120 mm age-A granite = 3.89). Insulation is
|
||||||
|
# Unknown (wall_insulation_type None) — no longer a gate. Cert 000565
|
||||||
|
# BP Ext1 is this fixture (U985 worksheet U = 1.70).
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = u_wall(
|
||||||
|
country=Country.ENG,
|
||||||
|
age_band="E",
|
||||||
|
construction=WALL_STONE_GRANITE,
|
||||||
|
insulation_thickness_mm=None,
|
||||||
|
insulation_present=False,
|
||||||
|
wall_insulation_type=None,
|
||||||
|
dry_lined=False,
|
||||||
|
wall_thickness_mm=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert abs(result - 1.70) <= 1e-3
|
||||||
|
|
||||||
|
|
||||||
|
def test_u_wall_stone_sandstone_age_e_thick_wall_uses_5_6_formula_below_1_7() -> None:
|
||||||
|
# Arrange — footnote (a) at age E: use the §5.6 formula when it gives
|
||||||
|
# < 1.7. A 600 mm sandstone wall → U = 54.876 × 600^(-0.561) = 1.5165
|
||||||
|
# (< 1.7), so the formula value is used, NOT the 1.7 default.
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = u_wall(
|
||||||
|
country=Country.ENG,
|
||||||
|
age_band="E",
|
||||||
|
construction=WALL_STONE_SANDSTONE,
|
||||||
|
insulation_thickness_mm=None,
|
||||||
|
insulation_present=False,
|
||||||
|
wall_insulation_type=None,
|
||||||
|
dry_lined=False,
|
||||||
|
wall_thickness_mm=600,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert abs(result - 1.5165) <= 1e-3
|
||||||
|
|
||||||
|
|
||||||
|
def test_u_wall_stone_sandstone_scotland_age_e_caps_at_1_5_not_1_7() -> None:
|
||||||
|
# Arrange — RdSAP 10 Table 7 (Scotland, PDF p.35) sandstone/limestone
|
||||||
|
# age E default is "1.5 a" (granite/whinstone stays 1.7). A 500 mm
|
||||||
|
# sandstone wall's §5.6 formula = 54.876 × 500^(-0.561) = 1.68 (> 1.5),
|
||||||
|
# so the Scotland age-E default 1.5 stands → min(1.68, 1.5) = 1.50.
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = u_wall(
|
||||||
|
country=Country.SCT,
|
||||||
|
age_band="E",
|
||||||
|
construction=WALL_STONE_SANDSTONE,
|
||||||
|
insulation_thickness_mm=None,
|
||||||
|
insulation_present=False,
|
||||||
|
wall_insulation_type=None,
|
||||||
|
dry_lined=False,
|
||||||
|
wall_thickness_mm=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert abs(result - 1.50) <= 1e-3
|
||||||
|
|
||||||
|
|
||||||
|
def test_u_wall_stone_granite_scotland_age_e_50mm_stays_capped_at_1_7() -> None:
|
||||||
|
# Arrange — Scotland granite/whinstone age E default is 1.7 (only
|
||||||
|
# sandstone/limestone drops to 1.5, Table 7 PDF p.35). A 50 mm granite
|
||||||
|
# wall's formula 6.09 (> 1.7) → min(6.09, 1.7) = 1.70, NOT 1.5.
|
||||||
|
|
||||||
|
# Act
|
||||||
|
result = u_wall(
|
||||||
|
country=Country.SCT,
|
||||||
|
age_band="E",
|
||||||
|
construction=WALL_STONE_GRANITE,
|
||||||
|
insulation_thickness_mm=None,
|
||||||
|
insulation_present=False,
|
||||||
|
wall_insulation_type=None,
|
||||||
|
dry_lined=False,
|
||||||
|
wall_thickness_mm=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert abs(result - 1.70) <= 1e-3
|
||||||
|
|
||||||
|
|
||||||
def test_u_wall_curtain_wall_missing_age_lodgement_defaults_to_pre_2023_u_2p0_per_rdsap_5_18() -> None:
|
def test_u_wall_curtain_wall_missing_age_lodgement_defaults_to_pre_2023_u_2p0_per_rdsap_5_18() -> None:
|
||||||
# Arrange — when the cert lodges `Type: CW Curtain Wall` but no
|
# Arrange — when the cert lodges `Type: CW Curtain Wall` but no
|
||||||
# `Curtain Wall Age` line (older Elmhurst Summary PDFs, or API EPCs
|
# `Curtain Wall Age` line (older Elmhurst Summary PDFs, or API EPCs
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue