diff --git a/backend/Property.py b/backend/Property.py index 4ae65d7d..4d5a93a7 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -350,8 +350,21 @@ class Property: r for r in property_representative_recommendations if r["phase"] <= phase ] - epc_transformations = [x["description_simulation"] for x in represenative_recs_to_this_phase] - + + # TODO: This is placeholder, but it's to handle the case of having both internal and external wall + # insulation as options. This will cause the process below to fall over, so we take just + # external wall insulation in epc_transformations, if we have both + types = [ + x["type"] for x in represenative_recs_to_this_phase + ] + if "external_wall_insulation" in types and "internal_wall_insulation" in types: + epc_transformations = [ + x["description_simulation"] for x in represenative_recs_to_this_phase if + x["type"] != "internal_wall_insulation" + ] + else: + epc_transformations = [x["description_simulation"] for x in represenative_recs_to_this_phase] + # It is possible that we could have two simulations applied to the same descriptions # We extract these out phase_epc_transformation = {} diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index 8e6bc3b1..00e73b56 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -488,13 +488,6 @@ async def trigger_plan(body: PlanTriggerRequest): "carbon_ending"] ) - from utils.s3 import save_dataframe_to_s3_parquet - save_dataframe_to_s3_parquet( - bucket_name="retrofit-datalake-dev", - file_key="recommendations_scoring_data_11th_july.parquet", - df=recommendations_scoring_data - ) - model_api = ModelApi(portfolio_id=body.portfolio_id, timestamp=created_at) all_predictions = model_api.predictions_template() @@ -510,8 +503,6 @@ async def trigger_plan(body: PlanTriggerRequest): for key, scored in predictions_dict.items(): all_predictions[key] = pd.concat([all_predictions[key], scored]) - prediction_df = all_predictions["heating_cost_predictions"] - # Insert the predictions into the recommendations and run the optimiser # TODO: If a recommendation has a negative impact on SAP, we should remove it - this seems to have become a # possibility with heating system diff --git a/etl/sfr/example_retrofit_plan.py b/etl/sfr/example_retrofit_plan.py new file mode 100644 index 00000000..fd75cbe1 --- /dev/null +++ b/etl/sfr/example_retrofit_plan.py @@ -0,0 +1,37 @@ +import pandas as pd +from utils.s3 import save_csv_to_s3 + +PORTFOLIO_ID = 85 +USER_ID = 8 + + +def app(): + asset_list = [ + { + "address": "120 Yarningale Road", + "postcode": "B14 6NB", + "uprn": 100070575194 + } + ] + + asset_list = pd.DataFrame(asset_list) + + filename = f"{USER_ID}/{PORTFOLIO_ID}/sample.csv" + save_csv_to_s3( + dataframe=asset_list, + bucket_name="retrofit-plan-inputs-dev", + file_name=filename + ) + + body = { + "portfolio_id": str(PORTFOLIO_ID), + "housing_type": "Private", + "goal": "Increase EPC", + "goal_value": "C", + "trigger_file_path": filename, + "already_installed_file_path": "", + "patches_file_path": "", + "non_invasive_recommendations_file_path": "", + "budget": None, + } + print(body) diff --git a/recommendations/HeatingControlRecommender.py b/recommendations/HeatingControlRecommender.py index 1aae3973..fe3e577d 100644 --- a/recommendations/HeatingControlRecommender.py +++ b/recommendations/HeatingControlRecommender.py @@ -100,9 +100,10 @@ class HeatingControlRecommender: We can then consider the heating system itself :return: """ + new_description = "Controls for high heat retention storage heaters" # We recommend upgrading to Celect type controls - ending_config = MainheatControlAttributes("Controls for high heat retention storage heaters").process() + ending_config = MainheatControlAttributes(new_description).process() # We look at what has changed in the ending config, and compare it to the current config simulation_config = check_simulation_difference( new_config=ending_config, old_config=self.property.main_heating_controls @@ -110,11 +111,17 @@ class HeatingControlRecommender: # This upgrade will only take the heating system to average energy efficiency simulation_config["mainheatc_energy_eff_ending"] = "Good" + description_simulation = { + "mainheatcont-description": new_description, + "mainheatc-energy-eff": simulation_config["mainheatc_energy_eff_ending"] + } + self.recommendation.append( { "description": "upgrade heating controls to High Heat Retention Storage Heater Controls", **self.costs.celect_type_controls(), - "simulation_config": simulation_config + "simulation_config": simulation_config, + "description_simulation": description_simulation } ) @@ -152,7 +159,9 @@ class HeatingControlRecommender: if not can_recommend: return - ending_config = MainheatControlAttributes("Programmer, room thermostat and TRVS").process() + new_controls_description = "Programmer, room thermostat and TRVS" + + ending_config = MainheatControlAttributes(new_controls_description).process() # We use this to determine how we should be updating the config simulation_config = check_simulation_difference( new_config=ending_config, old_config=self.property.main_heating_controls @@ -161,6 +170,13 @@ class HeatingControlRecommender: # If the current system is below good, we make it good if self.property.data["mainheatc-energy-eff"] in ["Poor", "Very Poor", "Average"]: simulation_config["mainheatc_energy_eff_ending"] = "Good" + else: + simulation_config["mainheatc_energy_eff_ending"] = self.property.data["mainheatc-energy-eff"] + + description_simulation = { + "mainheatcont-description": new_controls_description, + "mainheatc-energy-eff": simulation_config["mainheatc_energy_eff_ending"] + } has_programmer = not needs_programmer has_room_thermostat = not needs_room_thermostat @@ -191,10 +207,7 @@ class HeatingControlRecommender: "sap_points": None, "already_installed": already_installed, "simulation_config": simulation_config, - "description_simulation": { - "mainheatcont-description": "Programmer, room thermostat and TRVS", - "mainheatc-energy-eff": "Good" - } + "description_simulation": description_simulation } ) @@ -221,7 +234,9 @@ class HeatingControlRecommender: # No recommendation needed return - ending_config = MainheatControlAttributes("Time and temperature zone control").process() + new_controls_description = "Time and temperature zone control" + + ending_config = MainheatControlAttributes(new_controls_description).process() # We use this to determine how we should be updating the config simulation_config = check_simulation_difference( @@ -231,7 +246,13 @@ class HeatingControlRecommender: # If the current system is below very good, we make it very good if self.property.data["mainheatc-energy-eff"] in ["Poor", "Very Poor", "Average", "Good"]: simulation_config["mainheatc_energy_eff_ending"] = "Very Good" + else: + simulation_config["mainheatc_energy_eff_ending"] = self.property.data["mainheatc-energy-eff"] + description_simulation = { + "mainheatcont-description": new_controls_description, + "mainheatc-energy-eff": simulation_config["mainheatc_energy_eff_ending"] + } cost_result = self.costs.time_and_temperature_zone_control( number_heated_rooms=int(self.property.data["number-heated-rooms"]) ) @@ -255,9 +276,6 @@ class HeatingControlRecommender: "sap_points": None, "already_installed": already_installed, "simulation_config": simulation_config, - "description_simulation": { - "mainheatcont-description": "Time and temperature zone control", - "mainheatc-energy-eff": "Very Good" - } + "description_simulation": description_simulation } ) diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py index 0afdc18f..07bac2cd 100644 --- a/recommendations/HeatingRecommender.py +++ b/recommendations/HeatingRecommender.py @@ -55,7 +55,7 @@ class HeatingRecommender: # TODO: We could have a system flush recommendation for an existing boiler, where there is no need to replace # the boiler, but instead flushing the system will make it run more efficiently. There is a cost for this # in the Costs class, stored as SYSTEM_FLUSH_COST - + exclusions = [] if exclusions is None else exclusions self.heating_recommendations = [] @@ -86,7 +86,8 @@ class HeatingRecommender: electic_heating_has_mains = self.has_electric_heating_description and self.property.data["mains-gas-flag"] portable_heaters_has_mains = ( - self.property.main_heating["clean_description"] in ["Portable electric heaters assumed for most rooms"] and + self.property.main_heating["clean_description"] in ["Portable electric heaters assumed for most rooms"] + and self.property.data["mains-gas-flag"] ) @@ -238,28 +239,31 @@ class HeatingRecommender: description = description + (f" The cost includes the £" f"{BOILER_UPGRADE_SCHEME_ASHP_VALUE} boiler upgrade scheme grant") + new_heating_description = "Air source heat pump, radiators, electric" + new_hot_water_description = "From main system" simulation_config = { "mainheat_energy_eff_ending": "Good", "hot_water_energy_eff_ending": "Good" } description_simulation = { - "mainheat-description": "Air source heat pump, radiators, electric", - "mainheat-energy-eff": "Good", - "hot-water-energy-eff": "Good", - "hotwater-description": "From main system", + "mainheat-description": new_heating_description, + "mainheat-energy-eff": simulation_config["mainheat_energy_eff_ending"], + "hot-water-energy-eff": simulation_config["hot_water_energy_eff_ending"], + "hotwater-description": new_hot_water_description, } # Installation of a boiler improves the hot water system so we need to reflect this in # the outcome of the recommendation - heating_ending_config = MainHeatAttributes("Air source heat pump, radiators, electric").process() - hotwater_ending_config = HotWaterAttributes("From main system").process() + heating_ending_config = MainHeatAttributes(new_heating_description).process() + hotwater_ending_config = HotWaterAttributes(new_hot_water_description).process() # If the property does not currently have electric main fuel, we'll simulate the change fuel_ending_config = {} if self.property.main_fuel["fuel_type"] != "electricity": - fuel_ending_config = MainFuelAttributes("electricity (not community)").process() + new_fuel_description = "electricity (not community)" + fuel_ending_config = MainFuelAttributes(new_fuel_description).process() description_simulation = { **description_simulation, - "main-fuel": "electricity (not community)" + "main-fuel": new_fuel_description } # Check the simulation differences @@ -292,8 +296,7 @@ class HeatingRecommender: description_simulation = { **description_simulation, - "mainheatcont-description": "time and temperature zone control", - "mainheatc-energy-eff": "Very Good" + **controls_recommender.recommendation[0]["description_simulation"] } ashp_recommendation = { @@ -330,7 +333,14 @@ class HeatingRecommender: return differences def combine_heating_and_controls( - self, controls_recommendations, heating_simulation_config, costs, description, phase, heating_controls_only, + self, + controls_recommendations, + heating_simulation_config, + heating_description_simulation, + costs, + description, + phase, + heating_controls_only, system_change ): """ @@ -338,6 +348,7 @@ class HeatingRecommender: into a single recommendation :param controls_recommendations: The heating controls recommendations :param heating_simulation_config: The simulation configuration for the heating system + :param heating_description_simulation: The simulation configuration for the heating description :param costs: The costs of the heating system :param description: The description of the recommendation :param phase: The phase of the recommendation @@ -361,6 +372,7 @@ class HeatingRecommender: for controls_switch in heating_controls_switch: total_costs = costs.copy() recommendation_simulation_config = heating_simulation_config.copy() + recommendation_description_simulation = heating_description_simulation.copy() recommendation_description = description if controls_switch: # We add the costs of the heating controls, onto each key in the costs dictionary @@ -371,6 +383,12 @@ class HeatingRecommender: **recommendation_simulation_config, **controls_recommendations[0]["simulation_config"] } + + recommendation_description_simulation = { + **recommendation_description_simulation, + **controls_recommendations[0]["description_simulation"] + } + controls_description = controls_recommendations[0]['description'] # Make the first letter of the description lowercase controls_description = ( @@ -396,7 +414,8 @@ class HeatingRecommender: "sap_points": None, "already_installed": already_installed, **total_costs, - "simulation_config": recommendation_simulation_config + "simulation_config": recommendation_simulation_config, + "description_simulation": recommendation_description_simulation } output.append(recommendation) @@ -474,8 +493,10 @@ class HeatingRecommender: # No recommendation needed return + new_heating_description = "Electric storage heaters, radiators" + # Set up artefacts, suitable for the simulation and regardless of controls - heating_ending_config = MainHeatAttributes("Electric storage heaters, radiators").process() + heating_ending_config = MainHeatAttributes(new_heating_description).process() heating_simulation_config = check_simulation_difference( new_config=heating_ending_config, old_config=self.property.main_heating ) @@ -497,9 +518,15 @@ class HeatingRecommender: ) description = "Install high heat retention electric storage heaters" + heating_description_simulation = { + "mainheat-description": new_heating_description, + "mainheat-energy-eff": heating_simulation_config["mainheat_energy_eff_ending"], + } + recommendations = self.combine_heating_and_controls( controls_recommendations=controls_recommender.recommendation, heating_simulation_config=heating_simulation_config, + heating_description_simulation=heating_description_simulation, costs=costs, description=description, phase=phase, @@ -580,6 +607,7 @@ class HeatingRecommender: simulation_config = {} boiler_costs = {} boiler_recommendation = {} + description_simulation = {} has_inefficient_space_heating = self.property.data["mainheat-energy-eff"] in ["Very Poor", "Poor", "Average"] @@ -603,12 +631,22 @@ class HeatingRecommender: "mainheat_energy_eff_ending": "Good", "hot_water_energy_eff_ending": "Good" } + + description_simulation = { + "mainheat-energy-eff": simulation_config["mainheat_energy_eff_ending"], + "hot-water-energy-eff": simulation_config["hot_water_energy_eff_ending"], + } + if system_change: # Installation of a boiler improves the hot water system so we need to reflect this in # the outcome of the recommendation - heating_ending_config = MainHeatAttributes("Boiler and radiators, mains gas").process() - hotwater_ending_config = HotWaterAttributes("From main system").process() - fuel_ending_config = MainFuelAttributes("mains gas (not community)").process() + new_heating_description = "Boiler and radiators, mains gas" + new_hotwater_description = "From main system" + new_fuel_description = "mains gas (not community)" + + heating_ending_config = MainHeatAttributes(new_heating_description).process() + hotwater_ending_config = HotWaterAttributes(new_hotwater_description).process() + fuel_ending_config = MainFuelAttributes(new_fuel_description).process() heating_simulation_config = check_simulation_difference( new_config=heating_ending_config, old_config=self.property.main_heating @@ -627,6 +665,13 @@ class HeatingRecommender: **fuel_simulation_config, } + description_simulation = { + **description_simulation, + "mainheat-description": new_heating_description, + "hotwater-description": new_hotwater_description, + "main-fuel": new_fuel_description + } + boiler_costs = self.costs.boiler( size=f"{boiler_size}kw", exising_room_heaters=exising_room_heaters, @@ -652,6 +697,7 @@ class HeatingRecommender: "sap_points": None, "already_installed": already_installed, "simulation_config": simulation_config, + "description_simulation": description_simulation, **boiler_costs } @@ -675,6 +721,7 @@ class HeatingRecommender: combined_recommendation = self.combine_heating_and_controls( controls_recommendations=[controls_recommendation], heating_simulation_config=simulation_config, + heating_description_simulation=description_simulation, costs=boiler_costs, description=boiler_recommendation["description"], phase=recommendation_phase, diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py index a1a1491b..448b34e8 100644 --- a/recommendations/WallRecommendations.py +++ b/recommendations/WallRecommendations.py @@ -524,6 +524,10 @@ class WallRecommendations(Definitions): "already_installed": already_installed, "sap_points": None, "simulation_config": simulation_config, + "description_simulation": { + "walls-description": new_description, + "walls-energy-eff": simulation_config["walls_energy_eff_ending"] + }, **cost_result } )