diff --git a/backend/Property.py b/backend/Property.py index 4bd77ec8..2358cbc2 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -3,6 +3,7 @@ import re from epc_api.client import EpcClient from model_data.config import EPC_AUTH_TOKEN from model_data.BaseUtility import Definitions +from recommendations.rdsap_tables import england_wales_age_band_lookup class Property(Definitions): @@ -29,6 +30,7 @@ class Property(Definitions): lighting = None coordinates = None + age_band = None def __init__(self, id, postcode, address1, epc_client=None, data=None): self.id = id @@ -245,6 +247,7 @@ class Property(Definitions): self.set_floor_height() self.set_wall_area() self.set_floor_area() + self.set_age_band() for description, attribute in cleaned.items(): @@ -263,6 +266,17 @@ class Property(Definitions): raise ValueError("Either No attributes or multiple found for %s" % description) setattr(self, self.ATTRIBUTE_MAP[description], attributes[0]) + def set_age_band(self): + """ + Sets a cleaned version of the age band of the property given the EPC data + :return: + """ + + if not self.data: + raise ValueError("Property does not contain data") + + self.age_band = england_wales_age_band_lookup[self.data["construction-age-band"]] + def set_is_in_conservation_area(self, in_conservation_area): """ Sets whether the property is in a conservation area given the output of the ConservationAreaClient diff --git a/model_data/simulation_system/generate_rdsap_change.py b/model_data/simulation_system/generate_rdsap_change.py index 3797024e..56bf86a6 100644 --- a/model_data/simulation_system/generate_rdsap_change.py +++ b/model_data/simulation_system/generate_rdsap_change.py @@ -539,8 +539,6 @@ def app(): if pd.isnull(data_by_urpn_df).sum().sum(): raise ValueError("Null values found in dataset after process_and_prune_desriptions") - data_by_urpn_df = DataProcessor.difference_data(data_by_urpn_df) - dataset.append(data_by_urpn_df) cleaning_averages["LOCAL_AUTHORITY"] = df["LOCAL_AUTHORITY"].values[0] @@ -555,6 +553,9 @@ def app(): ) output = pd.concat(dataset) + + output = DataProcessor.difference_data(output) + save_dataframe_to_s3_parquet( df=output, bucket_name="retrofit-data-dev", diff --git a/recommendations/FloorRecommendations.py b/recommendations/FloorRecommendations.py index 4fda5791..52b9d35d 100644 --- a/recommendations/FloorRecommendations.py +++ b/recommendations/FloorRecommendations.py @@ -3,7 +3,6 @@ from typing import List from model_data.BaseUtility import Definitions from datatypes.enums import QuantityUnits from backend.Property import Property -from recommendations.rdsap_tables import england_wales_age_band_lookup from recommendations.recommendation_utils import ( r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns, update_lowest_selected_u_value, get_recommended_part, estimate_perimeter, estimate_perimeter_2_rooms, get_wall_type, @@ -40,12 +39,10 @@ class FloorRecommendations(Definitions): 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 # For audit purposes, when estimating u values we'll store it self.estimated_u_value = None @@ -119,14 +116,13 @@ class FloorRecommendations(Definitions): else: estimated_perimeter = estimate_perimeter(total_floor_area / num_floors, scaled_num_rooms) - age_band = england_wales_age_band_lookup[self.property.data["construction-age-band"]] wall_type = get_wall_type(**self.property.walls) self.estimated_u_value = get_floor_u_value( floor_type="suspended" if is_suspended else "solid", area=total_floor_area, perimeter=estimated_perimeter, - age_band=age_band, + age_band=self.property.age_band, insulation_thickness=insulation_thickness, wall_type=wall_type ) diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py index fceee205..c46a495f 100644 --- a/recommendations/WallRecommendations.py +++ b/recommendations/WallRecommendations.py @@ -7,7 +7,7 @@ from backend.Property import Property from model_data.BaseUtility import Definitions from recommendations.recommendation_utils import ( r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns, update_lowest_selected_u_value, - get_recommended_part, get_uvalue_estimate + get_recommended_part, get_wall_u_value ) @@ -44,13 +44,12 @@ class WallRecommendations(Definitions): } def __init__( - self, property_instance: Property, - uvalue_estimates: List, + self, + property_instance: Property, 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 # For audit purposes, when estimating u values we'll store it self.estimated_u_value = None @@ -116,18 +115,15 @@ class WallRecommendations(Definitions): raise NotImplementedError("Not implemented yet") - if is_solid_brick: + u_value = get_wall_u_value( + clean_description=self.property.walls["clean_description"], + age_band=self.property.age_band, + is_granite_or_whinstone=self.property.walls["is_granite_or_whinstone"], + is_sandstone_or_limestone=self.property.walls["is_sandstone_or_limestone"], + ) + self.estimated_u_value = u_value - if insulation_thickness == "none": - # This is an estimated figure based on industry standards - u_value = self.DEFAULT_U_VALUES["solid_brick"] - else: - u_value = get_uvalue_estimate( - uvalue_estimates=self.uvalue_estimates, - property=self.property, - total_floor_area_group_decile=self.total_floor_area_group_decile - ) - self.estimated_u_value = u_value + if is_solid_brick: if u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: self.find_insulation(u_value) diff --git a/recommendations/tests/test_data/uvalue_estimates.pkl b/recommendations/tests/test_data/uvalue_estimates.pkl deleted file mode 100644 index 767d1300..00000000 Binary files a/recommendations/tests/test_data/uvalue_estimates.pkl and /dev/null differ diff --git a/recommendations/tests/test_wall_recommendations.py b/recommendations/tests/test_wall_recommendations.py index afd396e2..4641346c 100644 --- a/recommendations/tests/test_wall_recommendations.py +++ b/recommendations/tests/test_wall_recommendations.py @@ -206,13 +206,6 @@ class TestWallRecommendations: ) 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 @@ -220,26 +213,22 @@ class TestWallRecommendations: 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, "Decile 1", materials=wall_parts + property_mock, "Decile 1", materials=wall_parts ) return mock_wall_rec_instance - def test_init(self, input_properties, uvalue_estimates): + def test_init(self, input_properties): obj = WallRecommendations( property_instance=input_properties[0], - uvalue_estimates=uvalue_estimates, total_floor_area_group_decile="Decile 1", materials=wall_parts ) assert obj assert obj.property - assert obj.uvalue_estimates assert obj.total_floor_area_group_decile == "Decile 1" - def test_uvalue_0_16(self, input_properties, uvalue_estimates): + def test_uvalue_0_16(self, input_properties): """ This tests the wall description Average thermal transmittance 0.16 W/m-¦K The important data for this recommendation is: @@ -251,7 +240,6 @@ class TestWallRecommendations: input_properties[0].year_built = 2014 recommender = WallRecommendations( property_instance=input_properties[0], - uvalue_estimates=uvalue_estimates, total_floor_area_group_decile="Decile 1", materials=wall_parts ) @@ -260,7 +248,7 @@ class TestWallRecommendations: # This should be empty assert recommender.recommendations == [] - def test_solid_brick_no_insulation(self, input_properties, uvalue_estimates): + def test_solid_brick_no_insulation(self, input_properties): """ 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 @@ -271,10 +259,12 @@ class TestWallRecommendations: """ input_properties[1].year_built = 1930 input_properties[1].insulation_wall_area = 100 + input_properties[1].walls["clean_description"] = "Solid brick, as built, no insulation" + input_properties[1].walls["is_sandstone_or_limestone"] = False + input_properties[1].age_band = "A" recommender = WallRecommendations( property_instance=input_properties[1], - uvalue_estimates=uvalue_estimates, total_floor_area_group_decile="Decile 1", materials=wall_parts ) @@ -296,7 +286,7 @@ class TestWallRecommendations: recommender.recommendations ) - def test_solid_brick_insulation(self, input_properties, uvalue_estimates): + def test_solid_brick_insulation(self, input_properties): """ 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 @@ -311,7 +301,6 @@ class TestWallRecommendations: input_properties[6].year_built = 1991 recommender = WallRecommendations( property_instance=input_properties[6], - uvalue_estimates=uvalue_estimates.walls.to_dict("records"), total_floor_area_group_decile="Decile 1", materials=wall_parts ) @@ -390,39 +379,10 @@ class TestWallRecommendationsBase: 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): + def wall_recommendations_instance(self, property_mock): wall_recommendations_instance = WallRecommendations( - property_mock, uvalue_estimations_mock, "Decile 1", materials=wall_parts + property_mock, "Decile 1", materials=wall_parts ) - 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): @@ -443,7 +403,11 @@ class TestWallRecommendationsBase: "thermal_transmittance": None, "is_solid_brick": False, "is_cavity_wall": False, - "insulation_thickness": "none" + "insulation_thickness": "none", + "clean_description": "Solid brick, as built, no insulation", + "is_granite_or_whinstone": False, + "is_sandstone_or_limestone": False, } + wall_recommendations_instance.property.age_band = "A" with pytest.raises(NotImplementedError): wall_recommendations_instance.recommend()