From 6484610b6c0ea426a05e05bb71a7965f13f336f3 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 4 Jun 2026 21:02:06 +0000 Subject: [PATCH] feat(modelling): recommend_roof_insulation insulates a sloping ceiling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 1 of the roof-insulation generator (ADR-0021). New `recommend_roof_insulation` dispatcher keys on the `roof_construction_type` string: a "sloping ceiling" roof that is uninsulated (roof_insulation_thickness 0/None) gets a single `sloping_ceiling_insulation` Option whose overlay raises roof_insulation_thickness to 100 mm. Pinned against the Elmhurst before→after cert 001431 at 1e-4. Co-Authored-By: Claude Opus 4.8 --- .../generators/roof_recommendation.py | 67 ++++++++++++++++++ .../fixtures/sloping_ceiling_001431_after.pdf | Bin 0 -> 80806 bytes .../sloping_ceiling_001431_before.pdf | Bin 0 -> 80938 bytes .../modelling/test_elmhurst_cascade_pins.py | 32 ++++++++- 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 tests/domain/modelling/fixtures/sloping_ceiling_001431_after.pdf create mode 100644 tests/domain/modelling/fixtures/sloping_ceiling_001431_before.pdf diff --git a/domain/modelling/generators/roof_recommendation.py b/domain/modelling/generators/roof_recommendation.py index 6b689733..8bbbd34b 100644 --- a/domain/modelling/generators/roof_recommendation.py +++ b/domain/modelling/generators/roof_recommendation.py @@ -18,12 +18,19 @@ from domain.modelling.simulation import BuildingPartOverlay, EpcSimulation from repositories.product.product_repository import ProductRepository _LOFT_MEASURE_TYPE = "loft_insulation" +_SLOPING_CEILING_MEASURE_TYPE = "sloping_ceiling_insulation" # RdSAP 10 Table 16: 0 mm lodged roof insulation is an uninsulated loft. _ROOF_UNINSULATED_MM = 0 +# A roof is uninsulated when its lodged insulation thickness is 0 or absent — +# the Elmhurst mapper resolves "As Built" to 0 for pitched/sloping roofs and to +# None for flat roofs (ADR-0021). +_ROOF_UNINSULATED_THICKNESSES: tuple[Optional[int], ...] = (None, 0) # Recommended loft-insulation depth (mm). Elmhurst re-lodges a loft-insulation # measure at 300 mm; pinning the before→after cascade (000490/001431) requires # the overlay to match that depth exactly (see test_elmhurst_cascade_pins). _RECOMMENDED_LOFT_THICKNESS_MM = 300 +# Recommended sloping-ceiling depth (mm); Elmhurst re-lodges 100 mm (ADR-0021). +_RECOMMENDED_SLOPING_CEILING_THICKNESS_MM = 100 def recommend_loft_insulation( @@ -62,3 +69,63 @@ def recommend_loft_insulation( material_id=product.id, ) return Recommendation(surface="Roof", options=(option,)) + + +def recommend_roof_insulation( + epc: EpcPropertyData, products: ProductRepository +) -> Optional[Recommendation]: + """Return the single roof-insulation Recommendation for the MAIN roof, + dispatched by roof type (ADR-0021): a sloping ceiling is insulated at the + rafters to 100 mm. Returns None when the roof type has no applicable measure + or the roof is already insulated.""" + main = next( + part + for part in epc.sap_building_parts + if part.identifier is BuildingPartIdentifier.MAIN + ) + roof_type: str = (main.roof_construction_type or "").lower() + + if "sloping ceiling" in roof_type: + if main.roof_insulation_thickness not in _ROOF_UNINSULATED_THICKNESSES: + return None + return _roof_recommendation( + epc, + products, + measure_type=_SLOPING_CEILING_MEASURE_TYPE, + description="Sloping-ceiling insulation (insulate at the rafters)", + thickness_mm=_RECOMMENDED_SLOPING_CEILING_THICKNESS_MM, + ) + return None + + +def _roof_recommendation( + epc: EpcPropertyData, + products: ProductRepository, + *, + measure_type: str, + description: str, + thickness_mm: int, +) -> Recommendation: + """Build a single-Option "Roof" Recommendation: the measure's insulation + overlay (raising `roof_insulation_thickness` to the recommended depth) + priced at the roof area.""" + product = products.get(measure_type) + area: float = roof_area(epc, BuildingPartIdentifier.MAIN) + cost = Cost( + total=area * product.unit_cost_per_m2, + contingency_rate=product.contingency_rate, + ) + option = MeasureOption( + measure_type=measure_type, + description=description, + overlay=EpcSimulation( + building_parts={ + BuildingPartIdentifier.MAIN: BuildingPartOverlay( + roof_insulation_thickness=thickness_mm + ) + } + ), + cost=cost, + material_id=product.id, + ) + return Recommendation(surface="Roof", options=(option,)) diff --git a/tests/domain/modelling/fixtures/sloping_ceiling_001431_after.pdf b/tests/domain/modelling/fixtures/sloping_ceiling_001431_after.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4d506a80623f413684df9e8e30e28ce4f6f9f29f GIT binary patch literal 80806 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
    wnBV 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 E0*v4$O#lD@ literal 0 HcmV?d00001 diff --git a/tests/domain/modelling/fixtures/sloping_ceiling_001431_before.pdf b/tests/domain/modelling/fixtures/sloping_ceiling_001431_before.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9e346c341b4cc656ab7d3842c48b14a413237e9b GIT binary patch literal 80938 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 T{rPsn`5;0N9A@TwDv199TC}JF literal 0 HcmV?d00001 diff --git a/tests/domain/modelling/test_elmhurst_cascade_pins.py b/tests/domain/modelling/test_elmhurst_cascade_pins.py index 5821b5c1..6bffc3e2 100644 --- a/tests/domain/modelling/test_elmhurst_cascade_pins.py +++ b/tests/domain/modelling/test_elmhurst_cascade_pins.py @@ -27,7 +27,10 @@ from domain.modelling.scoring.package_scorer import PackageScorer, Score from domain.modelling.product import Product from domain.modelling.recommendation import Recommendation from domain.modelling.generators.floor_recommendation import recommend_floor_insulation -from domain.modelling.generators.roof_recommendation import recommend_loft_insulation +from domain.modelling.generators.roof_recommendation import ( + recommend_loft_insulation, + recommend_roof_insulation, +) from domain.modelling.simulation import BuildingPartOverlay, EpcSimulation from domain.modelling.generators.wall_recommendation import recommend_cavity_wall from domain.geospatial.planning_restrictions import PlanningRestrictions @@ -336,6 +339,33 @@ def test_loft_overlay_reproduces_the_relodged_after() -> None: ) +def test_roof_generator_insulates_a_sloping_ceiling_pinning_its_after() -> None: + # Arrange — a pitched roof with an uninsulated sloping ceiling; the re-lodged + # after raises its insulation from As Built to 100 mm (ADR-0021). + before: EpcPropertyData = parse_recommendation_summary( + "sloping_ceiling_001431_before.pdf" + ) + after: EpcPropertyData = parse_recommendation_summary( + "sloping_ceiling_001431_after.pdf" + ) + + # Act — the dispatcher detects "sloping ceiling" and offers the sloping + # measure (not loft). + recommendation: Recommendation | None = recommend_roof_insulation( + before, _AnyProduct() + ) + assert recommendation is not None + options: dict[str, MeasureOption] = { + option.measure_type: option for option in recommendation.options + } + + # Assert — one sloping-ceiling Option whose overlay reproduces the after. + assert set(options) == {"sloping_ceiling_insulation"} + _assert_overlay_reproduces_after( + before, after, options["sloping_ceiling_insulation"].overlay + ) + + def test_solid_floor_overlay_reproduces_the_relodged_after() -> None: # Arrange before: EpcPropertyData = parse_recommendation_summary(