diff --git a/domain/modelling/generators/solid_wall_recommendation.py b/domain/modelling/generators/solid_wall_recommendation.py index 14b84535..31e59403 100644 --- a/domain/modelling/generators/solid_wall_recommendation.py +++ b/domain/modelling/generators/solid_wall_recommendation.py @@ -29,8 +29,9 @@ from repositories.product.product_repository import ProductRepository _EXTERNAL_MEASURE_TYPE: Final[str] = "external_wall_insulation" _INTERNAL_MEASURE_TYPE: Final[str] = "internal_wall_insulation" -# RdSAP `wall_construction` code for solid brick (consistent across paths). +# RdSAP `wall_construction` codes (consistent across paths for 1-5). _WALL_SOLID_BRICK: Final[int] = 3 +_WALL_TIMBER_FRAME: Final[int] = 5 # `wall_insulation_type`: 4 = as-built / assumed (uninsulated) — the trigger. _WALL_AS_BUILT: Final[int] = 4 # `wall_insulation_type` the overlay lodges: 1 = external, 3 = internal. @@ -41,10 +42,11 @@ _WALL_INSULATION_INTERNAL: Final[int] = 3 _SOLID_WALL_INSULATION_MM: Final[int] = 100 # Which solid-wall Options each construction can take (ADR-0019). Solid brick -# takes both; timber-frame (IWI only), system-built, and the breathable -# cob/stone exclusions land in later slices. +# takes both; timber-frame takes IWI only (EWI not constructable). System-built +# and the breathable cob/stone exclusions land in a later slice. _CONSTRUCTABLE_OPTIONS: Final[dict[int, tuple[str, ...]]] = { _WALL_SOLID_BRICK: (_EXTERNAL_MEASURE_TYPE, _INTERNAL_MEASURE_TYPE), + _WALL_TIMBER_FRAME: (_INTERNAL_MEASURE_TYPE,), } _INSULATION_TYPE: Final[dict[str, int]] = { diff --git a/tests/domain/modelling/fixtures/timber_frame_iwi_001431_after.pdf b/tests/domain/modelling/fixtures/timber_frame_iwi_001431_after.pdf new file mode 100644 index 00000000..c24ae877 Binary files /dev/null and b/tests/domain/modelling/fixtures/timber_frame_iwi_001431_after.pdf differ diff --git a/tests/domain/modelling/fixtures/timber_frame_iwi_001431_before.pdf b/tests/domain/modelling/fixtures/timber_frame_iwi_001431_before.pdf new file mode 100644 index 00000000..fec38abb Binary files /dev/null and b/tests/domain/modelling/fixtures/timber_frame_iwi_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 929c6d70..3be44f56 100644 --- a/tests/domain/modelling/test_elmhurst_cascade_pins.py +++ b/tests/domain/modelling/test_elmhurst_cascade_pins.py @@ -171,6 +171,29 @@ def test_solid_brick_generator_offers_ewi_and_iwi_each_pinning_its_after() -> No ) +def test_timber_frame_generator_offers_iwi_only_pinning_its_after() -> None: + # Arrange — timber frame takes IWI but EWI is not constructable (ADR-0019). + before: EpcPropertyData = parse_recommendation_summary( + "timber_frame_iwi_001431_before.pdf" + ) + iwi_after: EpcPropertyData = parse_recommendation_summary( + "timber_frame_iwi_001431_after.pdf" + ) + + # Act + recommendation: Recommendation | None = recommend_solid_wall(before, _AnyProduct()) + assert recommendation is not None + options: dict[str, MeasureOption] = { + option.measure_type: option for option in recommendation.options + } + + # Assert — IWI only, and it reproduces the re-lodged after. + assert set(options) == {"internal_wall_insulation"} + _assert_overlay_reproduces_after( + before, iwi_after, options["internal_wall_insulation"].overlay + ) + + def test_loft_overlay_reproduces_the_relodged_after() -> None: # Arrange before: EpcPropertyData = parse_recommendation_summary(