mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Dependency signals are priced in the goal objective's currency 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a8e2d99018
commit
66748ba26d
1 changed files with 37 additions and 20 deletions
|
|
@ -21,7 +21,7 @@ from __future__ import annotations
|
|||
|
||||
import itertools
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Protocol, Sequence
|
||||
from typing import Callable, Optional, Protocol, Sequence
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
||||
from domain.modelling.measure_type import FABRIC_MEASURE_TYPES, MeasureType
|
||||
|
|
@ -171,6 +171,12 @@ class OptimisedPackage:
|
|||
score: Score
|
||||
|
||||
|
||||
def sap_rating(score: Score) -> float:
|
||||
"""The default Optimiser objective: the un-rounded SAP rating (higher is
|
||||
better) — what every goal optimised before goal-aligned objectives."""
|
||||
return score.sap_continuous
|
||||
|
||||
|
||||
def optimise_package(
|
||||
*,
|
||||
groups: list[list[ScoredOption]],
|
||||
|
|
@ -179,6 +185,7 @@ def optimise_package(
|
|||
budget: Optional[float],
|
||||
target_sap: Optional[float],
|
||||
dependencies: Sequence[MeasureDependency] = (),
|
||||
objective: Callable[[Score], float] = sap_rating,
|
||||
) -> OptimisedPackage:
|
||||
"""Select the Optimised Package for one Property + Scenario (ADR-0016 +
|
||||
its amendment).
|
||||
|
|
@ -197,26 +204,32 @@ def optimise_package(
|
|||
Without a ``target_sap`` (other goals) it is max-gain-within-budget. Either
|
||||
way forced dependencies are injected on every path and their cost counts
|
||||
toward the spend; the returned `selected` includes them. ``budget`` of None
|
||||
means unconstrained."""
|
||||
baseline_sap: float = _score(scorer, baseline_epc, []).sap_continuous
|
||||
means unconstrained.
|
||||
|
||||
``objective`` is the currency every internally-computed figure is measured
|
||||
in (ADR-0062): the goal's metric, higher is better — SAP by default, CO2
|
||||
reduction / bill saving for the goal-aligned Scenarios. The caller must
|
||||
supply the group signals in the same currency; ``target_sap`` (when given)
|
||||
is a value on the same scale."""
|
||||
baseline_value: float = objective(_score(scorer, baseline_epc, []))
|
||||
# Score each forced dependency's independent (role-1) impact so the selection
|
||||
# can price the ventilation a wall drags in — negative for ventilation.
|
||||
deps: list[MeasureDependency] = _with_role1_signals(
|
||||
dependencies, scorer, baseline_epc, baseline_sap
|
||||
dependencies, scorer, baseline_epc, baseline_value, objective
|
||||
)
|
||||
|
||||
if target_sap is None:
|
||||
return _max_gain_package(groups, scorer, baseline_epc, budget, deps)
|
||||
|
||||
target_gain: float = target_sap - baseline_sap
|
||||
target_gain: float = target_sap - baseline_value
|
||||
chosen: Optional[list[ScoredOption]] = optimise_min_cost(
|
||||
groups, budget, target_gain, deps
|
||||
)
|
||||
if chosen is not None:
|
||||
package: OptimisedPackage = _repair_to_target(
|
||||
chosen, groups, deps, scorer, baseline_epc, budget, target_sap
|
||||
chosen, groups, deps, scorer, baseline_epc, budget, target_sap, objective
|
||||
)
|
||||
if package.score.sap_continuous >= target_sap:
|
||||
if objective(package.score) >= target_sap:
|
||||
return package
|
||||
# Target unreachable within budget (warm-start infeasible, or the repaired
|
||||
# package still falls short) → best effort: the most improvement budget buys.
|
||||
|
|
@ -383,18 +396,20 @@ def _with_role1_signals(
|
|||
dependencies: Sequence[MeasureDependency],
|
||||
scorer: Scorer,
|
||||
baseline_epc: EpcPropertyData,
|
||||
baseline_sap: float,
|
||||
baseline_value: float,
|
||||
objective: Callable[[Score], float],
|
||||
) -> list[MeasureDependency]:
|
||||
"""Replace each dependency's placeholder role-1 signal with its true
|
||||
independent-vs-baseline SAP impact, so the selectors price what the
|
||||
dependency really does to the package (ADR-0016 amendment)."""
|
||||
independent-vs-baseline impact **in the objective's currency**, so the
|
||||
selectors price what the dependency really does to the package (ADR-0016
|
||||
amendment; ADR-0062 for the currency)."""
|
||||
scored: list[MeasureDependency] = []
|
||||
for dependency in dependencies:
|
||||
signal: float = (
|
||||
scorer.score(
|
||||
baseline_epc, [dependency.required.option.overlay]
|
||||
).sap_continuous
|
||||
- baseline_sap
|
||||
objective(
|
||||
scorer.score(baseline_epc, [dependency.required.option.overlay])
|
||||
)
|
||||
- baseline_value
|
||||
)
|
||||
scored.append(
|
||||
MeasureDependency(
|
||||
|
|
@ -432,16 +447,17 @@ def _repair_to_target(
|
|||
baseline_epc: EpcPropertyData,
|
||||
budget: Optional[float],
|
||||
target_sap: float,
|
||||
objective: Callable[[Score], float] = sap_rating,
|
||||
) -> OptimisedPackage:
|
||||
"""Inject dependencies onto the warm-start, re-score for the truth, then
|
||||
greedy-add the untreated-group Option with the best marginal SAP-per-£ (its
|
||||
own dependency folded in) until the true SAP clears ``target_sap`` or no
|
||||
affordable improving Option remains."""
|
||||
greedy-add the untreated-group Option with the best marginal objective-per-£
|
||||
(its own dependency folded in) until the true objective value clears
|
||||
``target_sap`` or no affordable improving Option remains."""
|
||||
selected: list[ScoredOption] = _inject(chosen, dependencies)
|
||||
score: Score = _score(scorer, baseline_epc, selected)
|
||||
while score.sap_continuous < target_sap:
|
||||
while objective(score) < target_sap:
|
||||
candidate = _best_repair_candidate(
|
||||
groups, chosen, dependencies, scorer, baseline_epc, score, budget
|
||||
groups, chosen, dependencies, scorer, baseline_epc, score, budget, objective
|
||||
)
|
||||
if candidate is None:
|
||||
break
|
||||
|
|
@ -499,6 +515,7 @@ def _best_repair_candidate(
|
|||
baseline_epc: EpcPropertyData,
|
||||
current: Score,
|
||||
budget: Optional[float],
|
||||
objective: Callable[[Score], float] = sap_rating,
|
||||
) -> Optional[ScoredOption]:
|
||||
"""The untreated-group Option giving the best **marginal** SAP-per-£ when
|
||||
added to the current package — re-scored (not the role-1 signal) with any
|
||||
|
|
@ -520,7 +537,7 @@ def _best_repair_candidate(
|
|||
if budget is not None and package_cost > budget:
|
||||
continue
|
||||
trial: Score = _score(scorer, baseline_epc, trial_selected)
|
||||
marginal: float = trial.sap_continuous - current.sap_continuous
|
||||
marginal: float = objective(trial) - objective(current)
|
||||
if marginal <= 0.0:
|
||||
continue
|
||||
incremental: float = package_cost - base_cost
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue