mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Merge branch 'main' into feature/automate-categorisation-of-works
This commit is contained in:
commit
e0857ab7a2
10 changed files with 76 additions and 29 deletions
1
.idea/inspectionProfiles/profiles_settings.xml
generated
1
.idea/inspectionProfiles/profiles_settings.xml
generated
|
|
@ -1,5 +1,6 @@
|
||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<settings>
|
<settings>
|
||||||
|
<option name="PROJECT_PROFILE" value="Default" />
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
<version value="1.0" />
|
<version value="1.0" />
|
||||||
</settings>
|
</settings>
|
||||||
|
|
|
||||||
|
|
@ -69,24 +69,24 @@ def app():
|
||||||
Property UPRN
|
Property UPRN
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Aspire"
|
data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/West Kent"
|
||||||
data_filename = "ASPIRE ASSET LIST.xlsx"
|
data_filename = "West Kent Asset List.xlsx"
|
||||||
sheet_name = "Asset List"
|
sheet_name = "Sheet1"
|
||||||
postcode_column = "Postcode"
|
postcode_column = "POSTCODE"
|
||||||
address1_column = None
|
address1_column = None
|
||||||
address1_method = "house_number_extraction"
|
address1_method = "house_number_extraction"
|
||||||
fulladdress_column = "Address"
|
fulladdress_column = "ADDRESS"
|
||||||
address_cols_to_concat = []
|
address_cols_to_concat = []
|
||||||
missing_postcodes_method = None
|
missing_postcodes_method = None
|
||||||
landlord_year_built = None
|
landlord_year_built = None
|
||||||
landlord_os_uprn = None
|
landlord_os_uprn = None
|
||||||
landlord_property_type = "Property Type"
|
landlord_property_type = "PROPERTY TYPE"
|
||||||
landlord_built_form = None
|
landlord_built_form = None
|
||||||
landlord_wall_construction = None
|
landlord_wall_construction = "wall combined"
|
||||||
landlord_roof_construction = None
|
landlord_roof_construction = "HEATING SYSTEM"
|
||||||
landlord_heating_system = None
|
landlord_heating_system = None
|
||||||
landlord_existing_pv = None
|
landlord_existing_pv = None
|
||||||
landlord_property_id = "LLUPRN"
|
landlord_property_id = "UPRN"
|
||||||
landlord_sap = None
|
landlord_sap = None
|
||||||
outcomes_filename = None
|
outcomes_filename = None
|
||||||
outcomes_sheetname = None
|
outcomes_sheetname = None
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,18 @@ ROOF_CONSTRUCTION_MAPPINGS = {
|
||||||
'Flat: No Insulation': 'flat uninsulated',
|
'Flat: No Insulation': 'flat uninsulated',
|
||||||
'AnotherDwellingAbove: Unknown, PitchedNormalLoftAccess: 250mm': 'another dwelling above',
|
'AnotherDwellingAbove: Unknown, PitchedNormalLoftAccess: 250mm': 'another dwelling above',
|
||||||
'PitchedNormalLoftAccess: 175mm': 'pitched insulated',
|
'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'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -363,6 +363,12 @@ WALL_CONSTRUCTION_MAPPINGS = {
|
||||||
'Timber Frame, As Built': 'timber frame unknown insulation',
|
'Timber Frame, As Built': 'timber frame unknown insulation',
|
||||||
'Solid Brick, Internal Insulation': 'insulated solid brick',
|
'Solid Brick, Internal Insulation': 'insulated solid brick',
|
||||||
'Granite or Whinstone, As Built': 'granite or whinstone unknown insulation',
|
'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'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ def get_data(
|
||||||
searcher.find_property(skip_os=True)
|
searcher.find_property(skip_os=True)
|
||||||
|
|
||||||
# Check if we have a flat or appartment
|
# 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:
|
# Try again:
|
||||||
if SearchEpc.get_house_number(address=str(house_number), postcode=postcode) is None:
|
if SearchEpc.get_house_number(address=str(house_number), postcode=postcode) is None:
|
||||||
# Backup
|
# Backup
|
||||||
|
|
@ -252,12 +252,12 @@ def get_data(
|
||||||
searcher.find_property(skip_os=True)
|
searcher.find_property(skip_os=True)
|
||||||
|
|
||||||
# As a final resort, we estimate the EPC
|
# 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.property_type = property_type
|
||||||
searcher.ordnance_survey_client.built_form = built_form
|
searcher.ordnance_survey_client.built_form = built_form
|
||||||
searcher.find_property(skip_os=True)
|
searcher.find_property(skip_os=True)
|
||||||
|
|
||||||
if searcher.newest_epc is None:
|
if not searcher.newest_epc:
|
||||||
no_epc.append(home[row_id_name])
|
no_epc.append(home[row_id_name])
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import Iterator
|
||||||
from backend.addresses.Address import Address
|
from backend.addresses.Address import Address
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -12,6 +13,9 @@ class Addresses:
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self._addresses)
|
return len(self._addresses)
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[Address]:
|
||||||
|
return iter(self._addresses)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_plan_input(cls, plan_input: list[dict], body) -> "Addresses":
|
def from_plan_input(cls, plan_input: list[dict], body) -> "Addresses":
|
||||||
addresses = []
|
addresses = []
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,8 @@ class RoofAttributes(Definitions):
|
||||||
"insulation_thickness",
|
"insulation_thickness",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
NODATA_NULLS = ["insulation_thickness", "thermal_transmittance", "thermal_transmittance_unit"]
|
||||||
|
|
||||||
def __init__(self, description: str):
|
def __init__(self, description: str):
|
||||||
"""
|
"""
|
||||||
:param description: Description of the roof.
|
:param description: Description of the roof.
|
||||||
|
|
@ -153,6 +155,10 @@ class RoofAttributes(Definitions):
|
||||||
if self.nodata:
|
if self.nodata:
|
||||||
for key in self.DEFAULT_KEYS:
|
for key in self.DEFAULT_KEYS:
|
||||||
result[key] = False
|
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
|
return result
|
||||||
|
|
||||||
description = self.description
|
description = self.description
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,10 @@ class TestRoofAttributes:
|
||||||
def test_empty_str(self):
|
def test_empty_str(self):
|
||||||
# Test initialization with an empty description
|
# Test initialization with an empty description
|
||||||
assert RoofAttributes('').process() == {
|
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_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):
|
def test_clean_roof(self):
|
||||||
result = RoofAttributes('Pitched, 270 mm loft insulation').process()
|
result = RoofAttributes('Pitched, 270 mm loft insulation').process()
|
||||||
|
|
@ -92,15 +91,6 @@ class TestRoofAttributes:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
RoofAttributes('nonsense string').process()
|
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):
|
def test_clean_roof_edge_cases(self):
|
||||||
# Insulation thickness edge case
|
# Insulation thickness edge case
|
||||||
assert RoofAttributes('Pitched, 99999 mm loft insulation').process()['insulation_thickness'] == "99999"
|
assert RoofAttributes('Pitched, 99999 mm loft insulation').process()['insulation_thickness'] == "99999"
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,9 @@ class RoofRecommendations:
|
||||||
|
|
||||||
# Extract the insulation thickness from the roof, which is used throughout this method
|
# Extract the insulation thickness from the roof, which is used throughout this method
|
||||||
self.insulation_thickness = convert_thickness_to_numeric(
|
self.insulation_thickness = convert_thickness_to_numeric(
|
||||||
self.property.roof["insulation_thickness"],
|
string_thickness=self.property.roof["insulation_thickness"],
|
||||||
self.property.roof["is_pitched"],
|
is_pitched=self.property.roof["is_pitched"],
|
||||||
self.property.roof["is_flat"]
|
is_flat=self.property.roof["is_flat"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -300,6 +300,10 @@ class RoofRecommendations:
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if self.property.roof["original_description"] is None:
|
||||||
|
# There is no description so we cannot make an assessment
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,30 @@ from recommendations.tests.test_data.materials import materials
|
||||||
|
|
||||||
class TestRoofRecommendations:
|
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):
|
def test_loft_insulation_recommendation_no_insulation(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.prepared_epc = {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue