mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
docs: lighting eligibility, overlay + pricing design (ADR-0023 + CONTEXT)
Resolved in a grill-with-docs pass. recommend_lighting converts 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 §12-1 rates LED efficacy (100) above LEL (80) / CFL (55). A free Optimiser candidate (it improves SAP), unlike ventilation's forced dependency. Its overlay is the first whole-dwelling, top-level surface: a LightingOverlay carrying the four bulb-count fields by their exact EPC names, folded directly onto EpcPropertyData (led=total, others 0). Priced per-bulb x non-LED count, contingency 0.26, measure_type low_energy_lighting (MEASURE_MAP-aligned; "LED" in the description). Validation: real before/after cascade pins (zero-existing-LEDs + some-existing-LEDs) at 1e-4, clean (no fabric coupling). Ground-truth confirmed: 20 incandescent -> 20 LED drops lighting (232) 783.7 -> 232.7 kWh/yr. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
07dbaa5361
commit
b460f81233
2 changed files with 46 additions and 0 deletions
|
|
@ -250,6 +250,10 @@ _Avoid_: "roof insulation" (name the specific Measure — loft / sloping-ceiling
|
|||
The rule fixing the single glazing Measure the **Windows** Recommendation offers. We upgrade **only single-glazed windows** (a pragmatic scope — already-double/secondary/triple windows are left alone), **all of them together** as one Measure. Planning status **hard-picks** the Measure (not a choice the Optimiser makes — ADR-0022): unrestricted → **double glazing** (replace the units); a **conservation area** / **listed** / **heritage** building → **secondary glazing** (an internal second pane, since the external units can't be replaced). Priced at a flat **average price per window** × the count of single-glazed windows (we have per-window areas but no size-varying prices, so size is ignored). When the dwelling has no single-glazed windows, no Recommendation is offered.
|
||||
_Avoid_: "windows" as a Measure (name **double glazing** / **secondary glazing**); pricing glazing by area (it's per-window count × average)
|
||||
|
||||
**Lighting Eligibility**:
|
||||
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)
|
||||
|
||||
### Valuation
|
||||
|
||||
**Property Valuation**:
|
||||
|
|
|
|||
42
docs/adr/0023-lighting-eligibility-and-overlay.md
Normal file
42
docs/adr/0023-lighting-eligibility-and-overlay.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Lighting Eligibility, Overlay, and Pricing
|
||||
|
||||
The lighting Recommendation Generator offers an LED upgrade. Three decisions are load-bearing and non-obvious: that it converts **all** non-LED bulbs to LED (not the legacy "fill to low-energy"), that its Simulation Overlay is the first **whole-dwelling, top-level** surface, and that it is a **free Optimiser candidate** rather than a forced dependency like ventilation.
|
||||
|
||||
## Decision
|
||||
|
||||
**One dispatching generator, one Measure, all non-LED bulbs to LED.** `recommend_lighting(epc, products)` emits one "Lighting" Recommendation with a **single** Measure Option:
|
||||
|
||||
- **Scope:** the dwelling's four lodged fixed-lighting bulb counts — `led_`, `cfl_`, `incandescent_`, `low_energy_fixed_lighting_bulbs_count` (the last is RdSAP "low energy, LED/CFL unknown", LEL). The Measure converts **every non-LED bulb** (incandescent + CFL + LEL) to **LED**. Trigger: `incandescent + cfl + low_energy_unknown > 0`. When that sum is zero — already all-LED, **or** no bulb counts lodged (the calculator's L5b/L8c fallback case, where we have no inventory to size against) — no Recommendation is offered.
|
||||
- **All the way to LED, not "low energy".** SAP 10.2 RdSAP §12-1 rates lamp efficacy LED **100** > LEL **80** > CFL **55** > incandescent **11.2** lm/W, so converting *every* non-LED type — including CFL and LEL — strictly improves efficacy and lighting energy (Appendix L, worksheet line (232)). The legacy generator only filled outlets to "low energy" and treated CFL as already-efficient; we go to LED for the larger, honest SAP gain.
|
||||
- **Free Optimiser candidate**, run in `_candidate_recommendations` alongside the fabric measures — **not** a forced Measure Dependency. Ventilation is forced precisely because it only ever *costs* SAP; an LED upgrade *improves* SAP at low cost, so the Optimiser should be free to keep or leave it for least-cost-to-target. (Validated on the real cert: 20 incandescent → 20 LED drops lighting (232) from 783.7 → 232.7 kWh/yr.)
|
||||
|
||||
**The overlay is the first whole-dwelling, top-level surface.** Unlike walls/roofs/floors (per `SapBuildingPart`) or glazing (per `sap_windows` index), the bulb counts live **top-level on `EpcPropertyData`**. `EpcSimulation` gains `lighting: Optional[LightingOverlay]` (the 4th overlay surface, after building parts, windows, ventilation):
|
||||
|
||||
```python
|
||||
@dataclass(frozen=True)
|
||||
class LightingOverlay: # all-optional partial; counts are absolute
|
||||
led_fixed_lighting_bulbs_count: Optional[int] = None
|
||||
cfl_fixed_lighting_bulbs_count: Optional[int] = None
|
||||
incandescent_fixed_lighting_bulbs_count: Optional[int] = None
|
||||
low_energy_fixed_lighting_bulbs_count: Optional[int] = None
|
||||
```
|
||||
|
||||
The field names match the EPC exactly so the applicator folds by `setattr` (as `_fold_ventilation` does), writing **directly onto the result `EpcPropertyData`** — no nested object, simpler than ventilation. The counts are **absolute target states** (like `wall_insulation_thickness=100`): the generator reads the baseline, sums `total`, and emits `led=total, cfl=0, incandescent=0, low_energy=0`. This handles both scenarios uniformly — "zero existing LEDs" (`led 0→total`) and "some existing LEDs" (`led tops up to total`).
|
||||
|
||||
**Pricing: flat per-bulb price × count of non-LED bulbs.** `Cost.total = (incandescent + cfl + low_energy_unknown) × product.unit_cost_per_m2`, reusing that field as a per-bulb price exactly as glazing reuses it per-window and ventilation per-unit; `contingency_rate = 0.26` (the legacy `low_energy_lighting` rate). LEL bulbs are priced too, since they're converted.
|
||||
|
||||
**`measure_type = "low_energy_lighting"`.** Although the Measure installs LED specifically, `measure_type` is a cross-cutting catalogue classification that `MEASURE_MAP` / `Funding` and reporting key on, so we keep the established legacy name and put "LED" in the Option **description** and the all-LED overlay — not in a new `led_lighting` type the rest of the system wouldn't recognise.
|
||||
|
||||
## Considered options
|
||||
|
||||
- **Fill to 100% "low energy" (legacy behaviour), leaving CFL as-is.** Rejected: the calculator rates LED above CFL (100 vs 55 lm/W), so going all-LED is a strictly larger, truthful SAP gain — and the real Elmhurst after-cert lodges LED, not a CFL/LEL mix.
|
||||
- **A forced Measure Dependency (like ventilation).** Rejected: lighting *improves* SAP, so there is a genuine cost/benefit choice for the Optimiser — a free candidate is honest; a forced injection is not.
|
||||
- **`measure_type = "led_lighting"`.** Rejected: precise but divergent — `MEASURE_MAP`/`Funding` and legacy reporting key on `low_energy_lighting`; the precision lives in the description instead.
|
||||
- **A leaner overlay (single `all_led` flag the applicator expands).** Rejected: the four absolute counts mirror the EPC by name, keep the generic by-name fold, and stay greppable — consistent with every other overlay carrying real EPC fields.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `EpcSimulation` grows its first **whole-dwelling, top-level** overlay surface (`lighting`) — the applicator's fold writes onto the `EpcPropertyData` directly rather than a nested object or a targeted part/window.
|
||||
- Lighting is wired into the free candidate pool (`_candidate_recommendations`), priced in the catalogue and contingency table under `low_energy_lighting`, and added to the `_GENERATOR_MEASURE_TYPES` forcing test.
|
||||
- Validation uses real Elmhurst before/after certs (two scenarios: "zero existing LEDs" and "some existing LEDs") as 1e-4 cascade pins. Lighting changes only bulb counts → Appendix L (232), with **no fabric coupling** (contrast glazing's draught-proofing/frame-factor), so the pins are expected to close cleanly with no xfail.
|
||||
- A genuinely all-incandescent dwelling that lodged **zero** bulb counts (a data gap) gets no Recommendation — a data-completeness limitation, not a modelling choice, since we refuse to fabricate an outlet count.
|
||||
Loading…
Add table
Reference in a new issue