mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-19 17:03:02 +00:00
The heating Recommendation Generator (HHRSH first). Emits one "Heating & Hot Water" Recommendation whose competing whole-system bundles the Optimiser picks from; this slice builds the high-heat-retention storage Option. Its overlay is the absolute HHR end-state (Table 4a code 409 + control 2404 + dual off-peak meter + off-peak electric cylinder), pinned against the relodged after-cert in the next slice. Eligibility translates legacy is_high_heat_retention_valid to structured predicates (electric or off-gas main, not already HHR/heat-pump). mains_gas and the heat emitter are unchanged by the measure, so unset. ADR-0024. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
121 lines
4.8 KiB
Python
121 lines
4.8 KiB
Python
"""Behaviour of the heating Recommendation Generator: detecting a dwelling
|
|
whose heating system should be replaced and emitting one "Heating & Hot Water"
|
|
Recommendation of competing whole-system bundles (ADR-0024). This slice covers
|
|
the high-heat-retention storage (HHRSH) bundle; ASHP and boiler bundles land in
|
|
later slices. Detection + pricing only; impact is produced by scoring (ADR-0016).
|
|
"""
|
|
|
|
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
|
from domain.modelling.generators.heating_recommendation import recommend_heating
|
|
from domain.modelling.product import Product
|
|
from domain.modelling.recommendation import Recommendation
|
|
from domain.modelling.simulation import HeatingOverlay
|
|
from repositories.product.product_repository import ProductRepository
|
|
from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import (
|
|
build_epc,
|
|
)
|
|
|
|
# Electricity main-fuel code (Elmhurst → SAP10) and the Table 4a SAP code an
|
|
# existing (non-HHR) electric storage system lodges.
|
|
_ELECTRICITY = 30
|
|
_OLD_STORAGE_SAP_CODE = 402
|
|
|
|
|
|
def _electric_storage_baseline() -> EpcPropertyData:
|
|
"""A 000490 dwelling re-cast as an existing (non-HHR) electric storage
|
|
system: electric main fuel, Table 4a code 402."""
|
|
epc: EpcPropertyData = build_epc()
|
|
main = epc.sap_heating.main_heating_details[0]
|
|
main.main_fuel_type = _ELECTRICITY
|
|
main.sap_main_heating_code = _OLD_STORAGE_SAP_CODE
|
|
return epc
|
|
|
|
|
|
class _StubProducts(ProductRepository):
|
|
"""In-memory ProductRepository returning a fixed HHRSH product cost."""
|
|
|
|
def get(self, measure_type: str) -> Product:
|
|
return Product(
|
|
measure_type=measure_type, unit_cost_per_m2=3500.0, contingency_rate=0.26
|
|
)
|
|
|
|
|
|
def test_electric_storage_dwelling_yields_an_hhr_storage_bundle() -> None:
|
|
# Arrange — an existing electric storage system (not HHR).
|
|
baseline: EpcPropertyData = _electric_storage_baseline()
|
|
|
|
# Act
|
|
recommendation: Recommendation | None = recommend_heating(baseline, _StubProducts())
|
|
|
|
# Assert — one "Heating & Hot Water" Recommendation carrying the HHRSH
|
|
# bundle, whose overlay is the absolute HHR end-state.
|
|
assert recommendation is not None
|
|
assert recommendation.surface == "Heating & Hot Water"
|
|
options = {o.measure_type: o for o in recommendation.options}
|
|
assert "high_heat_retention_storage_heaters" in options
|
|
assert options["high_heat_retention_storage_heaters"].overlay.heating == HeatingOverlay(
|
|
main_fuel_type=30,
|
|
sap_main_heating_code=409,
|
|
main_heating_control=2404,
|
|
water_heating_code=903,
|
|
water_heating_fuel=30,
|
|
cylinder_size=2,
|
|
cylinder_insulation_type=1,
|
|
cylinder_insulation_thickness_mm=120,
|
|
has_hot_water_cylinder=True,
|
|
meter_type="Dual",
|
|
)
|
|
|
|
|
|
def test_on_gas_boiler_dwelling_yields_no_hhr_storage_bundle() -> None:
|
|
# Arrange — 000490 is a mains-gas combi (fuel 26, mains_gas True). HHR
|
|
# storage suits off-gas / electric dwellings, not an on-gas gas boiler.
|
|
baseline: EpcPropertyData = build_epc()
|
|
|
|
# Act
|
|
recommendation: Recommendation | None = recommend_heating(baseline, _StubProducts())
|
|
|
|
# Assert — no HHRSH bundle (no other bundle is built in this slice → None).
|
|
if recommendation is not None:
|
|
assert "high_heat_retention_storage_heaters" not in {
|
|
o.measure_type for o in recommendation.options
|
|
}
|
|
|
|
|
|
def test_already_hhr_storage_dwelling_yields_no_bundle() -> None:
|
|
# Arrange — an electric dwelling already on HHR storage (Table 4a code 409)
|
|
# must not be told to install HHR storage again.
|
|
baseline: EpcPropertyData = _electric_storage_baseline()
|
|
baseline.sap_heating.main_heating_details[0].sap_main_heating_code = 409
|
|
|
|
# Act / Assert
|
|
assert recommend_heating(baseline, _StubProducts()) is None
|
|
|
|
|
|
def test_existing_heat_pump_dwelling_yields_no_hhr_storage_bundle() -> None:
|
|
# Arrange — an electric dwelling already on a heat pump (category 4) is never
|
|
# downgraded to storage heaters.
|
|
baseline: EpcPropertyData = _electric_storage_baseline()
|
|
baseline.sap_heating.main_heating_details[0].main_heating_category = 4
|
|
|
|
# Act / Assert
|
|
assert recommend_heating(baseline, _StubProducts()) is None
|
|
|
|
|
|
def test_hhr_storage_bundle_carries_the_product_cost_and_contingency() -> None:
|
|
# Arrange
|
|
baseline: EpcPropertyData = _electric_storage_baseline()
|
|
|
|
# Act
|
|
recommendation: Recommendation | None = recommend_heating(baseline, _StubProducts())
|
|
|
|
# Assert — the bundle's fully-loaded cost + contingency come from the product.
|
|
assert recommendation is not None
|
|
option = next(
|
|
o
|
|
for o in recommendation.options
|
|
if o.measure_type == "high_heat_retention_storage_heaters"
|
|
)
|
|
assert option.cost is not None
|
|
assert abs(option.cost.total - 3500.0) <= 1e-9
|
|
assert abs(option.cost.contingency_rate - 0.26) <= 1e-9
|