From dcc9f5d8aa5c5ccf55412f8c268a638b96618e82 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 9 Jul 2026 11:42:54 +0000 Subject: [PATCH] =?UTF-8?q?A=20dwelling=20with=20no=20fabric=20candidates?= =?UTF-8?q?=20proceeds=20straight=20to=20the=20full=20pool=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents behaviour already delivered: an empty phase 1 meets no target, so phase 2 optimises every group — identical to a plain run. Test passed on arrival. Co-Authored-By: Claude Fable 5 --- .../modelling/test_optimiser_fabric_first.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/domain/modelling/test_optimiser_fabric_first.py b/tests/domain/modelling/test_optimiser_fabric_first.py index 71ce7a4f6..1eb19b696 100644 --- a/tests/domain/modelling/test_optimiser_fabric_first.py +++ b/tests/domain/modelling/test_optimiser_fabric_first.py @@ -275,6 +275,29 @@ def test_ventilation_dependency_is_injected_once_across_both_phases() -> None: assert abs(package.score.sap_continuous - 68.0) <= 1e-9 +def test_no_fabric_candidates_proceeds_straight_to_the_full_pool() -> None: + # Arrange — the envelope work is already done (no fabric Recommendation + # survives generation); fabric first must not veto the run, it just means + # phase 1 has nothing to do. + groups: list[list[ScoredOption]] = [ + [_scored("air_source_heat_pump", gain=20.0, cost=8000.0, overlay=_HEATING_OVERLAY)], + ] + scorer = _StubScorer(base=60.0, wall=5.0, roof=0.0, heating=20.0) + + # Act — target 75. + package: OptimisedPackage = optimise_package_fabric_first( + groups=groups, + scorer=scorer, + baseline_epc=build_epc(), + budget=20000.0, + target_sap=75.0, + ) + + # Assert — the heat pump package, exactly as a plain run would produce. + assert _selected_types(package) == {"air_source_heat_pump"} + assert abs(package.score.sap_continuous - 80.0) <= 1e-9 + + class _InteractionScorer: """A stub whose boiler gain collapses once the wall is insulated (+10 raw, +3 post-fabric) while the heat pump's holds (+8 either way) — so a phase 2