added in cavity fill recommendation

This commit is contained in:
Khalim Conn-Kowlessar 2023-10-07 05:18:35 +01:00
parent cbef2b5e45
commit 2db0526003
4 changed files with 163 additions and 22 deletions

View file

@ -56,10 +56,26 @@ plan_input = local_data["plan_input"]
uprn_filenames = local_data["uprn_filenames"] uprn_filenames = local_data["uprn_filenames"]
local_property_data = local_data["local_property_data"] local_property_data = local_data["local_property_data"]
materials = local_data["materials"] materials = local_data["materials"]
materials_by_type = local_data["materials_by_type"] materials_by_type = filter_materials(materials)
cleaned = local_data["cleaned"] cleaned = local_data["cleaned"]
cleaning_data = local_data["cleaning_data"] cleaning_data = local_data["cleaning_data"]
# Need to find some proper materials
materials_by_type["walls"] += [
{'id': 4, 'type': 'cavity_wall_insulation', 'description': 'Example Material 1',
'depths': None,
'depth_unit': None, 'cost': 20,
'cost_unit': 'gbp_sq_meter', 'r_value_per_mm': 0.0278, 'r_value_unit': 'square_meter_kelvin_per_watt',
'thermal_conductivity': 0.036, 'thermal_conductivity_unit': 'watt_per_meter_kelvin',
'link': None, 'created_at': None, 'is_active': True},
{'id': 10, 'type': "cavity_wall_insulation", 'description': 'Example Material 2',
'depths': None, 'depth_unit': None, 'cost': 25, 'cost_unit': 'gbp_sq_meter',
'r_value_per_mm': 0.02631579, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.038,
'thermal_conductivity_unit': 'watt_per_meter_kelvin',
'link': None,
'created_at': None, 'is_active': True}
]
epc_client = EpcClient(auth_token="NO-TOKEN") epc_client = EpcClient(auth_token="NO-TOKEN")
input_properties = [] input_properties = []
@ -102,7 +118,7 @@ for p in input_properties:
# Floor recommendations # Floor recommendations
floor_recommender = FloorRecommendations( floor_recommender = FloorRecommendations(
property_instance=p, property_instance=p,
materials=materials_by_type["suspended_floor_insulation"] + materials_by_type["solid_floor_insulation"], materials=materials_by_type["floor"],
) )
floor_recommender.recommend() floor_recommender.recommend()
@ -113,7 +129,7 @@ for p in input_properties:
wall_recomender = WallRecommendations( wall_recomender = WallRecommendations(
property_instance=p, property_instance=p,
materials=materials_by_type["external_wall_insulation"] + materials_by_type["internal_wall_insulation"] materials=materials_by_type["walls"]
) )
wall_recomender.recommend() wall_recomender.recommend()

View file

@ -11,15 +11,17 @@ import msgpack
def filter_materials(materials): def filter_materials(materials):
materials_by_type = defaultdict(list) materials_by_type = defaultdict(list)
for material in materials: mapping = {
material = row2dict(material) "walls": ["internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation"],
material_type = material["type"] "floor": ["suspended_floor_insulation", "solid_floor_insulation"]
materials_by_type[material_type].append(material) }
# Optionally, you can convert the defaultdict to a normal dict if desired materials = [row2dict(material) for material in materials]
materials_by_type = dict(materials_by_type)
return materials_by_type for component, types in mapping.items():
materials_by_type[component] = [part for part in materials if part["type"] in types]
return dict(materials_by_type)
def insert_temp_recommendation_id(property_recommendations): def insert_temp_recommendation_id(property_recommendations):

View file

@ -76,9 +76,7 @@ class WallRecommendations(Definitions):
# recommend internal wall insulation as a possible measure # recommend internal wall insulation as a possible measure
u_value = self.property.walls["thermal_transmittance"] u_value = self.property.walls["thermal_transmittance"]
is_cavity_wall = self.property.walls["is_cavity_wall"] 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"] insulation_thickness = self.property.walls["insulation_thickness"]
# We check if the wall is already insulated and if so, we exit # We check if the wall is already insulated and if so, we exit
@ -86,9 +84,9 @@ class WallRecommendations(Definitions):
return return
if u_value: if u_value:
if self.property.walls["thermal_transmittance_unit"] != self.U_VALUE_UNIT: 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")
# We can't detect it's a cavity wall, but it was built after 1990 so likely built with insulation already # 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 # + it already has a U-value WORSE than the building regulations, so we recommend either internal or
# external wall insulation # external wall insulation
@ -117,7 +115,7 @@ class WallRecommendations(Definitions):
) )
self.estimated_u_value = u_value self.estimated_u_value = u_value
if is_solid_brick: if self.property.walls["is_solid_brick"]:
if u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: if u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
self.find_insulation(u_value) self.find_insulation(u_value)
@ -126,8 +124,72 @@ class WallRecommendations(Definitions):
# If the u-value is within regulations, we don't do anything # If the u-value is within regulations, we don't do anything
return return
if is_cavity_wall:
if u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
# Test filling cavity
self.find_cavity_insulation(u_value)
if insulation_thickness not in ["none", None]:
raise ValueError("Implement me")
return
raise NotImplementedError("Not implemented yet") raise NotImplementedError("Not implemented yet")
def find_cavity_insulation(self, u_value):
"""
This method tests different materials to fill the cavity wall, determining which
material will give us the best U-value.
We check for diminishing returns, however this function does not check for meeting building
part L regulations right now
:param u_value: u_value of the starting wall
:return:
"""
cavity_wall_fills = [m for m in self.materials if m["type"] == "cavity_wall_insulation"]
# TODO: Check this and also check the methodology
cavity_width = 125
# Test the different fill options
lowest_selected_u_value = None
recommendations = []
for part in cavity_wall_fills:
part_u_value = r_value_per_mm_to_u_value(cavity_width, part["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
):
continue
lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value)
estimated_cost = part["cost"] * self.property.insulation_wall_area
recommendations.append(
{
"parts": [
get_recommended_part(
part=part,
selected_depth=None,
quantity=self.property.insulation_wall_area,
quantity_unit=QuantityUnits.m2.value,
selected_total_cost=estimated_cost
)
],
"type": "wall_insulation",
"description": f"Fill cavity with {part['description']}",
"starting_u_value": u_value,
"new_u_value": new_u_value,
"sap_points": None,
"cost": estimated_cost,
}
)
self.recommendations = recommendations
def _find_insulation(self, parts, u_value): def _find_insulation(self, parts, u_value):
lowest_selected_u_value = None lowest_selected_u_value = None
recommendations = [] recommendations = []

View file

@ -1,6 +1,7 @@
import os import os
import pytest import pytest
import pickle import pickle
import numpy as np
from unittest.mock import Mock, MagicMock from unittest.mock import Mock, MagicMock
from recommendations.WallRecommendations import WallRecommendations from recommendations.WallRecommendations import WallRecommendations
from backend.Property import Property from backend.Property import Property
@ -190,7 +191,22 @@ internal_wall_insulation_parts = [
}, },
] ]
wall_parts = external_wall_insulation_parts + internal_wall_insulation_parts cavity_wall_insulation_parts = [
{'id': 4, 'type': 'cavity_wall_insulation', 'description': 'Example Material 1',
'depths': None,
'depth_unit': None, 'cost': 20,
'cost_unit': 'gbp_sq_meter', 'r_value_per_mm': 0.0278, 'r_value_unit': 'square_meter_kelvin_per_watt',
'thermal_conductivity': 0.036, 'thermal_conductivity_unit': 'watt_per_meter_kelvin',
'link': None, 'created_at': None, 'is_active': True},
{'id': 10, 'type': "cavity_wall_insulation", 'description': 'Example Material 2',
'depths': None, 'depth_unit': None, 'cost': 25, 'cost_unit': 'gbp_sq_meter',
'r_value_per_mm': 0.02631579, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.038,
'thermal_conductivity_unit': 'watt_per_meter_kelvin',
'link': None,
'created_at': None, 'is_active': True}
]
wall_parts = external_wall_insulation_parts + internal_wall_insulation_parts + cavity_wall_insulation_parts
class TestWallRecommendations: class TestWallRecommendations:
@ -210,19 +226,17 @@ class TestWallRecommendations:
property_mock.data = {"construction-age-band": "1950"} # or any other data that fits your tests property_mock.data = {"construction-age-band": "1950"} # or any other data that fits your tests
mock_wall_rec_instance = WallRecommendations( mock_wall_rec_instance = WallRecommendations(
property_mock, "Decile 1", materials=wall_parts property_mock, materials=wall_parts
) )
return mock_wall_rec_instance return mock_wall_rec_instance
def test_init(self, input_properties): def test_init(self, input_properties):
obj = WallRecommendations( obj = WallRecommendations(
property_instance=input_properties[0], property_instance=input_properties[0],
total_floor_area_group_decile="Decile 1",
materials=wall_parts materials=wall_parts
) )
assert obj assert obj
assert obj.property assert obj.property
assert obj.total_floor_area_group_decile == "Decile 1"
def test_uvalue_0_16(self, input_properties): def test_uvalue_0_16(self, input_properties):
""" """
@ -236,7 +250,6 @@ class TestWallRecommendations:
input_properties[0].year_built = 2014 input_properties[0].year_built = 2014
recommender = WallRecommendations( recommender = WallRecommendations(
property_instance=input_properties[0], property_instance=input_properties[0],
total_floor_area_group_decile="Decile 1",
materials=wall_parts materials=wall_parts
) )
assert recommender.property.walls["original_description"] == "Average thermal transmittance 0.16 W/m-¦K" assert recommender.property.walls["original_description"] == "Average thermal transmittance 0.16 W/m-¦K"
@ -261,7 +274,6 @@ class TestWallRecommendations:
recommender = WallRecommendations( recommender = WallRecommendations(
property_instance=input_properties[1], property_instance=input_properties[1],
total_floor_area_group_decile="Decile 1",
materials=wall_parts materials=wall_parts
) )
assert recommender.property.walls["original_description"] == "Solid brick, as built, no insulation (assumed)" assert recommender.property.walls["original_description"] == "Solid brick, as built, no insulation (assumed)"
@ -297,7 +309,6 @@ class TestWallRecommendations:
input_properties[6].year_built = 1991 input_properties[6].year_built = 1991
recommender = WallRecommendations( recommender = WallRecommendations(
property_instance=input_properties[6], property_instance=input_properties[6],
total_floor_area_group_decile="Decile 1",
materials=wall_parts materials=wall_parts
) )
@ -377,7 +388,7 @@ class TestWallRecommendationsBase:
@pytest.fixture @pytest.fixture
def wall_recommendations_instance(self, property_mock): def wall_recommendations_instance(self, property_mock):
wall_recommendations_instance = WallRecommendations( wall_recommendations_instance = WallRecommendations(
property_mock, "Decile 1", materials=wall_parts property_mock, materials=wall_parts
) )
return wall_recommendations_instance return wall_recommendations_instance
@ -407,3 +418,53 @@ class TestWallRecommendationsBase:
wall_recommendations_instance.property.age_band = "A" wall_recommendations_instance.property.age_band = "A"
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
wall_recommendations_instance.recommend() wall_recommendations_instance.recommend()
class TestCavityWallRecommensations:
data = {
'low-energy-fixed-light-count': '', 'address': '123 Fake Street',
'floor-height': '', 'construction-age-band': 'England and Wales: 1950-1966',
'address3': '', 'property-type': 'House', 'local-authority-label': 'Melton',
'county': 'Leicestershire', 'postcode': 'LE14 2QH',
'solar-water-heating-flag': 'N', 'constituency': 'E14000909',
'number-heated-rooms': '5', 'local-authority': 'E07000133', 'built-form': 'End-Terrace',
'address1': '1, 23 fake', 'total-floor-area': '85.0', 'environment-impact-current': '49',
'number-habitable-rooms': 3, 'address2': 'Fake', 'posttown': 'IDK',
'walls-energy-eff': 'Poor', 'current-energy-rating': 'D',
'transaction-type': 'ECO assessment', 'uprn': '999', 'current-energy-efficiency': '57',
'lodgement-date': '2019-07-10', 'lmk-key': '999', 'tenure': 'rental (private)', 'floor-level': 'NODATA!',
'walls-description': 'Cavity wall, as built, no insulation (assumed)',
}
def test_fill_empty_cavity(self):
input_property = Property(id=1, postcode="F4k3", address1="123 fake street", epc_client=Mock())
input_property.walls = {
'original_description': 'Cavity wall, as built, no insulation (assumed)',
'clean_description': 'Cavity wall, as built, no insulation',
'thermal_transmittance': None, 'thermal_transmittance_unit': None,
'is_cavity_wall': True, 'is_filled_cavity': False, 'is_solid_brick': False,
'is_system_built': False, 'is_timber_frame': False, 'is_granite_or_whinstone': False,
'is_as_built': True, 'is_cob': False, 'is_assumed': True,
'is_sandstone_or_limestone': False, 'is_park_home': False,
'insulation_thickness': 'none', 'external_insulation': False,
'internal_insulation': False
}
input_property.age_band = "C"
input_property.insulation_wall_area = 50
recommender = WallRecommendations(
property_instance=input_property,
materials=cavity_wall_insulation_parts
)
assert not recommender.recommendations
recommender.recommend()
assert recommender.recommendations
assert recommender.estimated_u_value == 1.5
assert np.isclose(recommender.recommendations[0]["new_u_value"], 0.25)
assert np.isclose(recommender.recommendations[0]["cost"], 1000)
assert np.isclose(recommender.recommendations[1]["new_u_value"], 0.26)
assert np.isclose(recommender.recommendations[1]["cost"], 1250)