2.3 KiB
Multi-phase scenarios with per-phase recompute against rolling state
The Scenario aggregate becomes ordered phases: each phase has a measure-type allowlist, an optional budget, and an optional goal. The ModellingPipeline walks the phases in order; for each phase it (1) generates candidate recommendations restricted to the phase's measure types, (2) re-runs ImpactPredictionService against the rolling Effective EPC state (baseline for phase 1; post-phase-1 for phase 2; etc.), (3) optimises within the phase's budget/goal, (4) applies the selected package and rolls the state forward. We considered scoring all measures once against the baseline and slicing the scored list by phase, and rejected that.
Per-phase recompute makes phase ordering load-bearing in the optimisation, not decorative. Installing fabric measures before a heat pump materially changes the heat pump's SAP impact; a single-pass-against-baseline pipeline forces that fact into the optimiser as a hard rule rather than a derived effect, and any cross-measure interaction we don't know to encode becomes silent error. The cost is ML calls scaling with N_phases × N_scenarios × N_candidate_measures per property — multi-phase scenarios pay their own ML bill, single-phase scenarios cost the same as today (the loop body runs once).
A single-phase Scenario is phases: [<one ScenarioPhase>] with all measure types allowed and the full budget on it. There is no special-case path for single-phase — the pipeline always loops. This avoids two code paths and lets the FE evolve from single-phase to multi-phase without rewiring the backend.
Consequences
Plancarriesphases: list[PlanPhase]rather than a flatOptimisedPackage. Every consumer of plan output (FE, exports, downstream reports) reads phases.- The optimiser must accept rolling-state input rather than only baseline state — a generalisation of today's single-shot pass.
- ML cost can be controlled at the scenario layer: keeping a scenario single-phase is the lever for "score once, optimise once" if cost becomes a problem.
- Open future change: SAP impact of a measure is not strictly additive even within a phase. The current per-measure scoring + linear optimisation approximates this. A future iteration may pre-define candidate packages and ML-score whole packages, accepting combinatorial cost for accuracy. Track in PRD §15.