From 36f74360a5376c26f773764edbbf831374387736 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 5 Jun 2026 10:49:50 +0000 Subject: [PATCH] feat(modelling): explicit park-home guard in the solid-wall generator ADR-0019 warns that wall_construction code 8 is Park home (PH), NOT system- built. It was already excluded (8 isn't in the constructable-options map), but only implicitly. Add an explicit early-return + named constant so a park home can never be mis-keyed as system-built, with a pin as the tripwire. A park home's proprietary panel is never EWI/IWI-suitable. Co-Authored-By: Claude Opus 4.8 --- .../generators/solid_wall_recommendation.py | 6 ++++++ .../modelling/test_wall_recommendation.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/domain/modelling/generators/solid_wall_recommendation.py b/domain/modelling/generators/solid_wall_recommendation.py index 9d4a67e0..3454341e 100644 --- a/domain/modelling/generators/solid_wall_recommendation.py +++ b/domain/modelling/generators/solid_wall_recommendation.py @@ -40,6 +40,10 @@ _WALL_TIMBER_FRAME: Final[int] = 5 # disambiguated from basement by `main_wall_is_basement` below — a basement wall # is never solid-wall-insulation-suitable regardless. _WALL_SYSTEM_BUILT: Final[int] = 6 +# Park home (`PH`, the Elmhurst code-8 wall) — NOT system-built (ADR-0019: "do +# not key system-built on 8"). A park home's wall is a proprietary panel system +# our EWI/IWI model doesn't represent, so it is never solid-wall-suitable. +_WALL_PARK_HOME: Final[int] = 8 # `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. @@ -142,6 +146,8 @@ def recommend_solid_wall( construction: object = main.wall_construction if not isinstance(construction, int): return None # a free-text site-notes construction is not a code we key on + if construction == _WALL_PARK_HOME: + return None # park home (code 8) — proprietary panel, never EWI/IWI measure_types = _CONSTRUCTABLE_OPTIONS.get(construction) if not measure_types: return None diff --git a/tests/domain/modelling/test_wall_recommendation.py b/tests/domain/modelling/test_wall_recommendation.py index 182ce2a3..b47d9eb1 100644 --- a/tests/domain/modelling/test_wall_recommendation.py +++ b/tests/domain/modelling/test_wall_recommendation.py @@ -12,6 +12,7 @@ from domain.modelling.scoring.overlay_applicator import apply_simulations from domain.modelling.product import Product from domain.modelling.recommendation import Recommendation from domain.modelling.generators.wall_recommendation import recommend_cavity_wall +from domain.modelling.generators.solid_wall_recommendation import recommend_solid_wall from repositories.product.product_repository import ProductRepository from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( build_epc, @@ -87,3 +88,21 @@ def test_non_cavity_main_wall_yields_no_recommendation() -> None: # Assert assert recommendation is None + + +def test_park_home_wall_yields_no_solid_wall_recommendation() -> None: + # Arrange — a park home (wall_construction code 8) with an uninsulated + # as-built wall. Code 8 is NOT system-built (ADR-0019); a park home's + # proprietary panel is never EWI/IWI-suitable, so the generator excludes it. + baseline: EpcPropertyData = build_epc() + main: SapBuildingPart = _part(baseline, BuildingPartIdentifier.MAIN) + main.wall_construction = 8 + main.wall_insulation_type = 4 # as-built / uninsulated — the trigger + + # Act + recommendation: Recommendation | None = recommend_solid_wall( + baseline, _StubProducts() + ) + + # Assert + assert recommendation is None