"""Behaviour of the lighting Recommendation Generator: detecting non-LED bulbs and emitting one "Lighting" Recommendation whose single Option upgrades every bulb to LED (ADR-0023). A free Optimiser candidate (it improves SAP), unlike ventilation's forced dependency. Detection + pricing only, no scores (ADR-0016). """ import copy from datatypes.epc.domain.epc_property_data import EpcPropertyData from domain.modelling.generators.lighting_recommendation import recommend_lighting from domain.modelling.product import Product from domain.modelling.recommendation import Recommendation from domain.modelling.simulation import LightingOverlay from repositories.product.product_repository import ProductRepository from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import ( build_epc, ) class _StubProducts(ProductRepository): """In-memory ProductRepository returning a fixed per-bulb lighting price. `unit_cost_per_m2` carries the catalogue row's fully-loaded total, reused as a flat average price per bulb (ADR-0023).""" def get(self, measure_type: str) -> Product: return Product( measure_type=measure_type, unit_cost_per_m2=5.0, contingency_rate=0.26, id=7, ) def _with_bulbs( epc: EpcPropertyData, *, led: int, cfl: int, incandescent: int, low_energy: int ) -> EpcPropertyData: """Return a copy of `epc` with the four fixed-lighting bulb counts set.""" clone: EpcPropertyData = copy.deepcopy(epc) clone.led_fixed_lighting_bulbs_count = led clone.cfl_fixed_lighting_bulbs_count = cfl clone.incandescent_fixed_lighting_bulbs_count = incandescent clone.low_energy_fixed_lighting_bulbs_count = low_energy return clone def test_dwelling_with_non_led_bulbs_yields_a_lighting_recommendation() -> None: # Arrange — a mixed inventory: 2 LED + 3 CFL + 4 incandescent + 1 LEL. baseline: EpcPropertyData = _with_bulbs( build_epc(), led=2, cfl=3, incandescent=4, low_energy=1 ) # Act recommendation: Recommendation | None = recommend_lighting( baseline, _StubProducts() ) # Assert — one all-LED Option: led = total (10), every other count zeroed. assert recommendation is not None assert recommendation.surface == "Lighting" assert len(recommendation.options) == 1 option = recommendation.options[0] assert option.measure_type == "low_energy_lighting" assert option.overlay.lighting == LightingOverlay( led_fixed_lighting_bulbs_count=10, cfl_fixed_lighting_bulbs_count=0, incandescent_fixed_lighting_bulbs_count=0, low_energy_fixed_lighting_bulbs_count=0, ) def test_already_all_led_dwelling_yields_no_recommendation() -> None: # Arrange — every bulb is already LED; nothing to upgrade. baseline: EpcPropertyData = _with_bulbs( build_epc(), led=9, cfl=0, incandescent=0, low_energy=0 ) # Act recommendation: Recommendation | None = recommend_lighting( baseline, _StubProducts() ) # Assert assert recommendation is None def test_dwelling_with_no_lodged_bulbs_yields_no_recommendation() -> None: # Arrange — no bulb counts lodged (the calculator's L5b fallback case): with # no inventory to size against, no Recommendation is offered (ADR-0023). baseline: EpcPropertyData = _with_bulbs( build_epc(), led=0, cfl=0, incandescent=0, low_energy=0 ) # Act recommendation: Recommendation | None = recommend_lighting( baseline, _StubProducts() ) # Assert assert recommendation is None def test_recommendation_prices_a_flat_average_per_non_led_bulb() -> None: # Arrange — 8 non-LED bulbs (3 CFL + 4 incandescent + 1 LEL) at £5 each; the # 2 existing LEDs are not priced (not replaced). baseline: EpcPropertyData = _with_bulbs( build_epc(), led=2, cfl=3, incandescent=4, low_energy=1 ) # Act recommendation: Recommendation | None = recommend_lighting( baseline, _StubProducts() ) # Assert assert recommendation is not None cost = recommendation.options[0].cost assert cost is not None assert cost.total == 8 * 5.0 assert cost.contingency_rate == 0.26 assert recommendation.options[0].material_id == 7