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

6.7 KiB

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 Overridecomplete 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 donorcarry 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 (the heating donor now carries the meter) and extends ADR-0032 (the override overlay derives coherent companions).