From 244c5b65edf0b0561cd04f7e0bb5eacb738bee5e Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 3 Jul 2026 09:21:47 +0000 Subject: [PATCH] Document heating-companion coherence: category/control forced, fuel/meter deferred (ADR-0048) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CONTEXT.md | 4 + ...egory-and-control-defers-fuel-and-meter.md | 132 ++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 docs/adr/0048-heating-override-forces-category-and-control-defers-fuel-and-meter.md diff --git a/CONTEXT.md b/CONTEXT.md index e0b4a8493..182959e66 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -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)**: diff --git a/docs/adr/0048-heating-override-forces-category-and-control-defers-fuel-and-meter.md b/docs/adr/0048-heating-override-forces-category-and-control-defers-fuel-and-meter.md new file mode 100644 index 000000000..5b2a1870f --- /dev/null +++ b/docs/adr/0048-heating-override-forces-category-and-control-defers-fuel-and-meter.md @@ -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 691–701** 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 691–701) 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.