From 477504abd136c17aaca4ba0ab8757d59bdf84e0a Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 3 Dec 2024 19:08:18 +0000 Subject: [PATCH] adding non-intrusive sap points and survey flag pick up for multiple recommendations --- backend/Property.py | 12 ++++++++++++ backend/app/assumptions.py | 1 + etl/customers/cottons/remote_assessments.py | 11 ++++++++++- recommendations/DraughtProofingRecommendations.py | 5 ++++- recommendations/HeatingRecommender.py | 13 ++++++++++++- recommendations/HotwaterRecommendations.py | 9 +++++---- recommendations/RoofRecommendations.py | 10 ++++++++-- recommendations/WallRecommendations.py | 10 ++++++++-- 8 files changed, 60 insertions(+), 11 deletions(-) diff --git a/backend/Property.py b/backend/Property.py index 31f207ab..cc5bf12b 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -426,6 +426,18 @@ class Property: if phase_epc_transformation[k] == v: continue + if k == "hotwater-description": + if ( + v == "From main system" + ) and ( + phase_epc_transformation["mainheat-description"] == "Electric storage heaters" + ) and ( + "Electric immersion" in phase_epc_transformation["hotwater-description"] + ): + # It means we've recommended HHR with electric immersion, and shouldn't overwrite + # the hot water description + continue + raise NotImplementedError( "Already have this key in the phase_epc_transformation - implement me" ) diff --git a/backend/app/assumptions.py b/backend/app/assumptions.py index 79f2a087..44838a47 100644 --- a/backend/app/assumptions.py +++ b/backend/app/assumptions.py @@ -50,4 +50,5 @@ DESCRIPTIONS_TO_FUEL_TYPES = { }, "Gas instantaneous at point of use": {"fuel": "Natural Gas", "cop": 0.85}, "Room heaters, wood logs": {"fuel": "Wood Logs", "cop": 1}, + "Boiler and radiators, coal": {"fuel": "Coal", "cop": 0.85}, } diff --git a/etl/customers/cottons/remote_assessments.py b/etl/customers/cottons/remote_assessments.py index fe195f7d..6ac895f1 100644 --- a/etl/customers/cottons/remote_assessments.py +++ b/etl/customers/cottons/remote_assessments.py @@ -40,6 +40,7 @@ def app(): asset_list["uprn"] = asset_list["uprn"].astype(int) extracted_data = [] + model_asset_list = [] for _, home in tqdm(asset_list.iterrows(), total=len(asset_list)): add1 = home["address1"] pc = home["postcode"] @@ -63,6 +64,14 @@ def app(): } ) + model_asset_list.append( + { + "uprn": home["uprn"], + "address": epc_searcher.newest_epc["address1"], + "postcode": epc_searcher.newest_epc["postcode"], + } + ) + non_invasive_recommendations = [ { "uprn": r["uprn"], @@ -72,7 +81,7 @@ def app(): filename = f"{USER_ID}/{PORTFOLIO_ID}/asset_list.csv" save_csv_to_s3( - dataframe=pd.DataFrame(asset_list), + dataframe=pd.DataFrame(model_asset_list), bucket_name="retrofit-plan-inputs-dev", file_name=filename ) diff --git a/recommendations/DraughtProofingRecommendations.py b/recommendations/DraughtProofingRecommendations.py index 4bd85a03..a16a94f6 100644 --- a/recommendations/DraughtProofingRecommendations.py +++ b/recommendations/DraughtProofingRecommendations.py @@ -26,6 +26,9 @@ class DraughtProofingRecommendations: if not draught_proofing_recommendation_config: return + # Cost is based on a £50 cost per window, based on Checkatrade + cost = draught_proofing_recommendation_config.get("cost", self.property.number_of_windows * 50) + description = ( "Draught proof doors and windows to improve energy efficiency" if not draught_proofing_recommendation_config.get("description") @@ -48,7 +51,7 @@ class DraughtProofingRecommendations: "kwh_savings": 0, "co2_equivalent_savings": 0, "energy_cost_savings": 0, - "total": draught_proofing_recommendation_config["cost"], + "total": cost, # We use a very simple and rough estimate of 4 hours per unit "labour_hours": draught_proofing_recommendation_config.get("labour_hours", 8), "labour_days": draught_proofing_recommendation_config.get("labour_days", 1), # Assume 8 hour day diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py index 7dc4f8b2..a4443bad 100644 --- a/recommendations/HeatingRecommender.py +++ b/recommendations/HeatingRecommender.py @@ -1,5 +1,6 @@ import re import backend.app.assumptions as assumptions +from etl.customers.immo.pilot.asset_list import non_invasive_recommendations from recommendations.Costs import Costs, BOILER_UPGRADE_SCHEME_ASHP_VALUE from recommendations.recommendation_utils import ( check_simulation_difference, override_costs, combine_recommendation_configs @@ -981,6 +982,10 @@ class HeatingRecommender: self.property.data["hot-water-energy-eff"] in ["Very Poor", "Poor", "Average"] ) + non_invasive_recommendation = next(( + r for r in self.property.non_invasive_recommendations if r["type"] == "boiler_upgrade" + ), {}) + if has_inefficient_space_heating or has_inefficient_water: boiler_size = self.estimate_boiler_size( property_type=self.property.data["property-type"], @@ -1079,12 +1084,13 @@ class HeatingRecommender: "description": description, "starting_u_value": None, "new_u_value": None, - "sap_points": None, + "sap_points": non_invasive_recommendation.get("sap_points", None), "already_installed": already_installed, "simulation_config": simulation_config, "description_simulation": description_simulation, **boiler_costs, "system_type": "boiler_upgrade", + "survey": non_invasive_recommendation.get("survey", None) } # We recommend the heating controls @@ -1105,6 +1111,11 @@ class HeatingRecommender: if not controls_recommender.recommendation and not boiler_recommendation: return + # If this is true, we set SAP points to None and survey to False for the boiler recommendation + if boiler_recommendation: + boiler_recommendation["sap_points"] = None + boiler_recommendation["survey"] = False + if not system_change and len(boiler_recommendation): # If there is not a system change, we add the boiler recommendation at point. self.heating_recommendations.extend([boiler_recommendation]) diff --git a/recommendations/HotwaterRecommendations.py b/recommendations/HotwaterRecommendations.py index b86329e4..d8404cc1 100644 --- a/recommendations/HotwaterRecommendations.py +++ b/recommendations/HotwaterRecommendations.py @@ -20,6 +20,8 @@ class HotwaterRecommendations: :return: """ # Reset the recommendations + recommendations_phase = phase + self.recommendations = [] non_invasive_recommendations = self.property.non_invasive_recommendations if non_invasive_recommendations: @@ -28,7 +30,6 @@ class HotwaterRecommendations: r["type"] in ["hot_water_tank_insulation", "cylinder_thermostat"] ] - recommendations_phase = phase for m in measures: non_invasive_rec = [ r for r in non_invasive_recommendations if r["type"] == m @@ -55,7 +56,7 @@ class HotwaterRecommendations: if self.property.hotwater["clean_description"] == "Gas boiler/circulator, no cylinder thermostat": # Handle this case specifically: - self.recommend_cylinder_thermostat_gas_boiler_circulator(phase=phase) + self.recommend_cylinder_thermostat_gas_boiler_circulator(phase=recommendations_phase) return # If there is no system present, but access to the mains, we @@ -68,14 +69,14 @@ class HotwaterRecommendations: (self.property.hotwater["no_system_present"] is None) & (len(has_tank_recommendation) == 0) ): - self.recommend_tank_insulation(phase=phase) + self.recommend_tank_insulation(phase=recommendations_phase) return has_cylinder_recommendation = [r for r in self.recommendations if r["type"] == "cylinder_thermostat"] if ((self.property.hotwater["clean_description"] == "From main system, no cylinder thermostat") & (len(has_cylinder_recommendation) == 0)): - self.recommend_cylinder_thermostat(phase=phase) + self.recommend_cylinder_thermostat(phase=recommendations_phase) return def recommend_tank_insulation(self, phase, sap_points=None, survey=False, _return=False): diff --git a/recommendations/RoofRecommendations.py b/recommendations/RoofRecommendations.py index 51264b75..4e29083f 100644 --- a/recommendations/RoofRecommendations.py +++ b/recommendations/RoofRecommendations.py @@ -290,6 +290,11 @@ class RoofRecommendations: insulation_materials = pd.DataFrame(insulation_materials) + non_invasive_recommendations = next( + (r for r in self.property.non_invasive_recommendations if + r["type"] == insulation_materials["type"].values[0]), {} + ) + lowest_selected_u_value = None recommendations = [] for _, insulation_material_group in insulation_materials.groupby("description"): @@ -429,14 +434,15 @@ class RoofRecommendations: "description": self.make_roof_insulation_description(material), "starting_u_value": u_value, "new_u_value": new_u_value, - "sap_points": None, + "sap_points": non_invasive_recommendations.get("sap_points", 0), "already_installed": already_installed, "simulation_config": simulation_config, "description_simulation": { "roof-description": new_description, "roof-energy-eff": new_efficiency }, - **cost_result + **cost_result, + "survey": non_invasive_recommendations.get("survey", False) } ) diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py index f77ae5a0..92147fb8 100644 --- a/recommendations/WallRecommendations.py +++ b/recommendations/WallRecommendations.py @@ -385,6 +385,11 @@ class WallRecommendations(Definitions): if insulation_thickness == "below average": cavity_width = cavity_width * (1 - PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION) + non_invasive_recommendations = next( + (r for r in self.property.non_invasive_recommendations if + r["type"] == insulation_materials["type"].values[0]), {} + ) + # Test the different fill options lowest_selected_u_value = None recommendations = [] @@ -475,14 +480,15 @@ class WallRecommendations(Definitions): "description": description, "starting_u_value": u_value, "new_u_value": new_u_value, - "sap_points": None, + "sap_points": non_invasive_recommendations.get("sap_points", None), "already_installed": already_installed, "simulation_config": simulation_config, "description_simulation": { "walls-description": "Cavity wall, filled cavity", "walls-energy-eff": "Good" }, - **cost_result + **cost_result, + "survey": non_invasive_recommendations.get("survey", False) } )