mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Resolve a from-main wood-logs water-heating override to the wood fuel 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9db8b60a54
commit
c24e465974
2 changed files with 136 additions and 0 deletions
|
|
@ -0,0 +1,123 @@
|
|||
# Water-heating override: community DHW is scored by its heat-network main, individual DHW by its own fuel
|
||||
|
||||
## Status
|
||||
|
||||
accepted
|
||||
|
||||
## Context
|
||||
|
||||
A **Landlord Override** for water heating names a canonical `"<system>, <fuel>"`
|
||||
**Recognised Internal Description** (`WaterHeatingType`), which the water-heating
|
||||
**Simulation Overlay** (`water_heating_overlay_for`) decomposes into the SAP
|
||||
`water_heating_code` (Table 4a system) + `water_heating_fuel` int codes the
|
||||
calculator reads (ADR-0032). The taxonomy was **too small** — it had no biomass /
|
||||
wood / dual-fuel / biodiesel water fuel — so the LLM classifier funnelled every
|
||||
such description into the nearest bucket, **`"From main system, house coal"`**
|
||||
(fuel 33). An audit found **131** `property_overrides` rows on that value; only
|
||||
**3** are genuinely house coal. This is the same missing-archetype defect as
|
||||
ADR-0041 (heating) — a dumping ground standing in for a gap in the taxonomy.
|
||||
|
||||
Investigating the mis-score revealed the fix is **not** a uniform "swap coal for
|
||||
biomass". The 131 rows split by whether the dwelling's **main heating is a heat
|
||||
network**, because the calculator scores hot water differently in the two cases:
|
||||
|
||||
- **Community main** (SAP Table 4a 301/302/304). When DHW inherits from the main
|
||||
(`water_heating_code` ∈ {901, 902, 914}) and the main is a costable heat
|
||||
network, `cert_to_inputs._is_community_heating_hw_from_main` computes the hot-
|
||||
water CO2/PE/cost from the **main's** heat-network fuel (via
|
||||
`_heat_network_community_fuel_code`, e.g. biomass-community fuel 31 → Table 12
|
||||
code 43), scaled by heat-source efficiency — and **ignores
|
||||
`water_heating_fuel` entirely**. Verified: HW CO2 is `0.036` whether the water
|
||||
fuel is 33 (coal) or 31 (biomass). So the **84** community rows already score
|
||||
their DHW as biomass-heat-network; the `"house coal"` value is a **cosmetic
|
||||
display lie**, not a scoring error.
|
||||
- **Individual main** (a real boiler / room heater). DHW inherits the explicitly
|
||||
lodged `water_heating_fuel`, so `33` scores as **house coal (0.395 kgCO2/kWh)** —
|
||||
a genuine ~14× carbon overstatement for a wood/biomass dwelling. **47** rows are
|
||||
mis-scored this way (20 wood logs, 4 dual fuel, 23 "electric immersion
|
||||
assumed"); **3** genuine house-coal rows score correctly.
|
||||
|
||||
The community-fuel collision (API enum 30/31/32 collide in value with the Table-32
|
||||
electricity tariff codes 30/31/32) is **already handled** at the fuel-type
|
||||
boundary, gated on heat-network context (`_heat_network_community_fuel_code`) — a
|
||||
deliberate design that must **not** be globally canonicalised, because the cascade
|
||||
uses bare code 30 internally as standard-rate grid electricity
|
||||
(`table_32.py` comment). So **no calculator change is required**: community DHW is
|
||||
scored correctly *given the coherent community main the override data already
|
||||
carries* (`main_heating_system = "Community heating, boilers"`,
|
||||
`main_fuel = "biomass (community)"`).
|
||||
|
||||
## Decision
|
||||
|
||||
Complete the water-heating taxonomy and reclassify the dumping-ground rows to
|
||||
**faithful** archetypes. No calculator change.
|
||||
|
||||
1. **Add the missing water archetypes**, each mapping to its RdSAP
|
||||
`water_heating_fuel` code in `water_heating_overlay._WATER_HEATING_CODES`:
|
||||
- `"From main system, wood logs"` → (901, **6**)
|
||||
- `"From main system, biomass (community)"` → (901, **31**)
|
||||
- `"From main system, dual fuel (mineral and wood)"` → (901, **9**)
|
||||
- `"From main system, biodiesel (community)"` → (901, **34**)
|
||||
|
||||
2. **Community DHW maps to its community fuel (31 / 34) and is score-neutral.**
|
||||
Because the community main drives the HW score, mapping the 75 community
|
||||
biomass/biodiesel rows to their honest community fuel corrects the **displayed
|
||||
value** without changing the score — we do **not** flatten them to individual
|
||||
wood (6), which would be a semantic lie. Correct labelling, zero scoring risk.
|
||||
|
||||
3. **Individual DHW maps to its real fuel and moves the score.** Wood-logs → 6
|
||||
(0.028), dual fuel → 9, off the coal 0.395 — the genuine fix for the 47
|
||||
mis-scored individual-main rows.
|
||||
|
||||
4. **"No hot water system present – electric immersion assumed" → the existing
|
||||
`"Electric immersion, electricity"` archetype (903, 29).** The description
|
||||
states the assessment's own assumption; honouring it is faithful. This corrects
|
||||
the 23 individual-main rows (coal → electricity) and re-labels the 9
|
||||
community-main rows off the accidental biomass score onto the assessed
|
||||
immersion — a small, defensible score change, surfaced here as the one non-
|
||||
cosmetic community reclassification.
|
||||
|
||||
5. **Genuine `"From main system, house coal"` (3 rows) is retained** — the taxonomy
|
||||
keeps house coal for the dwellings that really burn it.
|
||||
|
||||
6. **Deterministic guard + reclassify, TEXT-first / pgEnum-deferred.** A pure
|
||||
`water_heating_guard(description) -> Optional[WaterHeatingType]` (mirroring
|
||||
`glazing_mix_guard`) recognises the structured `"<system>: <fuel>"` phrasings
|
||||
and is wired via `GuardedColumnClassifier(guard, fallback=ChatGpt…)`; varied
|
||||
phrasings stay with the LLM. A one-time `scripts/reclassify_water_heating.py`
|
||||
reuses the same guard as its decision function (so backfill and live path can't
|
||||
drift), updates `property_overrides.override_value` (TEXT) unconditionally, and
|
||||
defers the `landlord_water_heating_overrides.value` pgEnum-cache write for any
|
||||
value the live enum doesn't yet carry — printing the deferred set for the FE
|
||||
owner (the `main-heating-system-pgenum-is-fe-owned` constraint).
|
||||
|
||||
## Consequences
|
||||
|
||||
- **FE-owned pgEnum additions (Dan).** Four new `landlord_water_heating_overrides`
|
||||
values: `"From main system, wood logs"`, `"From main system, biomass
|
||||
(community)"`, `"From main system, dual fuel (mineral and wood)"`, `"From main
|
||||
system, biodiesel (community)"`. The TEXT read path is fixed immediately; the
|
||||
cache write for these four defers until the migration lands.
|
||||
- **No `cert_to_inputs.py` change**, so no risk to the thousands of lodged certs
|
||||
that carry water fuel 31/33/34 directly. (A separate latent question — whether a
|
||||
lodged *individual*-main cert with `water_heating_fuel = 31` or `34` mis-scores
|
||||
as electricity because those codes are un-normalised outside heat-network
|
||||
context — is **out of scope** here and tracked separately; it does not affect any
|
||||
override row, all of which resolve correctly under this decision.)
|
||||
- Extends [ADR-0041](0041-landlord-heating-classification-targets-a-complete-modellable-taxonomy.md)
|
||||
(complete taxonomy, no dumping ground) and [ADR-0035](0035-coherent-heating-system-synthesis.md)
|
||||
(the community main and its DHW cohere — here the coherence is what makes the
|
||||
community rows score correctly). The visible baseline shift on the 47 individual
|
||||
rows is correct Rebaselining ([ADR-0039](0039-override-aware-rebaselining.md)).
|
||||
|
||||
### Alternatives rejected
|
||||
|
||||
- **Flatten community biomass to individual wood (code 6).** Scores ~identically
|
||||
(0.028 vs 0.029) but mislabels a district-heating dwelling as burning its own
|
||||
wood — a semantic lie for no benefit, since the community main already scores it.
|
||||
- **A calculator "collision fix" normalising 31/33/34 globally.** Rejected: 33 is
|
||||
already normalised (`canonical_fuel_code` 33→11); 31/32 must stay un-normalised
|
||||
outside heat-network context or genuine grid electricity mis-prices as community
|
||||
waste (`table_32.py`). No override row needs it.
|
||||
- **Defer the community rows to `None`.** Rejected per ADR-0041 dec-4 (community is
|
||||
modelled, not deferred) — and unnecessary, since they already score correctly.
|
||||
|
|
@ -73,6 +73,19 @@ def test_more_water_heating_combos_decode_to_their_codes(
|
|||
assert simulation.heating.water_heating_fuel == fuel
|
||||
|
||||
|
||||
def test_from_main_wood_logs_decodes_to_the_wood_fuel_not_house_coal() -> None:
|
||||
# Act
|
||||
simulation = water_heating_overlay_for("From main system, wood logs", 0)
|
||||
|
||||
# Assert — wood logs is RdSAP water_heating_fuel 6, from main system (901).
|
||||
# A wood-fired dwelling's DHW must not fall to house coal (33), which scores
|
||||
# ~14x the carbon (ADR-0043).
|
||||
assert simulation is not None
|
||||
assert simulation.heating is not None
|
||||
assert simulation.heating.water_heating_code == 901
|
||||
assert simulation.heating.water_heating_fuel == 6
|
||||
|
||||
|
||||
@pytest.mark.parametrize("water_heating_value", ["Unknown", ""])
|
||||
def test_unresolvable_water_heating_produces_no_overlay(
|
||||
water_heating_value: str,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue