Refactored the WallRecommendations code

This commit is contained in:
Khalim Conn-Kowlessar 2023-06-22 17:51:23 +01:00
parent 6afc8d2e67
commit 8a10cec739
2 changed files with 113 additions and 90 deletions

View file

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

View file

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