diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml index 105ce2da..dd4c951e 100644 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -1,5 +1,6 @@ + diff --git a/asset_list/app.py b/asset_list/app.py index 30172121..13a6a025 100644 --- a/asset_list/app.py +++ b/asset_list/app.py @@ -69,24 +69,24 @@ def app(): Property UPRN """ - data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Aspire" - data_filename = "ASPIRE ASSET LIST.xlsx" - sheet_name = "Asset List" - postcode_column = "Postcode" + data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/West Kent" + data_filename = "West Kent Asset List.xlsx" + sheet_name = "Sheet1" + postcode_column = "POSTCODE" address1_column = None address1_method = "house_number_extraction" - fulladdress_column = "Address" + fulladdress_column = "ADDRESS" address_cols_to_concat = [] missing_postcodes_method = None landlord_year_built = None landlord_os_uprn = None - landlord_property_type = "Property Type" + landlord_property_type = "PROPERTY TYPE" landlord_built_form = None - landlord_wall_construction = None - landlord_roof_construction = None + landlord_wall_construction = "wall combined" + landlord_roof_construction = "HEATING SYSTEM" landlord_heating_system = None landlord_existing_pv = None - landlord_property_id = "LLUPRN" + landlord_property_id = "UPRN" landlord_sap = None outcomes_filename = None outcomes_sheetname = None diff --git a/asset_list/mappings/roof.py b/asset_list/mappings/roof.py index cf829a5f..70cc8742 100644 --- a/asset_list/mappings/roof.py +++ b/asset_list/mappings/roof.py @@ -308,6 +308,18 @@ ROOF_CONSTRUCTION_MAPPINGS = { 'Flat: No Insulation': 'flat uninsulated', 'AnotherDwellingAbove: Unknown, PitchedNormalLoftAccess: 250mm': 'another dwelling above', 'PitchedNormalLoftAccess: 175mm': 'pitched insulated', - 'AnotherDwellingAbove: 300mm': 'another dwelling above' + 'AnotherDwellingAbove: 300mm': 'another dwelling above', + + 'Another dwelling above, As built': 'another dwelling above', + 'Pitched (slates or tiles) no loft access, 400mm+': 'pitched insulated', + 'Pitched (slates or tiles) access to loft, 400mm+': 'pitched insulated', + 'Pitched (slates or tiles) access to loft, 300mm': 'pitched insulated', + 'Pitched (slates or tiles) access to loft, 75mm': 'pitched less than 100mm insulation', + 'Pitched (slates or tiles) no loft access, 300mm': 'pitched insulated', + 'Pitched (slates or tiles) access to loft, 270mm': 'pitched insulated', + 'Pitched (slates or tiles) access to loft, 100mm': 'pitched insulated', + 'Pitched (slates or tiles) no loft access, 200mm': 'pitched insulated', + 'Pitched (slates or tiles) access to loft, 200mm': 'pitched insulated', + 'Pitched (slates or tiles) access to loft, 50mm': 'pitched less than 100mm insulation' } diff --git a/asset_list/mappings/walls.py b/asset_list/mappings/walls.py index 1bb02a9a..1a252b33 100644 --- a/asset_list/mappings/walls.py +++ b/asset_list/mappings/walls.py @@ -363,6 +363,12 @@ WALL_CONSTRUCTION_MAPPINGS = { 'Timber Frame, As Built': 'timber frame unknown insulation', 'Solid Brick, Internal Insulation': 'insulated solid brick', 'Granite or Whinstone, As Built': 'granite or whinstone unknown insulation', - 'Solid Brick, External': 'insulated solid brick' + 'Solid Brick, External': 'insulated solid brick', + + 'Cavity, Filled cavity': 'filled cavity', + 'Solid Brick, As built': 'solid brick unknown insulation', + 'System built, As built': 'system built unknown insulation', + 'Timber frame, As built': 'timber frame unknown insulation', + 'Cavity, As built': 'cavity unknown insulation' } diff --git a/asset_list/utils.py b/asset_list/utils.py index 8746c03a..d83a35f2 100644 --- a/asset_list/utils.py +++ b/asset_list/utils.py @@ -220,7 +220,7 @@ def get_data( searcher.find_property(skip_os=True) # Check if we have a flat or appartment - if searcher.newest_epc is None and uprn is None: + if not searcher.newest_epc and uprn is None: # Try again: if SearchEpc.get_house_number(address=str(house_number), postcode=postcode) is None: # Backup @@ -252,12 +252,12 @@ def get_data( searcher.find_property(skip_os=True) # As a final resort, we estimate the EPC - if property_type is not None and searcher.newest_epc is None: + if property_type is not None and not searcher.newest_epc: searcher.ordnance_survey_client.property_type = property_type searcher.ordnance_survey_client.built_form = built_form searcher.find_property(skip_os=True) - if searcher.newest_epc is None: + if not searcher.newest_epc: no_epc.append(home[row_id_name]) continue diff --git a/backend/addresses/Addresses.py b/backend/addresses/Addresses.py index 22822c6b..e81fef50 100644 --- a/backend/addresses/Addresses.py +++ b/backend/addresses/Addresses.py @@ -1,3 +1,4 @@ +from typing import Iterator from backend.addresses.Address import Address @@ -12,6 +13,9 @@ class Addresses: def __len__(self) -> int: return len(self._addresses) + def __iter__(self) -> Iterator[Address]: + return iter(self._addresses) + @classmethod def from_plan_input(cls, plan_input: list[dict], body) -> "Addresses": addresses = [] diff --git a/etl/epc_clean/epc_attributes/RoofAttributes.py b/etl/epc_clean/epc_attributes/RoofAttributes.py index 98998e5a..7e2aed2c 100644 --- a/etl/epc_clean/epc_attributes/RoofAttributes.py +++ b/etl/epc_clean/epc_attributes/RoofAttributes.py @@ -74,6 +74,8 @@ class RoofAttributes(Definitions): "insulation_thickness", ] + NODATA_NULLS = ["insulation_thickness", "thermal_transmittance", "thermal_transmittance_unit"] + def __init__(self, description: str): """ :param description: Description of the roof. @@ -153,6 +155,10 @@ class RoofAttributes(Definitions): if self.nodata: for key in self.DEFAULT_KEYS: result[key] = False + # Insulation thickness, thermal transmittance and thermal transmittance unit are set to None for nodata + # cases + for k in self.NODATA_NULLS: + result[k] = None return result description = self.description diff --git a/etl/epc_clean/tests/test_roof_attributes.py b/etl/epc_clean/tests/test_roof_attributes.py index 33c6a829..e400f95a 100644 --- a/etl/epc_clean/tests/test_roof_attributes.py +++ b/etl/epc_clean/tests/test_roof_attributes.py @@ -26,11 +26,10 @@ class TestRoofAttributes: def test_empty_str(self): # Test initialization with an empty description assert RoofAttributes('').process() == { - 'thermal_transmittance': False, 'thermal_transmittance_unit': False, 'is_pitched': False, + 'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False, 'is_roof_room': False, 'is_loft': False, 'is_flat': False, 'is_thatched': False, 'is_at_rafters': False, - 'is_assumed': False, 'has_dwelling_above': False, 'is_valid': False, 'insulation_thickness': False + 'is_assumed': False, 'has_dwelling_above': False, 'is_valid': False, 'insulation_thickness': None } - assert set(list(RoofAttributes('').process().values())) == {False} def test_clean_roof(self): result = RoofAttributes('Pitched, 270 mm loft insulation').process() @@ -92,15 +91,6 @@ class TestRoofAttributes: with pytest.raises(ValueError): RoofAttributes('nonsense string').process() - def test_clean_roof_no_description(self): - roof = RoofAttributes('').process() - assert roof == { - 'thermal_transmittance': False, 'thermal_transmittance_unit': False, 'is_pitched': False, - 'is_roof_room': False, 'is_loft': False, 'is_flat': False, 'is_thatched': False, - 'is_at_rafters': False, 'is_assumed': False, 'has_dwelling_above': False, 'is_valid': False, - 'insulation_thickness': False - } - def test_clean_roof_edge_cases(self): # Insulation thickness edge case assert RoofAttributes('Pitched, 99999 mm loft insulation').process()['insulation_thickness'] == "99999" diff --git a/recommendations/RoofRecommendations.py b/recommendations/RoofRecommendations.py index f88a672b..0021edcc 100644 --- a/recommendations/RoofRecommendations.py +++ b/recommendations/RoofRecommendations.py @@ -59,9 +59,9 @@ class RoofRecommendations: # Extract the insulation thickness from the roof, which is used throughout this method self.insulation_thickness = convert_thickness_to_numeric( - self.property.roof["insulation_thickness"], - self.property.roof["is_pitched"], - self.property.roof["is_flat"] + string_thickness=self.property.roof["insulation_thickness"], + is_pitched=self.property.roof["is_pitched"], + is_flat=self.property.roof["is_flat"] ) @classmethod @@ -300,6 +300,10 @@ class RoofRecommendations: ): return False + if self.property.roof["original_description"] is None: + # There is no description so we cannot make an assessment + return False + return True @staticmethod diff --git a/recommendations/tests/test_roof_recommendations.py b/recommendations/tests/test_roof_recommendations.py index 0879757f..64a4d9d6 100644 --- a/recommendations/tests/test_roof_recommendations.py +++ b/recommendations/tests/test_roof_recommendations.py @@ -8,6 +8,30 @@ from recommendations.tests.test_data.materials import materials class TestRoofRecommendations: + def test_null_roof_description(self): + epc_record = EPCRecord() + epc_record.prepared_epc = { + "county": "Cambridgeshire", + } + property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record) + property_instance.age_band = "F" + property_instance.insulation_floor_area = 100 + property_instance.roof = { + 'original_description': None, + 'clean_description': None, + 'thermal_transmittance': None, + 'thermal_transmittance_unit': None, + 'is_pitched': True, 'is_roof_room': False, 'is_loft': False, 'is_flat': False, 'is_thatched': False, + 'is_at_rafters': False, 'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True, + 'insulation_thickness': 'none', 'roof_thermal_transmittance': None, 'roof_insulation_thickness': None + } + property_instance.already_installed = [] + + roof_recommender = RoofRecommendations(property_instance=property_instance, materials=materials) + roof_recommender.recommend(phase=0) + + assert not roof_recommender.recommendations + def test_loft_insulation_recommendation_no_insulation(self): epc_record = EPCRecord() epc_record.prepared_epc = {