Model/docs/adr/0062-goal-aligned-optimiser-objectives.md
Khalim Conn-Kowlessar b71bc788a2 Record goal-aligned Optimiser objectives as ADR-0062
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:11:07 +00:00

3.4 KiB

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_gainNone) 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.