mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Resolve solid-fuel room-heater overrides off Gas CPSU to their SAP codes 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
de9159f9b8
commit
f253a7b9ba
2 changed files with 112 additions and 0 deletions
|
|
@ -0,0 +1,89 @@
|
|||
# The "Gas CPSU" dumping ground splits into solid-fuel room-heater and electric-boiler archetypes
|
||||
|
||||
## Status
|
||||
|
||||
accepted
|
||||
|
||||
## Context
|
||||
|
||||
ADR-0041 named **"Gas CPSU" (SAP Table 4a 120, mains gas)** as the original
|
||||
garbage-drawer archetype the LLM classifier over-used when the heating taxonomy
|
||||
lacked a correct target. An audit of every `main_heating_system` override still
|
||||
resolving to `"Gas CPSU"` found it standing in for three *different*, unrelated
|
||||
systems — a mains-gas CPSU is neither solid fuel nor electric, so every one of
|
||||
these is mis-scored (wrong system **and**, for the electric ones, wrong fuel):
|
||||
|
||||
| description | rows | true system |
|
||||
| --- | --- | --- |
|
||||
| Solid fuel room heaters: Open fire in grate | 16 | open fire (solid) |
|
||||
| Solid fuel room heaters: Open fire with back boiler (no radiators) | 19 | open fire + back boiler |
|
||||
| Solid fuel room heaters: Closed room heater with boiler (no radiators) | 2 | closed room heater + boiler |
|
||||
| Boiler: A rated NA | 58 | **electric** boiler |
|
||||
| Boiler: A rated CPSU | 1 | electric CPSU |
|
||||
| Boiler: C rated CPSU | 1 | *genuine* gas CPSU — correct |
|
||||
|
||||
**The electric boilers are the sharpest mis-score.** All 58 `"Boiler: A rated NA"`
|
||||
rows carry a `main_fuel = electricity` override — "NA" is the Hyde boiler-type
|
||||
value meaning "not a gas combi / regular / CPSU", i.e. an **electric boiler**.
|
||||
Classified as Gas CPSU they are scored as a mains-gas stored-water unit. A
|
||||
lodged-cert check showed **defer-to-lodged is not viable**: 34 of the 58 lodge
|
||||
**no** main-heating code at all (fuel 0), 11 lodge electric *room heaters* (691),
|
||||
a few lodge community heating — the very gap the landlord override exists to fill.
|
||||
So the faithful move is to model the electric boiler the landlord names, informed
|
||||
by the electricity fuel we hold, not to defer to an empty cert.
|
||||
|
||||
The correct SAP Table 4a codes are exact (confirmed against
|
||||
`domain/sap10_ml/sap_efficiencies.py`): **631** open fire (32%), **632** open fire
|
||||
+ back boiler (50%), **634** closed room heater + boiler (65%); **191**
|
||||
direct-acting electric boiler (100%). All are already scored by the calculator,
|
||||
and 631/632/634 already sit in the overlay's `_SOLID_FUEL_ROOM_HEATER_CODES` set,
|
||||
so their coherent companions (house-coal fuel 33, room-heater control 2601,
|
||||
category 10) drag automatically (ADR-0035).
|
||||
|
||||
## Decision
|
||||
|
||||
Add the missing archetypes and reclassify the bucket. No calculator change.
|
||||
|
||||
1. **Three solid-fuel room-heater archetypes** in `_MAIN_HEATING_CODES`:
|
||||
`"Solid fuel room heater, open fire"` → 631,
|
||||
`"Solid fuel room heater, open fire with back boiler"` → 632,
|
||||
`"Solid fuel room heater, closed with boiler"` → 634. Companions drag from the
|
||||
code (already-covered 631–636 set) — no overlay-logic change.
|
||||
2. **`"Electric boiler"` → SAP 191**, dragging **electricity (29)** via
|
||||
`_natural_fuel_for` — an electric boiler is unambiguously electric (ADR-0041
|
||||
"direct-acting/storage → electricity"), and this also closes the pre-existing
|
||||
gap where 191 dragged no fuel. Meter stays Single, control None (191 is neither
|
||||
a storage nor a wet-boiler-control system).
|
||||
3. **Deterministic `main_heating_guard` + reclassify**, TEXT-first /
|
||||
pgEnum-deferred. The guard resolves the structured phrasings by description
|
||||
alone: `"…open fire … back boiler"` → 632 (tested before the bare `"open fire"`
|
||||
→ 631), `"…closed … with boiler"` → 634, and `"Boiler: <band> NA"` →
|
||||
`"Electric boiler"` (the NA type is the electric signal). `scripts/
|
||||
reclassify_gas_cpsu.py` reuses the guard as its decision function and defers the
|
||||
four new `main_heating_system` pgEnum-cache values.
|
||||
|
||||
## Consequences
|
||||
|
||||
- ~95 rows leave the Gas CPSU garbage drawer for faithful archetypes: 37 solid
|
||||
fuel scored as solid fuel (not mains gas), 58 electric boilers scored as electric
|
||||
(not a mains-gas CPSU). Correct Rebaselining (ADR-0039).
|
||||
- **FE-owned pgEnum additions (Dan):** four new `main_heating_system` values —
|
||||
`"Solid fuel room heater, open fire"`, `"Solid fuel room heater, open fire with
|
||||
back boiler"`, `"Solid fuel room heater, closed with boiler"`, `"Electric
|
||||
boiler"`. TEXT read path fixed on merge; cache writes defer to the migration.
|
||||
- Extends [ADR-0041](0041-landlord-heating-classification-targets-a-complete-modellable-taxonomy.md)
|
||||
(complete taxonomy — retiring the Gas CPSU garbage drawer it named) and
|
||||
[ADR-0035](0035-coherent-heating-system-synthesis.md) (companions drag from the
|
||||
code).
|
||||
|
||||
### Out of scope
|
||||
|
||||
- **Electric CPSU (SAP 192), the 1 `"Boiler: A rated CPSU"` row.** Its description
|
||||
is identical to the genuine gas `"Boiler: C rated CPSU"` bar the efficiency
|
||||
band — the electric/gas split is **fuel-determined**, which a pure-string guard
|
||||
cannot see. Rather than break the guard = reclassify-decision-function symmetry
|
||||
for one row, this is deferred to a small fuel-aware follow-up; the gas CPSU row
|
||||
stays correctly on 120.
|
||||
- **Whether "NA" ever denotes a non-electric boiler.** It is 100% electric in the
|
||||
corpus and the guard treats it as electric; a future non-electric "NA" would be
|
||||
a re-open signal, not a silent mis-map (the fuel override would disagree).
|
||||
|
|
@ -78,6 +78,29 @@ def test_heating_archetypes_decode_to_their_sap_codes(
|
|||
assert simulation.heating.sap_main_heating_code == code
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("main_heating_value", "code"),
|
||||
[
|
||||
# Solid-fuel room heaters the LLM funnelled into "Gas CPSU" (120, mains
|
||||
# gas). SAP Table 4a: 631 open fire, 632 open fire + back boiler, 634
|
||||
# closed room heater + boiler (ADR-0045).
|
||||
("Solid fuel room heater, open fire", 631),
|
||||
("Solid fuel room heater, open fire with back boiler", 632),
|
||||
("Solid fuel room heater, closed with boiler", 634),
|
||||
],
|
||||
)
|
||||
def test_solid_fuel_room_heaters_decode_off_gas_cpsu(
|
||||
main_heating_value: str, code: int
|
||||
) -> None:
|
||||
# Act
|
||||
simulation = main_heating_overlay_for(main_heating_value, 0)
|
||||
|
||||
# Assert — its own solid-fuel code, not mains-gas CPSU (120).
|
||||
assert simulation is not None
|
||||
assert simulation.heating is not None
|
||||
assert simulation.heating.sap_main_heating_code == code
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("main_heating_value", "code"),
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue