diff --git a/domain/modelling/products.py b/domain/modelling/products.py index 4fa6ee31..132e3896 100644 --- a/domain/modelling/products.py +++ b/domain/modelling/products.py @@ -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 diff --git a/tests/domain/modelling/test_products.py b/tests/domain/modelling/test_products.py index 964ee7e5..6db7c458 100644 --- a/tests/domain/modelling/test_products.py +++ b/tests/domain/modelling/test_products.py @@ -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