import json import os import pytest from backend.documents_parser.extractor import PasHubRdSapSiteNotesExtractor from datatypes.epc.surveys.pashub_rdsap_site_notes import ( BuildingConstruction, BuildingMeasurements, ExtensionConstruction, ExtensionMeasurements, ExtensionRoofSpace, FloorConstruction, FloorMeasurement, General, MainBuildingConstruction, MainBuildingMeasurements, PasHubRdSapSiteNotes, RoofSpace, RoofSpaceDetail, ) FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures") def load_text_fixture() -> list[str]: with open(os.path.join(FIXTURES, "site_notes_example_text.json")) as f: return json.load(f) class TestGeneral: @pytest.fixture def general(self) -> General: return PasHubRdSapSiteNotesExtractor(load_text_fixture()).extract_general() def test_epc_checked_before_assessment(self, general: General) -> None: assert general.epc_checked_before_assessment is True def test_epc_exists_at_point_of_assessment(self, general: General) -> None: assert general.epc_exists_at_point_of_assessment is False def test_inspection_date(self, general: General) -> None: assert general.inspection_date == "2025-09-25" def test_transaction_type(self, general: General) -> None: assert general.transaction_type == "Grant-Scheme (ECO, RHI, etc.)" def test_tenure(self, general: General) -> None: assert general.tenure == "Rented Social" def test_property_type(self, general: General) -> None: assert general.property_type == "House" def test_detachment_type(self, general: General) -> None: assert general.detachment_type == "Mid-terrace" def test_number_of_storeys(self, general: General) -> None: assert general.number_of_storeys == 2 def test_number_of_extensions(self, general: General) -> None: assert general.number_of_extensions == 1 def test_electricity_smart_meter(self, general: General) -> None: assert general.electricity_smart_meter is True def test_mains_gas_available(self, general: General) -> None: assert general.mains_gas_available is True def test_measurements_location(self, general: General) -> None: assert general.measurements_location == "Internal" def test_full_general(self, general: General) -> None: assert general == General( epc_checked_before_assessment=True, epc_exists_at_point_of_assessment=False, inspection_date="2025-09-25", transaction_type="Grant-Scheme (ECO, RHI, etc.)", tenure="Rented Social", property_type="House", detachment_type="Mid-terrace", number_of_storeys=2, terrain_type="Suburban", number_of_extensions=1, electricity_smart_meter=True, electric_meter_type="Single", dwelling_export_capable=True, mains_gas_available=True, gas_smart_meter=True, gas_meter_accessible=True, measurements_location="Internal", ) class TestBuildingConstruction: @pytest.fixture def construction(self) -> BuildingConstruction: return PasHubRdSapSiteNotesExtractor( load_text_fixture() ).extract_building_construction() def test_main_building_wall_u_value_known_is_false( self, construction: BuildingConstruction ) -> None: assert construction.main_building.wall_u_value_known is False def test_main_building_wall_thickness_mm( self, construction: BuildingConstruction ) -> None: assert construction.main_building.wall_thickness_mm == 310 def test_main_building_filled_cavity_indicators_present( self, construction: BuildingConstruction ) -> None: assert ( construction.main_building.filled_cavity_indicators == "evidence of cavity fill drill holes" ) def test_extension_filled_cavity_indicators_absent( self, construction: BuildingConstruction ) -> None: assert construction.extensions is not None assert construction.extensions[0].filled_cavity_indicators is None def test_one_extension(self, construction: BuildingConstruction) -> None: assert construction.extensions is not None assert len(construction.extensions) == 1 def test_extension_id(self, construction: BuildingConstruction) -> None: assert construction.extensions is not None assert construction.extensions[0].id == 1 def test_full_building_construction( self, construction: BuildingConstruction ) -> None: assert construction == BuildingConstruction( main_building=MainBuildingConstruction( age_range="1950-1966", age_indicators="local knowledge, enquiries of owner", walls_construction_type="Cavity", cavity_construction_indicators="wall thickness over 270 mm", walls_insulation_type="Filled Cavity", filled_cavity_indicators="evidence of cavity fill drill holes", thermal_conductivity_of_wall_insulation="Unknown", wall_u_value_known=False, wall_thickness_mm=310, party_wall_construction_type="Cavity Masonry, Filled", ), floor=FloorConstruction( floor_type="Ground Floor", floor_construction="Solid", floor_insulation_type="As Built", floor_u_value_known=False, ), extensions=[ ExtensionConstruction( id=1, age_range="2003-2006", age_indicators="local knowledge, enquiries of owner", walls_construction_type="Cavity", cavity_construction_indicators="wall thickness over 270 mm", walls_insulation_type="As built", thermal_conductivity_of_wall_insulation="Unknown", wall_u_value_known=False, wall_thickness_mm=310, party_wall_construction_type="Cavity Masonry, Filled", filled_cavity_indicators=None, ) ], ) class TestBuildingMeasurements: @pytest.fixture def measurements(self) -> BuildingMeasurements: return PasHubRdSapSiteNotesExtractor( load_text_fixture() ).extract_building_measurements() def test_main_building_has_two_floors( self, measurements: BuildingMeasurements ) -> None: assert len(measurements.main_building.floors) == 2 def test_main_building_floor_area( self, measurements: BuildingMeasurements ) -> None: assert measurements.main_building.floors[0].area_m2 == 35.68 def test_integer_token_parses_to_float( self, measurements: BuildingMeasurements ) -> None: # "11" in the PDF (no decimal) should parse to 11.0 assert measurements.main_building.floors[1].heat_loss_perimeter_m == 11.0 def test_extension_measurements_present( self, measurements: BuildingMeasurements ) -> None: assert measurements.extensions is not None assert len(measurements.extensions) == 1 def test_extension_id(self, measurements: BuildingMeasurements) -> None: assert measurements.extensions is not None assert measurements.extensions[0].id == 1 def test_full_building_measurements( self, measurements: BuildingMeasurements ) -> None: assert measurements == BuildingMeasurements( main_building=MainBuildingMeasurements( floors=[ FloorMeasurement( name="Floor 1", area_m2=35.68, height_m=2.19, heat_loss_perimeter_m=13.44, pwl_m=10.62, ), FloorMeasurement( name="Floor 0", area_m2=35.68, height_m=2.17, heat_loss_perimeter_m=11.0, pwl_m=10.62, ), ] ), extensions=[ ExtensionMeasurements( id=1, floors=[ FloorMeasurement( name="Floor 0", area_m2=3.8, height_m=2.0, heat_loss_perimeter_m=5.7, pwl_m=0.0, ) ], ) ], ) class TestRoofSpace: @pytest.fixture def roof_space(self) -> RoofSpace: return PasHubRdSapSiteNotesExtractor(load_text_fixture()).extract_roof_space() def test_main_building_insulation_thickness_mm( self, roof_space: RoofSpace ) -> None: assert roof_space.main_building.insulation_thickness_mm == 100 def test_main_building_insulation_thickness_string_absent( self, roof_space: RoofSpace ) -> None: assert roof_space.main_building.insulation_thickness is None def test_main_building_rooms_in_roof(self, roof_space: RoofSpace) -> None: assert roof_space.main_building.rooms_in_roof is False def test_main_building_roof_u_value_known(self, roof_space: RoofSpace) -> None: assert roof_space.main_building.roof_u_value_known is False def test_extension_uses_string_thickness(self, roof_space: RoofSpace) -> None: assert roof_space.extensions is not None assert roof_space.extensions[0].insulation_thickness == "As built" assert roof_space.extensions[0].insulation_thickness_mm is None def test_full_roof_space(self, roof_space: RoofSpace) -> None: assert roof_space == RoofSpace( main_building=RoofSpaceDetail( construction_type="Pitched roof (Slates or tiles), Access to loft", insulation_at="Joists", roof_u_value_known=False, cavity_wall_construction_indicators="cavity visible in roof space", rooms_in_roof=False, insulation_thickness_mm=100, insulation_thickness=None, ), extensions=[ ExtensionRoofSpace( id=1, construction_type="Pitched roof, Sloping ceiling", insulation_at="Sloping ceiling insulation", roof_u_value_known=False, cavity_wall_construction_indicators="No indicator of construction visible", rooms_in_roof=False, insulation_thickness_mm=None, insulation_thickness="As built", ) ], )