Document heating-companion coherence: category/control forced, fuel/meter deferred (ADR-0048)

ADR-0048 records why a system-replacing heating override must emit a
complete companion set — category and control archetype-forced for
coherence, fuel and meter deferred where multiple values are valid, and
an unmapped companion logged rather than silently inherited. Adds the
Heating Companion Set glossary term to CONTEXT.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-03 09:21:47 +00:00
parent 962a443ed2
commit 244c5b65ed
2 changed files with 136 additions and 0 deletions

View file

@ -103,6 +103,10 @@ _Avoid_: patches (deprecated), corrections, manual EPC, edits
**Landlord-Description Classification**:
Resolving a **Landlord Description** (unbounded free-text a landlord supplies for one component — "CWI" / "Cav filled" / "cavity insulated" all name one thing) onto a **Recognised Internal Description** via an LLM classifier, persisted in the `landlord_*_overrides` table (`source=classifier`) as a reviewed cache. Four vocabularies are kept **distinct** and must not be conflated: a **Landlord Description** (unbounded input); a **Recognised Internal Description** (the closed target taxonomy — e.g. a `MainHeatingSystemType` archetype — each binding to a Simulation Overlay); a **Lodged Description** (the gov-EPC `main_heating[].description` rendering, e.g. "Room heaters, electric" — only an example of which system *types* occur, never a map key); and the **SAP main heating code** (Table 4a/4b, what the calculator consumes). The classifier maps Landlord → Recognised Internal → SAP code. When it cannot confidently place the text it emits **`None`** (no overlay → the lodged EPC stands, surfaced to the user as "no suitable match"), **never the nearest wrong archetype** — the target taxonomy must be complete enough that a real system always has a correct home, so the classifier never overflows into a garbage-drawer archetype (ADR-0041).
**Heating Companion Set**:
The coherent SAP inputs a **main heating system** override drags alongside the **SAP main heating code** so the **Effective EPC** reads as one internally-consistent system, not a hybrid of the new code and the *replaced* system's leftovers — heating **category** (SAP Table 4a group: gas→2, heat pump→4, network→6, storage→7, electric underfloor→8, warm air→9, room heater→10), **charge/heating control** (Table 4e group), **natural fuel**, and **meter** (see **Off-Peak Meter**). Companions divide by how firmly the archetype fixes them (ADR-0048): **category and control are archetype-forced** — always written, never inherited, because a wrong value silently mis-scores or mis-bills (a leftover storage **category 7** makes the Table 12a resolver bill peaky room heaters at the all-night rate; a leftover storage **control 2401** adds a +0.7 °C Table-4e penalty a room heater should not carry). **Fuel and meter defer** only where multiple values are genuinely coherent — fuel for an ambiguous carrier (community heating: gas CHP / biomass / waste heat), meter for a meter-flexible system (room heaters run on either; screed underfloor per ADR-0046) — while a meter-*locked* system still forces (storage/CPSU/HHRSH→Dual, non-electric→Single, ADR-0035). Because the overlay composes last-wins, an **unset (`None`)** forced companion is *inherited from the replaced system*, not cleared — so an archetype the overlay cannot yet fully companion (heat pump / community / underfloor / electric boiler control defaults are unmapped) is **logged as an error and continues** (log-not-raise, matching `flag_fuel_mismatch`), making the gap visible for review rather than shipping a silently-incoherent cert; the intended end-state is to fill each and flip the log to a raise.
_Avoid_: coherent companions (informal), dragged fields, heating defaults
_Avoid_: "the LLM mapper is unreliable" (the failure mode is a too-small target taxonomy, not LLM language ability); conflating the landlord input vocabulary with the gov-EPC lodged rendering or the RdSAP entry-tool catalogue; treating a deterministic dict as a *replacement* for the LLM rather than a reviewed cache of its output
**Lodgement Sentinels (ND / NI / AB)**:

View file

@ -0,0 +1,132 @@
# A system-replacing heating override forces category and control for coherence, and defers fuel and meter where multiple values are valid
## Status
accepted
## Context
A **Landlord Override** naming a main heating system resolves to a SAP Table 4a
code, and `main_heating_system_overlay` synthesises the **coherent companions**
that code implies (ADR-0035): heating category, charge control, natural fuel,
meter. The overlay is applied over the lodged cert by last-wins composition, so a
companion the overlay leaves **unset (`None`)** is *inherited from the replaced
system* — not cleared.
That inheritance is a silent mis-rating when the archetype genuinely determines
the companion. A portfolio-796 audit found the case end-to-end on **86+
properties**: a lodged **slimline storage heater** (SAP 402, category 7, control
2401, off-peak) overridden to **"Electric room heaters"** (SAP 691). The overlay
stamped code 691, fuel, and a Dual meter — but `_category_for(691)` and
`_control_for(691)` both returned `None`, because their room-heater branch keyed
on `_CATEGORY_10_ROOM_HEATER_CODES`, a set that held only the *fuel-burning*
(gas/oil/solid) room heaters and **excluded electric 691701** despite electric
room heaters also being Table 4a **Category 10**.
Two independent mis-ratings followed from the surviving storage companions:
1. **Category 7 survived.** The SAP 10.2 Table 12a resolver keys on category
first, read `OTHER_STORAGE_HEATERS`, and — with the dwelling on the §12 Rule 3
10-hour tariff — hit `(OTHER_STORAGE_HEATERS, TEN_HOUR)`, a combination the
Grid-1 table does not carry, so it fell to the all-low-rate fallback. The
peaky room heaters were billed **100% at the night rate** — the precise
"all-low over-credit" the overlay's own Dual-meter comment claims to avoid.
Effect: baseline SAP over-rated **64.1 vs the spec-correct 56.4**; the HHRSH
upgrade measure showed a **negative** bill saving (£13/yr) because the
baseline it was compared against was itself under-billed.
2. **Control 2401 survived.** `main_heating_control` fed Table 4e as the storage
**manual charge control → +0.7 °C** mean-internal-temperature adjustment — the
largest in the table — riding on a direct-acting room heater, which should
carry the Group 6 room-heater control **2601 → +0.3 °C**.
The companions are **not uniform** in how strongly the archetype determines
them, which is the crux of the decision:
- **Category is deterministic** — SAP Table 4a assigns exactly one category per
code (gas→2, heat pump→4, network→6, storage→7, **electric underfloor→8**,
warm-air→9, **room heater→10**). A `None` category is never valid.
- **Control is an assumption but always archetype-supplied** — the Table 4e
*Group* is fixed by category; the specific conservative default within the
group is a modelling choice (storage→manual 2401, HHRSH→2404 per
[ADR-0044](0044-hhrsh-baseline-archetype-drags-its-intrinsic-controls.md),
room heater→2601, gas boiler→full controls). It is never "inherit the replaced
system".
- **Fuel is sometimes genuinely ambiguous** — electric/gas/storage/heat-pump
archetypes pin their fuel, but **community heating** could be gas CHP, biomass,
or waste heat, so its fuel legitimately **defers** to a `main_fuel` override,
else the lodged value (ADR-0041 last-wins).
- **Meter is physics-locked for some systems, flexible for others** — storage /
CPSU / HHRSH **require** Dual (they charge overnight); non-electric systems are
stamped Single to stop an Economy-7 split bleeding onto a gas boiler
([ADR-0035](0035-coherent-heating-system-synthesis.md)); screed underfloor
defers to the cert ([ADR-0046](0046-electric-underfloor-scored-by-its-own-codes-meter-deferred-when-tariff-ambiguous.md)).
## Decision
A system-replacing heating override must emit a **complete, coherent companion
set**. Companions split by whether the archetype *determines* the value:
1. **Category and control are archetype-forced** — always written, never
inherited, because a wrong value mis-scores or mis-bills the cert. Electric
room heaters (Table 4a 691701) join the Category-10 room-heater branch of
both `_category_for` (→ category **10**) and `_control_for` (→ control
**2601**, the Group 6 conservative no-thermostat default, matching the
gas/oil/solid siblings). Their fuel (electricity 29) and Dual meter were
already correct and are unchanged.
2. **Fuel and meter defer where multiple values are coherent** — this is *not*
a gap. Fuel defers only for genuinely ambiguous carriers (community heating);
meter defers only for meter-flexible systems (room heaters run coherently on
either meter; screed underfloor per ADR-0046). Meter-locked systems still
force (storage/CPSU/HHRSH → Dual; non-electric → Single).
3. **An incomplete companion set is logged, not raised (for now).** Where the
overlay cannot yet supply a confident category or control — heat pumps
(Group 2), community (Group 3), electric underfloor (Group 7), electric
boiler / CPSU (Group 1) — `main_heating_overlay_for` **logs an error** naming
the archetype and code and continues, matching the `flag_fuel_mismatch`
log-not-raise convention. Log-and-continue keeps a mid-portfolio run alive
and surfaces the gap (CloudWatch) for review, rather than turning a latent
inheritance bug into an outage. The intended end-state is to fill each
archetype's category/control with spec sight and then **flip the log to a
raise**, so the coherence invariant is enforced.
Category expansion for the still-`None` archetypes (electric boiler/CPSU→2,
underfloor→8) is deterministic from Table 4a but **deferred** to its own change:
it flows into the category-keyed Table 12a resolver and needs a corpus check, so
it does not ride on the room-heater fix.
## Consequences
- The 86+ room-heater dwellings score against a correctly-billed baseline: the
space-heating high-rate fraction is the spec **0.50** (10-hour direct-acting),
not 0.0; baseline SAP corrects **64.1 → 56.4**; the HHRSH measure's bill saving
flips **£13/yr → +£235/yr** (property 710788, uprn 22003310, scenario 1268).
- The `refetch_solar`-corrected portfolio's `negative-bill-savings` audit group
loses its ~86-property HHRSH-on-room-heater cluster after re-model.
- The overlay now emits a visible error for any archetype it cannot fully
companion — a standing signal (wire a CloudWatch metric filter on the log
string) for which control default to fill next.
- Extends [ADR-0035](0035-coherent-heating-system-synthesis.md) (companions drag
from the code) and [ADR-0041](0041-landlord-heating-classification-targets-a-complete-modellable-taxonomy.md)
(complete taxonomy); sits beside
[ADR-0044](0044-hhrsh-baseline-archetype-drags-its-intrinsic-controls.md)
(a control named by the archetype) and
[ADR-0046](0046-electric-underfloor-scored-by-its-own-codes-meter-deferred-when-tariff-ambiguous.md)
(meter deferred where the tariff is ambiguous).
### Alternatives rejected
- **Make every companion total (force fuel and meter too).** Rejected: it breaks
the deliberate deferrals — community-heating fuel (ambiguous carrier) and the
ADR-0046 screed-underfloor meter — whose `None` is a load-bearing "defer to the
cert/override" signal, not a gap.
- **Fix the Table 12a resolver to prefer the SAP code over a stale category.**
A sound defence-in-depth follow-up, but it treats the symptom; the overlay
emitting an incoherent cert is the root cause, and category/control coherence
must hold for every consumer (predicted EPCs, raw lodged certs), not only the
billing resolver.
- **Raise on an incomplete companion set now.** Rejected for this change: heat
pump / community / underfloor overrides model today (by luck of inheritance),
so an immediate raise is a blast-radius change mid-campaign. Log now, raise once
the common archetypes are filled.