From 110f461d49bb4e48e050e15daabecb6f7ccf3f49 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sun, 30 Nov 2025 20:40:07 +0000 Subject: [PATCH] handling the case of there not being any suitable recommendations for a property --- backend/engine/engine.py | 65 +++++++++++-------- .../optimiser/funding_optimiser.py | 8 +++ 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/backend/engine/engine.py b/backend/engine/engine.py index 688c1379..93ddc085 100644 --- a/backend/engine/engine.py +++ b/backend/engine/engine.py @@ -1043,38 +1043,47 @@ async def model_engine(body: PlanTriggerRequest): work_package=eco_packages[p.id][2] ) - # If the solution isn't eligible, we can't really consider it - solutions = solutions[ - (solutions["is_eligible"] & (solutions["scheme"] != "none")) | (solutions["scheme"] == "none") - ] - - 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] + # if handle the empty case + if solutions.empty: + scheme = "none" + funded_measures, solution = [], [] + ( + project_funding, total_uplift, full_project_score, partial_project_score, uplift_project_score + ) = 0, 0, 0, 0, 0 else: - # Pick the cheapest - optimal_solution = solutions.iloc[0] - # This is the list of measures that we will recommend - scheme = optimal_solution["scheme"] + # If the solution isn't eligible, we can't really consider it + solutions = solutions[ + (solutions["is_eligible"] & (solutions["scheme"] != "none")) | (solutions["scheme"] == "none") + ] - # 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 [] + 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: + # Pick the cheapest + optimal_solution = solutions.iloc[0] - # 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"] - # This is the total amount of funding associated to the uplift (£) - total_uplift = optimal_solution["total_uplift"] - # This is the funding scheme selected - # This is the full project ABS - full_project_score = optimal_solution["project_score"] - # This is the partial project ABS - partial_project_score = optimal_solution["partial_project_score"] - # This is the uplift score ABS - uplift_project_score = optimal_solution["total_uplift_score"] + # This is the list of measures that we will recommend + scheme = optimal_solution["scheme"] + + # 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 [] + + # 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"] + # This is the total amount of funding associated to the uplift (£) + total_uplift = optimal_solution["total_uplift"] + # This is the funding scheme selected + # This is the full project ABS + full_project_score = optimal_solution["project_score"] + # This is the partial project ABS + partial_project_score = optimal_solution["partial_project_score"] + # This is the uplift score ABS + uplift_project_score = optimal_solution["total_uplift_score"] else: # We optimise and then we determine eligibility for funding, based on the measures selected optimiser = ( diff --git a/recommendations/optimiser/funding_optimiser.py b/recommendations/optimiser/funding_optimiser.py index 855d7e5c..925a818f 100644 --- a/recommendations/optimiser/funding_optimiser.py +++ b/recommendations/optimiser/funding_optimiser.py @@ -502,6 +502,10 @@ def optimise_with_funding_paths( solutions = pd.DataFrame(solutions) + if solutions.empty: + # We return a blank dataframe + return solutions + # Given the scheme, we now check if the packages are eligible. If they *are* eligible, but they don't meet the # final upgrade target, we then look to perform a final optimisation pass to meet the target gain. solutions["meets_upgrade_target"] = solutions["total_gain"] >= target_gain - 0.1 @@ -779,6 +783,10 @@ def run_optimizer(input_measures, budget=None, sub_target_gain=None, allow_slack Thin wrapper over your optimisers. Returns: list[dict] selected_options """ + + if not input_measures: + return None, 0.0, 0.0 + if budget is not None: opt = GainOptimiser( input_measures, max_cost=budget, max_gain=(sub_target_gain or float("inf")),