From ee91c8244b489dc628d042113b55201c0ac7e874 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 21 Jun 2023 12:13:35 +0100 Subject: [PATCH] Added in logic to allow both external and internal wall insulation as options. Currently lots of recommendation combinations can come through however we will trim them later and make them more relevant to the customer --- .../recommendations/WallRecommendations.py | 74 ++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/model_data/recommendations/WallRecommendations.py b/model_data/recommendations/WallRecommendations.py index b4a215fd..d8f7b27a 100644 --- a/model_data/recommendations/WallRecommendations.py +++ b/model_data/recommendations/WallRecommendations.py @@ -1,8 +1,10 @@ import pint import re +import itertools from model_data.Property import Property import pandas as pd +from copy import deepcopy external_wall_insulation_parts = [ { @@ -184,6 +186,8 @@ class WallRecommendations: YEAR_WALLS_BUILT_WITH_INSULATION = 1990 U_VALUE_UNIT = 'w/m-¦k' BUILDING_REGULATIONS_PART_L_MAX_U_VALUE = 0.18 + # Often cited minimum practical u-value + DIMINISHING_RETURNS_U_VALUE = 0.15 # Add some error so that if, for example, a new part we recommend provides a u-value of 0.19, # we still consider it as an option @@ -229,6 +233,7 @@ class WallRecommendations: is_cavity_wall = self.property.walls["is_cavity_wall"] is_solid_brick = self.property.walls["is_solid_brick"] + insulation_thickness = self.property.walls["insulation_thickness"] if u_value: if self.property.walls["thermal_transmittance_unit"] != self.U_VALUE_UNIT: @@ -252,17 +257,22 @@ class WallRecommendations: } ) - if is_solid_brick: + if is_solid_brick and insulation_thickness == "none": + + # TODO: what if we recommend both internal and external wall insulation? Individually, they might not + # get the wall to the required u-value, but together they might. We need to handle this case # This is an estimated figure based on industry standards u_value = self.DEFAULT_U_VALUES["solid_brick"] - # Recommend external and internal wall insulation - part_types = ["external_wall_insulation", "internal_wall_insulation"] if not self.in_converation_area else \ - ["internal_wall_insulation"] + ewi_parts = [ + part for part in wall_parts if part["type"] == "external_wall_insulation" + ] if not self.in_converation_area else [] - parts = [part for part in wall_parts if part["type"] in part_types] - for part in parts: + iwi_parts = [part for part in wall_parts if part["type"] == "internal_wall_insulation"] + + # Recommend external and internal wall insulation separately + for part in ewi_parts + iwi_parts: for depth in part["depths"]: part_u_value = self.r_value_per_mm_to_u_value(depth, part["r_value_per_mm"]) @@ -270,17 +280,63 @@ class WallRecommendations: _, new_u_value = self.calculate_u_value_uplift(u_value, part_u_value) new_u_value = round(new_u_value, 2) + if new_u_value < self.DIMINISHING_RETURNS_U_VALUE: + # We don't recommend an overkill solution + continue + # We allow a small tolerance for error so we don't discount the recommendation entirely # if it's close, since this is an estimated new u-value if new_u_value - self.U_VALUE_ERROR <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: self.recommendations.append( - { - **part, "new_u_value": new_u_value, - } + self._get_recommended_part(part, depth, new_u_value) ) + # We also can recommend both internal and external wall insulation together + for ewi_part in ewi_parts: + for iwi_part in iwi_parts: + for ewi_depth, iwi_depth in itertools.product(ewi_part["depths"], iwi_part["depths"]): + ewi_part_u_value = self.r_value_per_mm_to_u_value(ewi_depth, ewi_part["r_value_per_mm"]) + iwi_part_u_value = self.r_value_per_mm_to_u_value(iwi_depth, iwi_part["r_value_per_mm"]) + + # First calculate the new U-value after applying external wall insulation + _, ewi_new_u_value = self.calculate_u_value_uplift(u_value, ewi_part_u_value) + # Then calculate the new U-value after applying internal wall insulation + _, combined_new_u_value = self.calculate_u_value_uplift(ewi_new_u_value, iwi_part_u_value) + combined_new_u_value = round(combined_new_u_value, 2) + + if combined_new_u_value < self.DIMINISHING_RETURNS_U_VALUE: + # We don't recommend an overkill solution + continue + + # Check if the combined new U-value meets the requirement + if combined_new_u_value - self.U_VALUE_ERROR <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: + # Here you might want to define a way to add both recommendations together. + # For now, I'm adding them as separate items in the list + + recommendation = [ + self._get_recommended_part(ewi_part, ewi_depth, combined_new_u_value), + self._get_recommended_part(iwi_part, iwi_depth, combined_new_u_value) + ] + self.recommendations.append(recommendation) + raise NotImplementedError("Not implemented yet") + @staticmethod + def _get_recommended_part(part, selected_depth, new_u_value): + """ + Utility function to return a recommended part with the selected depth. + :param part: + :param selected_depth: + :param new_u_value: + :return: + """ + recommended_part = deepcopy(part) + recommended_part["depths"] = [selected_depth] + + return { + **recommended_part, "new_u_value": new_u_value, + } + @staticmethod def calculate_u_value_uplift(u_value, insulation_u_value): """