feat(modelling): ASHP reuse case prices flush + half distribution

Slice 3 of ADR-0025 costing. When the dwelling has a reusable wet system,
_distribution charges a power-flush (168) plus _REUSE_DISTRIBUTION_FRACTION
(0.5) of the full radiator band -- a documented stand-in for partial radiator
upsizing at ASHP flow temps, the headline uncertainty in the model. Without a
wet system the full new distribution is priced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-06 20:41:06 +00:00
parent cf7c2e017d
commit 22612a19eb
2 changed files with 34 additions and 1 deletions

View file

@ -63,6 +63,13 @@ _DISTRIBUTION_BY_RADIATORS: dict[int, float] = {
}
_MIN_RADIATORS = 4
_MAX_RADIATORS = 12
# Power-flush + inhibitor when reusing an existing wet system (ECOHT67).
_DISTRIBUTION_FLUSH = 168.0
# Fraction of a full new distribution charged when reusing an existing wet
# system — a stand-in for partial radiator upsizing at low ASHP flow temps.
# The headline uncertainty in the model; recalibrate against real reuse-job
# costs / survey data (ADR-0025).
_REUSE_DISTRIBUTION_FRACTION = 0.5
class AshpExistingSystem(Enum):
@ -133,4 +140,9 @@ class Products:
def _distribution(self, inputs: AshpCostInputs) -> float:
radiators: int = max(_MIN_RADIATORS, min(_MAX_RADIATORS, inputs.radiator_count))
return _DISTRIBUTION_BY_RADIATORS[radiators]
full: float = _DISTRIBUTION_BY_RADIATORS[radiators]
# An existing wet system is reused, not rebuilt: a flush plus a fraction
# of the full distribution to cover partial radiator upsizing.
if inputs.has_reusable_wet_system:
return _DISTRIBUTION_FLUSH + _REUSE_DISTRIBUTION_FRACTION * full
return full

View file

@ -63,3 +63,24 @@ def test_decommission_cost_varies_by_existing_system() -> None:
assert abs(products.ashp_bundle_cost(_large_no_reuse(AshpExistingSystem.OIL)).total - (common + 720.0)) <= 1e-9
assert abs(products.ashp_bundle_cost(_large_no_reuse(AshpExistingSystem.LPG)).total - (common + 960.0)) <= 1e-9
assert abs(products.ashp_bundle_cost(_large_no_reuse(AshpExistingSystem.ELECTRIC_STORAGE)).total - (common + 840.0)) <= 1e-9
def test_reusable_wet_system_prices_a_flush_plus_half_the_distribution() -> None:
# Arrange — a gas dwelling whose wet system is reusable: instead of a full
# new distribution, the ASHP pays a power-flush plus half the radiator band
# (a documented estimate for partial radiator upsizing — ADR-0025).
products = Products()
inputs = AshpCostInputs(
existing_system=AshpExistingSystem.GAS,
is_small_property=False,
design_heat_loss_kw=8.0,
radiator_count=8,
has_reusable_wet_system=True,
)
# Act
cost: Cost = products.ashp_bundle_cost(inputs)
# Assert — decommission 720 + pump 9840 + cylinder 2382.60 + distribution
# (flush 168 + 0.5 x 4152 = 2244) = 15186.60.
assert abs(cost.total - 15186.60) <= 1e-9