From 5d5422637865dc7ebdced280cd45a8b20cec0d6e Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 27 Nov 2023 17:10:47 +0000 Subject: [PATCH] Working on adding new Recommendations class for updating recommendations with impact --- .../db/functions/recommendations_functions.py | 2 + backend/app/plan/router.py | 26 ++++------ recommendations/Recommendations.py | 52 +++++++++++++++++++ 3 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 recommendations/Recommendations.py diff --git a/backend/app/db/functions/recommendations_functions.py b/backend/app/db/functions/recommendations_functions.py index 34c4ef96..ec22fad2 100644 --- a/backend/app/db/functions/recommendations_functions.py +++ b/backend/app/db/functions/recommendations_functions.py @@ -80,6 +80,8 @@ def upload_recommendations(session: Session, recommendations_to_upload, property "starting_u_value": rec.get("starting_u_value"), "new_u_value": rec.get("new_u_value"), "sap_points": rec["sap_points"], + "heat_demand": rec["heat_demand"], + "co2_equivalent_savings": rec["co2_equivalent_savings"], "total_work_hours": rec["labour_hours"], } for rec in recommendations_to_upload diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index 025b252e..2e262082 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -37,6 +37,7 @@ from recommendations.optimiser.CostOptimiser import CostOptimiser from recommendations.optimiser.GainOptimiser import GainOptimiser from recommendations.optimiser.optimiser_functions import prepare_input_measures from recommendations.WallRecommendations import WallRecommendations +from recommendations.Recommendations import Recommendations from utils.logger import setup_logger from utils.s3 import read_dataframe_from_s3_parquet @@ -245,28 +246,21 @@ async def trigger_plan(body: PlanTriggerRequest): logger.info("Optimising recommendations") for property_id in recommendations.keys(): - property = [p for p in input_properties if p.id == property_id][0] - predictions = all_predictions["sap_change_predictions"] - property_predictions = predictions[predictions["property_id"] == str(property_id)] + property_instance = [p for p in input_properties if p.id == property_id][0] - for recommendations_by_type in recommendations[property_id]: - for rec in recommendations_by_type: - new_sap = property_predictions[property_predictions["recommendation_id"] == str( - rec["recommendation_id"] - )]["predictions"].values[0] + recommendations_with_impact = Recommendations.calculate_recommendation_impact( + property_instance=property_instance, + all_predictions=all_predictions, + recommendations=recommendations + ) - rec["sap_points"] = new_sap - float(property.data["current-energy-efficiency"]) - - if rec["sap_points"] is None: - raise ValueError("Sap points missing") - - input_measures = prepare_input_measures(recommendations[property_id], body.goal) + input_measures = prepare_input_measures(recommendations_with_impact, body.goal) if body.budget: optimiser = GainOptimiser(input_measures, max_cost=body.budget) else: # The minimum gain is the minimum number of SAP points required to get to the target SAP band - current_sap_points = int(property.data["current-energy-efficiency"]) + current_sap_points = int(property_instance.data["current-energy-efficiency"]) target_sap_points = epc_to_sap_lower_bound(body.goal_value) # If the gain is negative, the optimiser will return an empty solution @@ -286,7 +280,7 @@ async def trigger_plan(body: PlanTriggerRequest): {**rec, "default": True if rec["recommendation_id"] in selected_recommendations else False} for rec in recommendations_by_type ] - for recommendations_by_type in recommendations[property_id] + for recommendations_by_type in recommendations_with_impact ] # We'll also unlist the recommendations so they're a bit easier to handle from here onwards diff --git a/recommendations/Recommendations.py b/recommendations/Recommendations.py new file mode 100644 index 00000000..5e4d1cc3 --- /dev/null +++ b/recommendations/Recommendations.py @@ -0,0 +1,52 @@ +class Recommendations: + """ + High level recommendations class, which sits above the measure specific recommendation classes + """ + + @classmethod + def calculate_recommendation_impact(cls, property_instance, all_predictions, recommendations): + + """ + Given predictions from the model apis, with method will update the recommendations with the predicted + impact of the recommendation on the property + + :param property_instance: Instance of the Property class, for the home associated to property_id + :param all_predictions: dictionary of predictions from the model apis + :param recommendations: dictionary of recommendations for the property + :return: + """ + + property_sap_predictions = all_predictions["sap_change_predictions"][ + all_predictions["sap_change_predictions"]["property_id"] == str(property_instance.id) + ] + property_heat_predictions = all_predictions["heat_demand_predictions"][ + all_predictions["heat_demand_predictions"]["property_id"] == str(property_instance.id) + ] + property_carbon_predictions = all_predictions["carbon_change_predictions"][ + all_predictions["carbon_change_predictions"]["property_id"] == str(property_instance.id) + ] + + property_recommendations = recommendations[property_instance.id].copy() + + for recommendations_by_type in property_recommendations: + for rec in recommendations_by_type: + new_sap = property_sap_predictions[property_sap_predictions["recommendation_id"] == str( + rec["recommendation_id"] + )]["predictions"].values[0] + + new_heat_demand = property_heat_predictions[property_heat_predictions["recommendation_id"] == str( + rec["recommendation_id"] + )]["predictions"].values[0] + + new_carbon = property_carbon_predictions[property_carbon_predictions["recommendation_id"] == str( + rec["recommendation_id"] + )]["predictions"].values[0] + + rec["sap_points"] = new_sap - float(property_instance.data["current-energy-efficiency"]) + rec["co2_equivalent_savings"] = float(property_instance.data["co2-emissions-current"]) - new_carbon + rec["heat_demand"] = float(property_instance.data["co2-emissions-current"]) - new_heat_demand + + if rec["sap_points"] is None: + raise ValueError("Sap points missing") + + return property_recommendations