mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
fix(u-value): "Unknown" roof insulation → Table 18 default, not 2.30
A roof lodged "Unknown loft insulation" carries roof_insulation_thickness "NI" (Not Indicated → parsed to 0) or "ND" (None): the thickness is UNDETERMINED, not zero. RdSAP 10 §5.11.4 (p.44) is deterministic here — "U-values in Table 18 are used when thickness of insulation cannot be determined" — so the roof takes the Table 18 age-band default (column (1) pitched / column (3) flat), NOT the uninsulated 2.30 the Table 16 row-0 lookup returns for a parsed-0 thickness. The "Unknown" text is RdSAP's rendering of the undetermined-thickness observation, distinct from a genuine "no insulation" lodgement (which keeps 2.30). u_roof gains an "unknown"-description branch ahead of the parsed-0 → 2.30 path, gated on undetermined thickness (None or 0). Top-floor flats with "Pitched/Flat, Unknown ... insulation" were the worst electric-flat under-raters: roof U=2.30 gave HLP ~3.7 on dwellings rated SAP 69-70. Cluster (14 certs, roof desc contains "unknown", no "no insulation"): mean |err| 7.79 → 1.82, within-0.5 1→4, within-1.0 1→6. Cert 9836 roof_w_per_k 58.2→10.1, SAP -27.8 → -3.5. Eval headline 44.4% → 44.8%, mean |err| 1.944 → 1.851. Two certs overshoot (other residuals the wrong roof-U was masking); the spec value is applied uniformly regardless. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3aed8f858a
commit
a64e857b94
2 changed files with 58 additions and 0 deletions
|
|
@ -837,6 +837,24 @@ def u_roof(
|
|||
# ("Average thermal transmittance X W/m²K"); spec §5.11 opening
|
||||
# clause defers to the assessor's value when present.
|
||||
return measured
|
||||
if (
|
||||
age_band is not None
|
||||
and description is not None
|
||||
and "unknown" in description.lower()
|
||||
and (insulation_thickness_mm is None or insulation_thickness_mm == 0)
|
||||
):
|
||||
# RdSAP 10 §5.11.4 (page 44): "U-values in Table 18 are used when
|
||||
# thickness of insulation cannot be determined." A roof lodged
|
||||
# "Unknown loft insulation" carries thickness "NI" (Not Indicated,
|
||||
# parsed to 0) or "ND" (None) — the thickness is UNDETERMINED, not
|
||||
# zero — so it takes the Table 18 age-band default (column (1)
|
||||
# pitched / column (3) flat), NOT the uninsulated 2.30 the Table 16
|
||||
# row-0 lookup would give for a parsed-0 thickness. Distinct from a
|
||||
# genuine "no insulation" lodgement, which keeps 2.30 (below). The
|
||||
# discriminator is the deterministic "Unknown" text RdSAP renders
|
||||
# for an undetermined-thickness observation.
|
||||
table_18 = _FLAT_ROOF_BY_AGE if is_flat_roof else _ROOF_BY_AGE
|
||||
return table_18.get(age_band.upper(), 0.4)
|
||||
if (
|
||||
is_sloping_ceiling
|
||||
and age_band is not None
|
||||
|
|
|
|||
|
|
@ -821,6 +821,46 @@ def test_u_roof_ni_thickness_with_no_insulation_description_stays_at_2_30() -> N
|
|||
assert result == pytest.approx(2.30, abs=0.01)
|
||||
|
||||
|
||||
def test_u_roof_unknown_loft_insulation_uses_table18_default_per_section_5_11_4() -> None:
|
||||
# Arrange — "Pitched, Unknown loft insulation" lodges
|
||||
# roof_insulation_thickness 'NI' (Not Indicated, parsed to 0) — the
|
||||
# thickness is UNDETERMINED, not zero. RdSAP 10 §5.11.4 (page 44):
|
||||
# "U-values in Table 18 are used when thickness of insulation cannot
|
||||
# be determined." So a pitched roof takes the Table 18 column (1)
|
||||
# age-band default (age A = 0.40), NOT the uninsulated 2.30 the
|
||||
# Table 16 row-0 lookup gives for a parsed-0 thickness. Cert
|
||||
# 9836-5829-1500-0803-7206 (top-floor flat, age A).
|
||||
|
||||
# Act
|
||||
result = u_roof(
|
||||
country=Country.ENG,
|
||||
age_band="A",
|
||||
insulation_thickness_mm=0, # parsed from "NI"
|
||||
description="Pitched, Unknown loft insulation",
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert abs(result - 0.40) <= 0.01
|
||||
|
||||
|
||||
def test_u_roof_unknown_flat_insulation_uses_table18_flat_column() -> None:
|
||||
# Arrange — an "Unknown" flat-roof lodgement with no determinable
|
||||
# thickness (None) takes Table 18 column (3) "Flat roof" age-band
|
||||
# default (age H = 0.35), per §5.11.4 — not 2.30.
|
||||
|
||||
# Act
|
||||
result = u_roof(
|
||||
country=Country.ENG,
|
||||
age_band="H",
|
||||
insulation_thickness_mm=None,
|
||||
description="Flat, Unknown insulation",
|
||||
is_flat_roof=True,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert abs(result - 0.35) <= 0.01
|
||||
|
||||
|
||||
def test_u_roof_age_band_j_pitched_returns_table18_value() -> None:
|
||||
# Arrange — Table 18, pitched insulation between joists, age J -> 0.16 W/m^2K.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue