Model/docs/adr/0035-coherent-heating-system-synthesis.md
Jun-te Kim a680d65188 fix(overlay): synthesise a coherent gas-boiler heating system on Landlord Override
A Landlord heating-system override was applied as a sparse patch, so the
replaced system's fields bled through. A storage flat reclassified as a gas
combi (property 728513) kept mains_gas=False, heating category 7, the 2401
storage charge control, a Dual meter and an electric-immersion cylinder — an
incoherent record that gated out the gas-boiler-upgrade Measure and made the
heating Generator read the dwelling as off-gas (offering HHRSH storage).

Extend the ADR-0035 drag-along to gas boilers (Table 4b 102/104/120): the
overlay now sets the whole coherent companion set — mains_gas, gas main fuel,
heating category 2, fanned flue, full modern controls (2106), a single-rate
meter, and hot water from the main system with the cylinder set from the boiler
type (combi → none, regular/CPSU → cylinder). The main_fuel overlay also flips
mains_gas=True for a "mains gas" fuel. Non-off-peak archetypes now drag an
explicit Single meter so a system switch never leaves a stale Dual.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:48:33 +00:00

114 lines
6.7 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, and every other code an
explicit `Single` meter — a system switch can never leave the previous system's
meter in place.
### Amendment: the gas-boiler companion set (property 728513)
The storage-heater drag-along above covered electric systems; a landlord-named
**gas boiler** was still synthesised as a sparse patch — only the SAP code (its
meter/charge-control companions are `None` for a boiler). The replaced system's
fields therefore bled through: an electric-storage flat reclassified as a gas
combi kept `mains_gas=False`, heating category 7, the 2401 storage charge
control, a `Dual` meter and an electric-immersion cylinder. That incoherent
record gated out the gas-boiler-upgrade Measure and made the heating Generator
read the dwelling as off-gas (offering HHRSH storage to a gas property).
A gas-boiler archetype (Table 4b 102 regular / 104 combi / 120 CPSU) now drags
its **whole** coherent set, code-derived like the storage companions:
- **`mains_gas=True` + `main_fuel_type=26`** — a gas boiler implies a mains-gas
connection and gas main fuel (so a heating-system-only override is
self-coherent, not dependent on a companion `main_fuel` override). The
`main_fuel` overlay independently sets `mains_gas=True` for a "mains gas" fuel;
the two agree (both 26 / True) so composition stays order-independent. A
non-gas `main_fuel` override (electricity/LPG/oil) leaves `mains_gas` **None**
(unchanged) — an electric main heating does not prove there is no gas hob.
- **`main_heating_category=2`** (gas boiler) and **`fan_flue_present=True`** — a
modern room-sealed condensing boiler.
- **`main_heating_control=2106`** (programmer + room thermostat + TRVs) — the
landlord names the boiler, not its controls. Unlike the storage charge control
(where the *conservative* lowest-SAP default is right because the control is
genuinely unknown), a gas boiler installed under modern Building Regs must
carry compliant controls, and the overlay already assumes the modern condensing
efficiency for the boiler — so **assuming modern controls is the coherent,
consistent default** (the alternative, a conservative "no controls" 2101, was
considered and rejected as inconsistent with the assumed-modern boiler). The
controls tune-up Measure still adds zone control on top.
- **Hot water from the main system** (`water_heating_code=901`,
`water_heating_fuel=26`) with the cylinder set from the boiler type — a combi
(104) has **no** cylinder (`has_hot_water_cylinder=False`), a regular boiler /
CPSU keeps one. (Mirrors Elmhurst: the cylinder choice is offered for a regular
condensing boiler and greyed out for a combi.)
- **`meter_type="Single"`** — a boiler is single-rate; the explicit Single meter
overwrites any inherited storage `Dual` (the symmetric mirror of storage→Dual).
A `mains_gas` flag was added to the `main_fuel` overlay. The companion-fuel and
heating-system overlays now share the `mains_gas` / `main_fuel_type` fields
rather than being strictly disjoint — accepted because they only ever agree for
the mains-gas archetypes. The archetype map holds only mains-gas boilers, so a
gas-boiler override always implies mains gas; a *contradictory* fuel override
(gas boiler + a non-gas fuel) is out-of-scope incoherent landlord input (a
finaliser-level validation, not an overlay concern).
## 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).