mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Record the fabric-first two-phase decision as ADR-0061
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
4d7434a954
commit
38206c227b
1 changed files with 73 additions and 0 deletions
73
docs/adr/0061-fabric-first-is-a-two-phase-optimisation.md
Normal file
73
docs/adr/0061-fabric-first-is-a-two-phase-optimisation.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
status: accepted (extends ADR-0016)
|
||||
---
|
||||
|
||||
# Fabric First is a two-phase optimisation with strict envelope priority
|
||||
|
||||
Landlords with a fabric-first retrofit policy require the building envelope —
|
||||
insulation and windows — to be treated before heating systems and renewables
|
||||
are considered. The legacy engine carried this as `enforce_fabric_first` on
|
||||
the plan-API request body (`funding_optimiser.optimise_with_scenarios`): an
|
||||
optimiser pass over fabric-only measures with the full budget, then a second
|
||||
pass over the remainder with the leftover budget and the residual target,
|
||||
summing approximate per-measure SAP points. The new engine needed the same
|
||||
capability on the truthful-re-score Optimiser core (ADR-0016).
|
||||
|
||||
Decided in a grilling session with Khalim, 2026-07-09.
|
||||
|
||||
## Decision
|
||||
|
||||
**`fabric_first` is a Scenario attribute, and the Optimiser honours it as two
|
||||
sequential `optimise_package` phases in which the envelope has strict first
|
||||
claim on the budget** (`optimise_package_fabric_first`, ADR-0016 core reused
|
||||
per phase).
|
||||
|
||||
- **The flag lives on the `scenario` table** (FE-owned Drizzle schema:
|
||||
`fabric_first boolean NOT NULL DEFAULT false`), mirrored in `ScenarioModel`
|
||||
and the domain `Scenario`. A Plan's provenance stays derivable from its
|
||||
Scenario row alone — unlike the legacy request-body flag, the same
|
||||
scenario_id cannot produce differently-constrained plans. **Deploy order**:
|
||||
the Drizzle migration must land before this mirror, or every scenario read
|
||||
crashes on the missing column.
|
||||
- **Fabric = the building envelope** (`FABRIC_MEASURE_TYPES`): wall (CWI /
|
||||
EWI / IWI), roof (loft / sloping-ceiling / flat-roof), floor (suspended /
|
||||
solid) insulation, plus double / secondary glazing — the legacy list
|
||||
exactly. Lighting, tune-ups, secondary-heating removal, heating and solar
|
||||
all wait for phase 2. Mechanical ventilation is unclassified: it is never
|
||||
selected, only injected as a forced Measure Dependency (ADR-0016) of the
|
||||
fabric that triggers it, in whichever phase that happens — and only once.
|
||||
- **Phase 1** runs `optimise_package` over the fabric groups with the full
|
||||
budget: least-cost-to-target, repair, max-gain fallback. If the truthful
|
||||
post-fabric score meets the Scenario target, the package is fabric-only —
|
||||
surplus budget is left unspent, per the ADR-0016 no-overshoot doctrine.
|
||||
- **Phase 2** (target unmet, or no target) optimises every group phase 1 did
|
||||
not consume — non-fabric, plus fabric groups it left unpicked, which may
|
||||
re-enter on their post-fabric worth — under the leftover budget. Candidates
|
||||
are valued **against the fabric-applied dwelling**: warm-start signals are
|
||||
re-scored and every package re-score is prefixed with the phase-1 overlays
|
||||
(`_PrefixedScorer`), so a heating system whose worth changes once the
|
||||
envelope is treated is re-ranked truthfully, and the returned Score remains
|
||||
the whole-package figure against the true baseline (bills and the role-3
|
||||
cascade stay honest).
|
||||
- **Strict priority, not target-aware compromise**: phase 1 commits the
|
||||
max-gain fabric package even when a cheaper fabric/heating split would have
|
||||
reached the target — a £4,000 budget buys floor insulation and leaves the
|
||||
£3,200 boiler unaffordable, and the target is missed rather than the fabric
|
||||
skipped. This is the landlord's explicit trade.
|
||||
- **Every goal honours the flag**, not just Increasing EPC: with no SAP
|
||||
target the two phases are both max-gain, so fabric still gets first claim
|
||||
on the budget.
|
||||
- **No Plan-contract change**: the Scenario row is the provenance; the
|
||||
best-practice cascade already orders fabric before heating in the persisted
|
||||
measures.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The Modelling orchestrator branches once, on `scenario.fabric_first`,
|
||||
between `optimise_package` and `optimise_package_fabric_first`; everything
|
||||
downstream (attribution, bills, persistence) is unchanged.
|
||||
- Phase 2 re-scores each remaining Option once against the post-fabric
|
||||
dwelling — a handful of extra calculator calls per fabric-first Plan.
|
||||
- A fabric-first Plan can undershoot a target a plain Plan would have reached
|
||||
within the same budget. This is by design and should be communicated when
|
||||
scenario results are compared.
|
||||
Loading…
Add table
Reference in a new issue