From e329c50fa36ab2e5f51b50cbe4ac762e37555e67 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 9 Jul 2026 13:13:23 +0000 Subject: [PATCH] =?UTF-8?q?Fabric-first=20phase=202=20re-scores=20candidat?= =?UTF-8?q?es=20in=20the=20goal=20objective's=20currency=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- domain/modelling/optimisation/optimiser.py | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/domain/modelling/optimisation/optimiser.py b/domain/modelling/optimisation/optimiser.py index 2f556e2a2..57976c4ca 100644 --- a/domain/modelling/optimisation/optimiser.py +++ b/domain/modelling/optimisation/optimiser.py @@ -276,7 +276,7 @@ def optimise_package_fabric_first( ) if ( target_sap is not None - and fabric_package.score.sap_continuous >= target_sap + and objective(fabric_package.score) >= target_sap ): return fabric_package if not fabric_package.selected: @@ -326,13 +326,15 @@ def optimise_package_fabric_first( remaining_groups, post_fabric_scorer, baseline_epc, - start_sap=fabric_package.score.sap_continuous, + objective=objective, + start_value=objective(fabric_package.score), ), scorer=post_fabric_scorer, baseline_epc=baseline_epc, budget=leftover_budget, target_sap=target_sap, dependencies=outstanding_dependencies, + objective=objective, ) return OptimisedPackage( selected=[*fabric_package.selected, *top_up.selected], @@ -353,23 +355,25 @@ def _rescored_groups( scorer: Scorer, baseline_epc: EpcPropertyData, *, - start_sap: float, + objective: Callable[[Score], float], + start_value: float, ) -> list[list[ScoredOption]]: """The groups with every Option's role-1 warm-start signal re-scored - through ``scorer`` — for phase 2, its independent gain on the post-fabric - dwelling rather than the raw baseline, so options whose worth changes once - the envelope is treated (a boiler on an insulated home) are re-ranked. - ``start_sap`` is the score of ``baseline_epc`` through ``scorer`` with no - candidate applied — the caller already has it (the phase-1 package score), - so it is threaded in rather than re-computed.""" + through ``scorer`` in the ``objective``'s currency — for phase 2, its + independent gain on the post-fabric dwelling rather than the raw baseline, + so options whose worth changes once the envelope is treated (a boiler on + an insulated home) are re-ranked. ``start_value`` is the objective value of + ``baseline_epc`` through ``scorer`` with no candidate applied — the caller + already has it (the phase-1 package score in the objective's currency), so + it is threaded in rather than re-computed.""" return [ [ ScoredOption( option=scored.option, - sap_gain=scorer.score( - baseline_epc, [scored.option.overlay] - ).sap_continuous - - start_sap, + sap_gain=objective( + scorer.score(baseline_epc, [scored.option.overlay]) + ) + - start_value, ) for scored in group ]