mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
fix(uvalues): apply §5.8 insulation R to stone walls (RdSAP 10 p.41-42)
The §5.8 Table-14 added-insulation R-value adjustment was gated to
WALL_SOLID_BRICK, so a stone (granite/sandstone) wall lodging
wall_insulation_type 1/3 ("External"/"Internal") + a thickness fell
through the §5.6 thin-wall branch and was billed at its UNINSULATED U
(e.g. sandstone 520 mm + 100 mm internal: 1.64 instead of 0.30 → ~5×
the wall heat loss). Mirror the brick insulation branch into the stone
block, feeding the RAW §5.6 U₀ into the §5.8 chain per the same rule the
brick branch and the dry-lined granite pin 000565 already follow (the
Table-6 footnote (a) 1.7 cap does not apply on the insulated path).
Corpus cert 100052159386 (sandstone 520 mm + 100 mm internal): -26.20 ->
-4.08 SAP, walls 300 -> 55 W/K. RdSAP-21.0.1 corpus within-0.5 68.6% ->
68.8% (SAP MAE 0.942 -> 0.888; PE MAE 14.3 -> 13.9; CO2 0.27 -> 0.26);
floors/ceilings ratcheted. Unit-pinned in test_rdsap_uvalues.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0d24f5b13a
commit
c5aa5620ca
3 changed files with 116 additions and 4 deletions
|
|
@ -589,6 +589,35 @@ def u_wall(
|
|||
):
|
||||
u0 = _u_stone_thin_wall_age_a_to_e(construction, wall_thickness_mm)
|
||||
if u0 is not None:
|
||||
# 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
|
||||
# RAW §5.6 stone result (the Table-6 footnote (a) 1.7 cap does NOT
|
||||
# apply to the insulated path — same rule the brick branch below
|
||||
# and the dry-lined granite pin 000565 follow). λ defaults to
|
||||
# 0.04 W/m·K per §5.8 final note. Mirrors the WALL_SOLID_BRICK
|
||||
# insulated branch; without it a stone wall lodging code 1/3 +
|
||||
# a thickness was billed at its UNINSULATED U (e.g. sandstone
|
||||
# 520 mm + 100 mm internal: 1.64 → 0.30), the dominant cause of
|
||||
# the wall_insulation_type=3 corpus under-rate cluster.
|
||||
if (
|
||||
wall_insulation_type in (
|
||||
_WALL_INSULATION_EXTERNAL, _WALL_INSULATION_INTERNAL,
|
||||
)
|
||||
and insulation_thickness_mm is not None
|
||||
and insulation_thickness_mm > 0
|
||||
):
|
||||
r_ins = _r_insulation_table_14(
|
||||
insulation_thickness_mm,
|
||||
_resolve_wall_insulation_lambda_w_per_mk(
|
||||
wall_insulation_thermal_conductivity
|
||||
),
|
||||
)
|
||||
u_unrounded = 1.0 / (1.0 / u0 + r_ins)
|
||||
return float(
|
||||
Decimal(str(u_unrounded)).quantize(
|
||||
Decimal("0.01"), rounding=ROUND_HALF_UP
|
||||
)
|
||||
)
|
||||
if dry_lined:
|
||||
# Round to 2 d.p. — worksheet (29a) A×U product uses
|
||||
# the 2-d.p.-displayed U (cf. 000565 Main alt_wall_1:
|
||||
|
|
|
|||
|
|
@ -725,6 +725,80 @@ def test_u_wall_stone_sandstone_thin_wall_age_a_120mm_uses_5_6_sandstone_formula
|
|||
assert abs(result - 3.7408) <= 1e-3
|
||||
|
||||
|
||||
def test_u_wall_stone_sandstone_with_internal_insulation_applies_5_8_table_14_r_value() -> None:
|
||||
# Arrange — RdSAP 10 §5.8 + Table 14 (PDF p.41-42): a stone wall lodging
|
||||
# External/Internal insulation (wall_insulation_type 1/3) + a thickness
|
||||
# gets the same R-value adjustment as solid brick, applied to the RAW §5.6
|
||||
# U₀. Mirrors corpus cert 100052159386 (Sandstone, 520 mm, 100 mm internal):
|
||||
# U₀ = 54.876 × 520^(-0.561) = 1.6433
|
||||
# R = 0.025 × 100 + 0.25 = 2.75 (Table 14, λ = 0.04)
|
||||
# U = 1 / (1/1.6433 + 2.75) = 0.2977 → 0.30 (2 d.p.)
|
||||
# Before this branch the wall was billed at its UNINSULATED U (≈1.64),
|
||||
# the dominant cause of the wall_insulation_type=3 corpus under-rate cluster.
|
||||
|
||||
# Act
|
||||
result = u_wall(
|
||||
country=Country.ENG,
|
||||
age_band="A",
|
||||
construction=WALL_STONE_SANDSTONE,
|
||||
insulation_thickness_mm=100,
|
||||
insulation_present=True,
|
||||
wall_insulation_type=3,
|
||||
dry_lined=False,
|
||||
wall_thickness_mm=520,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert abs(result - 0.30) <= 1e-4
|
||||
|
||||
|
||||
def test_u_wall_stone_sandstone_insulated_feeds_raw_u0_not_table_6_cap() -> None:
|
||||
# Arrange — the Table-6 footnote (a) 1.7 cap applies ONLY to the as-built
|
||||
# row; the insulated §5.8 path takes the RAW §5.6 U₀ (same rule the brick
|
||||
# branch and the dry-lined granite pin 000565 follow). At W=120 mm the raw
|
||||
# sandstone U₀ = 3.7408 (> 1.7), so the 100 mm internal result must be
|
||||
# 1 / (1/3.7408 + 2.75) = 0.331 → 0.33 (raw),
|
||||
# NOT the capped 1 / (1/1.7 + 2.75) = 0.30. The 0.33 vs 0.30 split proves
|
||||
# the cap is bypassed on the insulated path.
|
||||
|
||||
# Act
|
||||
result = u_wall(
|
||||
country=Country.ENG,
|
||||
age_band="A",
|
||||
construction=WALL_STONE_SANDSTONE,
|
||||
insulation_thickness_mm=100,
|
||||
insulation_present=True,
|
||||
wall_insulation_type=3,
|
||||
dry_lined=False,
|
||||
wall_thickness_mm=120,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert abs(result - 0.33) <= 1e-4
|
||||
|
||||
|
||||
def test_u_wall_stone_granite_with_external_insulation_applies_5_8_table_14_r_value() -> None:
|
||||
# Arrange — granite/whinstone §5.6 formula + §5.8 external insulation:
|
||||
# U₀ = 45.315 × 120^(-0.513) = 3.8871
|
||||
# R = 0.025 × 50 + 0.25 = 1.50 (Table 14, λ = 0.04)
|
||||
# U = 1 / (1/3.8871 + 1.50) = 0.567 → 0.57 (2 d.p.)
|
||||
|
||||
# Act
|
||||
result = u_wall(
|
||||
country=Country.ENG,
|
||||
age_band="A",
|
||||
construction=WALL_STONE_GRANITE,
|
||||
insulation_thickness_mm=50,
|
||||
insulation_present=True,
|
||||
wall_insulation_type=1,
|
||||
dry_lined=False,
|
||||
wall_thickness_mm=120,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert abs(result - 0.57) <= 1e-4
|
||||
|
||||
|
||||
def test_u_wall_stone_granite_age_g_with_wall_thickness_ignores_5_6_formula_per_age_a_to_e_gate() -> None:
|
||||
# Arrange — §5.6 (PDF p.40) heading explicitly scopes the formula
|
||||
# to "age bands A to E". For age F onwards Table 6 gives literal
|
||||
|
|
|
|||
|
|
@ -102,10 +102,19 @@ _CORPUS = Path(
|
|||
# part → SapConservatory → §6.1 window/rooflight/floor cascade + TFA, MIRRORING
|
||||
# the case-44 Summary path pinned to 1e-4) -> 68.6% (MAE 0.942). 5 type-4
|
||||
# certs were over-rating (conservatory dropped → too little heat loss).
|
||||
_MIN_WITHIN_HALF_SAP = 0.68
|
||||
_MAX_SAP_MAE = 0.95
|
||||
_MAX_CO2_MAE_TONNES = 0.35 # t CO2 / yr vs co2_emissions_current
|
||||
_MAX_PE_PER_M2_MAE = 16.0 # kWh / m2 / yr vs energy_consumption_current
|
||||
# STONE WALL + INTERNAL/EXTERNAL INSULATION (RdSAP 10 §5.8 + Table 14, p.41-42):
|
||||
# the §5.8 added-insulation R-value adjustment was applied ONLY to WALL_SOLID_
|
||||
# BRICK; a stone (granite/sandstone) wall lodging wall_insulation_type 1/3 + a
|
||||
# thickness fell through the §5.6 branch and was billed at its UNINSULATED U
|
||||
# (e.g. sandstone 520 mm + 100 mm internal: 1.64 instead of 0.30 → 5× wall heat
|
||||
# loss). Mirroring the brick branch into the stone block recovered the worst of
|
||||
# the wall_insulation_type=3 under-rate cluster (cert 100052159386 -26.2 -> -4.1
|
||||
# SAP, walls 300 -> 55 W/K). within-0.5 68.6% -> 68.8% (MAE 0.942 -> 0.888;
|
||||
# PE MAE 14.3 -> 13.9; CO2 MAE 0.27 -> 0.26). Unit-pinned in test_rdsap_uvalues.
|
||||
_MIN_WITHIN_HALF_SAP = 0.685
|
||||
_MAX_SAP_MAE = 0.89
|
||||
_MAX_CO2_MAE_TONNES = 0.30 # t CO2 / yr vs co2_emissions_current
|
||||
_MAX_PE_PER_M2_MAE = 14.5 # kWh / m2 / yr vs energy_consumption_current
|
||||
|
||||
|
||||
def _load_corpus() -> list[dict[str, Any]]:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue