mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
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>
121 lines
4.2 KiB
Python
121 lines
4.2 KiB
Python
"""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
|