From 8a10cec739972ef4809ce49b19023ded656067de Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 22 Jun 2023 17:51:23 +0100 Subject: [PATCH] Refactored the WallRecommendations code --- model_data/app.py | 63 ++++---- .../recommendations/WallRecommendations.py | 140 ++++++++++-------- 2 files changed, 113 insertions(+), 90 deletions(-) diff --git a/model_data/app.py b/model_data/app.py index 9cef7e1e..90dc158a 100644 --- a/model_data/app.py +++ b/model_data/app.py @@ -126,35 +126,9 @@ def handler(): input_properties[6].data["postcode"] walls_df["address1"].values[6] walls_df["original_description"].values[6] - # Walls - # Property 0 - # '28 Distillery Wharf', 'Average thermal transmittance 0.16 W/m-¦K' - # Because the insulation is already within the max threshold for new builds - # Also, I know that the property was built after 1990 so was built with insulation (let's get land registry data) - # It's likely not worth doing an insulation upgrade, however if anything, we would do internal wall insulation - # logic: - # if building built after 1990 + we're able to identify U-value + U-value less than 0.18 - # and if in or close to a conversation area, recommend internal wall insulation - # Property 1 - # 'Flat 14 Godley V C House', Solid brick, as built, no insulation (assumed) - # Since the wall is solid brick (therefore no cavity), we can recommend the following: - # External wall insulation - # Internal wall insulation - # Property 2 - # '49, Elderfield Road', Solid brick, as built, no insulation (assumed) - # Same as property 1 - # Property 3 - # 26, Stanhope Road', 'Average thermal transmittance 0.14 W/m-¦K' - # Same as property 0 - # Property 4 - # 'Flat 3 Frederick Building' 'Solid brick, as built, no insulation (assumed)' - # Same as property 1 - # 'Flat 4 Frederick Building' 'Solid brick, as built, no insulation (assumed)' - # Same as property 1 - # 'Flat 28, 22 Adelina Grove' 'Solid brick, as built, insulated (assumed)' from model_data.recommendations.WallRecommendations import WallRecommendations - self = WallRecommendations(property_instance=input_properties[6], uvalue_estimates=uvalue_estimates) + self = WallRecommendations(property_instance=input_properties[7], uvalue_estimates=uvalue_estimates) # We need to deduce a U-value for "Good" energy effieciency @@ -171,3 +145,38 @@ def handler(): p = input_properties[6] df = pd.DataFrame(data) + + res = [] + for p in input_properties: + distances = [] + for borehole in tqdm(borehole_client.data, total=len(borehole_client.data)): + dist_meeters, _ = borehole_client.distance_between_bng_coords( + x1_bng=p.coordinates['x_coordinate'], + y1_bng=p.coordinates['y_coordinate'], + x2_bng=float(borehole['EASTING']), + y2_bng=float(borehole['NORTHING']) + ) + distances.append(dist_meeters) + + res.append( + { + "uprn": int(p.data["uprn"]), + "meters_to_nearest_borehole": min(distances) + } + + ) + res = pd.DataFrame(res) + + properties_dataset = [ + { + **p.data, + "in_conservation_area": p.in_conservation_area, + **p.coordinates, + + } for p in input_properties + ] + + properties_dataset = pd.DataFrame(properties_dataset) + properties_dataset = properties_dataset.merge(res, on="uprn", how="left") + + properties_dataset.to_csv("properties_dataset.csv") diff --git a/model_data/recommendations/WallRecommendations.py b/model_data/recommendations/WallRecommendations.py index 96fcf73b..f92421ea 100644 --- a/model_data/recommendations/WallRecommendations.py +++ b/model_data/recommendations/WallRecommendations.py @@ -5,6 +5,7 @@ import itertools from model_data.Property import Property from model_data.ConservationAreaClient import ConservationAreaClient from model_data.analysis.UvalueEstimations import UvalueEstimations +from model_data.BaseUtility import BaseUtility import pandas as pd from copy import deepcopy @@ -184,7 +185,7 @@ internal_wall_insulation_parts = [ wall_parts = external_wall_insulation_parts + internal_wall_insulation_parts -class WallRecommendations: +class WallRecommendations(BaseUtility): YEAR_WALLS_BUILT_WITH_INSULATION = 1990 U_VALUE_UNIT = 'w/m-¦k' BUILDING_REGULATIONS_PART_L_MAX_U_VALUE = 0.18 @@ -207,6 +208,19 @@ class WallRecommendations: # Will contains a list of recommended measures self.recommendations = [] + @property + def ewi_valid(self): + """ + This method check available data, to determine if a property is suitable for external wall insulation + """ + + # Current logic: If the property is in a conservation area or a flat, it is not suitable for EWI + if (self.property.in_conservation_area in ["in_conversation_area"]) or \ + (self.property.data["property-type"].lower() == "flat"): + return False + + return True + def _year_property_was_built(self): """ Estimates when the property was built based on as much available data as possible. @@ -216,13 +230,15 @@ class WallRecommendations: if self.property.full_sap_epc: return pd.to_datetime(self.property.full_sap_epc["lodgement-date"]).year - if self.property.data["construction-age-band"]: + if self.property.data["construction-age-band"] not in self.DATA_ANOMALY_MATCHES: # Take the lower limit. If we're pessimistic about the age of the property, that at least means we have # more options for recommendations if that age falls before the year that insulation in walls became # common practice band = [int(x) for x in re.findall(r'\b\d{4}\b', self.property.data["construction-age-band"])] return band[0] + # + raise NotImplementedError("Implement me!") def recommend(self): @@ -243,20 +259,8 @@ class WallRecommendations: if (not is_cavity_wall) and (self.year_built >= self.YEAR_WALLS_BUILT_WITH_INSULATION) and ( u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE ): - # Recommend internal wall insulation - iwi_parts = [part for part in wall_parts if part["type"] == "internal_wall_insulation"] - for part in iwi_parts: - _, new_u_value = self.calculate_u_value_uplift(u_value, part["u_value"]) - new_u_value = round(new_u_value, 2) - - # 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, - } - ) + # Recommend insulation + self.find_insulation(u_value) if is_solid_brick and insulation_thickness == "none": @@ -266,62 +270,72 @@ class WallRecommendations: else: u_value = self._get_walls_uvalue_estimate() - ewi_parts = [ - part for part in wall_parts if part["type"] == "external_wall_insulation" - ] if self.property.in_conservation_area == ConservationAreaClient.IN_CONSERVATION_AREA else [] + if u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: + self.find_insulation(u_value) - iwi_parts = [part for part in wall_parts if part["type"] == "internal_wall_insulation"] + raise NotImplementedError("Not implemented yet") - # Recommend external and internal wall insulation separately - for part in ewi_parts + iwi_parts: + def find_insulation(self, u_value): + """ + This function contains the logic for finding potential insulation measures for a property, depending + on the parts available and whether the property can have external wall insulation installed + :return: + """ - for depth in part["depths"]: - part_u_value = self.r_value_per_mm_to_u_value(depth, part["r_value_per_mm"]) + ewi_parts = [ + part for part in wall_parts if part["type"] == "external_wall_insulation" + ] if self.ewi_valid else [] - _, new_u_value = self.calculate_u_value_uplift(u_value, part_u_value) - new_u_value = round(new_u_value, 2) + iwi_parts = [part for part in wall_parts if part["type"] == "internal_wall_insulation"] - if new_u_value < self.DIMINISHING_RETURNS_U_VALUE: + # 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"]) + + _, 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( + self._get_recommended_part(part, depth, new_u_value) + ) + + # We also can recommend both internal and external wall insulation together + # By looping through ewi first, if there is nothing there, that ensures not combinations are tested + 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 - # 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( - self._get_recommended_part(part, depth, new_u_value) - ) + # 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 - # We also can recommend both internal and external wall insulation together - # By looping through ewi first, if there is nothing there, that ensures not combinations are tested - 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") + 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) def _get_walls_uvalue_estimate(self):