Fabric gets first claim on the budget for every goal, not just Increasing EPC 🟩

Documents behaviour already delivered: with no SAP target both phases run
max-gain, so the envelope still consumes the budget first. Test passed on
arrival.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-09 11:42:54 +00:00
parent dcc9f5d8aa
commit d51801089b

View file

@ -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