diff --git a/tests/domain/modelling/test_optimiser_fabric_first.py b/tests/domain/modelling/test_optimiser_fabric_first.py index 1eb19b696..09b01e646 100644 --- a/tests/domain/modelling/test_optimiser_fabric_first.py +++ b/tests/domain/modelling/test_optimiser_fabric_first.py @@ -298,6 +298,30 @@ def test_no_fabric_candidates_proceeds_straight_to_the_full_pool() -> None: assert abs(package.score.sap_continuous - 80.0) <= 1e-9 +def test_without_a_target_fabric_still_gets_first_claim_on_the_budget() -> None: + # Arrange — a max-gain goal (no SAP target). Plain max-gain would spend the + # whole £8000 on the heat pump (+20); fabric first commits the wall (+5) + # before the remainder is considered, pricing the heat pump out. + groups: list[list[ScoredOption]] = [ + [_scored("cavity_wall_insulation", gain=5.0, cost=1000.0, overlay=_WALL_OVERLAY)], + [_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 — no target: the flag applies to every goal, not just Increasing EPC. + package: OptimisedPackage = optimise_package_fabric_first( + groups=groups, + scorer=scorer, + baseline_epc=build_epc(), + budget=8000.0, + target_sap=None, + ) + + # Assert — wall first; the heat pump no longer fits the leftover £7000. + assert _selected_types(package) == {"cavity_wall_insulation"} + assert abs(package.score.sap_continuous - 65.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