From 3bbe293f68e1b7763e7c619e5bfe223ad8340e50 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 10 Jul 2026 16:07:48 +0000 Subject: [PATCH] =?UTF-8?q?Gate=20the=20loft=20top-up=20strictly=20below?= =?UTF-8?q?=20the=20270mm=20building-regs=20depth=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Locks the gate edges — a lodged pitched loft below 270mm is recommended, a loft at 270mm is left alone, and the 'Nmm+' form ('300mm+') parses to its number and stays ineligible. These passed on arrival (they fell out of the numeric-parse gate added in the previous commit); pinned as regression guards. ADR-0063. Co-Authored-By: Claude Fable 5 --- .../modelling/test_roof_recommendation.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/domain/modelling/test_roof_recommendation.py b/tests/domain/modelling/test_roof_recommendation.py index 55b459da7..781814a8a 100644 --- a/tests/domain/modelling/test_roof_recommendation.py +++ b/tests/domain/modelling/test_roof_recommendation.py @@ -242,6 +242,55 @@ def test_loft_below_building_regs_depth_yields_loft_insulation() -> None: ) +def test_lodged_pitched_loft_below_regs_yields_loft_insulation() -> None: + # Arrange — the below-regs gate also fires on a KNOWN pitched loft with a + # partial lodged depth (150 mm), not only the unlodged shape: the numeric + # rule applies regardless of roof type (ADR-0063). + baseline: EpcPropertyData = build_epc() + main: SapBuildingPart = _part(baseline, BuildingPartIdentifier.MAIN) + main.roof_construction_type = "Pitched (slates/tiles), access to loft" + main.roof_insulation_thickness = "150mm" + + # Act + recommendation: Recommendation | None = recommend_roof_insulation( + baseline, _StubProducts() + ) + + # Assert + assert recommendation is not None + assert recommendation.options[0].measure_type == "loft_insulation" + + +def test_loft_at_the_building_regs_depth_yields_no_recommendation() -> None: + # Arrange — 270 mm is the compliance gate: a loft AT the standard is not + # sub-standard, so it is left alone (strict below-270, ADR-0063). + baseline: EpcPropertyData = build_epc() + _part(baseline, BuildingPartIdentifier.MAIN).roof_insulation_thickness = "270mm" + + # Act + recommendation: Recommendation | None = recommend_roof_insulation( + baseline, _StubProducts() + ) + + # Assert + assert recommendation is None + + +def test_loft_with_a_plus_suffix_depth_above_regs_yields_no_recommendation() -> None: + # Arrange — the "Nmm+" lodged form ("300mm+") parses to its number (300), + # which is at/above the gate, so no top-up is offered (ADR-0063). + baseline: EpcPropertyData = build_epc() + _part(baseline, BuildingPartIdentifier.MAIN).roof_insulation_thickness = "300mm+" + + # Act + recommendation: Recommendation | None = recommend_roof_insulation( + baseline, _StubProducts() + ) + + # Assert + assert recommendation is None + + def test_loft_option_carries_cost_from_roof_area_and_product() -> None: # Arrange baseline: EpcPropertyData = build_epc() # MAIN roof area 14.85 m^2