From 13ceb4031d7125aaa7a0039638308276d3edab0f Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 20 Oct 2023 18:51:49 +1100 Subject: [PATCH] implementing loft insulation wip --- recommendations/RoofRecommendations.py | 134 ++++++++++++++++++++++++ recommendations/WallRecommendations.py | 49 +++++---- recommendations/recommendation_utils.py | 17 +++ 3 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 recommendations/RoofRecommendations.py diff --git a/recommendations/RoofRecommendations.py b/recommendations/RoofRecommendations.py new file mode 100644 index 00000000..13cf958e --- /dev/null +++ b/recommendations/RoofRecommendations.py @@ -0,0 +1,134 @@ +import math +from backend import Property +from typing import List +from datatypes.enums import QuantityUnits +from recommendations.recommendation_utils import ( + get_roof_u_value, r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns, + update_lowest_selected_u_value, get_recommended_part +) + + +class RoofRecommendations: + # part L building regulations indicate that any rennovations on an existing property's roof should + # achieve a U-value of no higher than 0.16 + # This can be seen in table 4.3 in building regulations part L: + # https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1133079 + # /Approved_Document_L__Conservation_of_fuel_and_power__Volume_1_Dwellings__2021_edition_incorporating_2023_amendments.pdf + BUILDING_REGULATIONS_PART_L_MAX_U_VALUE = 0.16 + + DIMINISHING_RETURNS_U_VALUE = 0.14 + + def __init__( + self, + property_instance: Property, + materials: List + ): + self.property = property_instance + # For audit purposes, when estimating u values we'll store it + self.estimated_u_value = None + + # Will contains a list of recommended measures + self.recommendations = [] + + self.materials = materials + + def recommend(self): + u_value = self.property.roof["thermal_transmittance"] + + insulation_thickness = self.property.walls["insulation_thickness"] + + # We check if the roof is already insulated and if so, we exit + if insulation_thickness in ["average", "above average"]: + return + + # If we have a u-value already, need to implement this + if u_value: + raise NotImplementedError("Implement me") + + u_value = get_roof_u_value(**{**self.property.roof, "age_band": self.property.age_band}) + + # With loft insulation, 100mm goes between the joists and the rest is rolled on top + # Therefore the price is 100mm + whatever thickness is rolled on top, rolled at a 90 degree angle + # from the base layer + materials = [ + { + 'id': 4, + 'type': 'loft_insulation', + 'description': 'Iso Spacesaver Mineral Wool insulation', + 'depths': [270, 300], + 'depth_unit': 'mm', + 'cost': [9, 10], + 'cost_unit': 'gbp_sq_meter', + 'r_value_per_mm': 0.022727272727272728, + 'r_value_unit': 'square_meter_kelvin_per_watt', + 'thermal_conductivity': 0.044, + 'thermal_conductivity_unit': 'watt_per_meter_kelvin', + 'link': "https://flooringwarehousedirect.co.uk/product/isover-spacesaver-roll-100mm-x-1160mm-x-12-18m" + "-14-13m2/", + 'is_active': True + }, + ] + + self.materials = materials + + if self.property.roof["is_pitched"]: + # We recommend loft insulation + self.recommend_loft_insulation(u_value) + return + + def recommend_loft_insulation(self, u_value): + + """ + This method will recommend which insulation materials to use + :return: + """ + + loft_insulation_materials = [m for m in self.materials if m["type"] == "loft_insulation"] + + lowest_selected_u_value = None + recommendations = [] + for material in loft_insulation_materials: + + for depth, cost_per_unit in zip(material["depths"], material["cost"]): + + part_u_value = r_value_per_mm_to_u_value(depth, material["r_value_per_mm"]) + + _, new_u_value = calculate_u_value_uplift(u_value, part_u_value) + new_u_value = math.ceil(new_u_value * 100.0) / 100.0 + + # If I have a lowest U value and my new u value is higher than that but lower than the + # diminishing returns threshold, it can be considered + + # If I have a lowest U value and my new u value is lower than the lowest value, it's + # further into the diminishing returns threshold and can shouldn't be + + if is_diminishing_returns( + recommendations, new_u_value, lowest_selected_u_value, self.DIMINISHING_RETURNS_U_VALUE + ): + continue + + # We allow a small tolerance for error so we don't discount the recommendation entirely + if new_u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: + lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value) + + estimated_cost = cost_per_unit * self.property.insulation_wall_area + + recommendations.append( + { + "parts": [ + get_recommended_part( + part=material, + selected_depth=depth, + quantity=self.property.insulation_wall_area, + quantity_unit=QuantityUnits.m2.value, + selected_total_cost=estimated_cost + ) + ], + "type": "roof_insulation", + "description": "TODO ", + "starting_u_value": u_value, + "new_u_value": new_u_value, + "sap_points": None, + "cost": estimated_cost, + } + ) diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py index ad2ca861..12085840 100644 --- a/recommendations/WallRecommendations.py +++ b/recommendations/WallRecommendations.py @@ -23,11 +23,17 @@ class WallRecommendations(Definitions): # part L building regulations indicate that any rennovations on an existing property's walls should # achieve a U-value of no higher than 0.3 + # This can be seen in table 4.3 in building regulations part L: + # https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1133079 + # /Approved_Document_L__Conservation_of_fuel_and_power__Volume_1_Dwellings__2021_edition_incorporating_2023_amendments.pdf BUILDING_REGULATIONS_PART_L_MAX_U_VALUE = 0.3 # We don't recommend measures that are too low because it becomes expensive, therefore we aim to avoid # diminishing returns. This value should be verified with Osmosis (TODO) DIMINISHING_RETURNS_U_VALUE = 0.25 + # Building regulations part L also indicates that cavity wall insulation should result in 0.55 u-value + BUILDING_REGULATIONS_PART_L_CAVITY_WALL_MAX_U_VALUE = 0.55 + # Part L regulations indicate that any new build should have walls that achieve a u-value of no higher # than 0.18. BUILDING_REGULATIONS_PART_L_NEW_BUILD_MAX_U_VALUE = 0.18 @@ -167,29 +173,30 @@ class WallRecommendations(Definitions): ): continue - lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value) + if new_u_value <= self.BUILDING_REGULATIONS_PART_L_CAVITY_WALL_MAX_U_VALUE: + lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value) - estimated_cost = part["cost"] * self.property.insulation_wall_area + estimated_cost = part["cost"] * self.property.insulation_wall_area - recommendations.append( - { - "parts": [ - get_recommended_part( - part=part, - selected_depth=None, - quantity=self.property.insulation_wall_area, - quantity_unit=QuantityUnits.m2.value, - selected_total_cost=estimated_cost - ) - ], - "type": "wall_insulation", - "description": f"Fill cavity with {part['description']}", - "starting_u_value": u_value, - "new_u_value": new_u_value, - "sap_points": None, - "cost": estimated_cost, - } - ) + recommendations.append( + { + "parts": [ + get_recommended_part( + part=part, + selected_depth=None, + quantity=self.property.insulation_wall_area, + quantity_unit=QuantityUnits.m2.value, + selected_total_cost=estimated_cost + ) + ], + "type": "wall_insulation", + "description": f"Fill cavity with {part['description']}", + "starting_u_value": u_value, + "new_u_value": new_u_value, + "sap_points": None, + "cost": estimated_cost, + } + ) self.recommendations = recommendations diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py index cd7bb3f8..4f0813ab 100644 --- a/recommendations/recommendation_utils.py +++ b/recommendations/recommendation_utils.py @@ -517,3 +517,20 @@ def estimate_wall_area(num_floors, floor_height, perimeter): total_wall_area = wall_area_one_floor * num_floors return total_wall_area + + +def calculate_r_value_per_mm(thickness_mm, thermal_conductivity_w_mK): + """ + # Calculate R-value (thermal resistance) using the formula: R = thickness / thermal_conductivity + # Note: The thickness should be converted to meters for the units to be consistent. + :param thickness_mm: + :param thermal_conductivity_w_mK: + :return: + """ + + r_value_m2k_w = (thickness_mm / 1000) / thermal_conductivity_w_mK + + # Calculate R-value per mm + r_value_per_mm = r_value_m2k_w / thickness_mm + + return r_value_per_mm