From 3ecd7a974276bb6f4296124c6acf7e55f280e574 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 11 Apr 2024 19:14:49 +0100 Subject: [PATCH] added simulation for secondary heating --- backend/Property.py | 6 ++- recommendations/Costs.py | 45 ++++++++++++++++------ recommendations/HeatingRecommender.py | 2 +- recommendations/Recommendations.py | 8 ++++ recommendations/SecondaryHeating.py | 55 +++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 recommendations/SecondaryHeating.py diff --git a/backend/Property.py b/backend/Property.py index 950c1ac9..0f5e7e77 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -456,7 +456,9 @@ class Property: "double glazing installed during or after 2002" ) - if recommendation["type"] in ["heating", "hot_water_tank_insulation", "heating_control"]: + if recommendation["type"] in [ + "heating", "hot_water_tank_insulation", "heating_control", "secondary_heating" + ]: # We update the data, as defined in the recommendaton simulation_config = recommendation["simulation_config"] @@ -477,7 +479,7 @@ class Property: "loft_insulation", "room_roof_insulation", "flat_roof_insulation", "solid_floor_insulation", "suspended_floor_insulation", "exposed_floor_insulation", "windows_glazing", "solar_pv", "heating", "hot_water_tank_insulation", - "heating_control", + "heating_control", "secondary_heating" ]: raise NotImplementedError( "Implement me, given type %s" % recommendation["type"] diff --git a/recommendations/Costs.py b/recommendations/Costs.py index f4ac259b..45c17102 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -1104,6 +1104,28 @@ class Costs: "labour_days": labour_days, } + def heater_removal(self, n_rooms): + """ + Estimates the costs of removal of heaters, including the redecoration costs of the space behind the heater + :return: + """ + + removal_cost = ROOM_HEATER_REMOVAL_COST * n_rooms + removal_labour_hours = ROOM_HEATER_REMOVAL_LABOUR_HOURS * n_rooms + + vat = removal_cost * self.VAT_RATE + + subtotal_before_vat = removal_cost + total_cost = subtotal_before_vat + vat + + return { + "total": total_cost, + "subtotal": subtotal_before_vat, + "vat": vat, + "labour_hours": removal_labour_hours, + "labour_days": np.ceil(removal_labour_hours / 8), + } + def boiler(self, is_combi, size, exising_room_heaters, n_heated_rooms): """ Based on a basic estimate of median value £2600 to install a low carbon combi boiler @@ -1114,6 +1136,7 @@ class Costs: # The unit cost is the cost without VAT # We now need to estimate the cost of the works labour_days = 2 + labour_hours = labour_days * 8 labour_rate = 500 # Average cost of installation is 1 (maybe 2days) at £300 per day @@ -1123,26 +1146,26 @@ class Costs: # Add contingency and preliminaries labour_cost = labour_cost * (1 + self.CONTINGENCY + self.PRELIMINARIES) - # if there are existing room heaters, we need to add the cost of removing them - if exising_room_heaters: - removal_cost = ROOM_HEATER_REMOVAL_COST * n_heated_rooms - removal_labour_hours = ROOM_HEATER_REMOVAL_LABOUR_HOURS * n_heated_rooms - else: - removal_cost = 0 - removal_labour_hours = 0 - - labour_cost = labour_cost + removal_cost - labour_days = labour_days + (removal_labour_hours / 8) + # labour_days = labour_days + (removal_labour_hours / 8) vat = labour_cost * self.VAT_RATE subtotal_before_vat = unit_cost + labour_cost total_cost = subtotal_before_vat + vat + # if there are existing room heaters, we need to add the cost of removing them + if exising_room_heaters: + removal_costing = self.heater_removal(n_rooms=n_heated_rooms) + # Add the totals to the existing totals + total_cost += removal_costing["total"] + subtotal_before_vat += removal_costing["subtotal"] + labour_hours += removal_costing["labour_hours"] + labour_days += removal_costing["labour_days"] + return { "total": total_cost, "subtotal": subtotal_before_vat, "vat": vat, - "labour_hours": labour_days * 8, + "labour_hours": labour_hours, "labour_days": labour_days, } diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py index 14509eea..92457a27 100644 --- a/recommendations/HeatingRecommender.py +++ b/recommendations/HeatingRecommender.py @@ -319,7 +319,7 @@ class HeatingRecommender: # Otherwise, we recommend a gas condensing boiler, which will server a larger property, that has multiple # bathrooms is_combi = ( - (self.property.data["number-heated-rooms"] <= 4) and + (self.property.number_of_rooms <= 4) and (self.property.n_bathrooms in [None, 0, 1]) ) if is_combi: diff --git a/recommendations/Recommendations.py b/recommendations/Recommendations.py index 902023dc..68fead16 100644 --- a/recommendations/Recommendations.py +++ b/recommendations/Recommendations.py @@ -11,6 +11,7 @@ from recommendations.SolarPvRecommendations import SolarPvRecommendations from recommendations.WindowsRecommendations import WindowsRecommendations from recommendations.HeatingRecommender import HeatingRecommender from recommendations.HotwaterRecommendations import HotwaterRecommendations +from recommendations.SecondaryHeating import SecondaryHeating from backend.ml_models.AnnualBillSavings import AnnualBillSavings @@ -46,6 +47,7 @@ class Recommendations: self.solar_recommender = SolarPvRecommendations(property_instance=property_instance) self.heating_recommender = HeatingRecommender(property_instance=property_instance) self.hotwater_recommender = HotwaterRecommendations(property_instance=property_instance) + self.secondary_heating_recommender = SecondaryHeating(property_instance=property_instance) def recommend(self): @@ -130,6 +132,12 @@ class Recommendations: property_recommendations.append(self.lighting_recommender.recommendation) phase += 1 + if "secondary_heating" not in self.exclusions: + self.secondary_heating_recommender.recommend(phase=phase) + if self.secondary_heating_recommender.recommendation: + property_recommendations.append(self.secondary_heating_recommender.recommendation) + phase += 1 + # Renewables if "solar_pv" not in self.exclusions: self.solar_recommender.recommend(phase=phase) diff --git a/recommendations/SecondaryHeating.py b/recommendations/SecondaryHeating.py new file mode 100644 index 00000000..f31c4c05 --- /dev/null +++ b/recommendations/SecondaryHeating.py @@ -0,0 +1,55 @@ +from recommendations.Costs import Costs +from backend.Property import Property + + +class SecondaryHeating: + """ + This class recommends the removal of the secondary heating system for properties that have a primary heating + system. + """ + + # The list of existing heating systems that are accepted + ACCEPTED_MAINHEAT_DESCRIPTIONS = ["Boiler and radiators, mains gas"] + ACCEPTED_SECONDHEAT_DESCRIPTIONS = ["Room heaters, electric"] + # These are the heaters where works are required to remove them + FIXED_HEATER_DESCRIPTIONS = ["Room heaters, electric"] + + def __init__(self, property_instance: Property): + self.property = property_instance + self.costs = Costs(self.property) + + self.recommendation = [] + + def recommend(self, phase: int): + # Reset + self.recommendation = [] + + if self.property.main_heating["clean_description"] not in self.ACCEPTED_MAINHEAT_DESCRIPTIONS: + return + + # TODO: We need to clean secondary data + if self.property.data['secondheat-description'] not in self.ACCEPTED_SECONDHEAT_DESCRIPTIONS: + return + + if self.property.data['secondheat-description'] in self.FIXED_HEATER_DESCRIPTIONS: + # We have an associated cost otherwise, there is no cost + n_rooms = self.property.data['number-heated-rooms'] + else: + n_rooms = 0 + + costs = self.costs.heater_removal(n_rooms=n_rooms) + self.recommendation.append( + { + "phase": phase, + "parts": [], + "type": "secondary_heating", + "description": "Remove the secondary heating system", + "starting_u_value": None, + "new_u_value": None, + "sap_points": None, + **costs, + "simulation_config": { + "secondheat_description_ending": "None" + } + } + )