diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py index 9edbe969..fceee205 100644 --- a/recommendations/WallRecommendations.py +++ b/recommendations/WallRecommendations.py @@ -1,5 +1,6 @@ import itertools import math +from typing import List from datatypes.enums import QuantityUnits from backend.Property import Property @@ -9,181 +10,6 @@ from recommendations.recommendation_utils import ( get_recommended_part, get_uvalue_estimate ) -external_wall_insulation_parts = [ - { - # Example product - # https://insulationgo.co.uk/100mm-rockwool-external-wall-insulation-dual-density-slabs-a1-non-combustible - # -slab-ewi-render-fire/ - "type": "external_wall_insulation", - "description": "Mineral Wool External Wall Insulation", - "depths": [30, 50, 70, 80, 90, 100, 150, 200], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "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" - }, - { - # Example product - # https://www.insulationking.co.uk/products/polystyrene-eps70?variant=44156186558759 - "type": "external_wall_insulation", - "description": "Expanded Polystyrene External Wall Insulation", - "depths": [25, 50, 100, 125], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": 0.02703, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": 0.037, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - }, - { - # Example product - # https://www.insulationshop.co/20mm_kooltherm_k5_external_wall_kingspan.html - "type": "external_wall_insulation", - "description": "Phenolic Foam External Wall Insulation", - "depths": [20, 50, 100], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": 0.043478260869565216, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": 0.023, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - - }, - { - "type": "external_wall_insulation", - "description": "Polyisocyanurate/Polyurethane Foam External Wall Insulation", - "depths": [], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": None, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": None, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - }, - { - # Example product - # https://www.mikewye.co.uk/product/steico-duo-dry/ - "type": "external_wall_insulation", - "description": "Wood Fiber External Wall Insulation", - "depths": [40, 60], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": 0.023255813953488375, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": 0.043, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - }, - { - # Example product - # https://www.thermablok.co.uk/site/wp-content/uploads/2022/09/Thermablok-Aerogel-Insulation-Blanket-TDS-AIS - # -and-Steel-Related-Details.pdf - "type": "external_wall_insulation", - "description": "Aerogel External Wall Insulation", - "depths": [10, 20, 30, 40, 50, 60, 70], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": 0.06666666666666667, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": 0.015, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - }, - { - "type": "external_wall_insulation", - "description": "Vacuum Insulation Panels External Wall Insulation", - "depths": [45, 60], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": 0.16666666666666666, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": 0.006, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - } -] - -internal_wall_insulation_parts = [ - { - # Example product - # https://www.insulationshop.co/25mm_polystyrene_insulation_eps_70jablite.html - "type": "internal_wall_insulation", - "description": "Rigid Insulation Boards Internal Wall Insulation", - "depths": [25, 40, 50, 75, 100], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": 0.026315789473684213, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": 0.038, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - }, - { - # Example product - # https://www.rockwool.com/siteassets/rw-uk/downloads/datasheets/flexi.pdf - "type": "internal_wall_insulation", - "description": "Mineral Wool Internal Wall Insulation", - "depths": [140], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": 0.02857142857142857, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": 0.035, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - }, - { - # Example product - # https://www.kingspan.com/gb/en/products/insulation-boards/wall-insulation-boards/kooltherm-k118-insulated - # -plasterboard/ - "type": "internal_wall_insulation", - "description": "Insulated Plasterboard Internal Wall Insulation", - "depths": [25, 80], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": 0.02857142857142857, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": 0.019, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - }, - { - "type": "internal_wall_insulation", - "description": "Reflective Internal Wall Insulation", - "depths": [], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": None, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": None, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - }, - { - # Example product - # https://www.insulationsuperstore.co.uk/product/vacutherm-vacupor-nt-b2-vacuum-insulated-panel-1m-x-600mm-x - # -30mm.html - "type": "internal_wall_insulation", - "description": "Vacuum Insulation Panels Wall Insulation", - "depths": [20, 30], - "depth_unit": "mm", - "cost": None, - "cost_unit": None, - "r_value_per_mm": 0.125, - "r_value_unit": "square_meter_kelvin_per_watt", - "thermal_conductivity": 0.008, - "thermal_conductivity_unit": "watt_per_meter_kelvin" - }, -] - -wall_parts = external_wall_insulation_parts + internal_wall_insulation_parts - class WallRecommendations(Definitions): YEAR_WALLS_BUILT_WITH_INSULATION = 1990 @@ -217,7 +43,12 @@ class WallRecommendations(Definitions): "solid_brick": 2, } - def __init__(self, property_instance: Property, uvalue_estimates, total_floor_area_group_decile, materials=None): + def __init__( + self, property_instance: Property, + uvalue_estimates: List, + total_floor_area_group_decile: str, + materials: List + ): self.property = property_instance self.uvalue_estimates = uvalue_estimates self.total_floor_area_group_decile = total_floor_area_group_decile @@ -227,10 +58,7 @@ class WallRecommendations(Definitions): # Will contains a list of recommended measures self.recommendations = [] - if materials: - self.materials = materials - else: - self.materials = wall_parts + self.materials = materials @property def ewi_valid(self): diff --git a/recommendations/tests/test_recommendation_utils.py b/recommendations/tests/test_recommendation_utils.py index 92212208..83a35587 100644 --- a/recommendations/tests/test_recommendation_utils.py +++ b/recommendations/tests/test_recommendation_utils.py @@ -1,6 +1,7 @@ import pytest from unittest.mock import MagicMock from recommendations import recommendation_utils +from datatypes.enums import QuantityUnits class TestRecommendationUtils: @@ -38,7 +39,9 @@ class TestRecommendationUtils: def test_get_recommended_part(self): part = {'depths': [1, 2, 3]} - assert recommendation_utils.get_recommended_part(part, 1) == {'depths': [1]} + assert recommendation_utils.get_recommended_part( + part=part, selected_depth=1, selected_total_cost=50, quantity=99, quantity_unit="m2" + ) == {'depths': [1], 'estimated_cost': 50, 'quantity': 99, 'quantity_unit': QuantityUnits.m2.value} def test_get_uvalue_estimate(self, property_mock): uvalue_estimates = [ diff --git a/recommendations/tests/test_wall_recommendations.py b/recommendations/tests/test_wall_recommendations.py index c053de03..afd396e2 100644 --- a/recommendations/tests/test_wall_recommendations.py +++ b/recommendations/tests/test_wall_recommendations.py @@ -10,6 +10,192 @@ from model_data.analysis.UvalueEstimations import UvalueEstimations from backend.Property import Property from recommendations.recommendation_utils import is_diminishing_returns +# with open( +# os.path.abspath(os.path.dirname(__file__)) + "/recommendations/tests/test_data/input_properties.pkl", "rb" +# ) as f: +# input_properties = pickle.load(f) +# +# with open( +# os.path.abspath(os.path.dirname(__file__)) + "/recommendations/tests/test_data/uvalue_estimates.pkl", "rb" +# ) as f: +# uvalue_estimates = pickle.load(f) + + +external_wall_insulation_parts = [ + { + # Example product + # https://insulationgo.co.uk/100mm-rockwool-external-wall-insulation-dual-density-slabs-a1-non-combustible + # -slab-ewi-render-fire/ + "type": "external_wall_insulation", + "description": "Mineral Wool External Wall Insulation", + "depths": [30, 50, 70, 80, 90, 100, 150, 200], + "depth_unit": "mm", + "cost": [30, 50, 70, 80, 90, 100, 150, 200], + "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" + }, + { + # Example product + # https://www.insulationking.co.uk/products/polystyrene-eps70?variant=44156186558759 + "type": "external_wall_insulation", + "description": "Expanded Polystyrene External Wall Insulation", + "depths": [25, 50, 100, 125], + "depth_unit": "mm", + "cost": [25, 50, 100, 125], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": 0.02703, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": 0.037, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + }, + { + # Example product + # https://www.insulationshop.co/20mm_kooltherm_k5_external_wall_kingspan.html + "type": "external_wall_insulation", + "description": "Phenolic Foam External Wall Insulation", + "depths": [20, 50, 100], + "depth_unit": "mm", + "cost": [20, 50, 100], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": 0.043478260869565216, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": 0.023, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + + }, + { + "type": "external_wall_insulation", + "description": "Polyisocyanurate/Polyurethane Foam External Wall Insulation", + "depths": [], + "depth_unit": "mm", + "cost": [], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": None, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": None, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + }, + { + # Example product + # https://www.mikewye.co.uk/product/steico-duo-dry/ + "type": "external_wall_insulation", + "description": "Wood Fiber External Wall Insulation", + "depths": [40, 60], + "depth_unit": "mm", + "cost": [40, 60], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": 0.023255813953488375, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": 0.043, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + }, + { + # Example product + # https://www.thermablok.co.uk/site/wp-content/uploads/2022/09/Thermablok-Aerogel-Insulation-Blanket-TDS-AIS + # -and-Steel-Related-Details.pdf + "type": "external_wall_insulation", + "description": "Aerogel External Wall Insulation", + "depths": [10, 20, 30, 40, 50, 60, 70], + "depth_unit": "mm", + "cost": [10, 20, 30, 40, 50, 60, 70], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": 0.06666666666666667, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": 0.015, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + }, + { + "type": "external_wall_insulation", + "description": "Vacuum Insulation Panels External Wall Insulation", + "depths": [45, 60], + "depth_unit": "mm", + "cost": [45, 60], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": 0.16666666666666666, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": 0.006, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + } +] + +internal_wall_insulation_parts = [ + { + # Example product + # https://www.insulationshop.co/25mm_polystyrene_insulation_eps_70jablite.html + "type": "internal_wall_insulation", + "description": "Rigid Insulation Boards Internal Wall Insulation", + "depths": [25, 40, 50, 75, 100], + "depth_unit": "mm", + "cost": [25, 40, 50, 75, 100], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": 0.026315789473684213, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": 0.038, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + }, + { + # Example product + # https://www.rockwool.com/siteassets/rw-uk/downloads/datasheets/flexi.pdf + "type": "internal_wall_insulation", + "description": "Mineral Wool Internal Wall Insulation", + "depths": [140], + "depth_unit": "mm", + "cost": [140], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": 0.02857142857142857, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": 0.035, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + }, + { + # Example product + # https://www.kingspan.com/gb/en/products/insulation-boards/wall-insulation-boards/kooltherm-k118-insulated + # -plasterboard/ + "type": "internal_wall_insulation", + "description": "Insulated Plasterboard Internal Wall Insulation", + "depths": [25, 80], + "depth_unit": "mm", + "cost": [25, 80], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": 0.02857142857142857, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": 0.019, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + }, + { + "type": "internal_wall_insulation", + "description": "Reflective Internal Wall Insulation", + "depths": [], + "depth_unit": "mm", + "cost": [], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": None, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": None, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + }, + { + # Example product + # https://www.insulationsuperstore.co.uk/product/vacutherm-vacupor-nt-b2-vacuum-insulated-panel-1m-x-600mm-x + # -30mm.html + "type": "internal_wall_insulation", + "description": "Vacuum Insulation Panels Wall Insulation", + "depths": [20, 30], + "depth_unit": "mm", + "cost": [20, 30], + "cost_unit": "gbp_sq_meter", + "r_value_per_mm": 0.125, + "r_value_unit": "square_meter_kelvin_per_watt", + "thermal_conductivity": 0.008, + "thermal_conductivity_unit": "watt_per_meter_kelvin" + }, +] + +wall_parts = external_wall_insulation_parts + internal_wall_insulation_parts + class TestWallRecommendations: @@ -36,14 +222,17 @@ class TestWallRecommendations: uvalue_estimates_mock = Mock() - mock_wall_rec_instance = WallRecommendations(property_mock, uvalue_estimates_mock, "Decile 1") + mock_wall_rec_instance = WallRecommendations( + property_mock, uvalue_estimates_mock, "Decile 1", materials=wall_parts + ) return mock_wall_rec_instance def test_init(self, input_properties, uvalue_estimates): obj = WallRecommendations( property_instance=input_properties[0], uvalue_estimates=uvalue_estimates, - total_floor_area_group_decile="Decile 1" + total_floor_area_group_decile="Decile 1", + materials=wall_parts ) assert obj assert obj.property @@ -63,7 +252,8 @@ class TestWallRecommendations: recommender = WallRecommendations( property_instance=input_properties[0], uvalue_estimates=uvalue_estimates, - total_floor_area_group_decile="Decile 1" + total_floor_area_group_decile="Decile 1", + materials=wall_parts ) assert recommender.property.walls["original_description"] == "Average thermal transmittance 0.16 W/m-¦K" recommender.recommend() @@ -80,10 +270,13 @@ class TestWallRecommendations: This property is not in a conservation area, however it's a flat so we don't recommend external wall insulation """ input_properties[1].year_built = 1930 + input_properties[1].insulation_wall_area = 100 + recommender = WallRecommendations( property_instance=input_properties[1], uvalue_estimates=uvalue_estimates, - total_floor_area_group_decile="Decile 1" + total_floor_area_group_decile="Decile 1", + materials=wall_parts ) assert recommender.property.walls["original_description"] == "Solid brick, as built, no insulation (assumed)" assert not recommender.ewi_valid @@ -110,10 +303,7 @@ class TestWallRecommendations: However, we're told this property is solid brik so we assume no cavity. We're also told that it has some insulation already - We'll need to estimate the u-value for this property since we don't know what the insulation is - and how thick it is. We'll estimate the u-value based on similar properties, by matching properties - with similar attributes such as the walls energy efficiency rating, the property type, the number of rooms, - etc + Since the walls are already insulated, we don't recommend further measures This property is not in a conservation area, however it's a flat so we don't recommend external wall insulation """ @@ -122,7 +312,8 @@ class TestWallRecommendations: recommender = WallRecommendations( property_instance=input_properties[6], uvalue_estimates=uvalue_estimates.walls.to_dict("records"), - total_floor_area_group_decile="Decile 1" + total_floor_area_group_decile="Decile 1", + materials=wall_parts ) assert recommender.property.walls["original_description"] == "Solid brick, as built, insulated (assumed)" @@ -133,21 +324,8 @@ class TestWallRecommendations: recommender.recommend() - # We should result in some recommendations, all of which should be internal wall insulation - assert recommender.recommendations - assert recommender.estimated_u_value == 0.4115686274509804 - - rec_types = {part["type"] for rec in recommender.recommendations for part in rec["parts"]} - assert rec_types == {"internal_wall_insulation"} - - # Check the recommendations provide a u value below the minimum - assert all( - rec["new_u_value"] < WallRecommendations.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE for rec in - recommender.recommendations - ) - - for rec in recommender.recommendations: - assert rec["new_u_value"] < 0.3 + assert not recommender.recommendations + assert not recommender.estimated_u_value def test_is_diminishing_returns_no_recommendations(self): # We have no recommendations but the new u value is below the diminishing returns threshold @@ -238,7 +416,9 @@ class TestWallRecommendationsBase: @pytest.fixture def wall_recommendations_instance(self, property_mock, uvalue_estimations_mock): - wall_recommendations_instance = WallRecommendations(property_mock, uvalue_estimations_mock, "Decile 1") + wall_recommendations_instance = WallRecommendations( + property_mock, uvalue_estimations_mock, "Decile 1", materials=wall_parts + ) wall_recommendations_instance.uvalue_estimates.walls_decile_data = { "decile_labels": MagicMock(), "decile_boundaries": MagicMock()