Model/docs/adr/0035-coherent-heating-system-synthesis.md
Khalim Conn-Kowlessar db3bf00602 fix(synthesis): Coherent Heating System — synthesisers own the whole boundary
A dwelling's heating is one conceptual system, but its fields are scattered
across EpcPropertyData (a gov-API schema mirror): the cluster on sap_heating, the
electricity tariff on sap_energy_source.meter_type, hot-water flags loose at top
level. Three places synthesise a heating system — Measure Options, Landlord
Overrides, EPC Prediction's donor — and each hand-copied a different ad-hoc
subset. The override and donor both dropped meter_type, so an electric-storage
system landed on the template's single-rate meter and billed overnight heat at
the peak rate: property 713406 scored SAP 13 (G) vs ~50 (E), inflating the HHRSH
measure to +45.8 and overshooting the plan to band A.

Establish a single Coherent Heating System boundary (CONTEXT.md) that every
synthesiser must cover, with a source-appropriate fill policy (ADR-0035):

- Override overlay *completes* the partial system the landlord named. Companion
  fields are now DERIVED from the SAP code, not hand-attached per archetype: the
  off-peak meter from the calculator's single off-peak classification (new
  OFF_PEAK_IMPLYING_HEATING_CODES = SAP §12 Rules 1-2), and an unobserved storage
  charge control defaults to the conservative manual control (Table 4e 2401). So
  adding a heating archetype is just adding its code — companions can't be
  forgotten. A contract test guards it (every off-peak code drags a Dual meter).
- Prediction's heating donor now *carries* the donor's meter_type alongside its
  sap_heating cluster — the donor is already coherent.

Coherence is a synthesis-time obligation only; the calculator still scores a real
lodged cert exactly as lodged.

Verified on 713406: baseline 13 -> 47.8 (E), matching its recorded rating; the
phantom HHRSH recommendation is gone and the plan no longer overshoots to A.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:46:11 +00:00

64 lines
3.5 KiB
Markdown

# Coherent Heating System is owned by the synthesiser, not the calculator
## Status
accepted
## Context
A dwelling's heating is one conceptual **system**, but its fields are physically
scattered across `EpcPropertyData` because that type mirrors the gov-API / RdSAP
schema: the main cluster lives on `sap_heating`, the electricity tariff on
`sap_energy_source.meter_type`, and hot-water flags (`has_hot_water_cylinder`,
`solar_water_heating`) on loose top-level fields.
Three places **synthesise** a heating system — a Measure Option (HHRSH/ASHP), a
Landlord Override, and EPC Prediction's heating donor — and each was hand-copying
a *different ad-hoc subset* of those fields. The override and the donor both
omitted `meter_type`. Property 713406 (a predicted property whose landlord
override set "Electric storage heaters") landed on the oil structural template's
single-rate meter, so its overnight storage heat billed at the peak electricity
rate: the calculator scored it **SAP 13 (G)** instead of **~50 (E)**, which then
inflated the HHRSH measure to **+45.8 SAP** and pushed the plan to band A.
## Decision
Treat **"a heating system"** as one named boundary (a **Coherent Heating
System** — see CONTEXT.md). Whoever *synthesises* a heating system owns covering
the whole boundary, with a fill policy suited to its source:
- **Measure** → set a designed end-state (already did).
- **Landlord Override** → *complete* the partial system the landlord named.
Companion fields are **derived from the SAP code**, not hand-attached per
archetype: the off-peak meter from the calculator's single off-peak
classification (`OFF_PEAK_IMPLYING_HEATING_CODES`, SAP §12 Rules 1-2), and —
where a field has no logical pairing — a **conservative** default (storage
charge control → manual, SAP Table 4e 2401, the lowest-SAP storage control).
So adding a heating archetype is just adding its code; coherent companions
fall out and cannot be forgotten.
- **EPC Prediction donor** → *carry* a real neighbour's whole coherent set
(including its `meter_type`), since the donor is already internally coherent.
**Coherence is a synthesis-time obligation, never a calculator normalisation.**
A real lodged cert is scored exactly as lodged — the calculator must not "fix" a
genuinely single-rate storage dwelling (its existing Unknown-meter inference is
a separate, spec-faithful net for certs that lodged *Unknown*, and does not fire
on an explicit meter). A contract test guards the override path: every off-peak
code the archetype map can emit must drag a `Dual` meter.
## Considered Options
- **Reify a `HeatingSystem` value object** all three produce/consume — rejected:
overlaps `HeatingOverlay` and is a large, risky restructure for the value.
- **A global coherence pass over the assembled effective EPC** — rejected: it
would corrupt real lodged certs (force storage→dual on a genuine single-rate
lodgement). Coherence must be scoped to synthesis points.
- **Set `meter_type="Unknown"` and lean on the calculator's storage inference**
— rejected as the primary mechanism: an explicit `Dual` is self-contained,
matches the measure overlay, carries display value, and does not depend on a
repo *extension* that could later be tightened toward spec.
This amends [ADR-0029](0029-epc-prediction-from-comparable-properties.md) (the
heating donor now carries the meter) and extends
[ADR-0032](0032-landlord-override-epc-overlay.md) (the override overlay derives
coherent companions).