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>
8.2 KiB
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_heatersair_source_heat_pumpboiler_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 legacybuilt_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 onclean_descriptionstring 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:
main_heating_details[0]—main_fuel_type,sap_main_heating_codeormain_heating_index_number+main_heating_category,main_heating_controlsap_heating(top-level) —water_heating_code,water_heating_fuel,cylinder_size/_insulation_type/_insulation_thickness_mmEpcPropertyData(top-level) —has_hot_water_cylindersap_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
Productwith PCDB index / SAP code / controls). Rejected for now: a largeProductextension that diverges from the fixed-target pattern the other five generators use. A fixed representative product per bundle is simpler and matches howloft/glazinghardcode 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
EpcSimulationgrows its 5th overlay surface,heating: Optional[HeatingOverlay], and the applicator gains_fold_heating— the first fold writing acrossmain_heating_details[0],sap_heating, top-levelEpcPropertyData, andsap_energy_sourceat 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.