From 72e1e67c6d20b1588b449dd7cfa347d78467b821 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 30 Jun 2026 14:33:54 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20ADR-0041=20=E2=80=94=20landlord-heating?= =?UTF-8?q?=20classification=20targets=20a=20complete=20taxonomy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grill-with-docs output for PRD #1361 Class A. The band-G baselines are not a fabric/rebaseline bug (walls are correct — landlord asserted as-built); they are a target-vocabulary gap in the landlord-description heating classifier. The LLM maps unbounded free-text onto a closed internal taxonomy, but that taxonomy had 9 archetypes vs ~13 RdSAP families, so it overflowed into "Gas CPSU"/storage (the "convector" collision sent direct-acting room heaters to off-peak storage 403 → SAP ~10). - ADR-0041: fix the taxonomy not the mapper; unmapped input -> None (keep lodged EPC, surface to user), never a forced archetype; heat pumps modellable via a default Table 4a code (no PCDB id); community heating via codes 301/302/304 with a documented DLF-1.50 / boiler-source default. - CONTEXT.md: sharpen Landlord-Description Classification and the four distinct vocabularies (landlord / recognised-internal / lodged / SAP code). Co-Authored-By: Claude Opus 4.8 (1M context) --- CONTEXT.md | 4 + ...-targets-a-complete-modellable-taxonomy.md | 103 ++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 docs/adr/0041-landlord-heating-classification-targets-a-complete-modellable-taxonomy.md diff --git a/CONTEXT.md b/CONTEXT.md index fe6493ee4..19825e15a 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -101,6 +101,10 @@ _Avoid_: energy assessment, site survey, field survey, Domna survey, Hestia surv Property data supplied by a landlord that may correct or supplement the public EPC for a single Property; triggers Rebaselining when applied; not applicable when Site Notes are present. _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). +_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 + ### Modelling **Effective EPC**: diff --git a/docs/adr/0041-landlord-heating-classification-targets-a-complete-modellable-taxonomy.md b/docs/adr/0041-landlord-heating-classification-targets-a-complete-modellable-taxonomy.md new file mode 100644 index 000000000..2028d037d --- /dev/null +++ b/docs/adr/0041-landlord-heating-classification-targets-a-complete-modellable-taxonomy.md @@ -0,0 +1,103 @@ +# Landlord-heating classification targets a complete, modellable taxonomy; unmapped input is no-override + +## Status + +accepted + +## Context + +Landlord supplementary data describes a dwelling's components in **unbounded +free-text** — a wall is "cavity insulated" / "CWI" / "Cav filled"; a heating +system is "communal gas boiler" / "Quantum storage" / a boiler make. An LLM +classifier maps that free-text onto a **closed internal taxonomy** of recognised +descriptions, each of which binds to a Simulation Overlay applied to +`EpcPropertyData` (ADR-0032). For heating the taxonomy is the +`MainHeatingSystemType` enum, mapped to a representative SAP Table 4a/4b code by +`main_heating_system_overlay`, with coherent companions dragged from the code +(ADR-0035). The LLM is the right tool for the unbounded-free-text problem; the +deterministic dicts in legacy `asset_list` were a *reviewed cache of LLM output*, +not a replacement, and the `landlord_*_overrides` table already serves as that +verified cache (keyed `portfolio_id + description`). + +The taxonomy was **too small** — 9 heating archetypes against a ~13-family RdSAP +main-heating taxonomy. With no correct target the classifier force-picked the +nearest wrong archetype, and treated **"Gas CPSU" as a garbage drawer**: oil and +solid-fuel room heaters, community heating, and `"boiler: a rated na"` all +classified to Gas CPSU; the word "convector" in `"electric (direct acting) room +heaters: panel, convector or radiant heaters"` pulled it to `"Electric storage +heaters, convector"` (code 403, off-peak storage) instead of `"Electric room +heaters"` (691, direct-acting, single-rate). + +Folded into the Effective EPC, a single-rate dwelling modelled as off-peak +storage scores **SAP ~10 (band G)**. This is the true cause of **PRD #1361 +Class A** — 14 properties lodged in band C/D rebaselining to band G — which had +been mis-attributed to a fabric "no insulation (assumed)" assumption and the +pre-SAP10 rebaseline. The walls are correct (landlord asserted as-built); the +heating is the crater. Exemplar **property 718066**: lodged SAP 57, landlord +described "electric (direct acting) room heaters: panel, convector or radiant" +(correct code 691, single-rate), mis-stored as code 403 → live SAP **9.9**. The +stored override was classified `source=classifier` on 2026-06-20, **before** the +691 "Electric room heaters" archetype existed (commit ada2bc07, 2026-06-29) — so +it is also stale. + +## Decision + +The fix is the **target taxonomy, not the mapper**. The LLM stays. + +1. **The classifier targets a complete, modellable taxonomy.** Expand + `MainHeatingSystemType` + `_MAIN_HEATING_CODES` to cover every RdSAP + main-heating family the calculator can score: room heaters by fuel + (gas / oil / solid), warm air, **heat pumps**, and **community heating**. Each + maps to a representative Table 4a/4b code; coherent companions drag from the + code per ADR-0035, so adding an archetype is still "just add its code". + +2. **Unmapped free-text → `None`, never a forced archetype.** The overlay already + returns `None` (no overlay → the lodged EPC heating stands) for any value not + in `_MAIN_HEATING_CODES`; the classifier must *emit* Unknown/`None` when it + cannot confidently place the text, rather than the nearest wrong archetype. + "Gas CPSU" (or any other archetype) is never a fallback. The `None` mapping is + persisted and surfaced to the user as "no suitable match" for later edit. A + forced archetype overwrites a correct lodged cert; `None` (keep lodged) is the + only safe and honest behaviour. + +3. **Heat pumps are modellable without a PCDB index** — a "model unknown" heat + pump maps to a default Table 4a heat-pump code (211–224) with the table's + default seasonal efficiency; no `main_heating_index_number` is required. + +4. **Community heating is modelled via its explicit codes** — 301 (boiler + community) / 302 (CHP + boilers) / 304 (electric heat-pump community), which + the catalogue strings disambiguate ("community boilers only" / "community CHP + and boilers" / "community heat pump"). The calculator (Appendix C §C3.2 + distribution loss, Table 12c DLF, Table 12 source factors) and Bill Derivation + (`HEAT_NETWORK` indicative rate) already support these. **Load-bearing + assumption:** when the landlord text does not supply them, the **DLF defaults + to Table 12c 1.50** and the source to a boiler — community SAP is *sensitive* + to the DLF (1.0 vs 1.5 is a large swing), so this default is documented here + and surfaced as an assumption. Coarse "communal heating" with **no named + source** falls to `None` rather than inventing a heat-network model. + +5. **Re-classify the stale `source=classifier` overrides** once the taxonomy is + complete, so the band-G properties pick up their correct code. + +## Considered Options + +- **Replace the LLM with a deterministic catalogue map** — rejected: landlord + input is unbounded free-text, not a closed vocabulary. The LLM is the right + front door; determinism belongs as a downstream *verified* cache (the override + table), built from LLM output + review, exactly as `asset_list` was. +- **Keep the 9-member enum, tune the LLM prompt** — rejected: with no correct + target the LLM must pick a wrong archetype regardless of prompt. Target + completeness is the fix; prompt quality is secondary. +- **Force unmapped input to a conservative archetype** (e.g. direct-acting + electric) — rejected: any forced archetype overwrites a correct lodged cert. +- **Defer community heating to `None`** (grill option b) — rejected in favour of + modelling the three explicit community codes, since calculator + bill already + support heat networks and the catalogue strings disambiguate the cases; only + coarse unnamed "communal heating" falls to `None`. + +This extends [ADR-0032](0032-landlord-override-epc-overlay.md) (the override +overlay) and [ADR-0035](0035-coherent-heating-system-synthesis.md) (coherent +companions drag from the code). The visible baseline shift it produces is +correct Rebaselining per [ADR-0039](0039-override-aware-rebaselining.md). It +**corrects the stated cause of PRD #1361 Class A** (classifier taxonomy gap, not +fabric/rebaseline).