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: def __init__(self, property_instance: Property): self.property = property_instance self.costs = Costs(self.property) 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": # Recommend high heat retention storage heaters # self.recommend_room_heaters_electric(phase=phase, system_change=False, heating_controls_only=True) # self.recommend_electric_storage_heaters(phase=phase, system_change=True, heating_controls_only=False) return if self.property.main_heating["clean_description"] == "Electric storage heaters, radiators": self.recommend_electric_storage_heaters(phase=phase, system_change=False, heating_controls_only=True) return @staticmethod def check_simulation_difference(old_config, new_config): """ Given two dictionaries, that describe the heating control configurations, this method will compare the two and pick out the differences. These differences will be things that have been added and things that have been removed. This will be used to determine how we should be updating the configuration in the simulation :return: """ differences = {key + "_ending": new_config[key] for key in new_config if old_config[key] != new_config[key]} return differences @staticmethod def combine_heating_and_controls( controls_recommendations, heating_simulation_config, costs, description, phase, heating_controls_only, system_change ): """ 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 :param system_change: Indicates if we are recommending a different type of heating system, compared to the current system. If we have a system change and we have a heat control recommendation, we only recommend both heating and controls together :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 = [] if system_change and len(controls_recommendations): heating_controls_switch = [True] output = [] for controls_switch in heating_controls_switch: total_costs = costs.copy() recommendation_simulation_config = heating_simulation_config.copy() recommendation_description = description 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"] } recommendation_description = f"{description} and {controls_recommendations[0]['description']}" recommendation = { "phase": phase, "parts": [ # TODO ], "type": "heating", "description": recommendation_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, "parts": [ # TODO ], "type": "heating", "starting_u_value": None, "new_u_value": None, "sap_points": None, **heating_control_recommendation } ) return output def recommend_electric_storage_heaters(self, phase, system_change, heating_controls_only): """ We recommend electric storage heaters as an upgrade to the heating system. :param phase: The phase of the recommendation :param system_change: Indicates if we are recommending a different type of heating system, compared to the current system :param heating_controls_only: Indicates if we should include a recommendation for just heating controls :return: """ controls_recommender = HeatingControlRecommender(self.property) # The heating controls we're recommending for are based on the recommended heating system # We only recommend Celect-type controls if the current heating system is not Celect-type controls if self.property.main_heating_controls["clean_description"] != "Celect-type controls": controls_recommender.recommend(heating_description="Electric storage heaters, radiators") # Conditions for not needing this recommendation efficient_room_heaters = self.property.main_heating["clean_description"] == "Room heaters, electric" and ( self.property.data["mainheat-energy-eff"] not in ["Poor", "Very Poor", "Average"] ) efficient_storage_heaters = ( (self.property.main_heating["clean_description"] in [ "Electric storage heaters, radiators", "Electric storage heaters" ]) and self.property.data["mainheat-energy-eff"] not in ["Poor", "Very Poor", "Average"] ) # Conditions for not recommending electric storage heaters if efficient_room_heaters or efficient_storage_heaters: # We do just heating controls self.recommendations.extend( self.combine_heating_and_controls( controls_recommendations=controls_recommender.recommendation, heating_simulation_config={}, costs={}, description="", phase=phase, heating_controls_only=heating_controls_only, system_change=system_change ) ) 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["mainheat_energy_eff_ending"] = "Average" # 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, system_change=system_change ) self.recommendations.extend(recommendations) def recommend_room_heaters_electric(self, phase, system_change, 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. We can then consider the heating system itself :param phase: The phase of the recommendation :param system_change: Indicates if we are recommending a different type of heating system, compared to the current system :param heating_controls_only: Indicates if we should include a recommendation for just heating controls :return: """ controls_recommender = HeatingControlRecommender(self.property) if self.property.main_heating_controls["clean_description"] != "Programmer and appliance thermostats": 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 self.recommendations.extend( self.combine_heating_and_controls( controls_recommendations=controls_recommender.recommendation, heating_simulation_config={}, costs={}, description="", phase=phase, heating_controls_only=heating_controls_only, system_change=system_change ) ) return costs = self.costs.electric_room_heaters( number_heated_rooms=self.property.data["number-heated-rooms"] ) description = "Upgrade electric room heaters to efficient electric radiators" heating_simulation_config = {"mainheat_energy_eff_ending": "Average"} 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, system_change=system_change ) self.recommendations.extend(recommendations)