import os import pandas as pd import pytest import pickle import numpy as np from unittest.mock import Mock, MagicMock from model_data.recommendations.WallRecommendations import WallRecommendations from model_data.analysis.UvalueEstimations import UvalueEstimations from model_data.Property import Property from model_data.recommendations.recommendation_utils import is_diminishing_returns class TestWallRecommendations: @pytest.fixture def input_properties(self): with open( os.path.abspath(os.path.dirname(__file__)) + "/test_data/input_properties.pkl", "rb" ) as f: return pickle.load(f) @pytest.fixture def uvalue_estimates(self): with open( os.path.abspath(os.path.dirname(__file__)) + "/test_data/uvalue_estimates.pkl", "rb" ) as f: return pickle.load(f) @pytest.fixture def mock_wall_rec_instance(self): # Creating a mock instance of WallRecommendations with the necessary attributes property_mock = Mock() property_mock.full_sap_epc = {"lodgement-date": "2000-01-01"} # or any date you want property_mock.data = {"construction-age-band": "1950"} # or any other data that fits your tests uvalue_estimates_mock = Mock() mock_wall_rec_instance = WallRecommendations(property_mock, uvalue_estimates_mock) return mock_wall_rec_instance def test_init(self, input_properties, uvalue_estimates): obj = WallRecommendations(property_instance=input_properties[0], uvalue_estimates=uvalue_estimates) assert obj assert obj.property assert obj.uvalue_estimates def test_uvalue_0_16(self, input_properties, uvalue_estimates): """ This tests the wall description Average thermal transmittance 0.16 W/m-¦K The important data for this recommendation is: - u value of 0.16 - property built in 2014 Since properties built after 1990 are typically built with insulation and this property already has really good insulation, we do NOT recommend any measures for this property """ input_properties[0].year_built = 2014 recommender = WallRecommendations(property_instance=input_properties[0], uvalue_estimates=uvalue_estimates) assert recommender.property.walls["original_description"] == "Average thermal transmittance 0.16 W/m-¦K" recommender.recommend() # This should be empty assert recommender.recommendations == [] def test_solid_brick_no_insulation(self, input_properties, uvalue_estimates): """ This tests a property with a wall description of Solid brick, as built, no insulation (assumed) The property was built in 1930, right on the threshold for when cavity walls were introduced However, we're told this property is solid brik so we assume no cavity. We're also told that it has no insulation, so we will recommend internal/external wall insulation 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 recommender = WallRecommendations(property_instance=input_properties[1], uvalue_estimates=uvalue_estimates) assert recommender.property.walls["original_description"] == "Solid brick, as built, no insulation (assumed)" assert not recommender.ewi_valid assert recommender.property.in_conservation_area == "not_in_conservation_area" assert recommender.property.data["property-type"] == "Flat" recommender.recommend() # This should result in some recommendations, all of which should be internal insulation assert recommender.recommendations 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 ) def test_solid_brick_insulation(self, input_properties, uvalue_estimates): """ This tests a property with a wall description of Solid brick, as built, insulation (assumed) The property was built in 1991, after cavity walls were introduced 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 This property is not in a conservation area, however it's a flat so we don't recommend external wall insulation """ input_properties[6].year_built = 1991 recommender = WallRecommendations(property_instance=input_properties[6], uvalue_estimates=uvalue_estimates) assert recommender.property.walls["original_description"] == "Solid brick, as built, insulated (assumed)" assert not recommender.ewi_valid assert recommender.property.in_conservation_area == "not_in_conservation_area" assert recommender.property.data["property-type"] == "Flat" assert recommender.estimated_u_value is None 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.45 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 def test_is_diminishing_returns_no_recommendations(self): # We have no recommendations but the new u value is below the diminishing returns threshold # however since there are no recommendaions yet, we should include it and say # it is NOT diminishing new_u_value = 0.24 assert new_u_value < WallRecommendations.DIMINISHING_RETURNS_U_VALUE assert not is_diminishing_returns([], new_u_value, None, WallRecommendations.DIMINISHING_RETURNS_U_VALUE) def test_is_diminishing_returns_diminishing(self): # We have a recommendation already and the new u value falls below the diminishing returns # threshold and is also lower than the lowest selected u value, so we should say this IS # diminishing returns new_u_value = 0.2 lowest_selected_u_value = 0.23 assert new_u_value < WallRecommendations.DIMINISHING_RETURNS_U_VALUE assert is_diminishing_returns([Mock()], new_u_value, lowest_selected_u_value, WallRecommendations.DIMINISHING_RETURNS_U_VALUE) def test_is_diminishing_returns_is_diminishing(self): # We have a recommendation already and the new u value falls below the diminishing returns # threshold and it's lower than the lowest selected u value, so we should say this IS DIMINISHIN new_u_value = 0.24 lowest_selected_u_value = 0.26 assert new_u_value < WallRecommendations.DIMINISHING_RETURNS_U_VALUE assert is_diminishing_returns([Mock()], new_u_value, lowest_selected_u_value, WallRecommendations.DIMINISHING_RETURNS_U_VALUE) def test_is_diminishing_returns_not_diminishing(self): # We have a recommendation already and the new u value falls below the diminishing returns # threshold however it's higher than the lowest selected u value, so we should say this is NOT # diminishing returns new_u_value = 0.24 lowest_selected_u_value = 0.22 assert new_u_value < WallRecommendations.DIMINISHING_RETURNS_U_VALUE assert not is_diminishing_returns([Mock()], new_u_value, lowest_selected_u_value, WallRecommendations.DIMINISHING_RETURNS_U_VALUE) def test_is_diminishing_returns_error_in_recommendations(self, mock_wall_rec_instance): # Testing case where there's an error in recommendations mock_wall_rec_instance.recommendations = [] with pytest.raises(ValueError): is_diminishing_returns([], 0.2, 0.24, 0.25) class TestWallRecommendationsBase: @pytest.fixture def property_mock(self): property_mock = MagicMock(spec=Property) property_mock.full_sap_epc = {"lodgement-date": "1999-12-31"} property_mock.in_conservation_area = "not_in_conservation_area" return property_mock @pytest.fixture def uvalue_estimations_mock(self): uvalue_estimations_mock = MagicMock(spec=UvalueEstimations) uvalue_estimations_mock.walls = pd.DataFrame([ { 'local-authority': 'E09000012', 'property-type': 'Bungalow', 'walls-energy-eff': 'Very Good', 'walls-env-eff': 'Very Good', 'built-form': 'End-Terrace', 'number-habitable-rooms': '', 'number-heated-rooms': '', 'total-floor-area_group': 'Decile 1', 'median_thermal_transmittance': 0.15, 'n_samples': 1 } ]) uvalue_estimations_mock.walls_decile_data = { 'decile_labels': ['Decile 1', 'Decile 2', 'Decile 3', 'Decile 4', 'Decile 5', 'Decile 6', 'Decile 7', 'Decile 8', 'Decile 9', 'Decile 10'], 'decile_boundaries': np.array([11., 49., 52., 56., 63., 70., 74., 79., 90., 103.8, 1936.])} uvalue_estimations_mock.classify_decile_newvalues.return_value = ["Decile 1"] return uvalue_estimations_mock @pytest.fixture def wall_recommendations_instance(self, property_mock, uvalue_estimations_mock): wall_recommendations_instance = WallRecommendations(property_mock, uvalue_estimations_mock) wall_recommendations_instance.uvalue_estimates.walls_decile_data = { "decile_labels": MagicMock(), "decile_boundaries": MagicMock() } return wall_recommendations_instance def test_ewi_valid_in_conservation_area(self, wall_recommendations_instance): wall_recommendations_instance.property.in_conservation_area = "in_conversation_area" assert wall_recommendations_instance.ewi_valid is False def test_ewi_valid_is_flat(self, wall_recommendations_instance): wall_recommendations_instance.property.data = {"property-type": "flat"} assert wall_recommendations_instance.ewi_valid is False def test_ewi_valid_not_in_conservation_area_and_not_flat(self, wall_recommendations_instance): wall_recommendations_instance.property.in_conservation_area = "not_in_conversation_area" wall_recommendations_instance.property.data = {"property-type": "house"} assert wall_recommendations_instance.ewi_valid is True def test_get_walls_uvalue_estimate(self, wall_recommendations_instance, uvalue_estimations_mock): wall_recommendations_instance.uvalue_estimates = uvalue_estimations_mock wall_recommendations_instance.property.data = { "local-authority": "E09000012", "property-type": "Bungalow", "built-form": "End-Terrace", "walls-energy-eff": "Very Good", "walls-env-eff": "Very Good", "total-floor-area": 10, "number-habitable-rooms": "", "number-heated-rooms": "" } assert wall_recommendations_instance._get_walls_uvalue_estimate() == 0.15 def test_recommend_without_u_value(self, wall_recommendations_instance): wall_recommendations_instance.property.walls = { "thermal_transmittance": None, "is_solid_brick": False, "is_cavity_wall": False, "insulation_thickness": "none" } with pytest.raises(NotImplementedError): wall_recommendations_instance.recommend()