import os from datetime import date import pytest from backend.documents_parser.extractor import PasHubRdSapSiteNotesExtractor from backend.documents_parser.pdf import pdf_to_text_list from datatypes.epc.domain.epc_property_data import ( BuildingPartIdentifier, EpcPropertyData, InstantaneousWwhrs, MainHeatingDetail, SapBuildingPart, SapEnergySource, SapFloorDimension, SapHeating, SapVentilation, SapWindow, ShowerOutlet, ShowerOutlets, ) from datatypes.epc.domain.mapper import EpcPropertyDataMapper PDF_PATH = os.path.join(os.path.dirname(__file__), "fixtures", "PasHubSiteNotes_1.pdf") PDF_PATH_2 = os.path.join( os.path.dirname(__file__), "fixtures", "PasHubSiteNotes_2.pdf" ) class TestPdfToEpcPropertyData: @pytest.fixture def result(self) -> EpcPropertyData: with open(PDF_PATH, "rb") as f: pdf_bytes = f.read() site_notes = PasHubRdSapSiteNotesExtractor( pdf_to_text_list(pdf_bytes) ).extract() return EpcPropertyDataMapper.from_site_notes(site_notes) def test_full_epc_property_data(self, result: EpcPropertyData) -> None: assert result == EpcPropertyData( dwelling_type="Mid-terrace house", inspection_date=date(2025, 9, 25), tenure="Rented Social", transaction_type="Grant-Scheme (ECO, RHI, etc.)", roofs=[], walls=[], floors=[], main_heating=[], door_count=2, sap_heating=SapHeating( instantaneous_wwhrs=InstantaneousWwhrs(), main_heating_details=[ MainHeatingDetail( has_fghrs=False, main_fuel_type="Mains gas", heat_emitter_type="Radiators", emitter_temperature="Unknown", main_heating_control="Programmer, room thermostat and TRVs", fan_flue_present=True, condensing=True, weather_compensator=False, central_heating_pump_age_str="Unknown", ) ], has_fixed_air_conditioning=False, shower_outlets=ShowerOutlets( shower_outlet=ShowerOutlet( shower_outlet_type="Non-Electric Shower" ), ), ), sap_windows=[ SapWindow( frame_material="Wooden or PVC", glazing_gap="16 mm or more", orientation="North West", window_type="Window", glazing_type="Double glazing, Unknown install date", window_width=2.3, window_height=1.2, draught_proofed=True, window_location="Main Building", window_wall_type="External wall", permanent_shutters_present=False, ), SapWindow( frame_material="Wooden or PVC", glazing_gap="16 mm or more", orientation="North West", window_type="Window", glazing_type="Double glazing, Unknown install date", window_width=1.0, window_height=1.2, draught_proofed=True, window_location="Main Building", window_wall_type="External wall", permanent_shutters_present=False, ), SapWindow( frame_material="Wooden or PVC", glazing_gap="16 mm or more", orientation="North East", window_type="Window", glazing_type="Double glazing, Unknown install date", window_width=1.0, window_height=0.9, draught_proofed=True, window_location="Main Building", window_wall_type="External wall", permanent_shutters_present=False, ), SapWindow( frame_material="Wooden or PVC", glazing_gap="16 mm or more", orientation="North", window_type="Window", glazing_type="Double glazing, Unknown install date", window_width=1.0, window_height=0.9, draught_proofed=True, window_location="Extension 1", window_wall_type="External wall", permanent_shutters_present=False, ), SapWindow( frame_material="Wooden or PVC", glazing_gap="16 mm or more", orientation="North East", window_type="Window", glazing_type="Double glazing, Unknown install date", window_width=1.7, window_height=0.9, draught_proofed=True, window_location="Extension 1", window_wall_type="External wall", permanent_shutters_present=False, ), SapWindow( frame_material="Wooden or PVC", glazing_gap="16 mm or more", orientation="North West", window_type="Window", glazing_type="Double glazing, Unknown install date", window_width=2.3, window_height=0.9, draught_proofed=True, window_location="Extension 1", window_wall_type="External wall", permanent_shutters_present=False, ), SapWindow( frame_material="Wooden or PVC", glazing_gap="16 mm or more", orientation="North West", window_type="Window", glazing_type="Double glazing, Unknown install date", window_width=1.2, window_height=1.0, draught_proofed=True, window_location="Extension 1", window_wall_type="External wall", permanent_shutters_present=False, ), SapWindow( frame_material="Wooden or PVC", glazing_gap="16 mm or more", orientation="North East", window_type="Window", glazing_type="Double glazing, Unknown install date", window_width=1.0, window_height=0.9, draught_proofed=True, window_location="Extension 1", window_wall_type="External wall", permanent_shutters_present=False, ), ], sap_energy_source=SapEnergySource( mains_gas=True, meter_type="Single", pv_battery_count=0, wind_turbines_count=0, gas_smart_meter_present=True, is_dwelling_export_capable=True, wind_turbines_terrain_type="Suburban", electricity_smart_meter_present=True, ), sap_building_parts=[ SapBuildingPart( identifier=BuildingPartIdentifier.MAIN, construction_age_band="1950-1966", wall_construction="Cavity", wall_insulation_type="Filled Cavity", wall_thickness_measured=True, party_wall_construction="Cavity Masonry, Filled", sap_floor_dimensions=[ SapFloorDimension( room_height_m=2.19, total_floor_area_m2=35.68, party_wall_length_m=10.62, heat_loss_perimeter_m=13.44, floor=1, ), SapFloorDimension( room_height_m=2.17, total_floor_area_m2=35.68, party_wall_length_m=10.62, heat_loss_perimeter_m=11.0, floor=0, ), ], wall_thickness_mm=310, roof_insulation_location="Joists", roof_insulation_thickness=100, floor_type="Ground Floor", floor_construction_type="Solid", floor_insulation_type_str="As Built", floor_u_value_known=False, ), SapBuildingPart( identifier=BuildingPartIdentifier.EXTENSION_1, construction_age_band="2003-2006", wall_construction="Cavity", wall_insulation_type="As built", wall_thickness_measured=True, party_wall_construction="Cavity Masonry, Filled", sap_floor_dimensions=[ SapFloorDimension( room_height_m=2.0, total_floor_area_m2=3.8, party_wall_length_m=0.0, heat_loss_perimeter_m=5.7, floor=0, ), ], wall_thickness_mm=310, roof_insulation_location="Sloping ceiling insulation", roof_insulation_thickness="As built", ), ], solar_water_heating=False, has_hot_water_cylinder=False, has_fixed_air_conditioning=False, wet_rooms_count=0, extensions_count=1, heated_rooms_count=0, open_chimneys_count=0, habitable_rooms_count=3, insulated_door_count=0, cfl_fixed_lighting_bulbs_count=1, led_fixed_lighting_bulbs_count=0, incandescent_fixed_lighting_bulbs_count=4, total_floor_area_m2=75.16, built_form="Mid-terrace", property_type="House", has_conservatory=False, blocked_chimneys_count=0, draughtproofed_door_count=2, address_line_1="40, Abbey Place", post_town="Crewe", postcode="CW1 4JR", report_reference="6EA2A86D-94CE-4792-8D49-AB495C744EDD", number_of_storeys=2, any_unheated_rooms=False, waste_water_heat_recovery="None", hydro=False, photovoltaic_array=False, sap_ventilation=SapVentilation( ventilation_type="Mechanical Extract - Decentralised", draught_lobby=False, pressure_test="No test", open_flues_count=0, closed_flues_count=0, boiler_flues_count=0, other_flues_count=0, extract_fans_count=0, passive_vents_count=0, flueless_gas_fires_count=0, ventilation_in_pcdf_database=False, ), ) class TestPdfToEpcPropertyDataFixture2: @pytest.fixture def result(self) -> EpcPropertyData: with open(PDF_PATH_2, "rb") as f: pdf_bytes = f.read() site_notes = PasHubRdSapSiteNotesExtractor( pdf_to_text_list(pdf_bytes) ).extract() return EpcPropertyDataMapper.from_site_notes(site_notes) def test_cylinder_insulation_thickness(self, result: EpcPropertyData) -> None: assert result.sap_heating.cylinder_insulation_thickness_mm == 38 def test_cylinder_size(self, result: EpcPropertyData) -> None: assert result.sap_heating.cylinder_size == "Normal (90-130 litres)" def test_secondary_heating_type(self, result: EpcPropertyData) -> None: assert result.sap_heating.secondary_heating_type == "Open fire in grate" PDF_PATH_3 = os.path.join( os.path.dirname(__file__), "fixtures", "PasHubSiteNotes_3.pdf" ) class TestPdfToEpcPropertyDataFixture3: @pytest.fixture def result(self) -> EpcPropertyData: with open(PDF_PATH_3, "rb") as f: pdf_bytes = f.read() site_notes = PasHubRdSapSiteNotesExtractor( pdf_to_text_list(pdf_bytes) ).extract() return EpcPropertyDataMapper.from_site_notes(site_notes) def test_immersion_heating_type(self, result: EpcPropertyData) -> None: assert result.sap_heating.immersion_heating_type == "Dual" def test_pv_connection(self, result: EpcPropertyData) -> None: assert ( result.sap_energy_source.pv_connection == "Connected to dwellings electricity meter" ) def test_photovoltaic_supply_percent_roof(self, result: EpcPropertyData) -> None: assert result.sap_energy_source.photovoltaic_supply is not None assert ( result.sap_energy_source.photovoltaic_supply.none_or_no_details.percent_roof_area == 45 ) def test_electric_storage_heater_fuel_type(self, result: EpcPropertyData) -> None: assert ( result.sap_heating.main_heating_details[0].main_fuel_type == "Electricity" ) PDF_PATH_4 = os.path.join( os.path.dirname(__file__), "fixtures", "PasHubSiteNotes_4.pdf" ) class TestPdfToEpcPropertyDataFixture4: @pytest.fixture def result(self) -> EpcPropertyData: with open(PDF_PATH_4, "rb") as f: pdf_bytes = f.read() site_notes = PasHubRdSapSiteNotesExtractor( pdf_to_text_list(pdf_bytes) ).extract() return EpcPropertyDataMapper.from_site_notes(site_notes) def test_cylinder_insulation_type(self, result: EpcPropertyData) -> None: assert result.sap_heating.cylinder_insulation_type == "Factory fitted" def test_heat_pump_fuel_type(self, result: EpcPropertyData) -> None: assert ( result.sap_heating.main_heating_details[0].main_fuel_type == "Electricity" ) def test_roof_insulation_location_unknown(self, result: EpcPropertyData) -> None: assert result.sap_building_parts[0].roof_insulation_location == "Unknown" def test_roof_insulation_thickness_none(self, result: EpcPropertyData) -> None: assert result.sap_building_parts[0].roof_insulation_thickness is None PDF_PATH_5 = os.path.join( os.path.dirname(__file__), "fixtures", "PasHubSiteNotes_5.pdf" ) class TestPdfToEpcPropertyDataFixture5: @pytest.fixture def result(self) -> EpcPropertyData: with open(PDF_PATH_5, "rb") as f: pdf_bytes = f.read() site_notes = PasHubRdSapSiteNotesExtractor( pdf_to_text_list(pdf_bytes) ).extract() return EpcPropertyDataMapper.from_site_notes(site_notes) def test_cfl_bulb_count(self, result: EpcPropertyData) -> None: assert result.cfl_fixed_lighting_bulbs_count == 2 def test_secondary_heating_type(self, result: EpcPropertyData) -> None: assert ( result.sap_heating.secondary_heating_type == "Panel, convector or radiant heaters" ) def test_electric_shower_outlet_type(self, result: EpcPropertyData) -> None: assert result.sap_heating.shower_outlets is not None assert ( result.sap_heating.shower_outlets.shower_outlet.shower_outlet_type == "Electric Shower" ) PDF_PATH_6 = os.path.join( os.path.dirname(__file__), "fixtures", "PasHubSiteNotes_6.pdf" ) class TestPdfToEpcPropertyDataFixture6: @pytest.fixture def result(self) -> EpcPropertyData: with open(PDF_PATH_6, "rb") as f: pdf_bytes = f.read() site_notes = PasHubRdSapSiteNotesExtractor( pdf_to_text_list(pdf_bytes) ).extract() return EpcPropertyDataMapper.from_site_notes(site_notes) def test_party_wall_construction(self, result: EpcPropertyData) -> None: assert ( result.sap_building_parts[0].party_wall_construction == "Solid Masonry, Timber Frame, or System Built" )