Model/docs/adr/0028-secondary-heating-removal.md
Khalim Conn-Kowlessar f9a89a8e11 docs(adr): secondary heating removal — ADR-0028 + CONTEXT term
Records the four load-bearing design decisions from the grill-with-docs session:
standalone co-selectable rec; eligibility = lodged-only (no effectiveness gate,
electric-storage §A.2.2 no-op is the Optimiser's call); dedicated clearing
SecondaryHeatingOverlay; flat per-dwelling cost (a lodged secondary is fixed per
RdSAP, so a real decommission job, not room-scaled).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 15:32:14 +00:00

4.5 KiB
Raw Blame History

Secondary Heating Removal — standalone recommendation, eligibility, overlay, costing

We model "removal of secondary heating" — stripping a dwelling's lodged secondary heating system so the main system serves 100% of space heating — as a standalone, co-selectable Recommendation (MeasureType.SECONDARY_HEATING_REMOVAL), built API-inputs-first. This records the four load-bearing, non-obvious choices made designing it.

Status

Accepted.

Decisions

1. Standalone Recommendation, not an Option in the Heating & Hot Water rec

The heating expansion (ADR-0024) consolidated heating into one rec whose Options are mutually-exclusive (the Optimiser picks ≤1) because they are whole-system replacements you would never combine. Secondary-heating removal is different in kind: it is independent of, and freely combinable with, a tune-up or a boiler upgrade (you can remove a panel heater and upgrade the controls). Making it an Option would falsely force mutual exclusivity with the partial heating upgrades. As a standalone rec it composes additively like loft/cavity/glazing.

The redundancy risk with a whole-system bundle is self-resolving: an ASHP is calculator-category 4 → secondary fraction is already 0.00, so removal adds zero marginal SAP on top of it, and a SAP-maximising Optimiser never pays for a zero-gain measure. The two overlays touch disjoint fields, so there is no double-SAP-counting either.

2. Eligibility is physical only — offer iff a secondary is lodged; no effectiveness gate

Offered iff sap_heating.secondary_heating_type is not None (a surveyor-lodged secondary exists to remove). Per ADR-0024's principle — eligibility encodes only physical/planning installability; the Optimiser owns the economics — we deliberately do not gate out the cases where removal cannot move SAP.

The surprising case: on an electric-storage main (SAP codes 401407/409/421), RdSAP §A.2.2 forces a default secondary (693, portable electric) back even after the lodged one is removed, so removal is a guaranteed zero-SAP no-op. That is an effectiveness fact, not an installability one — so eligibility still offers it, and the Optimiser de-selects it (zero gain, real cost). This is why our only example cert (001431, main 402) shows F35→F35 unchanged, matching Elmhurst exactly.

3. A dedicated SecondaryHeatingOverlay that clears fields

Every other Simulation Overlay obeys "a None field means leave the baseline unchanged" and writes target states. Removal is the opposite — it must set secondary_heating_type and secondary_fuel_type to None, which that convention structurally cannot express. Rather than wart the absolute-target-state HeatingOverlay with a remove-flag, removal gets its own minimal overlay surface (SecondaryHeatingOverlay, with an explicit remove flag) + EpcSimulation slot + _fold_secondary_heating, mirroring the one-overlay-per-measure-family pattern. It is the one overlay that sets a value to absent.

4. Flat per-dwelling decommission cost, not room-scaled

Legacy recommendations/SecondaryHeating.py scaled cost by a room count (habitable heated). We price a flat per-dwelling scalar (~£250, contingency 0.25) instead. Two reasons: (a) the EPC lodges a single secondary system with no heater count, so the legacy room proxy is unfounded; (b) because RdSAP only records a secondary when a fixed emitter is present (portable plug-in heaters are ignored), a lodged secondary is by definition a fixed unit — its removal is a real but roughly-fixed job (one electrician visit to disconnect/isolate a hard-wired panel/convector/radiant heater + a blanking plate + localised making-good of the wall). The contingency absorbs the unknown count / hard-wire status / repaint extent. No composite Products machinery and no rates.json entry — a future data-only upgrade if a real per-unit rate sheet arrives.

Validation

The before/after example (cert 001431, main 402) is a forced-secondary delta-0 case, and shares the boiler-fixtures' Summary-path roof-fidelity gap, so the cascade pin asserts the field-delta (score(before + remove-overlay) == score(before) at delta 0), proving the overlay clears the fields and the calculator correctly re-forces the §A.2.2 default. A synthetic unit test recasts 001431's main to a non-forced gas-boiler code and asserts removal yields a positive SAP delta (Table 11 fraction → 0), exercising the value path without a second real cert.