from recommendations.Costs import Costs from recommendations.recommendation_utils import check_simulation_difference, override_costs from backend.Property import Property from etl.epc_clean.epc_attributes.MainheatControlAttributes import MainheatControlAttributes class HeatingControlRecommender: def __init__(self, property_instance: Property): self.property = property_instance self.costs = Costs(self.property) self.recommendation = [] def recommend(self, heating_description, phase, description_prefix="", description_suffix=""): # TODO: Many of these functions are quite similar. We can possibly create a single wrapper function that # takes in the heating description and the description prefix/suffix, and then creates the appropriate # output # Reset the recommendations self.recommendation = [] # This first iteration of the recommender will provide very basic recommendation # We recommend heating controls based on the main heating system if heating_description in ["Room heaters, electric"]: self.recommend_room_heaters_electric_controls(phase=phase) return if heating_description in ["Electric storage heaters", "Electric storage heaters, radiators"]: self.recommend_high_heat_retention_controls(description_prefix=description_prefix, phase=phase) return if heating_description in ["Boiler and radiators, mains gas"]: # We can recommend roomstat programmer trvs self.recommend_roomstat_programmer_trvs(description_suffix=description_suffix, phase=phase) # We can also recommend time and temperature zone controls self.recommend_time_temperature_zone_controls(description_suffix=description_suffix, phase=phase) return if heating_description in ["Boiler and radiators, electric"]: self.recommend_roomstat_programmer_trvs(phase=phase) return if heating_description in ["Air source heat pump, radiators, electric"]: # For an ASHP, we can recommend time and temperature zone controls, as well as programmer, trvs and a bypass # which are common configurations for ASHPs self.recommend_time_temperature_zone_controls(phase=phase) # self.recommend_programmer_trvs_bypass() def recommend_room_heaters_electric_controls(self, phase): """ 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 :return: """ if (self.property.data["mainheatc-energy-eff"] in ["Poor", "Very Poor", "Average"]) or ( self.property.main_heating_controls["clean_description"] in ["Programmer and room thermostat"] ): # We recommend Programmer and appliance thermostats as the heating control. This has an average energy # efficiency rating, and is likely to be more efficient than the current heating controls. if the # rating is poor or very poor, the home may have a Programmer and room thermostat, which is less efficient # than a Programmer and appliance thermostats, because it allows for much more granular control at not # just a room level but individual heater/appliance level # Note: A room thermostat is commonly placed in a hallway, and it measures the temperature of the air # surrounding it. It then sends a signal to the heating system to turn on or off, depending on the # temperature. An appliance thermostat, on the other hand, is placed on the heater/appliance itself, and # measures the temperature of the heater/appliance. This allows for much more granular control, and # prevents overheating. # In order to cost, we check if the property already has a programmer, and therefor we will just need to # add the cost of the appliance thermostats has_programmer = self.property.main_heating_controls["switch_system"] == "programmer" ending_config = MainheatControlAttributes("Programmer and appliance thermostats").process() # We look at what has changed in the ending config, and compare it to the current config # 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 ) # This upgrade will only take the heating system to average energy efficiency simulation_config["mainheatc_energy_eff_ending"] = "Good" self.recommendation.append( { "phase": phase, "type": "heating", "measure_type": "programmer_appliance_thermostat", "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 } ) # We don't implement any other recommendations right now return def recommend_high_heat_retention_controls(self, phase, description_prefix=""): """ When applicable, we recommend upgrading the heating controls to high heat retention controls. This is a specific type of control system that is designed to work with electric storage heaters. It is a more efficient control system than the standard controls that come with electric storage heaters. We can then consider the heating system itself If there is a description prefix, this means there is a dual heating system and so we need to add this to the description :return: """ new_description = "Controls for high heat retention storage heaters" if description_prefix: new_description = f"{description_prefix}, {new_description}" # We recommend upgrading to Celect type controls 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 ) # This upgrade will only take the heating system to average energy efficiency 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_description, "mainheatc-energy-eff": simulation_config["mainheatc_energy_eff_ending"] } self.recommendation.append( { "phase": phase, "type": "heating", "measure_type": "celect_type_controls", "description": "Upgrade heating controls to High Heat Retention Storage Heater Controls", **self.costs.celect_type_controls(), "simulation_config": simulation_config, "description_simulation": description_simulation } ) # We don't implement any other recommendations right now return def recommend_roomstat_programmer_trvs(self, phase, description_suffix=""): """ If the home has a boiler and radiators, mains gas, we start by identifying potential heating controls that could be upgraded, that would provide a practical impact. The criteria for recommending an upgrade to heating controls are (one of these must be true) 1) There are no controls 2) No programmer 3) No room thermostat 4) No TRVs :return: """ # We check if we have the conditions to recommend this upgrade needs_programmer = self.property.main_heating_controls["switch_system"] is None needs_room_thermostat = self.property.main_heating_controls["thermostatic_control"] is None needs_trvs = self.property.main_heating_controls["trvs"] is None can_recommend = ( (self.property.main_heating_controls["no_control"] is not None) or needs_programmer or needs_room_thermostat or needs_trvs ) if not can_recommend: return new_controls_description = "Programmer, room thermostat and TRVS" if description_suffix: new_controls_description = f"{new_controls_description}, {description_suffix}" 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 ) # This upgrade will only take the heating system to average energy efficiency # 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 has_trvs = not needs_trvs cost_result = self.costs.roomstat_programmer_trvs( number_heated_rooms=int(self.property.data["number-heated-rooms"]), has_programmer=has_programmer, has_room_thermostat=has_room_thermostat, has_trvs=has_trvs ) description = "Upgrade heating controls to Room thermostat, programmer and TRVs" already_installed = "roomstat_programmer_trvs" in self.property.already_installed if already_installed: cost_result = override_costs(cost_result) description = "Heating controls have already been upgraded, no further action needed." self.recommendation.append( { "type": "heating", "measure_type": "roomstat_programmer_trvs", "phase": phase, "parts": [], "description": description, **cost_result, "starting_u_value": None, "new_u_value": None, "sap_points": None, "already_installed": already_installed, "simulation_config": simulation_config, "description_simulation": description_simulation } ) return def recommend_time_temperature_zone_controls(self, phase, description_suffix=""): """ If the home has a boiler, we can recommend time and temperature zone controls. This is a more advanced and more efficient control system than the standard controls that come with a boiler. However, it may come with a higher cost and more involved usage :return: """ # We check if the efficiency of the current heating controls is good or below, and # Conditions for installation are as follows: # 1) The current heating controls are not time and temperature zone controls # 2) The current heating controls are not already at 'Very Good' or above if ( (self.property.main_heating_controls["thermostatic_control"] == "time and temperature zone control") or (self.property.data["mainheatc-energy-eff"] in ["Very Good"]) ): # No recommendation needed return new_controls_description = "Time and temperature zone control" if description_suffix: new_controls_description = f"{new_controls_description}, {description_suffix}" 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 ) # 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"]) ) description = ( "Upgrade heating controls to Smart Thermostats, room sensors and smart radiator valves (time & " "temperature zone control)" ) already_installed = "time_temperature_zone_control" in self.property.already_installed if already_installed: cost_result = override_costs(cost_result) description = "Heating controls have already been upgraded, no further action needed." self.recommendation.append( { "type": "heating", "phase": phase, "measure_type": "time_temperature_zone_control", "parts": [], "description": description, **cost_result, "starting_u_value": None, "new_u_value": None, "sap_points": None, "already_installed": already_installed, "simulation_config": simulation_config, "description_simulation": description_simulation } ) def recommend_programmer_trvs_bypass(self): # We don't perform any checks here - this is likely to be used in conjunction with an ASHP recommendation new_controls_description = "Programmer, TRVs and bypass" ending_config = MainheatControlAttributes(new_controls_description).process() simulation_config = check_simulation_difference( new_config=ending_config, old_config=self.property.main_heating_controls ) # Only adjust if the current system is below good if self.property.data["mainheatc-energy-eff"] in ["Poor", "Very Poor"]: simulation_config["mainheatc_energy_eff_ending"] = "Average" 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 = self.property.main_heating_controls["switch_system"] == "programmer" has_trvs = self.property.main_heating_controls["trvs"] is not None has_bypass = self.property.main_heating_controls["auxiliary_systems"] == "bypass" cost_result = self.costs.programmer_trvs_bypass( number_heated_rooms=int(self.property.data["number-heated-rooms"]), has_trvs=has_trvs, has_programmer=has_programmer, has_bypass=has_bypass ) description = "Install a Bypass valve, TRVs and a Programmer" already_installed = "programmer_trvs_bypass" in self.property.already_installed if already_installed: cost_result = override_costs(cost_result) description = "Heating controls have already been upgraded, no further action needed." self.recommendation.append( { "type": "heating", "measure_type": "programmer_trvs_bypass", "parts": [], "description": description, **cost_result, "starting_u_value": None, "new_u_value": None, "sap_points": None, "already_installed": already_installed, "simulation_config": simulation_config, "description_simulation": description_simulation } )