diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index 56d586ae..a4ba3e08 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -166,7 +166,9 @@ async def trigger_plan(body: PlanTriggerRequest): p.get_components(cleaned, photo_supply_lookup, floor_area_decile_thresholds) recommender = Recommendations(property_instance=p, materials=materials) - property_recommendations, property_representative_recommendations = recommender.recommend() + # TODO: portfolio id as an input is temp + print("DELETE PORTFOLIO ID AS AN INPUT!!") + property_recommendations, property_representative_recommendations = recommender.recommend(body.portfolio_id) if not property_recommendations: continue diff --git a/etl/customers/slide_utils.py b/etl/customers/slide_utils.py index a0f6a68c..e215af80 100644 --- a/etl/customers/slide_utils.py +++ b/etl/customers/slide_utils.py @@ -1,5 +1,5 @@ import os -from pptx.enum.text import PP_ALIGN +from pptx.enum.text import PP_ALIGN # NOQA from pptx import Presentation from pptx.util import Inches, Pt import matplotlib.pyplot as plt @@ -8,6 +8,7 @@ from sqlalchemy.sql import true from backend.app.db.utils import row2dict from backend.app.db.models.portfolio import PropertyModel, PropertyDetailsEpcModel from backend.app.db.models.recommendations import Recommendation +from backend.app.db.models.recommendations import Plan EPC_COLOURS = { "A": "#028051", @@ -70,6 +71,23 @@ def get_property_details_by_portfolio_id(session: Session, portfolio_id: int): return property_details_dict +def get_plan_by_portfolio_id(session: Session, portfolio_id: int): + """ + This function retrieves all plans associated with a given portfolio_id. + + :param session: The SQLAlchemy session used to execute the query. + :param portfolio_id: The ID of the portfolio for which to retrieve plans. + :return: A list of dictionaries, where each dictionary represents a plan. + Returns an empty list if no plans are found. + """ + plans = session.query(Plan).filter(Plan.portfolio_id == portfolio_id).all() + + # Convert the SQLAlchemy objects to dictionaries + plans_dict = [row2dict(plan) for plan in plans] if plans else [] + + return plans_dict + + def plot_epc_distribution(df, customer_key, title='Your Units', background_color='white', bar_height=0.4, font_size=15): """ Plots a horizontal bar chart of EPC rating distribution with adjustable bar thickness and text sizes. diff --git a/etl/customers/urban_splash/slides.py b/etl/customers/urban_splash/slides.py index 4c2593ba..19e1d579 100644 --- a/etl/customers/urban_splash/slides.py +++ b/etl/customers/urban_splash/slides.py @@ -11,6 +11,7 @@ from sqlalchemy.orm import sessionmaker from etl.customers.slide_utils import ( plot_epc_distribution, get_property_details_by_portfolio_id, + get_plan_by_portfolio_id, get_properties_with_default_recommendations, create_powerpoint ) @@ -37,6 +38,9 @@ def app(): property_details = get_property_details_by_portfolio_id(session, PORTFOLIO_ID) property_details_df = pd.DataFrame(property_details) + plans = get_plan_by_portfolio_id(session, PORTFOLIO_ID) + plans_df = pd.DataFrame(plans) + # Unnest the recommendations. Each recommendation is a list of dictionaries recommendations_exploded = properties_df["recommendations"].explode().tolist() recommendations_df = pd.DataFrame([r for r in recommendations_exploded if not pd.isnull(r)]) @@ -103,8 +107,13 @@ def app(): property_details_df["co2_emissions"].mean() ) - # Valuation: upper and lower bounds - TODO! - min_valuation, max_valuation, average_valuation = (0, 0, 0) + # Valuation: upper and lower bounds and average - take positive values in case we have just a sample + valuation_df = properties_df[properties_df["current_valuation"] > 0] + min_valuation, max_valuation, average_valuation = ( + valuation_df["current_valuation"].min(), + valuation_df["current_valuation"].max(), + valuation_df["current_valuation"].median() + ) recommendations_df.keys() @@ -134,10 +143,13 @@ def app(): measures = "Electrical heating system upgrades & heating controls and Hot water system improvements" # Per property + # Take positive entries just in case we we have a sample + valuation_impact_df = plans_df[plans_df["property_id"].isin(units_hitting_target["property_id"])] + valuation_impact_df = valuation_impact_df[valuation_impact_df["valuation_increase_lower_bound"] > 0] min_valuation_impact, max_valuation_impact, average_valuation_impact = ( - units_hitting_target["total_valuation_impact"].min(), - units_hitting_target["total_valuation_impact"].max(), - units_hitting_target["total_valuation_impact"].mean() + valuation_impact_df["valuation_increase_lower_bound"].median(), + valuation_impact_df["valuation_increase_upper_bound"].median(), + valuation_impact_df["valuation_increase_average"].median() ) # Bill savings per property diff --git a/recommendations/Recommendations.py b/recommendations/Recommendations.py index f785b31d..9f838e1c 100644 --- a/recommendations/Recommendations.py +++ b/recommendations/Recommendations.py @@ -45,7 +45,7 @@ class Recommendations: self.heating_recommender = HeatingRecommender(property_instance=property_instance) self.hotwater_recommender = HotwaterRecommendations(property_instance=property_instance) - def recommend(self): + def recommend(self, portfolio_id): """ This method runs the recommendations for the individual measures and then appends them to a list for output @@ -59,21 +59,23 @@ class Recommendations: phase = 0 print("WALL RECOMMENDATIONS HAVE BEEN COMMENTED OUT TEMPORARILY - ADD ME BACK IN") - # # Building Fabric - # self.wall_recomender.recommend(phase=phase) - # if self.wall_recomender.recommendations: - # property_recommendations.append(self.wall_recomender.recommendations) - # phase += 1 - # - # # Ventilation recommendations - # # We only produce a ventilation recommendation if the property is recommended to have wall or roof insulation - # # We will not attribute a SAP impact to the ventilation recommendation, since we've seen that this has no - # # real impact on the SAP score. Therefore, we don't need to include phasing for ventilation. If we have any - # # wall or roof recommendations, we will ensure that ventilation is included in the simulation - # if self.wall_recomender.recommendations or self.roof_recommender.recommendations: - # self.ventilation_recomender.recommend() - # if self.ventilation_recomender.recommendation: - # property_recommendations.append(self.ventilation_recomender.recommendation) + if portfolio_id != 66: + # Building Fabric + self.wall_recomender.recommend(phase=phase) + if self.wall_recomender.recommendations: + property_recommendations.append(self.wall_recomender.recommendations) + phase += 1 + + # Ventilation recommendations + # We only produce a ventilation recommendation if the property is recommended to have wall or roof + # insulation + # We will not attribute a SAP impact to the ventilation recommendation, since we've seen that this has no + # real impact on the SAP score. Therefore, we don't need to include phasing for ventilation. If we have any + # wall or roof recommendations, we will ensure that ventilation is included in the simulation + if self.wall_recomender.recommendations or self.roof_recommender.recommendations: + self.ventilation_recomender.recommend() + if self.ventilation_recomender.recommendation: + property_recommendations.append(self.ventilation_recomender.recommendation) self.roof_recommender.recommend(phase=phase) if self.roof_recommender.recommendations: