docs(adr): ADR-0068 accepted+implemented; CONTEXT Boiler Efficiency Band glossary

Folds the design docs (previously PR #1705, docs-only) into the implementation PR
so it is self-contained: ADR-0068 status -> accepted/implemented, and the CONTEXT
glossary gains the Boiler Efficiency Band term + the fifth-Heating-Companion note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-29 16:40:50 +00:00
parent c690cd3183
commit c35fb0afb9
2 changed files with 163 additions and 2 deletions

View file

@ -159,8 +159,12 @@ _Avoid_: roof warning (unspecific), re-roof recommendation (there is no engine m
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
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. A **fifth** archetype-forced companion — **boiler efficiency**, keyed by the **Boiler Efficiency Band** — is implemented per ADR-0068 (the override wins over the cert's efficiency, as a replacement makes the lodged value stale).
_Avoid_: coherent companions (informal), dragged fields, heating defaults; treating efficiency as a *deferring* companion (it is forced — the override is the surveyed current boiler)
**Boiler Efficiency Band**:
The SEDBUK **AG** rating a landlord carries in the **Landlord Description** for a boiler override (`Boiler: C rated Combi`, `Boiler: D rated Regular Boiler`) — an *attribute* of the override, **not** a **Recognised Internal Description** archetype. It names *how well the boiler burns*, orthogonal to the `MainHeatingSystemType` archetype that names the *system*; it must never mint per-band archetypes because that enum is **FE-owned** ([[main-heating-system-pgenum-is-fe-owned]]). It resolves to a **`(winter, summer)`** seasonal-efficiency pair — the fifth **Heating Companion** (ADR-0068) — that the calculator consumes ahead of the Table 4b code default (as a PCDB `winter_efficiency_pct` already does). C-and-below reuse accredited Table 4b code values (D→80 %, E→74 %, F→70 %, G→66 % winter); **A/B exceed Table 4b's 84 % gas ceiling** and take SEDBUK-derived values validated against an accredited Elmhurst build. Dropping the band (the pre-ADR-0068 overlay mapped every band to the flat condensing 84 %) over-credits DG and under-credits A/B. It applies to the **SEDBUK-rated wet boilers only — gas/LPG/biogas *and* oil** (both were inflated to their condensing code); **solid-fuel (Table 4a 75 %), electric (100 %) and CPSU** boilers carry an *intrinsic* fixed efficiency and take no band. Stored as its own `boiler_efficiency_band` override row (AG); absence is modelled as no value (no slot → the code's Table 4b default stands).
_Avoid_: "SEDBUK band" as a synonym for the **SAP main heating code** (the band picks the efficiency, not the code); conflating with the gov-EPC `mainheat_energy_eff` element rating (an AG *presentation* band on the cert, not this override attribute); "boiler rating" (ambiguous with the EPC band)
_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,157 @@
# Boiler efficiency band is the fifth Heating Companion
## Status
accepted — implemented (band parser + overlay `(winter, summer)` slot +
calculator precedence + classifier/backfill; C-and-below live, A/B pending the
Elmhurst validation below)
## Context
A `main_heating_system` **Landlord Override** maps a **Recognised Internal
Description** archetype to a **SAP main heating code** and drags a **Heating
Companion Set** (ADR-0048): category and control are *archetype-forced*, fuel
and meter *defer*. The overlay
(`domain/epc/property_overlays/main_heating_system_overlay.py`) maps every gas
boiler to a single modern/condensing Table 4b code — `Gas boiler, regular` → 102,
`Gas boiler, combi` → 104, both **84 % winter / 7475 % summer**.
The Landlord spreadsheet's **SEDBUK AG efficiency band** — carried in the
**Landlord Description** as `Boiler: C rated Combi`, `Boiler: D rated Regular
Boiler` — is **dropped at classification**: the `override_value` collapses to the
bare archetype (`Gas boiler, combi`) and the band survives only in
`property_overrides.original_spreadsheet_description`. So the companion set has
**no efficiency dimension** (the overlay docstring already flags "no efficiency
slot on the overlay/`MainHeatingDetail`"), and *every* overridden gas boiler is
modelled at the condensing efficiency regardless of its real band.
Portfolio-796 audit evidence (2026-07-28), of 24 555 overridden gas boilers:
| SEDBUK band | count | SEDBUK eff | modelled at 84 % is… |
|---|---:|---|---|
| A | 14 590 | ≥ 90 % | **under**-credited |
| B | 260 | 8690 % | slightly under |
| C | 6 251 | 8286 % | ~right |
| D | 1 691 | 7882 % | over |
| E | 1 196 | 7478 % | over |
| F | 206 | 7074 % | over |
| G | 360 | < 70 % | over-credited |
This is a **code-fidelity** gap (the overlay throws away information the landlord
gave us), distinct from the sibling **fuel-override data-quality** question
(#1701 — whether a landlord's `main_fuel` assertion is itself correct).
## Decision
1. **Efficiency becomes the fifth Heating Companion, in the *archetype-forced*
tier** (alongside category and control). When the override's band-derived
efficiency disagrees with the lodged cert, **the override wins** — a
system-replacing override is the landlord telling us the current truth, so the
cert efficiency is treated as stale.
2. **The SEDBUK band is a new *attribute*, not a new archetype.** The
`MainHeatingSystemType` value stays `Gas boiler, combi`; a separate
**Boiler Efficiency Band** field rides alongside it. The archetype describes
the *system*, the band describes *how well it burns* — orthogonal. Crucially,
the `MainHeatingSystemType` PG enum is **FE-owned**
([[main-heating-system-pgenum-is-fe-owned]]); minting `Gas-boiler-combi-D-rated`
archetypes would explode a shared cross-team contract 7×.
3. **Full fidelity (option a), symmetric — not a downward-only cap.** The
companion carries every band's efficiency: A/B rise **above** 84 %, DG fall.
"Override wins" is symmetric by construction; a one-sided clamp would encode a
policy ("we only correct downward") that a later reader would silently "fix"
into (a) anyway.
4. **Mechanism: `(archetype + fuel) → base code`, then `band → (winter, summer)
efficiency slot` that overrides the code's default.** The **archetype and its
resolved fuel** pick the *base* `sap_main_heating_code` — this is already
ADR-0067's "resolve fuel at resolution" (`Gas boiler, regular` + an oil
`main_fuel``Oil boiler, regular` → an oil code), and the code still drives
system-type behaviour (combi vs regular hot water, etc.). The **band** then
sets a `(winter, summer)` seasonal-efficiency pair on the overlay /
`MainHeatingDetail` that the calculator consumes **ahead of** the code's
Table 4b default — mirroring the existing PCDB precedence
(`pcdb_main.winter_efficiency_pct`, `cert_to_inputs.py:2103`). So efficiency is
looked up by **(fuel family, boiler type, band)**, never a single annual number.
5. **The band mechanism covers exactly the SEDBUK-rated wet boilers — gas and
oil — and both are currently inflated.** Mains gas, LPG and biogas **share** the
gas codes **101119**; oil is the separate liquid-fuel range **124132**. The
overlay today defaults **both** to their condensing/modern variant — gas 102/104
(84 %) *and* oil 127/130 (84 %/82 %) — so the DG over-credit exists for oil as
well as gas. Every other boiler archetype we hold has **no SEDBUK band** and is
already modelled at a sensible, non-inflated fixed efficiency, so it needs no
band and is recorded here as *intrinsic-efficiency*:
| Boiler archetype | Base code | Efficiency | Treatment |
|---|---|---|---|
| Gas boiler, regular / combi | 102 / 104 | Table 4b, condensing | **SEDBUK band** (below) |
| Oil boiler, regular / combi | 127 / 130 | Table 4b, condensing | **SEDBUK band** (below) |
| Solid fuel boiler | 151 | Table 4a **75 %** (`cert_to_inputs.py:2309`) | intrinsic — SEDBUK doesn't rate solid fuel; a PCDB Table-122 product index refines it |
| Electric boiler | 191 | Table 4a **100 %** (direct-acting) | intrinsic — electric |
| Electric CPSU | 192 | Table 4a, electric | intrinsic — electric |
| Gas CPSU | 120 | Table 4b 74 % (non-condensing default) | intrinsic — not inflated; CPSU isn't SEDBUK-banded |
CPSU (120123) and range cookers (133141) are distinct archetypes, not the
regular/combi boiler a band describes. **Solid-fuel efficiency *granularity*
(manual vs auto-feed / pellet) is an archetype question, not a band one** — the
single `Solid fuel boiler` member (all coal/wood/dual collapse to it, ADR-0067)
is a separate follow-up if finer solid-fuel efficiency is wanted.
The per-fuel band → efficiency lookup for the two banded fuels, `(winter, summer)`:
| Band | Gas regular / combi | Oil regular / combi | Source |
|---|---|---|---|
| A | **slot** ~(90, 80) | **slot** ~(88, 78) | SEDBUK ≥ 90 %, **needs oracle** |
| B | **slot** ~(88, 78) | **slot** ~(87, 77) | SEDBUK 8690 %, **needs oracle** |
| C | 102 (84,74) / 104 (84,75) | 127 (84,72) / 130 (82,73) | Table 4b (accredited) |
| D | 106 (80,70) / 108 (80,71) | 126 (80,68) / 129 (77,68) | Table 4b |
| E | 101 (74,64) / 103 (74,65) | 125 (71,59) / 129 (77,68) ⚠ | Table 4b |
| F | 105 (70,60) / 107 (70,61) | 125 (71,59) / 128 (71,62) | Table 4b |
| G | 115 (66,56) / 118 (66,57) | 124 (66,54) / 128 (71,62) ⚠ | Table 4b |
**C-and-below reuse accredited Table 4b code values** — no oracle needed.
**A/B exceed Table 4b's 84 % ceiling** (gas) / 8284 % (oil), so they *must*
use the slot with SEDBUK-derived values — the only judgement calls, carrying
the validation obligation below. ⚠ **Oil's code set is sparser** than gas and
does not line up cleanly with SEDBUK bands (no oil code in the 7478 % E range
for regular; nothing below ~66/71 % for G-combi), so oil DG are *approximate*
nearest-fit — a follow-up may extend the slot to more oil bands rather than
borrow a mismatched code.
6. **Data / migration.** Add a `boiler_efficiency_band` to the override
representation; **backfill** it for already-ingested overrides by re-parsing
`original_spreadsheet_description`, and extend the **Landlord Description →
Recognised Internal Description** classifier to capture the band going forward.
Then **re-model** the affected overridden-boiler population.
## Considered options
- **Code-only (reuse existing Table 4b codes per band), no slot.** Rejected as
the *sole* mechanism: Table 4b's non-PCDB gas codes cap at 84 %, so A/B cannot
rise — that silently delivers the downward-only cap, not full fidelity. Retained
for C-and-below, where accredited codes exist and no slot is required.
- **Downward-only clamp (ship "cap the over-credit" first).** Rejected: "override
wins" is symmetric; a clamp encodes a temporary policy that reads as a bug later.
- **A new archetype per band.** Rejected: explodes the FE-owned
`MainHeatingSystemType` enum.
- **Defer to the cert efficiency when it "looks fine".** Rejected: the divergent
case *is* the entire bug, and a replacement override makes the cert efficiency
stale — so the check only spares the cases that were never wrong.
## Consequences
- **Positive.** Over-credited DG baselines corrected down to published Table 4b
values; A/B corrected up; the override finally encodes the efficiency the
landlord surveyed.
- **Cost / risk — the A/B upside.** It moves ~14 850 accredited baselines **up**
(~+13 SAP each, small per home but **systematic**); some cross a band boundary,
rippling into **Recommendations** and **funding eligibility**. Therefore the
A/B `(winter, summer)` numbers (rows A, B only) **must be validated against an
accredited Elmhurst SAP 10.2 build** before the upside ships. The C-and-below
downside uses published Table 4b values and needs no oracle.
- **Migration + re-model** of the overridden-boiler population; the stored
baselines change, so this is an operational re-base, not a silent code swap.
- Sibling data-quality reconciliation: the cheaper-direction `main_fuel`
overrides (#1701).