mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
feat(modelling): ASHP option carries the composite per-dwelling cost
Slice 9 of ADR-0025 costing. _ashp_option now prices via Products.ashp_bundle_ cost(ashp_cost_inputs(epc)) instead of the flat catalogue scalar; the catalogue row is still read for its material_id. Pinned on boiler-3: gas reuse dwelling composes to 15600.60 (decommission 720 + pump 9720 + cylinder 2382.60 + reuse distribution 2778) with 25% contingency. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6f136a8d6a
commit
f06a048a6f
2 changed files with 31 additions and 5 deletions
|
|
@ -17,7 +17,7 @@ from typing import Optional
|
||||||
from datatypes.epc.domain.epc_property_data import EpcPropertyData, MainHeatingDetail
|
from datatypes.epc.domain.epc_property_data import EpcPropertyData, MainHeatingDetail
|
||||||
from datatypes.epc.domain.field_mappings import PROPERTY_TYPE_LOOKUP
|
from datatypes.epc.domain.field_mappings import PROPERTY_TYPE_LOOKUP
|
||||||
from domain.geospatial.planning_restrictions import PlanningRestrictions
|
from domain.geospatial.planning_restrictions import PlanningRestrictions
|
||||||
from domain.modelling.products import AshpCostInputs, AshpExistingSystem
|
from domain.modelling.products import AshpCostInputs, AshpExistingSystem, Products
|
||||||
from domain.modelling.recommendation import Cost, MeasureOption, Recommendation
|
from domain.modelling.recommendation import Cost, MeasureOption, Recommendation
|
||||||
from domain.modelling.simulation import EpcSimulation, HeatingOverlay
|
from domain.modelling.simulation import EpcSimulation, HeatingOverlay
|
||||||
from repositories.product.product_repository import ProductRepository
|
from repositories.product.product_repository import ProductRepository
|
||||||
|
|
@ -214,7 +214,10 @@ def _ashp_option(
|
||||||
that is not listed/heritage and not already a heat pump."""
|
that is not listed/heritage and not already a heat pump."""
|
||||||
if not _ashp_eligible(epc, restrictions):
|
if not _ashp_eligible(epc, restrictions):
|
||||||
return None
|
return None
|
||||||
|
# Cost is composed per-dwelling from the rate sheet (ADR-0025), not the
|
||||||
|
# single catalogue scalar; the catalogue row is still read for its id.
|
||||||
product = products.get(_ASHP_MEASURE_TYPE)
|
product = products.get(_ASHP_MEASURE_TYPE)
|
||||||
|
cost: Cost = Products().ashp_bundle_cost(ashp_cost_inputs(epc))
|
||||||
return MeasureOption(
|
return MeasureOption(
|
||||||
measure_type=_ASHP_MEASURE_TYPE,
|
measure_type=_ASHP_MEASURE_TYPE,
|
||||||
description=(
|
description=(
|
||||||
|
|
@ -222,9 +225,7 @@ def _ashp_option(
|
||||||
"temperature-zone controls and a heat-pump hot-water cylinder"
|
"temperature-zone controls and a heat-pump hot-water cylinder"
|
||||||
),
|
),
|
||||||
overlay=EpcSimulation(heating=_ASHP_OVERLAY),
|
overlay=EpcSimulation(heating=_ASHP_OVERLAY),
|
||||||
cost=Cost(
|
cost=cost,
|
||||||
total=product.unit_cost_per_m2, contingency_rate=product.contingency_rate
|
|
||||||
),
|
|
||||||
material_id=product.id,
|
material_id=product.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ from domain.modelling.product import Product
|
||||||
from domain.modelling.recommendation import Recommendation
|
from domain.modelling.recommendation import Recommendation
|
||||||
from domain.modelling.simulation import HeatingOverlay
|
from domain.modelling.simulation import HeatingOverlay
|
||||||
from repositories.product.product_repository import ProductRepository
|
from repositories.product.product_repository import ProductRepository
|
||||||
|
from tests.domain.modelling._elmhurst_recommendation import (
|
||||||
|
parse_recommendation_summary,
|
||||||
|
)
|
||||||
from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import (
|
from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import (
|
||||||
build_epc,
|
build_epc,
|
||||||
)
|
)
|
||||||
|
|
@ -152,7 +155,7 @@ def test_gas_boiler_house_yields_an_ashp_bundle() -> None:
|
||||||
assert options["air_source_heat_pump"].overlay.heating == HeatingOverlay(
|
assert options["air_source_heat_pump"].overlay.heating == HeatingOverlay(
|
||||||
main_fuel_type=30,
|
main_fuel_type=30,
|
||||||
main_heating_control=2210,
|
main_heating_control=2210,
|
||||||
main_heating_index_number=101413,
|
main_heating_index_number=110257,
|
||||||
main_heating_category=4,
|
main_heating_category=4,
|
||||||
water_heating_code=901,
|
water_heating_code=901,
|
||||||
water_heating_fuel=30,
|
water_heating_fuel=30,
|
||||||
|
|
@ -166,6 +169,28 @@ def test_gas_boiler_house_yields_an_ashp_bundle() -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ashp_bundle_carries_the_composite_per_dwelling_cost() -> None:
|
||||||
|
# Arrange — a mains-gas regular boiler with a cylinder (90 m2, 7 habitable
|
||||||
|
# rooms): the ASHP reuses the existing wet system (ADR-0025).
|
||||||
|
epc: EpcPropertyData = parse_recommendation_summary(
|
||||||
|
"ashp_from_system_boiler_with_cylinder_001431_before.pdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
recommendation: Recommendation | None = recommend_heating(epc, _StubProducts())
|
||||||
|
assert recommendation is not None
|
||||||
|
option = next(
|
||||||
|
o for o in recommendation.options if o.measure_type == "air_source_heat_pump"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert — composite: decommission (gas) 720 + pump (4.5 kW band) 9720 +
|
||||||
|
# cylinder 2382.60 + reuse distribution (flush 168 + 0.5 x dist(10) 5220 =
|
||||||
|
# 2778) = 15600.60, with the separate 25% ASHP contingency.
|
||||||
|
assert option.cost is not None
|
||||||
|
assert abs(option.cost.total - 15600.60) <= 1e-9
|
||||||
|
assert abs(option.cost.contingency_rate - 0.25) <= 1e-9
|
||||||
|
|
||||||
|
|
||||||
def test_listed_building_yields_no_ashp_bundle() -> None:
|
def test_listed_building_yields_no_ashp_bundle() -> None:
|
||||||
# Arrange — a listed building protects the fabric; an external ASHP unit is
|
# Arrange — a listed building protects the fabric; an external ASHP unit is
|
||||||
# not auto-offered (ADR-0024). The dwelling is on gas, so HHRSH is also out.
|
# not auto-offered (ADR-0024). The dwelling is on gas, so HHRSH is also out.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue