Model/recommendations/HeatingControlRecommender.py
2024-09-17 09:15:31 +01:00

354 lines
16 KiB
Python

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, 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()
return
if heating_description in ["Electric storage heaters", "Electric storage heaters, radiators"]:
self.recommend_high_heat_retention_controls(description_prefix=description_prefix)
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)
# We can also recommend time and temperature zone controls
self.recommend_time_temperature_zone_controls(description_suffix=description_suffix)
return
if heating_description in ["Boiler and radiators, electric"]:
self.recommend_roomstat_programmer_trvs()
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()
# self.recommend_programmer_trvs_bypass()
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.
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(
{
"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, 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(
{
"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, 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 = "heating_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_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
}
)
return
def recommend_time_temperature_zone_controls(self, 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 = "heating_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_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 = "heating_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_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
}
)