Fabric short of the target is topped up with non-fabric measures 🟩

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-09 11:39:21 +00:00
parent e880ae868c
commit e652780873

View file

@ -248,7 +248,41 @@ def optimise_package_fabric_first(
target_sap=target_sap,
dependencies=dependencies,
)
return fabric_package
if (
target_sap is not None
and fabric_package.score.sap_continuous >= target_sap
):
return fabric_package
# Phase 2 — the upgrade requirement is not met by fabric alone: optimise
# the remaining groups (non-fabric, plus any fabric group phase 1 left
# unpicked) on top of the committed fabric. Every score call is prefixed
# with the phase-1 overlays, so candidates are valued against the
# fabric-applied dwelling and the resulting package score stays the
# truthful whole-package figure against the original baseline.
remaining_groups: list[list[ScoredOption]] = [
group
for index, group in enumerate(groups)
if index not in _used_group_indices(groups, fabric_package.selected)
]
post_fabric_scorer = _PrefixedScorer(
scorer, [scored.option.overlay for scored in fabric_package.selected]
)
leftover_budget: Optional[float] = (
None if budget is None else budget - _package_cost(fabric_package.selected)
)
top_up: OptimisedPackage = optimise_package(
groups=remaining_groups,
scorer=post_fabric_scorer,
baseline_epc=baseline_epc,
budget=leftover_budget,
target_sap=target_sap,
dependencies=dependencies,
)
return OptimisedPackage(
selected=[*fabric_package.selected, *top_up.selected],
score=top_up.score,
)
def _is_fabric_group(group: list[ScoredOption]) -> bool:
@ -259,6 +293,23 @@ def _is_fabric_group(group: list[ScoredOption]) -> bool:
)
class _PrefixedScorer:
"""A Scorer view of the dwelling with a committed package already applied:
every score call sees the ``prefix`` overlays before the candidate's own.
Phase 2 of Fabric First scores through this, so its candidates are valued
against the post-fabric dwelling while the returned Score remains the
truthful whole-package figure against the true baseline."""
def __init__(self, inner: Scorer, prefix: Sequence[EpcSimulation]) -> None:
self._inner = inner
self._prefix = list(prefix)
def score(
self, baseline: EpcPropertyData, simulations: Sequence[EpcSimulation]
) -> Score:
return self._inner.score(baseline, [*self._prefix, *simulations])
def _with_role1_signals(
dependencies: Sequence[MeasureDependency],
scorer: Scorer,