diff --git a/domain/modelling/generators/roof_recommendation.py b/domain/modelling/generators/roof_recommendation.py index c7ab223e..9a3fff36 100644 --- a/domain/modelling/generators/roof_recommendation.py +++ b/domain/modelling/generators/roof_recommendation.py @@ -32,6 +32,9 @@ _ROOF_UNINSULATED_MM = 0 _RECOMMENDED_LOFT_THICKNESS_MM = 300 # Recommended sloping-ceiling depth (mm); Elmhurst re-lodges 100 mm (ADR-0021). _RECOMMENDED_SLOPING_CEILING_THICKNESS_MM = 100 +_FLAT_ROOF_MEASURE_TYPE = "flat_roof_insulation" +# Recommended flat-roof depth (mm); Elmhurst re-lodges 200 mm (ADR-0021). +_RECOMMENDED_FLAT_ROOF_THICKNESS_MM = 200 def recommend_roof_insulation( @@ -65,6 +68,19 @@ def recommend_roof_insulation( thickness_mm=_RECOMMENDED_SLOPING_CEILING_THICKNESS_MM, ) + if "flat" in roof_type: + # A flat roof lodges no thickness when uninsulated ("As Built" → None + # on the Elmhurst path); a lodged thickness means it's already done. + if main.roof_insulation_thickness is not None: + return None + return _roof_recommendation( + epc, + products, + measure_type=_FLAT_ROOF_MEASURE_TYPE, + description="Flat-roof insulation", + thickness_mm=_RECOMMENDED_FLAT_ROOF_THICKNESS_MM, + ) + if "no access" in roof_type: return None # the roof void can't be reached to insulate it diff --git a/tests/domain/modelling/fixtures/flat_roof_001431_after.pdf b/tests/domain/modelling/fixtures/flat_roof_001431_after.pdf new file mode 100644 index 00000000..1457baf6 Binary files /dev/null and b/tests/domain/modelling/fixtures/flat_roof_001431_after.pdf differ diff --git a/tests/domain/modelling/fixtures/flat_roof_001431_before.pdf b/tests/domain/modelling/fixtures/flat_roof_001431_before.pdf new file mode 100644 index 00000000..2220f616 Binary files /dev/null and b/tests/domain/modelling/fixtures/flat_roof_001431_before.pdf differ diff --git a/tests/domain/modelling/test_elmhurst_cascade_pins.py b/tests/domain/modelling/test_elmhurst_cascade_pins.py index d6e38983..704cc1fb 100644 --- a/tests/domain/modelling/test_elmhurst_cascade_pins.py +++ b/tests/domain/modelling/test_elmhurst_cascade_pins.py @@ -392,6 +392,32 @@ def test_roof_generator_insulates_a_thatched_roof_as_loft_pinning_its_after() -> ) +def test_roof_generator_insulates_a_flat_roof_pinning_its_after() -> None: + # Arrange — a flat roof, uninsulated (As Built → None on the Elmhurst path); + # the re-lodged after raises it to 200 mm (ADR-0021). + before: EpcPropertyData = parse_recommendation_summary( + "flat_roof_001431_before.pdf" + ) + after: EpcPropertyData = parse_recommendation_summary( + "flat_roof_001431_after.pdf" + ) + + # Act + 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 flat-roof Option whose overlay reproduces the after. + assert set(options) == {"flat_roof_insulation"} + _assert_overlay_reproduces_after( + before, after, options["flat_roof_insulation"].overlay + ) + + def test_solid_floor_overlay_reproduces_the_relodged_after() -> None: # Arrange before: EpcPropertyData = parse_recommendation_summary(