mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
recommend_secondary_heating_removal offers one standalone Option that clears the lodged secondary system. Eligibility is purely physical (offer iff sap_heating.secondary_heating_type is set) — no effectiveness gate, since a lodged secondary is a fixed emitter per RdSAP (portables are ignored), and the electric-storage §A.2.2 no-op is the Optimiser's call (ADR-0028 decisions 1-2). Priced at a flat per-dwelling decommission cost, not room-scaled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
"""Behaviour of the Secondary Heating Removal Recommendation Generator: offering
|
|
to strip a dwelling's lodged secondary heating system so the main serves 100% of
|
|
space heating (ADR-0028). A standalone, co-selectable Recommendation; eligibility
|
|
is purely physical (offer iff a secondary is lodged) — the Optimiser de-selects
|
|
the cases where removal cannot move SAP. Detection + pricing only (ADR-0016).
|
|
"""
|
|
|
|
import copy
|
|
|
|
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
|
from domain.modelling.generators.secondary_heating_recommendation import (
|
|
recommend_secondary_heating_removal,
|
|
)
|
|
from domain.modelling.product import Product
|
|
from domain.modelling.recommendation import Recommendation
|
|
from domain.modelling.simulation import SecondaryHeatingOverlay
|
|
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 flat per-dwelling
|
|
decommission price (ADR-0028)."""
|
|
|
|
def get(self, measure_type: str) -> Product:
|
|
return Product(
|
|
measure_type=measure_type,
|
|
unit_cost_per_m2=250.0,
|
|
contingency_rate=0.25,
|
|
id=11,
|
|
)
|
|
|
|
|
|
def _without_secondary(epc: EpcPropertyData) -> EpcPropertyData:
|
|
"""Return a copy of `epc` with no secondary heating system lodged."""
|
|
clone: EpcPropertyData = copy.deepcopy(epc)
|
|
clone.sap_heating.secondary_heating_type = None
|
|
clone.sap_heating.secondary_fuel_type = None
|
|
return clone
|
|
|
|
|
|
def test_dwelling_with_a_lodged_secondary_yields_a_removal_recommendation() -> None:
|
|
# Arrange — 000490 lodges a secondary system (SAP code 691, electric panel/
|
|
# convector/radiant heaters).
|
|
baseline: EpcPropertyData = build_epc()
|
|
assert baseline.sap_heating.secondary_heating_type == 691
|
|
|
|
# Act
|
|
recommendation: Recommendation | None = recommend_secondary_heating_removal(
|
|
baseline, _StubProducts()
|
|
)
|
|
|
|
# Assert — one Option whose overlay clears the secondary.
|
|
assert recommendation is not None
|
|
assert recommendation.surface == "Secondary Heating"
|
|
assert len(recommendation.options) == 1
|
|
option = recommendation.options[0]
|
|
assert option.measure_type == "secondary_heating_removal"
|
|
assert option.overlay.secondary_heating == SecondaryHeatingOverlay()
|
|
|
|
|
|
def test_dwelling_without_a_secondary_yields_no_recommendation() -> None:
|
|
# Arrange — nothing lodged to remove (RdSAP only records a secondary when a
|
|
# fixed emitter is present; a portable would not be lodged at all).
|
|
baseline: EpcPropertyData = _without_secondary(build_epc())
|
|
|
|
# Act
|
|
recommendation: Recommendation | None = recommend_secondary_heating_removal(
|
|
baseline, _StubProducts()
|
|
)
|
|
|
|
# Assert
|
|
assert recommendation is None
|
|
|
|
|
|
def test_recommendation_prices_a_flat_per_dwelling_decommission() -> None:
|
|
# Arrange — a lodged secondary; the cost is a flat per-dwelling decommission
|
|
# figure (one electrician visit + localised making-good), not room-scaled.
|
|
baseline: EpcPropertyData = build_epc()
|
|
|
|
# Act
|
|
recommendation: Recommendation | None = recommend_secondary_heating_removal(
|
|
baseline, _StubProducts()
|
|
)
|
|
|
|
# Assert
|
|
assert recommendation is not None
|
|
cost = recommendation.options[0].cost
|
|
assert cost is not None
|
|
assert cost.total == 250.0
|
|
assert cost.contingency_rate == 0.25
|
|
assert recommendation.options[0].material_id == 11
|