handing HHRSH heating upgrade outside of ECO project

This commit is contained in:
Khalim Conn-Kowlessar 2025-11-06 17:36:02 +00:00
parent cb70cbf1da
commit 4640fa77bf
4 changed files with 65 additions and 12 deletions

View file

@ -1302,7 +1302,8 @@ class Property:
# If there is no existing solar PV, the photo-supply field will be None or a missing value
# We use inspections data to tell us this
if self.inspections:
if getattr(self.inspections, "roof_orientation", None):
has_no_existing_solar_pv = self.inspections.roof_orientation.value not in [
"already has solar pv", "roof too small", "no roof"
]

View file

@ -77,7 +77,8 @@ DESCRIPTIONS_TO_FUEL_TYPES = {
"Electric ceiling heating, electric": {"fuel": "Electricity", "cop": 1},
"Air source heat pump, warm air, electric": {
"fuel": "Electricity", "cop": AVERAGE_ASHP_EFFICIENCY / 100
}
},
"Electric heat pump for water heating only": {"fuel": "Electricity", "cop": 1},
}
# These are the measure types where if there is a ventilation recommendation, we force the inclusion of it

View file

@ -911,7 +911,8 @@ async def model_engine(body: PlanTriggerRequest):
housing_type=body.housing_type,
budget=body.budget,
target_gain=gain,
funding=funding
funding=funding,
work_package=eco_packages[p.id][2]
)
# Given the solutions we select the optimal one
@ -944,8 +945,19 @@ async def model_engine(body: PlanTriggerRequest):
# This is the list of measures that we will recommend
scheme = optimal_solution["scheme"]
funded_measures = optimal_solution["items"] if scheme != "none" else []
solution = optimal_solution["items"] + optimal_solution["unfunded_items"]
# We create this full list of selected measures, which is used in the next section for setting
# default measures
solution = deepcopy(optimal_solution["items"]) + deepcopy(optimal_solution["unfunded_items"])
funded_measures = deepcopy(optimal_solution["items"]) if scheme != "none" else []
unfunded_measures = deepcopy(optimal_solution["unfunded_items"])
# If we have an EPC D + HHRSH project, we move HHRSH out of funded measures
if eco_packages.get(p.id)[2] == "solar_hhrsh_eco4" and p.data["current-energy-rating"] == "D":
unfunded_measures.extend(
[x for x in funded_measures if x["type"] == "high_heat_retention_storage_heaters"]
)
funded_measures = [x for x in funded_measures if x["type"] != "high_heat_retention_storage_heaters"]
# This is the total amount of funding that the project will produce (EXCLUDING uplifts) (£)
project_funding = optimal_solution["full_project_funding"] if scheme == "eco4" else \
optimal_solution["partial_project_funding"]

View file

@ -198,7 +198,25 @@ def _ensure_unfunded_costs(groups):
return groups
def optimise_with_funding_paths(p, input_measures, housing_type, funding: Funding, budget=None, target_gain=None):
def _get_already_installed_gain(selected_measures, needs_pre_eco_hhrsh_upgrade):
"""
Calculate already installed gain, with special case for pre-ECO4 HHRSH upgrade.
:param selected_measures: List of selected measures
:param needs_pre_eco_hhrsh_upgrade: Boolean indicating if pre-ECO4 HHRSH upgrade is needed
:return:
"""
if needs_pre_eco_hhrsh_upgrade:
return sum(
[x["gain"] for x in selected_measures if
x["already_installed"] or x["type"] == "high_heat_retention_storage_heaters"]
)
return sum([x["gain"] for x in selected_measures if x["already_installed"]])
def optimise_with_funding_paths(
p, input_measures, housing_type, funding: Funding, budget=None, target_gain=None, work_package=None
):
"""
run_optimizer(sub_measures, budget, target_gain) -> (picked_options, sub_cost, sub_gain)
"""
@ -227,7 +245,9 @@ def optimise_with_funding_paths(p, input_measures, housing_type, funding: Fundin
})
# This function will filter down on innovation measures if we are social EPC D
funding_paths, optimisation_input_measures = make_funding_paths(p, input_measures, housing_type, funding)
funding_paths, optimisation_input_measures = make_funding_paths(
p, input_measures, housing_type, funding, work_package
)
# We now produce a fabric only path for ECO4
# We add in generic insulation funding paths (where there is no fixed measure)
@ -244,6 +264,10 @@ def optimise_with_funding_paths(p, input_measures, housing_type, funding: Fundin
] + funding_paths
)
needs_pre_eco_hhrsh_upgrade = (
(p.data["current-energy-rating"] == "D") and work_package == "solar_hhrsh_eco4"
)
for path_spec in funding_paths:
# ECO4 fabric only path = special case
@ -281,8 +305,11 @@ def optimise_with_funding_paths(p, input_measures, housing_type, funding: Fundin
scheme = _path_scheme([path_spec])
# We sum of gain, for already installed measures
already_installed_gain = sum([x["gain"] for x in picked if x["already_installed"]])
# We sum of gain, for already installed measures. In this, we also include HHRSH, when we have
# an EPC D property that needs HHRSH but HHRSH isn't an eligible measure
already_installed_gain = _get_already_installed_gain(
picked, needs_pre_eco_hhrsh_upgrade
)
solutions.append(
{
@ -422,7 +449,11 @@ def optimise_with_funding_paths(p, input_measures, housing_type, funding: Fundin
total_gain += unfunded_gain
# We now grab the "already installed gain"
already_installed_gain = sum([x["gain"] for x in total_picks if x["already_installed"]])
# We sum of gain, for already installed measures. In this, we also include HHRSH, when we have
# an EPC D property that needs HHRSH but HHRSH isn't an eligible measure
already_installed_gain = _get_already_installed_gain(
total_picks, needs_pre_eco_hhrsh_upgrade
)
solutions.append({
"fixed_ids": fixed_ids,
@ -837,7 +868,7 @@ def _make_generic_gbis_funding_paths(input_gbis_measures, funding_paths):
return funding_paths + gbis_funding_paths
def make_funding_paths(p, input_measures, housing_type, funding: Funding):
def make_funding_paths(p, input_measures, housing_type, funding: Funding, work_package=None):
"""
This function generates funding paths based on the input measures and the tenure of the property.
It checks for the presence of specific measures and creates paths that include necessary insulation measures
@ -848,6 +879,8 @@ def make_funding_paths(p, input_measures, housing_type, funding: Funding):
:param input_measures:
:param housing_type:
:param funding: The funding object that provides methods to check eligibility and calculate funding.
:param work_package: Optional work package information. We handle the case of an EPC D property needing a heating
upgrade, where the heating upgrade needs to be conducted before the solar PV work
:return:
"""
@ -890,6 +923,12 @@ def make_funding_paths(p, input_measures, housing_type, funding: Funding):
group_of_innovation_measures = []
group_of_gbis_innovation_measures = []
for measure in measures:
if measure["type"] == "high_heat_retention_storage_heaters" and work_package == "solar_hhrsh_eco4":
# With this work type, if the property is EPC D and doesn't have an eligible heating system
# we install HHRSH as a pre-requisite measure, before the ECO4 project if complete.
group_of_innovation_measures.append(measure)
if measure["innovation_uplift"] or measure["type"] in remaining_insulation_type or measure[
"already_installed"]:
group_of_innovation_measures.append(measure)
@ -906,7 +945,7 @@ def make_funding_paths(p, input_measures, housing_type, funding: Funding):
input_gbis_measures_innovation.extend(group_of_gbis_innovation_measures)
funding_paths = _make_solar_heating_funding_paths(
p, input_measures_innovation, funding_paths, remaining_insulation_type, housing_type, funding
p, input_measures_innovation, funding_paths, remaining_insulation_type, housing_type, funding,
)
# Can only be innovation GBIS measures