Model/domain/modelling/generators/lighting_recommendation.py
Khalim Conn-Kowlessar dbbfb8ea28 feat(modelling): recommend_lighting upgrades all non-LED bulbs to LED
Slice 2 of the lighting generator (ADR-0023): detect non-LED bulbs
(incandescent + CFL + low-energy-unknown > 0) and emit one "Lighting"
Recommendation whose single low_energy_lighting Option converts every bulb to
LED — the overlay sets led = total, the other three counts 0. Priced as a flat
per-bulb average x the non-LED count, contingency 0.26; the description names
"LED" while the measure_type stays MEASURE_MAP-aligned. None when already
all-LED or no bulb counts are lodged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:23:27 +00:00

63 lines
2.6 KiB
Python

"""The lighting Recommendation Generator (LED upgrade).
Detects a dwelling's non-LED fixed-lighting bulbs and emits one "Lighting"
Recommendation whose single Option converts **every** bulb to LED (ADR-0023).
SAP 10.2 RdSAP §12-1 rates lamp efficacy LED > low-energy-unknown > CFL >
incandescent, so converting every non-LED type — incandescent, CFL, and the
"low energy, type unknown" (LEL) bulbs alike — strictly improves the Appendix L
lighting energy (worksheet line (232)).
Unlike the fabric generators this is a **whole-dwelling** Measure: its overlay
writes the four top-level bulb counts directly (`led = total`, the rest 0). It
is a free Optimiser candidate — an LED upgrade improves SAP at low cost, so the
Optimiser keeps or leaves it for least-cost-to-target (contrast ventilation's
forced dependency). Detection + pricing only; impact is produced later by
scoring (ADR-0016).
"""
from typing import Final, Optional
from datatypes.epc.domain.epc_property_data import EpcPropertyData
from domain.modelling.recommendation import Cost, MeasureOption, Recommendation
from domain.modelling.simulation import EpcSimulation, LightingOverlay
from repositories.product.product_repository import ProductRepository
_LIGHTING_MEASURE_TYPE: Final[str] = "low_energy_lighting"
def recommend_lighting(
epc: EpcPropertyData, products: ProductRepository
) -> Optional[Recommendation]:
"""Return a lighting Recommendation upgrading every non-LED bulb to LED — its
single Option — else None when the dwelling has no non-LED bulbs (already
all-LED, or no bulb counts lodged)."""
led: int = epc.led_fixed_lighting_bulbs_count or 0
cfl: int = epc.cfl_fixed_lighting_bulbs_count or 0
incandescent: int = epc.incandescent_fixed_lighting_bulbs_count or 0
low_energy: int = epc.low_energy_fixed_lighting_bulbs_count or 0
non_led: int = cfl + incandescent + low_energy
if non_led == 0:
return None
product = products.get(_LIGHTING_MEASURE_TYPE)
overlay = EpcSimulation(
lighting=LightingOverlay(
led_fixed_lighting_bulbs_count=led + non_led,
cfl_fixed_lighting_bulbs_count=0,
incandescent_fixed_lighting_bulbs_count=0,
low_energy_fixed_lighting_bulbs_count=0,
)
)
cost = Cost(
total=non_led * product.unit_cost_per_m2,
contingency_rate=product.contingency_rate,
)
option = MeasureOption(
measure_type=_LIGHTING_MEASURE_TYPE,
description="Replace all non-LED bulbs with LED",
overlay=overlay,
cost=cost,
material_id=product.id,
)
return Recommendation(surface="Lighting", options=(option,))