diff --git a/docs/adr/0062-goal-aligned-optimiser-objectives.md b/docs/adr/0062-goal-aligned-optimiser-objectives.md new file mode 100644 index 000000000..afc87518b --- /dev/null +++ b/docs/adr/0062-goal-aligned-optimiser-objectives.md @@ -0,0 +1,62 @@ +--- +status: accepted (extends ADR-0016; composes with ADR-0061) +--- + +# Goal-aligned Optimiser objectives: each goal maximises its own metric + +Every Scenario goal used to optimise SAP. The legacy engine returned no +target for Energy Savings / Reducing CO2 (`optimiser_functions.calculate_gain` +→ `None`) and maximised SAP gain within budget regardless of the goal, and the +new engine inherited that: the goal label changed nothing but the words on the +brief. The scorer already computes each package's carbon and (via SapResult → +EnergyBreakdown → BillDerivation) its annual bill, so aligning the objective +is a selection change, not a calculator change. + +Decided in a grilling session with Khalim, 2026-07-09. + +## Decision + +**The Optimiser maximises the Scenario goal's own metric, as a pluggable +`objective: Callable[[Score], float]` (higher is better), with no target: +goal-aligned briefs are "reduce as much as possible within this budget".** + +- **Reducing CO2 emissions** maximises annual kg CO2 saved + (`-score.co2_kg_per_yr`). +- **Energy Savings** maximises the annual bill £ saved, priced at the **live + Fuel Rates snapshot** (ADR-0014), not SAP's internal tariff book — that + difference is the point of the goal. SAP is itself a cost-shaped rating, so + the two frequently agree; they diverge exactly when current tariffs disagree + with SAP's assumptions (e.g. the gas/electricity price ratio). +- **Increasing EPC** keeps its SAP objective and band-target semantics + (least-cost-to-target, repair, max-gain fallback) unchanged. +- **Valuation Improvement / None** stay max-SAP-within-budget — SAP is a + defensible valuation proxy and `None` has no semantics to encode. +- **`goal_value` is ignored for the goal-aligned goals** — no percentage or + absolute target exists yet. If targets arrive later they slot into the + existing target machinery on the objective's scale. +- **A budget is mandatory** for the goal-aligned goals: unconstrained + "as much as possible" would recommend every beneficial measure. A + budget-less Energy/CO2 Scenario raises a `ValueError` naming the scenario + and goal — a loud misconfiguration, not a maximal plan. +- **One currency everywhere**: the role-1 group signals + (`independent_option_signals`), the forced Measure Dependency pricing, the + greedy-repair marginals, and Fabric First's phase-2 re-scoring + (ADR-0061) are all measured by the same objective, so a ventilation that + costs SAP but is carbon-neutral cannot sink a carbon-improving wall, and a + fabric-first phase 2 picks its heating on post-fabric carbon, not + post-fabric SAP. + +## Consequences + +- Selection changes; truth-telling does not. The Plan's persisted Scores, + Bills, and role-3 SAP attribution are computed exactly as before — only + *which* package is chosen responds to the goal. +- At a £16,000 budget on the uninsulated solid-brick corpus dwelling + (001431), the SAP objective buys wall + floor + gas boiler (SAP 72.9, + 2,069 kg CO2/yr, £2,088/yr) while the carbon objective buys wall + floor + + storage heaters (SAP 69.2, 1,098 kg CO2/yr, £2,635/yr) — goals now trade + SAP, carbon and bills against each other visibly. +- The Energy Savings objective inherits the Fuel Rates snapshot's staleness + characteristics (quarterly Ofgem-cap cadence, ADR-0014). +- `independent_option_impacts` (role-1 SAP/CO2/kWh triple) is removed — + superseded by `independent_option_signals` in the objective's currency.