From 15789f5acf84099eac88c944118b4c65c7383542 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sun, 24 May 2026 17:10:43 +0000 Subject: [PATCH] Audit: pin u_wall description cascade against RdSAP10 Table 6 (England) for golden cert cohort MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Worksheet fixtures lodge walls=[] so the description-driven branches of u_wall — the codepath real API certs trigger — were never validated at cascade level. New parametrised test pins each (description, age) pair seen in the 8 golden certs against the Table 6 value the spec mandates. All 7 clean cases match spec: the description cascade is correct where Table 6 gives a direct value. Cases routing through §5.7 / §5.8 formulas are excluded pending separate pinning. Co-Authored-By: Claude Opus 4.7 --- .../src/domain/ml/tests/test_rdsap_uvalues.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/packages/domain/src/domain/ml/tests/test_rdsap_uvalues.py b/packages/domain/src/domain/ml/tests/test_rdsap_uvalues.py index af1b034c..f8a0abb1 100644 --- a/packages/domain/src/domain/ml/tests/test_rdsap_uvalues.py +++ b/packages/domain/src/domain/ml/tests/test_rdsap_uvalues.py @@ -16,6 +16,8 @@ defaults -> country defaults -> final mid-range value so that callers can treat the envelope as if RdSAP had assigned an as-built default. """ +from typing import Optional + import pytest from domain.ml.rdsap_uvalues import ( @@ -1166,3 +1168,65 @@ def test_u_rr_stud_wall_thickness_125mm_takes_nearest_tabulated_row_below() -> N # Assert assert result == pytest.approx(0.31, abs=0.001) + + +# ----- Description-cascade cohort pins (Walls) ----- +# +# The Elmhurst worksheet fixtures lodge `walls=[]` and so cannot exercise +# the description-driven branches of `u_wall`. The 8 golden API certs DO +# carry `walls[0].description` strings that route through `_described_as_ +# insulated`, `_wall_type_from_description`, and the Table 6 footnote +# 50 mm-bucket override. These tests pin every (description, age) pair +# seen in that cohort against the RdSAP10 Table 6 (England) value the +# spec mandates — closing the cascade-coverage gap identified during the +# 2026-05-24 audit (description cascade was 100% spec-correct on clean +# Table 6 rows; this test locks that in for regression). +# +# Cases routing through §5.7 (solid brick from wall thickness) or §5.8 +# (stone/brick with insulation, ages A–D — formula not table) are +# intentionally excluded — they need separate pinning when those +# formulas land. +_TABLE_6_ENG_WALL_COHORT_PINS: tuple[tuple[str, str, Optional[int], Optional[int], Optional[int], bool, float], ...] = ( + # (description, age_band, wall_construction, wall_insulation_type, + # insulation_thickness_mm, insulation_present, expected_u_w_per_m2k) + # `insulation_present` mirrors the heat_transmission cascade: type != 4 (NONE) + # OR description asserts insulation per _described_as_insulated. + ("Sandstone, as built, insulated (assumed)", "J", 2, 4, 0, True, 0.25), # cert 0240 (50 mm bucket per footnote) + ("Cavity wall, filled cavity", "C", 4, 2, 0, True, 0.7), # cert 8135 bp0 + ("Cavity wall, filled cavity", "D", 4, 2, 0, True, 0.7), # cert 0300, 7536 bp0 + ("Cavity wall, filled cavity", "F", 4, 2, 0, True, 0.40), # cert 7536 bp2 + ("Cavity wall, filled cavity", "G", 4, 2, 0, True, 0.35), # cert 8135 bp1 + ("Cavity wall, filled cavity", "L", 4, 4, 0, False, 0.28), # cert 7536 bp1 (assumed-as-built †) + ("Cavity wall, as built, no insulation (assumed)", "D", 4, 4, 0, False, 1.5), # cert 0390-2954, 9390 +) + + +@pytest.mark.parametrize( + "description, age_band, construction, insulation_type, thickness_mm, insulation_present, expected_u", + _TABLE_6_ENG_WALL_COHORT_PINS, +) +def test_u_wall_matches_table6_for_every_cohort_description_age_pair( + description: str, + age_band: str, + construction: Optional[int], + insulation_type: Optional[int], + thickness_mm: Optional[int], + insulation_present: bool, + expected_u: float, +) -> None: + # Arrange — inputs replicate what `heat_transmission_from_cert` feeds + # `u_wall` for the corresponding building part in the golden cert cohort. + + # Act + u = u_wall( + country=Country.ENG, + age_band=age_band, + construction=construction, + insulation_thickness_mm=thickness_mm, + insulation_present=insulation_present, + description=description, + wall_insulation_type=insulation_type, + ) + + # Assert + assert abs(u - expected_u) < 1e-4