mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Refactored the WallRecommendations code
This commit is contained in:
parent
6afc8d2e67
commit
8a10cec739
2 changed files with 113 additions and 90 deletions
|
|
@ -126,35 +126,9 @@ def handler():
|
||||||
input_properties[6].data["postcode"]
|
input_properties[6].data["postcode"]
|
||||||
walls_df["address1"].values[6]
|
walls_df["address1"].values[6]
|
||||||
walls_df["original_description"].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
|
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
|
# We need to deduce a U-value for "Good" energy effieciency
|
||||||
|
|
||||||
|
|
@ -171,3 +145,38 @@ def handler():
|
||||||
|
|
||||||
p = input_properties[6]
|
p = input_properties[6]
|
||||||
df = pd.DataFrame(data)
|
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")
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import itertools
|
||||||
from model_data.Property import Property
|
from model_data.Property import Property
|
||||||
from model_data.ConservationAreaClient import ConservationAreaClient
|
from model_data.ConservationAreaClient import ConservationAreaClient
|
||||||
from model_data.analysis.UvalueEstimations import UvalueEstimations
|
from model_data.analysis.UvalueEstimations import UvalueEstimations
|
||||||
|
from model_data.BaseUtility import BaseUtility
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
|
|
@ -184,7 +185,7 @@ internal_wall_insulation_parts = [
|
||||||
wall_parts = external_wall_insulation_parts + 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
|
YEAR_WALLS_BUILT_WITH_INSULATION = 1990
|
||||||
U_VALUE_UNIT = 'w/m-¦k'
|
U_VALUE_UNIT = 'w/m-¦k'
|
||||||
BUILDING_REGULATIONS_PART_L_MAX_U_VALUE = 0.18
|
BUILDING_REGULATIONS_PART_L_MAX_U_VALUE = 0.18
|
||||||
|
|
@ -207,6 +208,19 @@ class WallRecommendations:
|
||||||
# Will contains a list of recommended measures
|
# Will contains a list of recommended measures
|
||||||
self.recommendations = []
|
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):
|
def _year_property_was_built(self):
|
||||||
"""
|
"""
|
||||||
Estimates when the property was built based on as much available data as possible.
|
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:
|
if self.property.full_sap_epc:
|
||||||
return pd.to_datetime(self.property.full_sap_epc["lodgement-date"]).year
|
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
|
# 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
|
# more options for recommendations if that age falls before the year that insulation in walls became
|
||||||
# common practice
|
# common practice
|
||||||
band = [int(x) for x in re.findall(r'\b\d{4}\b', self.property.data["construction-age-band"])]
|
band = [int(x) for x in re.findall(r'\b\d{4}\b', self.property.data["construction-age-band"])]
|
||||||
return band[0]
|
return band[0]
|
||||||
|
|
||||||
|
#
|
||||||
|
|
||||||
raise NotImplementedError("Implement me!")
|
raise NotImplementedError("Implement me!")
|
||||||
|
|
||||||
def recommend(self):
|
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 (
|
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
|
u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE
|
||||||
):
|
):
|
||||||
# Recommend internal wall insulation
|
# Recommend insulation
|
||||||
iwi_parts = [part for part in wall_parts if part["type"] == "internal_wall_insulation"]
|
self.find_insulation(u_value)
|
||||||
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,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_solid_brick and insulation_thickness == "none":
|
if is_solid_brick and insulation_thickness == "none":
|
||||||
|
|
||||||
|
|
@ -266,62 +270,72 @@ class WallRecommendations:
|
||||||
else:
|
else:
|
||||||
u_value = self._get_walls_uvalue_estimate()
|
u_value = self._get_walls_uvalue_estimate()
|
||||||
|
|
||||||
ewi_parts = [
|
if u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
||||||
part for part in wall_parts if part["type"] == "external_wall_insulation"
|
self.find_insulation(u_value)
|
||||||
] if self.property.in_conservation_area == ConservationAreaClient.IN_CONSERVATION_AREA else []
|
|
||||||
|
|
||||||
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
|
def find_insulation(self, u_value):
|
||||||
for part in ewi_parts + iwi_parts:
|
"""
|
||||||
|
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"]:
|
ewi_parts = [
|
||||||
part_u_value = self.r_value_per_mm_to_u_value(depth, part["r_value_per_mm"])
|
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)
|
iwi_parts = [part for part in wall_parts if part["type"] == "internal_wall_insulation"]
|
||||||
new_u_value = round(new_u_value, 2)
|
|
||||||
|
|
||||||
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
|
# We don't recommend an overkill solution
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# We allow a small tolerance for error so we don't discount the recommendation entirely
|
# Check if the combined new U-value meets the requirement
|
||||||
# if it's close, since this is an estimated new u-value
|
if combined_new_u_value - self.U_VALUE_ERROR <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
||||||
if 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.
|
||||||
self.recommendations.append(
|
# For now, I'm adding them as separate items in the list
|
||||||
self._get_recommended_part(part, depth, new_u_value)
|
|
||||||
)
|
|
||||||
|
|
||||||
# We also can recommend both internal and external wall insulation together
|
recommendation = [
|
||||||
# By looping through ewi first, if there is nothing there, that ensures not combinations are tested
|
self._get_recommended_part(ewi_part, ewi_depth, combined_new_u_value),
|
||||||
for ewi_part in ewi_parts:
|
self._get_recommended_part(iwi_part, iwi_depth, combined_new_u_value)
|
||||||
for iwi_part in iwi_parts:
|
]
|
||||||
for ewi_depth, iwi_depth in itertools.product(ewi_part["depths"], iwi_part["depths"]):
|
self.recommendations.append(recommendation)
|
||||||
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")
|
|
||||||
|
|
||||||
def _get_walls_uvalue_estimate(self):
|
def _get_walls_uvalue_estimate(self):
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue