mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
676 lines
31 KiB
Python
676 lines
31 KiB
Python
from recommendations.Costs import Costs, BOILER_UPGRADE_SCHEME_ASHP_VALUE
|
|
from recommendations.recommendation_utils import check_simulation_difference, override_costs
|
|
from backend.Property import Property
|
|
from etl.epc_clean.epc_attributes.MainheatAttributes import MainHeatAttributes
|
|
from etl.epc_clean.epc_attributes.HotWaterAttributes import HotWaterAttributes
|
|
from etl.epc_clean.epc_attributes.MainFuelAttributes import MainFuelAttributes
|
|
from recommendations.HeatingControlRecommender import HeatingControlRecommender
|
|
|
|
|
|
class HeatingRecommender:
|
|
ELECTRIC_HEATING_DESCRIPTIONS = [
|
|
"Room heaters, electric",
|
|
"Electric storage heaters",
|
|
"Electric storage heaters, radiators",
|
|
"Portable electric heaters assumed for most rooms",
|
|
]
|
|
|
|
high_heat_retention_contols_desc = "Controls for high heat retention storage heaters"
|
|
|
|
def __init__(self, property_instance: Property):
|
|
self.property = property_instance
|
|
self.costs = Costs(self.property)
|
|
|
|
self.heating_recommendations = []
|
|
self.heating_control_recommendations = []
|
|
|
|
self.has_electric_heating_description = (
|
|
self.property.main_heating["clean_description"] in self.ELECTRIC_HEATING_DESCRIPTIONS
|
|
)
|
|
|
|
def is_high_heat_retention_valid(self):
|
|
"""
|
|
Check conditions if high heat retention storage is valid
|
|
:return:
|
|
"""
|
|
|
|
# If the property has assumed electric heating, regardless of whether or not it has a mains connection, we
|
|
# can consider hhr storage heaters
|
|
electric_heating_assumed = (
|
|
self.property.main_heating["clean_description"] in ["No system present, electric heaters assumed"]
|
|
)
|
|
|
|
return self.has_electric_heating_description or electric_heating_assumed
|
|
|
|
def recommend(self, has_cavity_or_loft_recommendations, phase=0):
|
|
"""
|
|
Produces heating recommendations
|
|
:param has_cavity_or_loft_recommendations: boolean indicating if we have produced a cavity or loft insulation
|
|
recommendation. If there are cavity or loft recommendations, the property would need to complete those measures
|
|
before being able to get the boiler upgrade scheme benefits. The messaging in the front end would be to
|
|
:param phase: indicates the phase of the retrofit programme
|
|
"""
|
|
|
|
# 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
|
|
|
|
self.heating_recommendations = []
|
|
self.heating_control_recommendations = []
|
|
# This first iteration of the recommender will provide very basic recommendation
|
|
# We recommend heating controls based on the main heating system
|
|
|
|
if self.is_high_heat_retention_valid():
|
|
# Recommend high heat retention storage heaters
|
|
# TODO: We need to allow for the possibility that the property aleady has storage heaters, but just
|
|
# needs the controls
|
|
self.recommend_hhr_storage_heaters(phase=phase, system_change=True, heating_controls_only=False)
|
|
|
|
# if the property has mains heating with boiler and radiators, we recommend optimal heating controls
|
|
has_boiler = self.property.main_heating["clean_description"] in ["Boiler and radiators, mains gas"]
|
|
|
|
# We also check that the property doesn't have a heating system, but it has access to the mains gas
|
|
no_heating_has_mains = self.property.main_heating["clean_description"] in [
|
|
'No system present, electric heaters assumed'
|
|
] and self.property.data["mains-gas-flag"]
|
|
|
|
has_gas_heaters = (
|
|
self.property.main_heating["clean_description"] in ["Room heaters, mains gas"] and
|
|
self.property.data["mains-gas-flag"]
|
|
)
|
|
|
|
# We also check if the property has electric heating, but it has access to the mains gas
|
|
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.data["mains-gas-flag"]
|
|
)
|
|
|
|
if (
|
|
has_boiler or
|
|
no_heating_has_mains or
|
|
electic_heating_has_mains or
|
|
has_gas_heaters or
|
|
portable_heaters_has_mains
|
|
):
|
|
# This indicates that the home previously did not have a boiler in place and so would require
|
|
# an overhaul to the system - right now, this is all reasons, apart from if there is an existing boiler
|
|
system_change = not has_boiler
|
|
exising_room_heaters = self.property.main_heating["clean_description"] in [
|
|
"Room heaters, electric", "Room heaters, mains gas"
|
|
]
|
|
|
|
self.recommend_boiler_upgrades(
|
|
phase=phase, system_change=system_change, exising_room_heaters=exising_room_heaters
|
|
)
|
|
|
|
# We recommend air source heat pumps
|
|
# Heat pumps are suitable for all property types:
|
|
# https://energysavingtrust.org.uk/from-flats-to-terraced-houses-heat-pumps-are-suitable-for-all-property-types/
|
|
# Just seems least probable for flats, so we'll allow houses and bungalows
|
|
# In the future, we'll allow overrides, so that non-intrusive surveys can contradict these conditions
|
|
# and either allow or prevent the recommendation of an air source heat pump
|
|
|
|
if self.is_ashp_valid():
|
|
self.recommend_air_source_heat_pump(
|
|
phase=phase, has_cavity_or_loft_recommendations=has_cavity_or_loft_recommendations
|
|
)
|
|
|
|
return
|
|
|
|
def recommend_electric_boiler_upgrade(self, phase):
|
|
|
|
# Small initial scope, just handles the case of properties that have electric boilers where the efficiency
|
|
# is poor or very poor
|
|
# We recommend upgrading to a new electric boiler
|
|
|
|
recommendation_phase = phase
|
|
|
|
if self.property.data["mainheat-energy-eff"] not in ["Poor", "Very Poor"]:
|
|
return
|
|
|
|
hotwater_from_mains = self.property.hotwater["clean_description"] in ["From main system"]
|
|
hotwater_from_cylinder = self.property.hotwater["clean_description"] in [
|
|
"From main system, no cylinder thermostat"
|
|
]
|
|
# if the hotwater is from the mains, we probably have a combi boiler so we recommend a new electric boiler
|
|
|
|
if hotwater_from_mains:
|
|
description = f"Upgrade to a higher efficiency electric boiler"
|
|
|
|
simulation_config = {
|
|
"mainheat_energy_eff_ending": "Average",
|
|
"hot_water_energy_eff_ending": "Average"
|
|
}
|
|
|
|
boiler_costs = self.costs.boiler(
|
|
size=None,
|
|
exising_room_heaters=False,
|
|
system_change=False,
|
|
n_heated_rooms=self.property.data["number-heated-rooms"],
|
|
n_rooms=self.property.number_of_rooms,
|
|
is_electric=True
|
|
)
|
|
|
|
already_installed = "heating" in self.property.already_installed
|
|
if already_installed:
|
|
boiler_costs = override_costs(boiler_costs)
|
|
description = "Heating system has already been upgraded, no further action needed."
|
|
|
|
boiler_recommendation = {
|
|
"phase": recommendation_phase,
|
|
"parts": [],
|
|
"type": "heating",
|
|
"description": description,
|
|
"starting_u_value": None,
|
|
"new_u_value": None,
|
|
"sap_points": None,
|
|
"already_installed": already_installed,
|
|
"simulation_config": simulation_config,
|
|
**boiler_costs
|
|
}
|
|
|
|
controls_recommender = HeatingControlRecommender(self.property)
|
|
controls_recommender.recommend(heating_description="Boiler and radiators, electric")
|
|
|
|
self.heating_recommendations.extend([boiler_recommendation] + controls_recommender.recommendation)
|
|
return
|
|
|
|
if hotwater_from_cylinder:
|
|
# We recommend a change from a system boiler, with a cylinder to a combi boiler
|
|
description = ("Replace the existing boiler and cylinder without a thermostat with a new electric combi "
|
|
"boiler")
|
|
|
|
def is_ashp_valid(self):
|
|
suitable_property_type = self.property.data["property-type"] in ["House", "Bungalow"]
|
|
has_air_source_heat_pump = self.property.main_heating["has_air_source_heat_pump"]
|
|
|
|
return suitable_property_type and not has_air_source_heat_pump
|
|
|
|
def recommend_air_source_heat_pump(self, phase, has_cavity_or_loft_recommendations, _return=False):
|
|
"""
|
|
This method will implement the recommendation for an air source heat pump
|
|
This is ultimately an overhaul to the heating system and so is recommended as an alternative to other
|
|
heating system recommendations
|
|
:return:
|
|
"""
|
|
|
|
controls_recommender = HeatingControlRecommender(self.property)
|
|
controls_recommender.recommend(heating_description="Air source heat pump, radiators, electric")
|
|
|
|
ashp_costs = self.costs.air_source_heat_pump()
|
|
# We add the costs of the heating controls, onto each key in the costs dictionary
|
|
if controls_recommender.recommendation:
|
|
for key in ashp_costs:
|
|
ashp_costs[key] += controls_recommender.recommendation[0][key]
|
|
|
|
already_installed = "air_source_heat_pump" in self.property.already_installed
|
|
if already_installed:
|
|
ashp_costs = override_costs(ashp_costs)
|
|
description = "The property already has an air source heat pump, no further action needed."
|
|
else:
|
|
if controls_recommender.recommendation:
|
|
description = ("Install an air source heat pump, and upgrade heating controls to Smart Thermostats, "
|
|
"room sensors and smart radiator valves (time & temperature zone control).")
|
|
else:
|
|
description = "Install an air source heat pump."
|
|
|
|
# If the property does not have existing cavity and loft insulation, we include a note that the cost
|
|
# includes the boiler upgrade scheme and that the cavity and loft need to be treated, to ensure access
|
|
# to the funding
|
|
if has_cavity_or_loft_recommendations:
|
|
description = description + (f" The cost includes the £"
|
|
f"{BOILER_UPGRADE_SCHEME_ASHP_VALUE} boiler upgrade scheme grant. "
|
|
f"You must ensure that the property has an insulated cavity and "
|
|
f"270mm+ loft insulation to qualify for the grant")
|
|
else:
|
|
description = description + (f" The cost includes the £"
|
|
f"{BOILER_UPGRADE_SCHEME_ASHP_VALUE} boiler upgrade scheme grant")
|
|
|
|
simulation_config = {
|
|
"mainheat_energy_eff_ending": "Good",
|
|
"hot_water_energy_eff_ending": "Good"
|
|
}
|
|
# 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()
|
|
|
|
# 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()
|
|
|
|
# Check the simulation differences
|
|
heating_simulation_config = check_simulation_difference(
|
|
new_config=heating_ending_config, old_config=self.property.main_heating
|
|
)
|
|
hotwater_simulation_config = check_simulation_difference(
|
|
new_config=hotwater_ending_config, old_config=self.property.hotwater
|
|
)
|
|
fuel_simulation_config = check_simulation_difference(
|
|
new_config=fuel_ending_config, old_config=self.property.main_fuel
|
|
)
|
|
|
|
simulation_config = {
|
|
**simulation_config,
|
|
**heating_simulation_config,
|
|
**hotwater_simulation_config,
|
|
**fuel_simulation_config,
|
|
}
|
|
|
|
if controls_recommender.recommendation:
|
|
# We should have just the single recommendation for heat controls, which is time
|
|
# and temperature zone controls
|
|
if len(controls_recommender.recommendation) != 1:
|
|
raise NotImplementedError("More than one heat controls recommendation for air source heat pump")
|
|
simulation_config = {
|
|
**simulation_config,
|
|
**controls_recommender.recommendation[0]["simulation_config"]
|
|
}
|
|
|
|
ashp_recommendation = {
|
|
"phase": phase,
|
|
"parts": [
|
|
# TODO
|
|
],
|
|
"type": "heating",
|
|
"description": description,
|
|
"starting_u_value": None,
|
|
"new_u_value": None,
|
|
"sap_points": None,
|
|
"already_installed": already_installed,
|
|
"simulation_config": simulation_config,
|
|
**ashp_costs
|
|
}
|
|
|
|
if _return:
|
|
return [ashp_recommendation]
|
|
self.heating_recommendations.append(ashp_recommendation)
|
|
|
|
@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
|
|
|
|
def combine_heating_and_controls(
|
|
self, 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"]
|
|
}
|
|
controls_description = controls_recommendations[0]['description']
|
|
# Make the first letter of the description lowercase
|
|
controls_description = (
|
|
controls_description[0].lower() + controls_description[1:]
|
|
)
|
|
|
|
recommendation_description = f"{description} and {controls_description}"
|
|
|
|
already_installed = "heating_controls" in self.property.already_installed
|
|
if already_installed:
|
|
total_costs = override_costs(total_costs)
|
|
recommendation_description = "Heating system has already been upgraded, no further action needed."
|
|
|
|
recommendation = {
|
|
"phase": phase,
|
|
"parts": [
|
|
# TODO
|
|
],
|
|
"type": "heating",
|
|
"description": recommendation_description,
|
|
"starting_u_value": None,
|
|
"new_u_value": None,
|
|
"sap_points": None,
|
|
"already_installed": already_installed,
|
|
**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 is_hhr_already_installed(self):
|
|
"""
|
|
Check if the property already has high heat retention storage heaters
|
|
:return:
|
|
"""
|
|
|
|
already_has_hhr = "Electric storage heaters" in self.property.main_heating["clean_description"]
|
|
|
|
# Some electric storage heaters will show that the controls are "Manual charge controls" which are indicative
|
|
# of the old model of electric storage heaters, originating from 1961.
|
|
# Newer HHR storage heaters will charge up over night but will retain the heat durin the day for when warmth
|
|
# is actually needed, unlike traditional storage heaters that charge up at night and release heat during the day
|
|
# which isn't always ideal for the occupants.
|
|
already_has_hhr_contols = (
|
|
self.property.main_heating_controls[
|
|
"clean_description"
|
|
].lower() == self.high_heat_retention_contols_desc.lower()
|
|
)
|
|
|
|
return already_has_hhr and already_has_hhr_contols
|
|
|
|
def recommend_hhr_storage_heaters(self, phase, system_change, heating_controls_only, _return=False):
|
|
"""
|
|
We will recommend upgrading to a high heat retention storage system, if the current system is not already
|
|
high heat retention storage
|
|
|
|
: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
|
|
:param _return: Indicates if we should return the recommendations, rather than appending them to the
|
|
recommendations list
|
|
: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"] != self.high_heat_retention_contols_desc:
|
|
controls_recommender.recommend(heating_description="Electric storage heaters, radiators")
|
|
|
|
has_hhr = self.is_hhr_already_installed()
|
|
# Conditions for not recommending electric storage heaters
|
|
if has_hhr:
|
|
# No recommendation needed
|
|
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"
|
|
|
|
# If the property is off-gas and has no heating system in place, the number of heated rooms will actually
|
|
# be 0, so we use the number of rooms as the figure
|
|
number_heated_rooms = (
|
|
self.property.data["number-heated-rooms"] if self.property.data["number-heated-rooms"] > 0
|
|
else (
|
|
self.property.number_of_rooms - 1 if self.property.number_of_rooms > 1 else
|
|
self.property.number_of_rooms
|
|
)
|
|
)
|
|
# Upgrade to electric storage heaters
|
|
costs = self.costs.high_heat_electric_storage_heaters(
|
|
number_heated_rooms=number_heated_rooms
|
|
)
|
|
description = "Install high heat retention 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
|
|
)
|
|
if _return:
|
|
return recommendations
|
|
|
|
self.heating_recommendations.extend(recommendations)
|
|
|
|
@staticmethod
|
|
def estimate_boiler_size(property_type, built_form, floor_area, floor_height, num_heated_rooms):
|
|
# Step 1: Base size estimation based on property type (as a starting point)
|
|
base_size = {
|
|
'Flat': 25,
|
|
'House': 30,
|
|
'Maisonette': 28,
|
|
'Bungalow': 27
|
|
}
|
|
|
|
# Step 2: Calculate the volume of the property
|
|
volume = floor_area * floor_height
|
|
|
|
# Step 3: Adjust base size for built form (to account for heat retention)
|
|
form_adjustment = {
|
|
'Mid-Terrace': 0,
|
|
'End-Terrace': 2,
|
|
'Semi-Detached': 4,
|
|
'Detached': 6
|
|
}
|
|
|
|
# Step 4: Further adjust for the total volume and number of heated rooms
|
|
volume_adjustment = (volume / 100) # Simplified adjustment factor for volume
|
|
rooms_adjustment = (num_heated_rooms - 5) * 0.5 # Assuming base case of 5 rooms
|
|
|
|
# Calculate the estimated boiler size
|
|
estimated_size = base_size[property_type] + form_adjustment[built_form] + volume_adjustment + rooms_adjustment
|
|
|
|
# Step 5: Align with available boiler sizes and ensure it does not exceed 35kW, as it's rare to need more
|
|
available_sizes = [30, 35, 40, 45, 50]
|
|
estimated_size = min(max(estimated_size, 30), 40) # Ensure within 30kW to 35kW range
|
|
|
|
# Find the closest available size (in this case, either rounding up or down to align with 30 or 35)
|
|
closest_size = min(available_sizes, key=lambda x: abs(x - estimated_size))
|
|
|
|
return closest_size
|
|
|
|
@staticmethod
|
|
def estimate_electric_boiler_size(num_heated_rooms):
|
|
"""
|
|
We use the approach similar to as defined in
|
|
https://www.greenmatch.co.uk/boilers/combi-boilers/electric-combi-boilers
|
|
Instead of radiators as a proxy, we do the number of heated rooms
|
|
|
|
:param num_heated_rooms: The number of heated rooms in the property
|
|
:return:
|
|
"""
|
|
|
|
return max(num_heated_rooms * 1.5, 6)
|
|
|
|
def recommend_boiler_upgrades(self, phase, system_change, exising_room_heaters):
|
|
"""
|
|
This boiler recommendation will only recommend a like-for-like upgrade, since changing the system
|
|
is generally more expensive
|
|
:param phase:
|
|
:param system_change: Indicates if the property would be undergoing a heating system change. This could be true
|
|
if the home didn't have a heating system in place, or if the home had electric heating
|
|
previously
|
|
:param exising_room_heaters: Indicates if the property had room heaters previously - if so, a boiler
|
|
recommendation will need to be accompanied by removal of the room heaters
|
|
:return:
|
|
"""
|
|
|
|
recommendation_phase = phase
|
|
|
|
# We now recommend boiler upgrades, if applicable
|
|
simulation_config = {}
|
|
boiler_costs = {}
|
|
boiler_recommendation = {}
|
|
|
|
has_inefficient_space_heating = self.property.data["mainheat-energy-eff"] in ["Very Poor", "Poor", "Average"]
|
|
|
|
has_inefficient_mains_water = (
|
|
self.property.hotwater["clean_description"] in ["From main system"] and
|
|
self.property.data["hot-water-energy-eff"] in ["Very Poor", "Poor", "Average"]
|
|
)
|
|
|
|
if has_inefficient_space_heating or has_inefficient_mains_water:
|
|
boiler_size = self.estimate_boiler_size(
|
|
property_type=self.property.data["property-type"],
|
|
built_form=self.property.data["built-form"],
|
|
floor_area=self.property.floor_area,
|
|
floor_height=self.property.floor_height,
|
|
num_heated_rooms=self.property.data["number-heated-rooms"],
|
|
)
|
|
|
|
description = "Upgrade to a new condensing boiler"
|
|
|
|
simulation_config = {
|
|
"mainheat_energy_eff_ending": "Good",
|
|
"hot_water_energy_eff_ending": "Good"
|
|
}
|
|
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()
|
|
|
|
heating_simulation_config = check_simulation_difference(
|
|
new_config=heating_ending_config, old_config=self.property.main_heating
|
|
)
|
|
hotwater_simulation_config = check_simulation_difference(
|
|
new_config=hotwater_ending_config, old_config=self.property.hotwater
|
|
)
|
|
fuel_simulation_config = check_simulation_difference(
|
|
new_config=fuel_ending_config, old_config=self.property.main_fuel
|
|
)
|
|
|
|
simulation_config = {
|
|
**simulation_config,
|
|
**heating_simulation_config,
|
|
**hotwater_simulation_config,
|
|
**fuel_simulation_config,
|
|
}
|
|
|
|
boiler_costs = self.costs.boiler(
|
|
size=f"{boiler_size}kw",
|
|
exising_room_heaters=exising_room_heaters,
|
|
system_change=system_change,
|
|
n_heated_rooms=self.property.data["number-heated-rooms"],
|
|
n_rooms=self.property.number_of_rooms
|
|
)
|
|
|
|
already_installed = "heating" in self.property.already_installed
|
|
if already_installed:
|
|
boiler_costs = override_costs(boiler_costs)
|
|
description = "Heating system has already been upgraded, no further action needed."
|
|
|
|
boiler_recommendation = {
|
|
"phase": recommendation_phase,
|
|
"parts": [
|
|
# TODO
|
|
],
|
|
"type": "heating",
|
|
"description": description,
|
|
"starting_u_value": None,
|
|
"new_u_value": None,
|
|
"sap_points": None,
|
|
"already_installed": already_installed,
|
|
"simulation_config": simulation_config,
|
|
**boiler_costs
|
|
}
|
|
|
|
# We recommend the heating controls
|
|
# If the property did not previously have a boiler, we combine
|
|
controls_recommender = HeatingControlRecommender(self.property)
|
|
controls_recommender.recommend(heating_description="Boiler and radiators, mains gas")
|
|
# We may have 2 recommendations from the heating controls
|
|
|
|
if not controls_recommender.recommendation and not boiler_recommendation:
|
|
return
|
|
|
|
if not system_change and len(boiler_recommendation):
|
|
# If there is not a system change, we add the boiler recommendation at point.
|
|
self.heating_recommendations.extend([boiler_recommendation])
|
|
|
|
if system_change:
|
|
# We combine the heating and controls recommendations, in the case of a system change
|
|
combined_recommendations = []
|
|
for controls_recommendation in controls_recommender.recommendation:
|
|
combined_recommendation = self.combine_heating_and_controls(
|
|
controls_recommendations=[controls_recommendation],
|
|
heating_simulation_config=simulation_config,
|
|
costs=boiler_costs,
|
|
description=boiler_recommendation["description"],
|
|
phase=recommendation_phase,
|
|
heating_controls_only=False,
|
|
system_change=True
|
|
)
|
|
combined_recommendations.extend(combined_recommendation)
|
|
|
|
# Overwrite the existing boiler recommendation
|
|
self.heating_recommendations.extend(combined_recommendations)
|
|
else:
|
|
# We increment the recommendation phase, since the heating controls are separate from the boiler upgrade
|
|
# but we'll only upgrade if we have a heating recommendation
|
|
has_heating_recommendation = any(
|
|
rec["type"] == "heating" for rec in self.heating_recommendations
|
|
)
|
|
if has_heating_recommendation:
|
|
recommendation_phase += 1
|
|
# The heating controls recommendation is distrinct from the boiler upgrade recommendation
|
|
# We insert phase into the recommendations for heating controls
|
|
for recommendation in controls_recommender.recommendation:
|
|
recommendation["phase"] = recommendation_phase
|
|
|
|
self.heating_control_recommendations.extend(controls_recommender.recommendation)
|
|
|
|
return
|