From ce362f5262eb3e9b31d4740a380537f97e4f1683 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 19 Aug 2025 17:58:02 +0100 Subject: [PATCH] implementing gain --- backend/Funding.py | 21 ++++++++++++++++ backend/engine/engine.py | 10 ++++++++ .../optimiser/funding_optimiser.py | 25 +++++++++++++++++-- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/backend/Funding.py b/backend/Funding.py index 4609e3d4..d08744b9 100644 --- a/backend/Funding.py +++ b/backend/Funding.py @@ -320,6 +320,17 @@ class Funding: return data["Cost Savings"].values[0] + def _calculate_full_project_abs(self, floor_area_band: str, starting_sap_band: str, ending_sap_band: str): + data = self.project_scores_matrix[ + (self.project_scores_matrix["Floor Area Segment"] == floor_area_band) & + (self.project_scores_matrix["Starting Band"] == starting_sap_band) & + (self.project_scores_matrix["Finishing Band"] == ending_sap_band) + ] + + if data.empty: + raise ValueError("Missing ABS rate, check the project scores matrix") + return data["Cost Savings"].values[0] + @staticmethod def get_starting_ending_uvalues(current_uvalue: float) -> tuple[str, str]: """ @@ -1058,3 +1069,13 @@ class Funding: ) raise ValueError("Invalid tenure type for innovation uplift calculation: {}".format(self.tenure)) + + def get_abs_rate(self, is_cavity: bool) -> float: + if self.tenure == "Social": + return self.eco4_social_cavity_abs_rate if is_cavity else self.eco4_social_solid_abs_rate + if self.tenure == "Private": + return self.eco4_private_cavity_abs_rate if is_cavity else self.eco4_private_solid_abs_rate + + raise NotImplementedError( + "Only 'Private' and 'Social' tenures are supported for ABS rate calculation." + ) diff --git a/backend/engine/engine.py b/backend/engine/engine.py index d97d96ab..808837ba 100644 --- a/backend/engine/engine.py +++ b/backend/engine/engine.py @@ -903,6 +903,16 @@ async def model_engine(body: PlanTriggerRequest): ) # Given the solutions we select the optimal one + solutions["cost_less_full_project_funding"] = solutions["total_cost"] - solutions[ + "eco4_full_project_funding"] + solutions = solutions.sort_values("cost_less_full_project_funding", ascending=True) + + if solutions["meets_upgrade_target"].any(): + # If we have a solution that meets the upgrade target, we select that one + optimal_solution = solutions[solutions["meets_upgrade_target"]].iloc[0] + else: + optimal_solution = optimal_solution.iloc[0] + # optimal_solution = if not body.optimise: diff --git a/recommendations/optimiser/funding_optimiser.py b/recommendations/optimiser/funding_optimiser.py index 65335e02..6116b868 100644 --- a/recommendations/optimiser/funding_optimiser.py +++ b/recommendations/optimiser/funding_optimiser.py @@ -290,7 +290,7 @@ def optimise_with_funding_paths(p, input_measures, housing_type, funding: Fundin if scheme == "eco4": # Need to strip out any measure types that are not eligible for ECO4 funding (e.g. secondary heating) - raise ValueError() + sub_measures = _filter_fundable_subgroups(sub_measures, scheme) # 4) run your existing optimiser for the remaining groups # If we have a budget, we need to ensure the subproblem respects it so we remove the fixed cost (which @@ -338,6 +338,26 @@ def optimise_with_funding_paths(p, input_measures, housing_type, funding: Fundin if not solutions[solutions["is_eligible"] & ~solutions["meets_upgrade_target"]].empty: raise NotImplementedError("Implement me") + # We now can calculate the project ABS, which subtracts from the cost, but this is only relevant for ECO4 + solutions["starting_sap"] = p.data["current-energy-efficiency"] + solutions["floor_area"] = p.floor_area + solutions["ending_sap"] = solutions["starting_sap"] + solutions["total_gain"] + solutions["starting_band"] = solutions["starting_sap"].apply(funding.get_sap_band) + solutions["ending_band"] = solutions["ending_sap"].apply(funding.get_sap_band) + solutions["floor_area_band"] = solutions["floor_area"].apply(funding.get_floor_area_band) + solutions["project_score"] = solutions.apply( + lambda x: funding._calculate_full_project_abs( + floor_area_band=x["floor_area_band"], + starting_sap_band=x["starting_band"], + ending_sap_band=x["ending_band"], + ), + axis=1 + ) + rate = funding.get_abs_rate(is_cavity=p.walls["is_cavity_wall"]) + solutions["eco4_full_project_funding"] = solutions["project_score"] * rate + # if the scheme is not ECO4, we set the funding to 0 with iloc + solutions.loc[solutions["scheme"] != "eco4", "eco4_full_project_funding"] = 0.0 + return solutions @@ -759,7 +779,8 @@ def make_funding_paths(p, input_measures, housing_type, funding: Funding): input_gbis_measures = [] for measures in input_measures: for measure in measures: - if measure["type"] in remaining_insulation_type + other_gbis_insulation_measures: + type_to_check = measure["type"].split("+")[0] if "+" in measure["type"] else measure["type"] + if type_to_check in remaining_insulation_type + other_gbis_insulation_measures: input_gbis_measures.append([measure]) funding_paths = _make_generic_gbis_funding_paths(input_gbis_measures, funding_paths)