diff --git a/etl/epc/Dataset.py b/etl/epc/Dataset.py index 36abd4ef..ee3e357c 100644 --- a/etl/epc/Dataset.py +++ b/etl/epc/Dataset.py @@ -229,7 +229,9 @@ class TrainingDataset(BaseDataset): """ # TODO: move into EPCRecord record uvalue_columns = [ - col for col in self.df.columns if "thermal_transmittance" in col + col + for col in self.df.columns + if "thermal_transmittance" in col and "_unit" not in col ] for uvalue_col in uvalue_columns: self.df[uvalue_col] = pd.to_numeric(self.df[uvalue_col]) @@ -703,6 +705,8 @@ class TrainingDataset(BaseDataset): "insulation_thickness_ending": f"{component}_insulation_thickness_ending", "thermal_transmittance": f"{component}_thermal_transmittance", "thermal_transmittance_ending": f"{component}_thermal_transmittance_ending", + "thermal_transmittance_unit": f"{component}_thermal_transmittance_unit", + "thermal_transmittance_unit_ending": f"{component}_thermal_transmittance_unit_ending", "tariff_type": f"{component}_tariff_type", "tariff_type_ending": f"{component}_tariff_type_ending", "clean_description": f"{component}_clean_description", diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py index 20fc453c..8ca34bc8 100644 --- a/recommendations/WallRecommendations.py +++ b/recommendations/WallRecommendations.py @@ -7,8 +7,13 @@ from datatypes.enums import QuantityUnits from backend.Property import Property from BaseUtility import Definitions from recommendations.recommendation_utils import ( - r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns, update_lowest_selected_u_value, - get_recommended_part, get_wall_u_value, override_costs + r_value_per_mm_to_u_value, + calculate_u_value_uplift, + is_diminishing_returns, + update_lowest_selected_u_value, + get_recommended_part, + get_wall_u_value, + override_costs, ) from recommendations.config import PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION from recommendations.Costs import Costs @@ -22,7 +27,7 @@ class WallRecommendations(Definitions): # After 1930, Solid brick walls became less populate and instead, cavity walls became a # more popular choice YEARS_CAVITY_WALLS_BEGAN = 1930 - U_VALUE_UNIT = 'w/m-¦k' + U_VALUE_UNIT = "w/m-¦k" # 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 @@ -53,11 +58,7 @@ class WallRecommendations(Definitions): # threshold NEW_BUILD_INSULATED = 0.75 - def __init__( - self, - property_instance: Property, - materials: List - ): + def __init__(self, property_instance: Property, materials: List): self.property = property_instance self.costs = Costs(self.property) # For audit purposes, when estimating u values we'll store it @@ -75,9 +76,10 @@ class WallRecommendations(Definitions): ] self.internal_wall_non_insulation_materials = [ - part for part in materials if part["type"] in [ - "iwi_wall_demolition", "iwi_vapour_barrier", "iwi_redecoration" - ] + part + for part in materials + if part["type"] + in ["iwi_wall_demolition", "iwi_vapour_barrier", "iwi_redecoration"] ] self.external_wall_insulation_materials = [ @@ -85,9 +87,10 @@ class WallRecommendations(Definitions): ] self.external_wall_non_insulation_materials = [ - part for part in materials if part["type"] in [ - "ewi_wall_demolition", "ewi_wall_preparation", "ewi_wall_redecoration" - ] + part + for part in materials + if part["type"] + in ["ewi_wall_demolition", "ewi_wall_preparation", "ewi_wall_redecoration"] ] @property @@ -98,7 +101,9 @@ class WallRecommendations(Definitions): # Current logic: If the property is in a conservation area/heritage building/listed building or a flat, # it is not suitable for EWI - if self.property.restricted_measures or (self.property.data["property-type"].lower() == "flat"): + if self.property.restricted_measures or ( + self.property.data["property-type"].lower() == "flat" + ): return False return True @@ -109,31 +114,43 @@ class WallRecommendations(Definitions): # recommend internal wall insulation as a possible measure u_value = self.property.walls["thermal_transmittance"] + u_value = None if math.isnan(u_value) else u_value + is_cavity_wall = self.property.walls["is_cavity_wall"] insulation_thickness = self.property.walls["insulation_thickness"] # We check if the wall is already insulated and if so, we exit - if ((insulation_thickness in ["average", "above average"]) or self.property.walls["is_filled_cavity"]) and ( - "cavity_extract_and_refill" not in self.property.non_invasive_recommendations + if ( + (insulation_thickness in ["average", "above average"]) + or self.property.walls["is_filled_cavity"] + ) and ( + "cavity_extract_and_refill" + not in self.property.non_invasive_recommendations ): return if u_value: if self.property.walls["thermal_transmittance_unit"] != self.U_VALUE_UNIT: - raise NotImplementedError("Haven't handled the case of other u value units yet") + raise NotImplementedError( + "Haven't handled the case of other u value units yet" + ) # If the property is a new build and the U-value is below 0.75, we don't recommend insulation because it's # not practical - if (self.property.data["transaction-type"] == "new dwelling") and (u_value <= self.NEW_BUILD_INSULATED): + if (self.property.data["transaction-type"] == "new dwelling") and ( + u_value <= self.NEW_BUILD_INSULATED + ): # Recommend nothing return # We can't detect it's a cavity wall, but it was built after 1990 so likely built with insulation already # + it already has a U-value WORSE than the building regulations, so we recommend either internal or # external wall insulation - if (not is_cavity_wall) and (self.property.year_built >= self.YEAR_WALLS_BUILT_WITH_INSULATION) and ( - u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE + if ( + (not is_cavity_wall) + and (self.property.year_built >= self.YEAR_WALLS_BUILT_WITH_INSULATION) + and (u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE) ): # Recommend insulation self.find_insulation(u_value, phase) @@ -141,8 +158,10 @@ class WallRecommendations(Definitions): # We can't detect it's a cavity wall, but it was built after 1990 so likely built with insulation already # + it already has a U-value better than the building regulations, so we don't need to recommend anything - if (not is_cavity_wall) and (self.property.year_built >= self.YEAR_WALLS_BUILT_WITH_INSULATION) and ( - u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE + if ( + (not is_cavity_wall) + and (self.property.year_built >= self.YEAR_WALLS_BUILT_WITH_INSULATION) + and (u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE) ): # Recommend nothing return @@ -205,28 +224,40 @@ class WallRecommendations(Definitions): recommendations = [] for _, material in insulation_materials.iterrows(): - part_u_value = r_value_per_mm_to_u_value(cavity_width, material["r_value_per_mm"]) + part_u_value = r_value_per_mm_to_u_value( + cavity_width, 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 is_diminishing_returns( - recommendations, new_u_value, lowest_selected_u_value, self.DIMINISHING_RETURNS_U_VALUE + recommendations, + new_u_value, + lowest_selected_u_value, + self.DIMINISHING_RETURNS_U_VALUE, ): continue 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) + lowest_selected_u_value = update_lowest_selected_u_value( + lowest_selected_u_value, new_u_value + ) - is_extraction_and_refill = "cavity_extract_and_refill" in self.property.non_invasive_recommendations + is_extraction_and_refill = ( + "cavity_extract_and_refill" + in self.property.non_invasive_recommendations + ) cost_result = self.costs.cavity_wall_insulation( wall_area=self.property.insulation_wall_area, material=material.to_dict(), - is_extraction_and_refill=is_extraction_and_refill + is_extraction_and_refill=is_extraction_and_refill, ) - already_installed = "cavity_wall_insulation" in self.property.already_installed + already_installed = ( + "cavity_wall_insulation" in self.property.already_installed + ) if already_installed: cost_result = override_costs(cost_result) @@ -246,7 +277,7 @@ class WallRecommendations(Definitions): part=material.to_dict(), quantity=self.property.insulation_wall_area, quantity_unit=QuantityUnits.m2.value, - cost_result=cost_result + cost_result=cost_result, ) ], "type": "cavity_wall_insulation", @@ -255,13 +286,15 @@ class WallRecommendations(Definitions): "new_u_value": new_u_value, "sap_points": None, "already_installed": already_installed, - **cost_result + **cost_result, } ) self.recommendations = recommendations - def _find_insulation(self, u_value, insulation_materials, non_insulation_materials, phase): + def _find_insulation( + self, u_value, insulation_materials, non_insulation_materials, phase + ): lowest_selected_u_value = None recommendations = [] @@ -269,7 +302,9 @@ class WallRecommendations(Definitions): for _, material in insulation_material_group.iterrows(): - part_u_value = r_value_per_mm_to_u_value(material["depth"], material["r_value_per_mm"]) + part_u_value = r_value_per_mm_to_u_value( + material["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 @@ -280,22 +315,30 @@ class WallRecommendations(Definitions): # 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 + 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) + lowest_selected_u_value = update_lowest_selected_u_value( + lowest_selected_u_value, new_u_value + ) if material["type"] == "internal_wall_insulation": cost_result = self.costs.internal_wall_insulation( wall_area=self.property.insulation_wall_area, material=material.to_dict(), - non_insulation_materials=non_insulation_materials + non_insulation_materials=non_insulation_materials, + ) + already_installed = ( + "internal_wall_insulation" + in self.property.already_installed ) - already_installed = "internal_wall_insulation" in self.property.already_installed if already_installed: cost_result = override_costs(cost_result) @@ -303,9 +346,12 @@ class WallRecommendations(Definitions): cost_result = self.costs.external_wall_insulation( wall_area=self.property.insulation_wall_area, material=material.to_dict(), - non_insulation_materials=non_insulation_materials + non_insulation_materials=non_insulation_materials, + ) + already_installed = ( + "external_wall_insulation" + in self.property.already_installed ) - already_installed = "external_wall_insulation" in self.property.already_installed if already_installed: cost_result = override_costs(cost_result) else: @@ -319,7 +365,7 @@ class WallRecommendations(Definitions): part=material.to_dict(), quantity=self.property.insulation_wall_area, quantity_unit=QuantityUnits.m2.value, - cost_result=cost_result + cost_result=cost_result, ) ], "type": material["type"], @@ -328,7 +374,7 @@ class WallRecommendations(Definitions): "new_u_value": new_u_value, "already_installed": already_installed, "sap_points": None, - **cost_result + **cost_result, } ) @@ -350,16 +396,18 @@ class WallRecommendations(Definitions): if self.ewi_valid: ewi_recommendations = self._find_insulation( u_value=u_value, - insulation_materials=pd.DataFrame(self.external_wall_insulation_materials), + insulation_materials=pd.DataFrame( + self.external_wall_insulation_materials + ), non_insulation_materials=self.external_wall_non_insulation_materials, - phase=phase + phase=phase, ) iwi_recommendations = self._find_insulation( u_value=u_value, insulation_materials=pd.DataFrame(self.internal_wall_insulation_materials), non_insulation_materials=self.internal_wall_non_insulation_materials, - phase=phase + phase=phase, ) self.recommendations += ewi_recommendations + iwi_recommendations @@ -367,12 +415,16 @@ class WallRecommendations(Definitions): @staticmethod def _make_description(material): if material["type"] == "internal_wall_insulation": - return (f"Install {int(material['depth'])}{material['depth_unit']} {material['description']} on internal " - f"walls") + return ( + f"Install {int(material['depth'])}{material['depth_unit']} {material['description']} on internal " + f"walls" + ) if material["type"] == "external_wall_insulation": - return (f"Install {int(material['depth'])}{material['depth_unit']} {material['description']} on external " - f"walls") + return ( + f"Install {int(material['depth'])}{material['depth_unit']} {material['description']} on external " + f"walls" + ) if material["type"] == "cavity_wall_insulation": return f"Fill cavity with {material['description']}" diff --git a/recommendations/WindowsRecommendations.py b/recommendations/WindowsRecommendations.py index b7c2823a..8c0cc493 100644 --- a/recommendations/WindowsRecommendations.py +++ b/recommendations/WindowsRecommendations.py @@ -4,7 +4,7 @@ import numpy as np from backend.Property import Property from recommendations.Costs import Costs -from recommendation_utils import override_costs +from recommendations.recommendation_utils import override_costs class WindowsRecommendations: @@ -14,7 +14,7 @@ class WindowsRecommendations: # glazed "most": 0.33, # If glazing is partial, we assume 50/50 split between glazed and unglazed - "partial": 0.5 + "partial": 0.5, } def __init__(self, property_instance: Property, materials: List): @@ -52,14 +52,20 @@ class WindowsRecommendations: if not number_of_windows: raise ValueError("Number of windows not specified") - if self.property.windows["has_glazing"] & (self.property.windows["glazing_coverage"] == "full"): + if self.property.windows["has_glazing"] & ( + self.property.windows["glazing_coverage"] == "full" + ): return # We scale the number of windows based on the proportion of existing glazing if self.property.data["multi-glaze-proportion"] != "": - n_windows_scalar = 1 - (int(self.property.data["multi-glaze-proportion"]) / 100) + n_windows_scalar = 1 - ( + int(self.property.data["multi-glaze-proportion"]) / 100 + ) else: - n_windows_scalar = self.COVERAGE_MAP.get(self.property.windows["glazing_coverage"], 1) + n_windows_scalar = self.COVERAGE_MAP.get( + self.property.windows["glazing_coverage"], 1 + ) number_of_windows *= n_windows_scalar number_of_windows = np.ceil(number_of_windows) @@ -68,7 +74,7 @@ class WindowsRecommendations: cost_result = self.costs.window_glazing( number_of_windows=number_of_windows, material=self.glazing_material, - is_secondary_glazing=is_secondary_glazing + is_secondary_glazing=is_secondary_glazing, ) already_installed = "windows_glazing" in self.property.already_installed @@ -76,18 +82,26 @@ class WindowsRecommendations: cost_result = override_costs(cost_result) description = "The property already has double glazing installed. No further action is required." else: - glazing_type = "secondary glazing" if is_secondary_glazing else "double glazing" + glazing_type = ( + "secondary glazing" if is_secondary_glazing else "double glazing" + ) if self.property.windows["glazing_coverage"] in ["partial", "most"]: description = f"Install {glazing_type} to the remaining windows" else: description = f"Install {glazing_type} to all windows" if self.property.is_listed: - description += ". Secondary glazing recommended due to listed building status" + description += ( + ". Secondary glazing recommended due to listed building status" + ) elif self.property.is_heritage: - description += ". Secondary glazing recommended due to herigate building status" + description += ( + ". Secondary glazing recommended due to herigate building status" + ) elif self.property.in_conservation_area: - description += ". Secondary glazing recommended due to conservation area status" + description += ( + ". Secondary glazing recommended due to conservation area status" + ) self.recommendation = [ { @@ -100,6 +114,6 @@ class WindowsRecommendations: "sap_points": None, "already_installed": already_installed, **cost_result, - "is_secondary_glazing": is_secondary_glazing + "is_secondary_glazing": is_secondary_glazing, } ]