diff --git a/backend/Property.py b/backend/Property.py index 45ed5198..47e885aa 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -343,7 +343,7 @@ class Property: else: output["glazed_type_ending"] = "double glazing installed during or after 2002" - if recommendation["type"] == "heating": + if recommendation["type"] in ["heating", "hot_water_tank_insulation"]: # We update the data, as defined in the recommendaton simulation_config = recommendation["simulation_config"] @@ -363,9 +363,9 @@ class Property: "internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation", "loft_insulation", "room_roof_insulation", "flat_roof_insulation", "solid_floor_insulation", "suspended_floor_insulation", "exposed_floor_insulation", - "windows_glazing", "solar_pv", "heating", + "windows_glazing", "solar_pv", "heating", "hot_water_tank_insulation" ]: - raise NotImplementedError("Implement me") + raise NotImplementedError("Implement me, given type %s" % recommendation["type"]) output['id'] = "+".join([str(property_id), str(primary_recommendation_id)]) diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index a75da345..a79af32b 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -78,6 +78,8 @@ async def trigger_plan(body: PlanTriggerRequest): # TODO: We should store the trigger file path in the database with the plan so we can track the file that # triggered the plan + # TODO: Create the ability to congigure/switch off certain measures + try: session.begin() logger.info("Getting the inputs") diff --git a/etl/customers/slide_utils.py b/etl/customers/slide_utils.py new file mode 100644 index 00000000..55e7659a --- /dev/null +++ b/etl/customers/slide_utils.py @@ -0,0 +1,65 @@ +import matplotlib.pyplot as plt +from sqlalchemy.orm import Session +from backend.app.db.utils import row2dict +from backend.app.db.models.portfolio import PropertyModel + +EPC_COLOURS = { + "A": "#008054", + "B": "#1ab559", + "C": "#8ccf45", + "D": "#ffd600", + "E": "#fcab66", + "F": "#f08024", + "G": "#e8143b" +} + + +def get_properties_by_portfolio_id(session: Session, portfolio_id: int): + """ + This function retrieves all properties 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 properties. + :return: A list of dictionaries, where each dictionary represents a property. + Returns an empty list if no properties are found. + """ + properties = session.query(PropertyModel).filter(PropertyModel.portfolio_id == portfolio_id).all() + + # Convert the SQLAlchemy objects to dictionaries + properties_dict = [row2dict(p) for p in properties] if properties else [] + + return properties_dict + + +def plot_epc_distribution(df, title='Your units', figsize=(10, 6)): + """ + Plots a horizontal bar chart of EPC rating distribution with percentages annotated on the bars. + + :param df: DataFrame with columns ['current_epc_rating', 'count', 'percentage'] + :param title: Title of the plot (default is 'EPC Rating Distribution by Percentage') + :param figsize: Figure size as a tuple (default is (10, 6)) + """ + # Sort the DataFrame for a consistent plotting order + df_sorted = df.sort_values('percentage', ascending=True) + + colors = df_sorted['current_epc_rating'].map(EPC_COLOURS) # Map the EPC ratings to colors + + # Create the horizontal bar chart + plt.figure(figsize=figsize) + bars = plt.barh(df_sorted['current_epc_rating'], df_sorted['percentage'], color=colors) + + # Annotate the bars with percentage values + for bar in bars: + width = bar.get_width() + label_x_pos = width + 1 # Adjust the offset for the label if necessary + plt.text(label_x_pos, bar.get_y() + bar.get_height() / 2, f'{width}%', va='center') + + # Customize the plot aesthetics for better readability and presentation + plt.xlabel('Percentage') + plt.ylabel('EPC Rating') + plt.title(title) + plt.tight_layout() # Adjust layout to not cut off labels + plt.grid(axis='x', linestyle='--') # Add a light grid for better readability + + # Show the plot + plt.show() diff --git a/etl/customers/urban_splash.py b/etl/customers/urban_splash/asset_list.py similarity index 100% rename from etl/customers/urban_splash.py rename to etl/customers/urban_splash/asset_list.py diff --git a/etl/customers/urban_splash/slides.py b/etl/customers/urban_splash/slides.py new file mode 100644 index 00000000..fa0df3c2 --- /dev/null +++ b/etl/customers/urban_splash/slides.py @@ -0,0 +1,26 @@ +""" +This script contains the code to generate the data required to populate the slides +We connect to the database amd extract the data for the portfolio needed so it is recommended to use +a environment akin to the backend to run this script +""" +import pandas as pd +from backend.app.db.connection import db_engine +from sqlalchemy.orm import sessionmaker +from etl.customers.slide_utils import get_properties_by_portfolio_id, plot_epc_distribution + +PORTFOLIO_ID = 66 + + +def app(): + # Connect to database + session = sessionmaker(bind=db_engine)() + + # Get the properties for the portfolio + properties = get_properties_by_portfolio_id(session, PORTFOLIO_ID) + + # The first visual we want to produce is a horizontal bar chart showing the number of properties at each current + # EPC band + + properties_df = pd.DataFrame(properties) + epc_rating_summary = properties_df.groupby("current_epc_rating").size().reset_index(name="count") + epc_rating_summary["percentage"] = epc_rating_summary["count"] / epc_rating_summary["count"].sum() * 100 diff --git a/recommendations/Recommendations.py b/recommendations/Recommendations.py index 3ea29ca2..f785b31d 100644 --- a/recommendations/Recommendations.py +++ b/recommendations/Recommendations.py @@ -58,21 +58,22 @@ class Recommendations: property_recommendations = [] phase = 0 - # 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) + 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) self.roof_recommender.recommend(phase=phase) if self.roof_recommender.recommendations: @@ -100,6 +101,12 @@ class Recommendations: property_recommendations.append(self.heating_recommender.recommendations) phase += 1 + # Hot water + self.hotwater_recommender.recommend(phase=phase) + if self.hotwater_recommender.recommendations: + property_recommendations.append(self.hotwater_recommender.recommendations) + phase += 1 + self.lighting_recommender.recommend(phase=phase) if self.lighting_recommender.recommendation: property_recommendations.append(self.lighting_recommender.recommendation)