docs: heating & hot-water eligibility, bundles, overlay (ADR-0024 + CONTEXT)

Designs the heating Recommendation Generator via /grill-with-docs. Load-bearing
decisions: HW + controls + fuel + meter fold into each competing whole-system
bundle (the legacy heating-vs-HW split double-counted); each bundle is a fixed,
real, contractor-installable end-state (ASHP via PCDB index, HHR storage via
sap_main_heating_code=409), Product stays cost-only; eligibility encodes only
physical/planning installability since the Optimiser owns the economics (the
legacy ASHP built-form / 120 m² rule is dropped — research found no
authoritative basis); the Simulation Overlay is the deepest surface yet,
spanning main_heating_details[0] + sap_heating + top-level EpcPropertyData +
sap_energy_source. Build order HHRSH -> ASHP -> boiler (deferred).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-05 18:51:33 +00:00
parent f68cea27c9
commit a61b6f90c9
2 changed files with 56 additions and 0 deletions

View file

@ -254,6 +254,10 @@ _Avoid_: "windows" as a Measure (name **double glazing** / **secondary glazing**
The rule fixing the single lighting Measure the **Lighting** Recommendation offers. We convert **all non-LED bulbs** (incandescent + CFL + low-energy-unknown) to **LED** — all the way to LED, not the legacy "fill to low energy", because SAP rates LED efficacy above CFL (ADR-0023). One Measure, no planning gate (lighting isn't planning-restricted). Offered only when the dwelling lodges at least one non-LED bulb; a dwelling already all-LED, or one that lodged **no** bulb counts (nothing to size against), gets no Recommendation. Unlike the fabric measures it is a **whole-dwelling** Measure — its **Simulation Overlay** writes the four top-level bulb counts directly (`led = total`, others 0), the first overlay surface that isn't a building part / window / system sub-object. Priced at a flat **average price per bulb** × the count of non-LED bulbs replaced. A free Optimiser candidate (it *improves* SAP), contrast the forced ventilation **Measure Dependency**.
_Avoid_: "low energy lighting" as the upgrade target (we go to **LED**); treating it as a forced dependency (it is a free candidate); pricing by floor area (it's per-bulb count × average)
**Heating Eligibility**:
The rule fixing which whole-system **Measure Options** the single **Heating & Hot Water** Recommendation offers (ADR-0024). The competing bundles — `high_heat_retention_storage_heaters`, `air_source_heat_pump`, and `boiler_upgrade` (deferred) — are **mutually-exclusive system replacements**; the Optimiser picks at most one. Each bundle is a **whole system change at once** — main heating + **controls + fuel + meter + the implied hot water** folded in, never a separate or complementary HW measure (the legacy heating-vs-HW split double-counted). Each is a **fixed, real, contractor-installable end-state** (a representative product Domna installs — ASHP via a fixed PCDB heat-pump index, HHR storage via `sap_main_heating_code=409`), not a derived ideal; **Product** stays cost-only. Eligibility encodes **only physical/planning installability** — the **Optimiser owns the economics**, so it must not re-gate on cost proxies: **ASHP** → houses/bungalows that are not **listed**/**heritage** and not already a heat pump (flats excluded — individual siting needs a survey; a **conservation area** still gets the offer, unlike glazing); **HHR storage** → off-gas or currently-electric dwellings, not community-heated or already HHR. Floor area, fabric, fuel, and built form are **not** gates (the legacy ASHP built-form / 120 m² rule is dropped — no authoritative basis). A free Optimiser candidate, not a forced **Measure Dependency**.
_Avoid_: separate "heating" and "hot water" recommendations (HW folds into the bundle); gating ASHP on floor area / built form / fabric (eligibility is physical/planning only — the Optimiser decides cost-effectiveness); "heating controls" as a standalone competing measure (folded into the bundle)
### Valuation
**Property Valuation**:

View file

@ -0,0 +1,52 @@
# Heating & Hot-Water Eligibility, Bundles, and Overlay
The heating Recommendation Generator offers a whole-system replacement. Four decisions are load-bearing and non-obvious: that **hot water, controls, fuel, and meter are folded into the heating bundle** (not offered as separate or complementary measures), that each bundle is a **fixed, real, contractor-installable end-state** (not a derived ideal), that **eligibility encodes only physical/planning installability** (the Optimiser owns the economics — so the legacy ASHP built-form/floor-area rule is dropped), and that the **Simulation Overlay is the deepest surface yet**, spanning five locations across `EpcPropertyData`.
## Decision
**One "Heating & Hot Water" Recommendation, competing whole-system bundles, the Optimiser picks at most one.** `recommend_heating(epc, products, restrictions=PlanningRestrictions())` emits a single Recommendation whose mutually-exclusive Measure Options are competing system replacements:
- `high_heat_retention_storage_heaters`
- `air_source_heat_pump`
- `boiler_upgrade` (deferred — see below)
`measure_type` keeps the legacy `MEASURE_MAP["heating"]` names so `Funding`/reporting key on them; the precise spec lives in each Option's description and overlay. The Recommendation is a **free Optimiser candidate** (in `_candidate_recommendations`), not a forced Measure Dependency — a bundle can lower SAP / raise cost for some baselines, and the Optimiser simply won't pick it.
**Hot water, controls, fuel, and meter fold into each bundle — never separate competing or complementary measures.** A heating system *implies* its hot-water arrangement: an ASHP brings a HWP cylinder + electric HW; HHR storage brings an off-peak immersion cylinder. Historically heating and hot water were assessed separately, which double-counted (recommend a heating upgrade that already changes HW, then *also* recommend a complementary HW upgrade). Each bundle's Option is therefore the **whole system change at once**: main heating + controls + fuel + meter + the implied HW. Standalone HW-only measures (cylinder thermostat, tank insulation for a dwelling whose main heating is already fine) and **secondary heating** (low-efficiency mounted heaters — residents resist removal, so an isolated concern) are **separate future generators**, not part of this bundle.
**Each bundle is a fixed, real, contractor-installable end-state.** Like `loft_insulation`=300 mm, each bundle encodes one representative target system as constants — chosen because Domna installs these specific products with its contractors, not as a theoretical ideal:
- **HHR storage**`sap_main_heating_code=409` (Table 4a) + control 2404 + off-peak meter + electric immersion cylinder; resolves efficiency by SAP code (no PCDB index).
- **ASHP** → a fixed representative heat-pump **PCDB index** (101413) + `main_heating_category=4` + control 2210 + HWP cylinder; the calculator resolves SCOP from the PCDB heat-pump record (there is no generic-SCOP path), so a real index is required.
`Product` stays **cost-only** — it prices the Option from the materials table; it does not carry the system identity. Promoting the catalogue to drive *which* product is a clean future change, not needed for the tracer.
**Eligibility encodes only physical/legal installability; the Optimiser owns the economics.** Because the Optimiser scores every offered bundle through SAP 10.2 and picks the most cost-effective package to the target, eligibility must *not* re-gate on economic proxies (double-gating drops options the Optimiser would correctly weigh). It encodes only what the Optimiser cannot see — can the system physically and legally be installed:
- **ASHP** (research-grounded, replacing the legacy rule): offer ⇔ `property_type ∈ {House, Bungalow}` ∧ not listed ∧ not heritage ∧ not already ASHP/GSHP. **Flats/maisonettes** are not auto-offered an individual air-to-water ASHP (siting / lease / MCS-020 need a survey). A **conservation area** does **not** exclude ASHP (offered with a planning caveat) — unlike glazing, where it downgrades the measure. Floor area, fuel, fabric, and terraced/enclosed built form are **not** gates — the legacy `built_form ∉ {enclosed terrace}``floor_area > 120 m²` rule is dropped (no authoritative basis; MCS-020 siting/noise is geometry the EPC cannot supply).
- **HHR storage**: off-gas (`not mains_gas`) or currently electric / room heaters; not community heating, not already HHR/ASHP/GSHP. Legacy keyed these on `clean_description` string sections; the rebuild translates them to structured predicates (`main_fuel_type`, `sap_main_heating_code`, `mains_gas`), grounded against the example certs.
**The Simulation Overlay is the deepest surface yet — five locations.** A flat `HeatingOverlay` carries the target system identity; `_fold_heating` routes each non-`None` field to its home (mirroring how `_fold_window` writes flat fields into nested `WindowTransmissionDetails`). It writes **absolute target states** (replacing the system regardless of the before), across:
1. `main_heating_details[0]``main_fuel_type`, `sap_main_heating_code` **or** `main_heating_index_number`+`main_heating_category`, `main_heating_control`
2. `sap_heating` (top-level) — `water_heating_code`, `water_heating_fuel`, `cylinder_size` / `_insulation_type` / `_insulation_thickness_mm`
3. `EpcPropertyData` (top-level) — `has_hot_water_cylinder`
4. `sap_energy_source``meter_type`, `mains_gas`
Only `main_heating_details[0]` (the primary system) is targeted; **dual-heating** dwellings (multiple `main_heating_details`) are out of scope for the tracer.
**Build order: HHRSH → ASHP → boiler.** HHRSH has clean before/after pairs across several base systems (electric storage / no-system / wrong-HW / room-heaters → one common HHR after) and is built first; ASHP follows; `boiler_upgrade` is deferred until a sound before/after example exists.
## Considered options
- **Separate heating and hot-water Recommendations (legacy).** Rejected: it double-counts, because a heating system change already determines the HW arrangement. HW folds into the bundle.
- **Catalogue-driven system identity (extend `Product` with PCDB index / SAP code / controls).** Rejected for now: a large `Product` extension that diverges from the fixed-target pattern the other five generators use. A fixed representative product per bundle is simpler and matches how `loft`/`glazing` hardcode their targets; catalogue-driving is a clean later promotion.
- **Porting the legacy ASHP eligibility rule (built form + 120 m² floor area).** Rejected: research found no authoritative basis — MCS-020 siting/noise depends on geometry the EPC cannot supply, and the rule contradicts its own cited evidence (EST "suitable for all property types"). The Optimiser now owns the economics the rule was proxying.
- **Treating the heating bundle as a forced Measure Dependency (like ventilation).** Rejected: ventilation only ever costs SAP, so it is forced; a heating replacement is a genuine cost/benefit choice the Optimiser should make freely.
## Consequences
- `EpcSimulation` grows its 5th overlay surface, `heating: Optional[HeatingOverlay]`, and the applicator gains `_fold_heating` — the first fold writing across `main_heating_details[0]`, `sap_heating`, top-level `EpcPropertyData`, and `sap_energy_source` at once.
- Heating is wired into the free candidate pool, priced in the catalogue + contingency table under the legacy `MEASURE_MAP["heating"]` names, and added to `_GENERATOR_MEASURE_TYPES` + `harness/report.py::_triggers_for`.
- Validation uses real Elmhurst before/after certs as 1e-4 cascade pins; the same HHRSH overlay is pinned against several base-system befores → one common after, exercising the absolute-target design. Fuel-switching (gas/LPG → electricity) is the most error-prone part for the cascade and is the focus of the pins.
- **Deferred (named gaps):** `boiler_upgrade` (pending a sound example); secondary heating (separate generator); standalone HW-only measures (separate generator — the "smaller alternative to a big upgrade" avenue); dual-heating dwellings; flats / communal-or-air-to-air ASHP; survey "caveat" flags on Options (conservation-area / terraced ASHP) — our Option model has no caveat field yet.