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): """