diff --git a/backend/condition/domain/asset_condition.py b/backend/condition/domain/asset_condition.py index df87fd9f..2b7946c2 100644 --- a/backend/condition/domain/asset_condition.py +++ b/backend/condition/domain/asset_condition.py @@ -6,6 +6,9 @@ from backend.condition.domain.element import Element @dataclass class AssetCondition: uprn: int - element: Element - condition_description: str - renewal_year: Optional[int] = None \ No newline at end of file + element: Element # TODO: should HHSRS elements be handled differently? + condition_description: str # TODO: this probably needs to be some sort of enum so it's searchable/filterable on the frontend + quantity: int + renewal_year: Optional[int] = None + source: Optional[str] = None + # TODO: add install_date diff --git a/backend/condition/domain/element.py b/backend/condition/domain/element.py index 89246f9c..021c8492 100644 --- a/backend/condition/domain/element.py +++ b/backend/condition/domain/element.py @@ -1,4 +1,123 @@ -from enum import Enum +from enum import StrEnum -class Element(Enum): - pass \ No newline at end of file + +class Element(StrEnum): + AHR_CAT = "Accessible Housing Register Category" + ASBESTOS = "Asbestos Present" + ASSETSAREA = "Assets Area for Decent Homes and Investment" + DECNTHMINC = "Include for Decent Homes Reporting - LBWF Stock" + EICINSFREQ = "EICR - Elec Install Conditions Report Inspection Frequency" + EXTBALCONY = "Private Balconies in External Area" + EXTBKSDDR1 = "Back and Side Doors 1 in External Area" + EXTBKSDDR2 = "Back and Side Doors 2 in External Area" + EXTBPOINTG = "Brickwork Pointing in External Area" + EXTCHIMNEY = "Chimneys in External Area" + EXTDWNPTYP = "Downpipes in External Area" + EXTDRPKERB = "Drop Kerb in External Area" + EXTEXTDECS = "External Decorations in External Area" + EXTFASOFBR = "Fascia / Soffit / Bargeboard in External Area" + EXTGARDOOR = "Garage Door in External Area" + EXTGARROOF = "Garage Roof in External Area" + EXTGARSTDR = "Garage and Store Doors in External Area" + EXTGARSTRF = "Garage and Store Roofs in External Area" + EXTGARSTWD = "Garage and Store Windows in External Area" + EXTGARWDWS = "Garage Windows in External Area" + EXTGUTRTYP = "Gutters in External Area" + EXTHARDSTD = "Hardstanding in External Area" + EXTINTDWNP = "Internal Downpipes in External Area" + EXTLINTELS = "Lintels in External Area" + EXTOUTBOH = "Overhaul of Outbuilding in External Area" + EXTPARKING = "Parking in External Area" + EXTPCHCNPY = "Porch and / or Canopy in External Area" + EXTPTFRDR1 = "Patio and French Doors 1 in External Area" + EXTROOF1 = "Roof Covering 1 in External Area" + EXTROOF2 = "Roof Covering 2 in External Area" + EXTROOF3 = "Roof Covering 3 in External Area" + EXTRFSTR1 = "Roof Structure 1 in External Area" + EXTRFSTR2 = "Roof Structure 2 in External Area" + EXTRFSTR3 = "Roof Structure 3 in External Area" + EXTSTOREY = "Number of Storeys within the Property or Block" + EXTSTRDOOR = "Store Door in External Area" + EXTSTRINSP = "Structural Defects in External Area" + EXTSTRROOF = "Store Roof in External Area" + EXTSTRWDWS = "Store Windows in External Area" + EXTWALLFN1 = "Wall Finish 1 in External Area" + EXTWALLFN2 = "Wall Finish 2 in External Area" + EXTWALLINS = "Wall Insulation Improvement in External Area" + EXTWALLSPL = "Wall Spalling in External Area" + EXTWALLSTR = "Wall Structure in External Area" + EXTWNDWS1 = "Windows 1 in External Area" + EXTWNDWS2 = "Windows 2 in External Area" + FFHHDAMP = "Fitness for Human Habitation - Serious problem with damp" + FFHHDRNWC = "Fitness for Human Habitation - Problems with the drainage or the lavatories" + FFHHHCWAT = "Fitness for Human Habitation - Problem with the supply of hot and cold water" + FFHHNEGLC = "Fitness for Human Habitation - Building neglected and is in a bad condition" + FFHHNONAT = "Fitness for Human Habitation - Not enough natural light" + FFHHNOVEN = "Fitness for Human Habitation - Not enough ventilation" + FFHHPRPCK = "Fitness for Human Habitation - Difficult to prepare and cook food or wash up" + FFHHUNLAY = "Fitness for Human Habitation - Unsafe layout" + FFHHUNSTA = "Fitness for Human Habitation - Building is unstable" + FRARISKRTG = "Fire Risk Assessment Rating" + FRAEVACSTR = "Fire Risk Assessment Evacuation Strategy" + FRATYPE = "Fire Risk Assessment Type" + FLVL = "Floor Level of Front Door" + HHSRSASB = "Asbestos (and MMF)" + HHSRSBIOC = "Biocides" + HHSRSCO = "Carbon monoxide" + HHSRSCOLD = "Excess cold" + HHSRSCLOW = "Collision hazards and low headroom" + HHSRSCROWD = "Crowding and space" + HHSRSDAMP = "Damp and mould growth" + HHSRSDOMES = "Domestic hygeine, Pests and Refuse" + HHSRSELEC = "Electrical hazards" + HHSRSENTRP = "Collision and entrapment" + HHSRSENTRY = "Entry by intruders" + HHSRSEXPLO = "Explosions" + HHSRSFBATH = "Falls associated with baths etc" + HHSRSFBETW = "Falling between levels" + HHSRSFIRE = "Fire" + HHSRSFLAME = "Flames, hot surfaces etc" + HHSRSFLEVE = "Falling on level surfaces etc" + HHSRSFOOD = "Food safety" + HHSRSFSTAI = "Falling on stairs etc" + HHSRSFUEL = "Uncombusted fuel gas" + HHSRSHEAT = "Excess heat" + HHSRSLEAD = "Lead" + HHSRSLIGHT = "Lighting" + HHSRSNO2 = "Nitrogen dioxide" + HHSRSNOISE = "Noise" + HHSRSORGAN = "Volatile organic compounds" + HHSRSPERS = "Personal hygeine, Sanitation and Drainage" + HHSRSPOSI = "Position and operability of amenities etc" + HHSRSRADIA = "Radiation" + HHSRSSO2 = "Sulphur dioxide and smoke" + HHSRSSTRUC = "Structural collapse and falling elements" + HHSRSWATER = "Water supply" + INTACCRAMP = "Access Ramp 1:12 Gradient to Property" + INTADDWCW = "Additional WCs and / or WHBs in Property" + INTBTHADEQ = "Adequacy of Bathroom Location in Property" + INTBTHREML = "Source of Bathroom Remaining Life in Property" + INTBTHRLOC = "Location of Bathroom in Property" + INTBOILERF = "Boiler Fuel in Property" + INTCHEXTNT = "Extent of Central Heating in Property" + INTCKRLOC = "Adequacy of Cooker Location in Property" + INTCOMHTG = "Community Heating in Property" + INTELECTRC = "Electrics Required in Property" + INTFLRLVL = "Floor Level Location for Property" + INTFRDOOR = "Type and Location of Front Door in Property" + INTFRDRFRR = "Front Door Fire Rating in Property" + INTGASAVAI = "Gas Available in Property" + INTHEATREC = "Heat Recovery Units in Property" + INTHTDISYS = "Heating Distribution System in Property" + INTHTIMP = "Heating Improvement Required in Property" + INTKITADEQ = "Adequacy of Kitchen and Type in Property" + INTKITREML = "Source of Kitchen Remaining Life in Property" + INTLOFTINS = "Size in mm of Loft Insulation Thickness in Property" + INTNSEINSL = "Adequacy of Noise Insulation in Property" + INTPROGHTG = "Programmable Heating in Property" + INTSMKDET = "Smoke Detectors in Property" + INTSTEPSFD = "Number of Steps to Front Door for Property" + INTTNTINST = "Tenant Installed Kitchen in Property" + INTWDWTYPE = "Windows in Property" + INTWTRHTNG = "Type of Water Heating in Property" + QUALITYSTD = "Quality standard" diff --git a/backend/condition/domain/mapping/lbwf_mapper.py b/backend/condition/domain/mapping/lbwf_mapper.py index 154f3db4..0bbdc0d6 100644 --- a/backend/condition/domain/mapping/lbwf_mapper.py +++ b/backend/condition/domain/mapping/lbwf_mapper.py @@ -1,9 +1,15 @@ -from typing import Any, List +from typing import Any, List, Optional from backend.condition.domain.asset_condition import AssetCondition +from backend.condition.domain.element import Element from backend.condition.domain.mapping.mapper import Mapper +from backend.condition.parsing.records.lbwf.lbwf_asset_condition import LbwfAssetCondition class LbwfMapper(Mapper): def map_asset_conditions(self, client_data: List[Any]) -> List[AssetCondition]: + raise NotImplementedError + + @staticmethod + def _map_element(lbwf_asset: LbwfAssetCondition) -> Optional[Element]: raise NotImplementedError \ No newline at end of file diff --git a/backend/condition/tests/mapping/test_lbwf_mapper.py b/backend/condition/tests/mapping/test_lbwf_mapper.py new file mode 100644 index 00000000..e26b9928 --- /dev/null +++ b/backend/condition/tests/mapping/test_lbwf_mapper.py @@ -0,0 +1,239 @@ +from typing import List +import pytest +from datetime import date + +from backend.condition.domain.mapping.lbwf_mapper import LbwfMapper +from backend.condition.parsing.records.lbwf.lbwf_house import LbwfHouse +from backend.condition.parsing.records.lbwf.lbwf_asset_condition import LbwfAssetCondition +from backend.condition.domain.element import Element +from backend.condition.domain.asset_condition import AssetCondition + +def test_lbwf_mapper_maps_house(): + # arrange + lbwf_house = LbwfHouse( + uprn=1, + reference=100, + address="123 Fake Street, London, A10 1AB", + epc="F", + shdf="NO", + house="HOUSE", + fail_decency=2025, + assets=[ + LbwfAssetCondition( + prop_ref=100, + domna=100, + address="123 Fake Street, London, A10 1AB", + ownership="LBWF_OWNED", + prop_status="OCCP", + prop_type="HOU", + prop_sub_type="TERRACED", + element_group="ASSETS", + element_code="AHR_CAT", + element_code_description="Accessible Housing Register Category", + attribute_code="F", + attribute_code_description="General Needs", + element_date_value=None, + element_numerical_value=None, + element_text_value=None, + quantity=1, + install_date=None, + remaining_life=None, + element_comments=None, + ), + LbwfAssetCondition( + prop_ref=100, + domna=100, + address="123 Fake Street, London, A10 1AB", + ownership="LBWF_OWNED", + prop_status="OCCP", + prop_type="HOU", + prop_sub_type="TERRACED", + element_group="ASSETS", + element_code="FLVL", + element_code_description="Floor Level of Front Door", + attribute_code="0G", + attribute_code_description="Ground Floor", + element_date_value=None, + element_numerical_value=None, + element_text_value=None, + quantity=1, + install_date=None, + remaining_life=None, + element_comments=None, + ), + LbwfAssetCondition( + prop_ref=100, + domna=100, + address="123 Fake Street, London, A10 1AB", + ownership="LBWF_OWNED", + prop_status="OCCP", + prop_type="HOU", + prop_sub_type="TERRACED", + element_group="ASSETS", + element_code="ASBESTOS", + element_code_description="Asbestos Present", + attribute_code="YES", + attribute_code_description="Yes", + element_date_value=None, + element_numerical_value=None, + element_text_value=None, + quantity=None, + install_date=None, + remaining_life=None, + element_comments="Source of Data = ACT", + ), + LbwfAssetCondition( + prop_ref=100, + domna=100, + address="123 Fake Street, London, A10 1AB", + ownership="LBWF_OWNED", + prop_status="OCCP", + prop_type="HOU", + prop_sub_type="TERRACED", + element_group="ASSETS", + element_code="INTBTHRLOC", + element_code_description="Location of Bathroom in Property", + attribute_code="ENTRANCE", + attribute_code_description="Bathroom on Entrance Level in Property", + element_date_value=None, + element_numerical_value=None, + element_text_value=None, + quantity=1, + install_date=None, + remaining_life=None, + element_comments="Source of Data = Codeman", + ), + LbwfAssetCondition( + prop_ref=100, + domna=100, + address="123 Fake Street, London, A10 1AB", + ownership="LBWF_OWNED", + prop_status="OCCP", + prop_type="HOU", + prop_sub_type="TERRACED", + element_group="ASSETS", + element_code="INTCHEXTNT", + element_code_description="Extent of Central Heating in Property", + attribute_code="NONE", + attribute_code_description="No Central Heating in Property", + element_date_value=None, + element_numerical_value=None, + element_text_value=None, + quantity=1, + install_date=None, + remaining_life=None, + element_comments="Source of Data = Codeman", + ), + LbwfAssetCondition( + prop_ref=100, + domna=100, + address="123 Fake Street, London, A10 1AB", + ownership="LBWF_OWNED", + prop_status="OCCP", + prop_type="HOU", + prop_sub_type="TERRACED", + element_group="ASSETS", + element_code="HHSRSFIRE", + element_code_description="Fire", + attribute_code="TYPRISK", + attribute_code_description="Category 4 - Typical Risk", + element_date_value=None, + element_numerical_value=None, + element_text_value=None, + quantity=1, + install_date=None, + remaining_life=None, + element_comments="Source of Data = Morgan Sindall", + ), + LbwfAssetCondition( + prop_ref=100, + domna=100, + address="123 Fake Street, London, A10 1AB", + ownership="LBWF_OWNED", + prop_status="OCCP", + prop_type="HOU", + prop_sub_type="TERRACED", + element_group="ASSETS", + element_code="EXTWALLFN1", + element_code_description="Wall Finish 1 in External Area", + attribute_code="RENDERPBBL", + attribute_code_description="Render or Pebbledash Wall Finish 1 in External Area", + element_date_value=None, + element_numerical_value=None, + element_text_value=None, + quantity=1, + install_date=date(2009,4,1), + remaining_life=26, + element_comments="Source of Data = Codeman", + ), + ] + ) + mapper = LbwfMapper() + + current_year = 2026 + + expected_assets: List[AssetCondition] = [ + AssetCondition( + uprn=1, + element=Element.AHR_CAT, + condition_description="General Needs", + quantity=1, + renewal_year=None, + source=None + ), + AssetCondition( + uprn=1, + element=Element.FLVL, + condition_description="Ground Floor", + quantity=1, + renewal_year=None, + source=None + ), + AssetCondition( + uprn=1, + element=Element.ASBESTOS, + condition_description="Yes", + quantity=None, + renewal_year=None, + source="Source of Data = ACT" + ), + AssetCondition( + uprn=1, + element=Element.INTBTHRLOC, + condition_description="Bathroom on Entrance Level in Property", + quantity=1, + renewal_year=None, + source="Source of Data = Codeman" + ), + AssetCondition( + uprn=1, + element=Element.INTCHEXTNT, + condition_description="No Central Heating in Property", + quantity=1, + renewal_year=None, + source="Source of Data = Codeman" + ), + AssetCondition( + uprn=1, + element=Element.HHSRSFIRE, + condition_description="Category 4 - Typical Risk", + quantity=1, + renewal_year=None, + source="Source of Data = Morgan Sindall" + ), + AssetCondition( + uprn=1, + element=Element.EXTWALLFN1, + condition_description="Render or Pebbledash Wall Finish 1 in External Area", + quantity=1, + renewal_year=2052, + source="Source of Data = Codeman" + ), + + ] + + # act + actual_assets: List[AssetCondition] = mapper.map_asset_conditions(lbwf_house) + + # assert + assert actual_assets == expected_assets \ No newline at end of file diff --git a/backend/condition/tests/parsing/test_lbwf_parser.py b/backend/condition/tests/parsing/test_lbwf_parser.py index 7556b845..beb81a03 100644 --- a/backend/condition/tests/parsing/test_lbwf_parser.py +++ b/backend/condition/tests/parsing/test_lbwf_parser.py @@ -112,7 +112,7 @@ def lbwf_homes_xlsx_bytes() -> BytesIO: return stream -def test_lbwf_parser_passes_houses(lbwf_homes_xlsx_bytes): +def test_lbwf_parser_parses_houses(lbwf_homes_xlsx_bytes): # arrange parser = LbwfParser()