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 f8a0abb1..1ac7e55f 100644 --- a/packages/domain/src/domain/ml/tests/test_rdsap_uvalues.py +++ b/packages/domain/src/domain/ml/tests/test_rdsap_uvalues.py @@ -1230,3 +1230,56 @@ def test_u_wall_matches_table6_for_every_cohort_description_age_pair( # Assert assert abs(u - expected_u) < 1e-4 + + +# ----- Description-cascade cohort pins (Roofs) ----- +# +# Mirror of the wall cohort pins above. The Elmhurst worksheet fixtures +# lodge `roofs=[]`, so the cascade-pin tests do not exercise u_roof's +# description path either. The 8 golden API certs lodge `roofs[]. +# description` strings — these tests pin each (description, age, +# thickness) tuple seen in that cohort against the RdSAP10 Table 16 +# (loft-insulation-thickness-known) value the spec mandates. +# +# Excluded: ambiguous joined-description cases where one bp lodges no +# thickness and another lodges a value — the calc routes through Table +# 18 defaults whose interaction with the description cascade needs +# separate pinning. "(another dwelling above)" is also excluded — its +# u_roof value is ignored by heat_transmission once roof_area is zeroed. +_TABLE_16_ENG_ROOF_COHORT_PINS: tuple[tuple[str, str, int, float], ...] = ( + # (joined_description, age_band, thickness_mm, expected_u_w_per_m2k) + # Table 16 col 1 — thickness-known path, U independent of age band. + ("Pitched, 100 mm loft insulation", "D", 100, 0.40), # cert 7536 bp0 + ("Pitched, 100 mm loft insulation | Pitched, insulated (assumed)", "D", 100, 0.40), # cert 7536 bp0 (joined) + ("Pitched, 270 mm loft insulation", "D", 270, 0.16), # cert 0300 bp0 + ("Pitched, 300 mm loft insulation | Flat, no insulation", "D", 300, 0.14), # cert 0390-2954 bp0 + ("Pitched, 300 mm loft insulation | Roof room(s), limited insulation (assumed)", "A", 300, 0.14), # cert 6035 bp0 + ("Pitched, 300 mm loft insulation | Flat, insulated", "C", 300, 0.14), # cert 8135 bp0 + ("Pitched, 300 mm loft insulation | Pitched, 100 mm loft insulation", "B", 300, 0.14), # cert 2130 bp0 + ("Pitched, 400+ mm loft insulation | Pitched, insulated (assumed)", "J", 400, 0.11), # cert 0240 bp0 +) + + +@pytest.mark.parametrize( + "description, age_band, thickness_mm, expected_u", + _TABLE_16_ENG_ROOF_COHORT_PINS, +) +def test_u_roof_matches_table16_for_every_cohort_description_thickness_pair( + description: str, + age_band: str, + thickness_mm: int, + expected_u: float, +) -> None: + # Arrange — inputs replicate what `heat_transmission_from_cert` feeds + # `u_roof` for the main building part in the golden cert cohort. + + # Act + u = u_roof( + country=Country.ENG, + age_band=age_band, + insulation_thickness_mm=thickness_mm, + description=description, + ) + + # Assert + assert abs(u - expected_u) < 1e-4