From be71af909edab7acca94100ea4f17fdba827894c Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 19 Feb 2024 19:35:25 +0000 Subject: [PATCH] coupling heating and heating controls recommendations --- backend/Property.py | 2 +- recommendations/HeatingControlRecommender.py | 26 ++- recommendations/HeatingRecommender.py | 189 ++++++++++++++----- recommendations/Recommendations.py | 7 - 4 files changed, 156 insertions(+), 68 deletions(-) diff --git a/backend/Property.py b/backend/Property.py index a8c8664c..840d503d 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -363,7 +363,7 @@ 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_control", + "windows_glazing", "solar_pv", "heating", "heating_control", ]: raise NotImplementedError("Implement me") diff --git a/recommendations/HeatingControlRecommender.py b/recommendations/HeatingControlRecommender.py index 84d37931..4ee28d7e 100644 --- a/recommendations/HeatingControlRecommender.py +++ b/recommendations/HeatingControlRecommender.py @@ -10,16 +10,20 @@ class HeatingControlRecommender: self.property = property_instance self.costs = Costs(self.property) - self.recommendations = [] + self.recommendation = [] + + def recommend(self, heating_description): + + # Reset the recommendations + self.recommendation = [] - def recommend(self, phase=0): # This first iteration of the recommender will provide very basic recommendation # We recommend heating controls based on the main heating system - if self.property.main_heating["clean_description"] == "Room heaters, electric": - self.recommend_room_heaters_electric_controls(phase=phase) + if heating_description in ["Room heaters, electric", "Electric storage heaters, radiators"]: + self.recommend_room_heaters_electric_controls() return - def recommend_room_heaters_electric_controls(self, phase): + def recommend_room_heaters_electric_controls(self): """ If the home has Room heaters, electric, we start by identifying potential heating controls that could be upgraded, that would provide a practical impact. This will be the least invasive improvement. @@ -57,18 +61,10 @@ class HeatingControlRecommender: # This upgrade will only take the heating system to average energy efficiency simulation_config["mainheatc_energy_eff_ending"] = "Good" - self.recommendations.append( + self.recommendation.append( { - "phase": phase, - "parts": [ - # TODO - ], "type": "heating_control", - "description": "Upgrade heating controls to Programmer and Appliance or Smart " - "Thermostats for more precise heating control, and prevention of overheating", - "starting_u_value": None, - "new_u_value": None, - "sap_points": None, + "description": "upgrade heating controls to Programmer and Appliance or Smart Thermostats", **self.costs.programmer_and_appliance_thermostat(has_programmer=has_programmer), "simulation_config": simulation_config } diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py index 19374316..859a440f 100644 --- a/recommendations/HeatingRecommender.py +++ b/recommendations/HeatingRecommender.py @@ -2,6 +2,7 @@ from recommendations.Costs import Costs from recommendations.recommendation_utils import check_simulation_difference from backend.Property import Property from etl.epc_clean.epc_attributes.MainheatAttributes import MainHeatAttributes +from recommendations.HeatingControlRecommender import HeatingControlRecommender class HeatingRecommender: @@ -13,10 +14,12 @@ class HeatingRecommender: self.recommendations = [] def recommend(self, phase=0): + self.recommendations = [] # This first iteration of the recommender will provide very basic recommendation # We recommend heating controls based on the main heating system if self.property.main_heating["clean_description"] == "Room heaters, electric": - self.recommend_room_heaters_electric(phase=phase) + self.recommend_room_heaters_electric(phase=phase, heating_controls_only=True) + self.recommend_electric_storage_heaters(phase=phase, heating_controls_only=False) return @staticmethod @@ -32,7 +35,128 @@ class HeatingRecommender: return differences - def recommend_room_heaters_electric(self, phase): + @staticmethod + def combine_heating_and_controls( + controls_recommendations, heating_simulation_config, costs, description, phase, heating_controls_only + ): + """ + Given a recommendation for heating controls, and a recommendation for the heating system, we combine the two + into a single recommendation + :param controls_recommendations: The heating controls recommendations + :param heating_simulation_config: The simulation configuration for the heating system + :param costs: The costs of the heating system + :param description: The description of the recommendation + :param phase: The phase of the recommendation + :param heating_controls_only: If True, we will also add a recommendation for heating controls only + :return: + """ + + # We produce recommendations with & without heating controls + # We will also produce a recommendation for heating controls only + heating_controls_switch = [True, False] if controls_recommendations else [False] + if not heating_simulation_config: + heating_controls_switch = [] + + output = [] + for controls_switch in heating_controls_switch: + total_costs = costs.copy() + recommendation_simulation_config = heating_simulation_config.copy() + if controls_switch: + # We add the costs of the heating controls, onto each key in the costs dictionary + for key in total_costs: + total_costs[key] += controls_recommendations[0][key] + + recommendation_simulation_config = { + **recommendation_simulation_config, + **controls_recommendations[0]["simulation_config"] + } + + description = f"{description} and {controls_recommendations[0]['description']}" + + recommendation = { + "phase": phase, + "parts": [ + # TODO + ], + "type": "heating", + "description": description, + "starting_u_value": None, + "new_u_value": None, + "sap_points": None, + **total_costs, + "simulation_config": recommendation_simulation_config + } + + output.append(recommendation) + + if heating_controls_only and len(controls_recommendations): + # Also add on a recommendation for heating controls only + heating_control_recommendation = controls_recommendations[0].copy() + # Capitalize the first letter of the description + heating_control_recommendation["description"] = ( + heating_control_recommendation["description"][0].upper() + + heating_control_recommendation["description"][1:] + ) + + output.append( + { + "phase": phase, + "starting_u_value": None, + "new_u_value": None, + "sap_points": None, + **heating_control_recommendation + } + ) + + return output + + def recommend_electric_storage_heaters(self, phase, heating_controls_only): + """ + We recommend electric storage heaters as an upgrade to the heating system. + :return: + """ + + controls_recommender = HeatingControlRecommender(self.property) + controls_recommender.recommend(heating_description="Room heaters, electric") + + if self.property.data["mainheat-energy-eff"] not in ["Poor", "Very Poor"]: + # We do just heating controls + recs = self.combine_heating_and_controls( + controls_recommendations=controls_recommender.recommendation, + heating_simulation_config={}, + costs={}, + description="", + phase=phase, + heating_controls_only=heating_controls_only + ) + return + + # Set up artefacts, suitable for the simulation and regardless of controls + heating_ending_config = MainHeatAttributes("Electric storage heaters, radiators").process() + heating_simulation_config = check_simulation_difference( + new_config=heating_ending_config, old_config=self.property.main_heating + ) + # This upgrade will only take the heating system to average energy efficiency + heating_simulation_config["mainheatc_energy_eff_ending"] = "Good" + + # Upgrade to electric storage heaters + costs = self.costs.electric_storage_heaters( + number_heated_rooms=self.property.data["number-heated-rooms"] + ) + description = "Install electric storage heaters" + + recommendations = self.combine_heating_and_controls( + controls_recommendations=controls_recommender.recommendation, + heating_simulation_config=heating_simulation_config, + costs=costs, + description=description, + phase=phase, + heating_controls_only=heating_controls_only + ) + + self.recommendations.extend(recommendations) + + def recommend_room_heaters_electric(self, phase, heating_controls_only): """ If the home has Room heaters, electric, we start by identifying potential heating controls that could be upgraded, that would provide a practical impact. This will be the least invasive improvement. @@ -40,49 +164,24 @@ class HeatingRecommender: We can then consider the heating system itself :return: """ - if self.property.data["mainheat-energy-eff"] in ["Poor", "Very Poor"]: - # Re recommend two possible upgrades: - # 1) Installation of more efficient electic room heaters - # 2) Installation of electric storage heaters + if self.property.data["mainheat-energy-eff"] not in ["Poor", "Very Poor"]: + return - room_heater_recommendation = { - "phase": phase, - "parts": [ - # TODO - ], - "type": "heating", - "description": "Upgrade electric room heaters to more electric radiators", - "starting_u_value": None, - "new_u_value": None, - "sap_points": None, - **self.costs.electric_room_heaters(number_heated_rooms=self.property.data["number-heated-rooms"]), - "simulation_config": {"mainheat_energy_eff_ending": "Average"} - } + controls_recommender = HeatingControlRecommender(self.property) + controls_recommender.recommend(heating_description="Room heaters, electric") + costs = self.costs.electric_room_heaters( + number_heated_rooms=self.property.data["number-heated-rooms"] + ) + description = "Upgrade electric room heaters to more electric radiators" + heating_simulation_config = {"mainheat_energy_eff_ending": "Average"} - ending_config = MainHeatAttributes("Electric storage heaters, radiators").process() - simulation_config = check_simulation_difference( - new_config=ending_config, old_config=self.property.main_heating - ) - # This upgrade will only take the heating system to average energy efficiency - simulation_config["mainheatc_energy_eff_ending"] = "Good" + recommendations = self.combine_heating_and_controls( + controls_recommender=controls_recommender, + heating_simulation_config=heating_simulation_config, + costs=costs, + description=description, + phase=phase, + heating_controls_only=heating_controls_only + ) - electric_storage_heaters_recommendation = { - "phase": phase, - "parts": [ - # TODO - ], - "type": "heating", - "description": "Install electric storage heaters", - "starting_u_value": None, - "new_u_value": None, - "sap_points": None, - **self.costs.electric_storage_heaters(number_heated_rooms=self.property.data["number-heated-rooms"]), - "simulation_config": simulation_config - } - - self.recommendations.extend( - [room_heater_recommendation, electric_storage_heaters_recommendation] - ) - - # We don't implement any other recommendations right now - return + self.recommendations.extend(recommendations) diff --git a/recommendations/Recommendations.py b/recommendations/Recommendations.py index 938debe1..44be78f6 100644 --- a/recommendations/Recommendations.py +++ b/recommendations/Recommendations.py @@ -10,7 +10,6 @@ from recommendations.LightingRecommendations import LightingRecommendations from recommendations.SolarPvRecommendations import SolarPvRecommendations from recommendations.WindowsRecommendations import WindowsRecommendations from recommendations.HeatingRecommender import HeatingRecommender -from recommendations.HeatingControlRecommender import HeatingControlRecommender from backend.ml_models.AnnualBillSavings import AnnualBillSavings @@ -43,7 +42,6 @@ class Recommendations: self.windows_recommender = WindowsRecommendations(property_instance=property_instance, materials=materials) self.solar_recommender = SolarPvRecommendations(property_instance=property_instance) self.heating_recommender = HeatingRecommender(property_instance=property_instance) - self.heating_control_recommender = HeatingControlRecommender(property_instance=property_instance) def recommend(self): @@ -99,11 +97,6 @@ class Recommendations: property_recommendations.append(self.heating_recommender.recommendations) phase += 1 - self.heating_control_recommender.recommend(phase=phase) - if self.heating_control_recommender.recommendations: - property_recommendations.append(self.heating_control_recommender.recommendations) - phase += 1 - self.lighting_recommender.recommend(phase=phase) if self.lighting_recommender.recommendation: property_recommendations.append(self.lighting_recommender.recommendation)