mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-19 17:03:02 +00:00
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>
42 lines
5.8 KiB
Markdown
42 lines
5.8 KiB
Markdown
# 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.
|