mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
added in cavity fill recommendation
This commit is contained in:
parent
cbef2b5e45
commit
2db0526003
4 changed files with 163 additions and 22 deletions
|
|
@ -56,10 +56,26 @@ plan_input = local_data["plan_input"]
|
|||
uprn_filenames = local_data["uprn_filenames"]
|
||||
local_property_data = local_data["local_property_data"]
|
||||
materials = local_data["materials"]
|
||||
materials_by_type = local_data["materials_by_type"]
|
||||
materials_by_type = filter_materials(materials)
|
||||
cleaned = local_data["cleaned"]
|
||||
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")
|
||||
|
||||
input_properties = []
|
||||
|
|
@ -102,7 +118,7 @@ for p in input_properties:
|
|||
# Floor recommendations
|
||||
floor_recommender = FloorRecommendations(
|
||||
property_instance=p,
|
||||
materials=materials_by_type["suspended_floor_insulation"] + materials_by_type["solid_floor_insulation"],
|
||||
materials=materials_by_type["floor"],
|
||||
)
|
||||
floor_recommender.recommend()
|
||||
|
||||
|
|
@ -113,7 +129,7 @@ for p in input_properties:
|
|||
|
||||
wall_recomender = WallRecommendations(
|
||||
property_instance=p,
|
||||
materials=materials_by_type["external_wall_insulation"] + materials_by_type["internal_wall_insulation"]
|
||||
materials=materials_by_type["walls"]
|
||||
)
|
||||
wall_recomender.recommend()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,15 +11,17 @@ import msgpack
|
|||
def filter_materials(materials):
|
||||
materials_by_type = defaultdict(list)
|
||||
|
||||
for material in materials:
|
||||
material = row2dict(material)
|
||||
material_type = material["type"]
|
||||
materials_by_type[material_type].append(material)
|
||||
mapping = {
|
||||
"walls": ["internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation"],
|
||||
"floor": ["suspended_floor_insulation", "solid_floor_insulation"]
|
||||
}
|
||||
|
||||
# Optionally, you can convert the defaultdict to a normal dict if desired
|
||||
materials_by_type = dict(materials_by_type)
|
||||
materials = [row2dict(material) for material in materials]
|
||||
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -76,9 +76,7 @@ class WallRecommendations(Definitions):
|
|||
# recommend internal wall insulation as a possible measure
|
||||
|
||||
u_value = self.property.walls["thermal_transmittance"]
|
||||
|
||||
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"]
|
||||
|
||||
# We check if the wall is already insulated and if so, we exit
|
||||
|
|
@ -86,9 +84,9 @@ class WallRecommendations(Definitions):
|
|||
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")
|
||||
|
||||
# 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
|
||||
|
|
@ -117,7 +115,7 @@ class WallRecommendations(Definitions):
|
|||
)
|
||||
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:
|
||||
self.find_insulation(u_value)
|
||||
|
|
@ -126,8 +124,72 @@ class WallRecommendations(Definitions):
|
|||
# If the u-value is within regulations, we don't do anything
|
||||
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")
|
||||
|
||||
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):
|
||||
lowest_selected_u_value = None
|
||||
recommendations = []
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
import pytest
|
||||
import pickle
|
||||
import numpy as np
|
||||
from unittest.mock import Mock, MagicMock
|
||||
from recommendations.WallRecommendations import WallRecommendations
|
||||
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:
|
||||
|
|
@ -210,19 +226,17 @@ class TestWallRecommendations:
|
|||
property_mock.data = {"construction-age-band": "1950"} # or any other data that fits your tests
|
||||
|
||||
mock_wall_rec_instance = WallRecommendations(
|
||||
property_mock, "Decile 1", materials=wall_parts
|
||||
property_mock, materials=wall_parts
|
||||
)
|
||||
return mock_wall_rec_instance
|
||||
|
||||
def test_init(self, input_properties):
|
||||
obj = WallRecommendations(
|
||||
property_instance=input_properties[0],
|
||||
total_floor_area_group_decile="Decile 1",
|
||||
materials=wall_parts
|
||||
)
|
||||
assert obj
|
||||
assert obj.property
|
||||
assert obj.total_floor_area_group_decile == "Decile 1"
|
||||
|
||||
def test_uvalue_0_16(self, input_properties):
|
||||
"""
|
||||
|
|
@ -236,7 +250,6 @@ class TestWallRecommendations:
|
|||
input_properties[0].year_built = 2014
|
||||
recommender = WallRecommendations(
|
||||
property_instance=input_properties[0],
|
||||
total_floor_area_group_decile="Decile 1",
|
||||
materials=wall_parts
|
||||
)
|
||||
assert recommender.property.walls["original_description"] == "Average thermal transmittance 0.16 W/m-¦K"
|
||||
|
|
@ -261,7 +274,6 @@ class TestWallRecommendations:
|
|||
|
||||
recommender = WallRecommendations(
|
||||
property_instance=input_properties[1],
|
||||
total_floor_area_group_decile="Decile 1",
|
||||
materials=wall_parts
|
||||
)
|
||||
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
|
||||
recommender = WallRecommendations(
|
||||
property_instance=input_properties[6],
|
||||
total_floor_area_group_decile="Decile 1",
|
||||
materials=wall_parts
|
||||
)
|
||||
|
||||
|
|
@ -377,7 +388,7 @@ class TestWallRecommendationsBase:
|
|||
@pytest.fixture
|
||||
def wall_recommendations_instance(self, property_mock):
|
||||
wall_recommendations_instance = WallRecommendations(
|
||||
property_mock, "Decile 1", materials=wall_parts
|
||||
property_mock, materials=wall_parts
|
||||
)
|
||||
return wall_recommendations_instance
|
||||
|
||||
|
|
@ -407,3 +418,53 @@ class TestWallRecommendationsBase:
|
|||
wall_recommendations_instance.property.age_band = "A"
|
||||
with pytest.raises(NotImplementedError):
|
||||
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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue