mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Introduce domain/modelling/measure_type.py — a StrEnum with one member per modelled measure (the 15 the generators emit). A StrEnum so each member *is* its string value: it persists straight into the `recommendation` varchar column, is the optimiser's group-by key, and compares equal to the catalogue / EPC strings — so it replaces the per-generator string constants with no persistence or optimiser change. Repoint every generator's measure-type constant/literal to a MeasureType member (wall, solid_wall, roof, floor, glazing, lighting, ventilation, heating, solar). Field annotations stay `str` for now; tightening them to MeasureType is the next slice. This is the enum the historical engine deferred (engine.py:970 "TODO - formalise property measure types into an enum") and the vocabulary the forthcoming `considered_measures` allowlist will speak (mirroring the legacy `inclusions`). Suite green: tests/domain/modelling + orchestration + harness 253 pass + 3 xfail; pyright clean on the enum + generators. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
2.7 KiB
Python
64 lines
2.7 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.measure_type import MeasureType
|
|
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[MeasureType] = MeasureType.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,))
|