diff --git a/backend/documents_parser/tests/fixtures/ExampleSiteNotes_5.pdf b/backend/documents_parser/tests/fixtures/ExampleSiteNotes_5.pdf new file mode 100644 index 00000000..25cc8204 Binary files /dev/null and b/backend/documents_parser/tests/fixtures/ExampleSiteNotes_5.pdf differ diff --git a/backend/documents_parser/tests/fixtures/site_notes_example_5_text.json b/backend/documents_parser/tests/fixtures/site_notes_example_5_text.json new file mode 100644 index 00000000..74b09972 --- /dev/null +++ b/backend/documents_parser/tests/fixtures/site_notes_example_5_text.json @@ -0,0 +1 @@ +["SMART EPC: Record of", "Inspection & Site Notes", "Inspection Surveyor:", "Kevin Rossiter", "E-Mail Address:", "domesticretrofitsolutions@yahoo.com", "Report Reference:", "B94B3A7D-5D32-4143-865C-55142AD997EC", "Created On:", "7 November 2025", "Date of Inspection:", "02 October 2025", "Property Address:", "20,", "Acton Road,", "Crewe,", "Cheshire,", "CW2 8TN", "Property Photo", "Page 1", "", "Photo of electricity meter:", "RdSAP Assessment", "General", "Confirm you have checked for the existence of an", "EPC before carrying out another energy assessment.", "Yes", "Does an EPC exist at the point of carrying out this", "energy assessment?", "Yes", "Please select why another energy assessment needs", "to be undertaken:", "Assessor instructed to produce a new EPC upon request from building", "owner/tenant/landlord after confirming to the requestor that a valid EPC", "already exists", "Inspection Date:", "02/10/2025", "Transaction Type:", "Grant-Scheme (ECO, RHI, etc.)", "Tenure:", "Rented Social", "Type of Property:", "House", "Detachment Type:", "Semi-Detached", "Number of storeys:", "2 Storeys", "Terrain Type:", "Suburban", "Number of Extensions:", "No Extensions", "Is an electricity smart meter present?", "Yes", "Electric meter type:", "Single", "Is the dwelling export-capable?", "Yes", "Is mains gas available?", "Yes", "Is there a gas smart meter?", "Yes", "Is the gas meter accessible?", "Yes", "Page 2", "", "Photo of Gas Meter:", "Photo indicators of filled cavity insulation:", "Photo indicators of filled cavity insulation:", "Select Measurements Location:", "Internal", "Building Construction", "Main Building", "Age Range:", "1950-1966", "Record indicators of property age:", "local knowledge, period building features", "Walls - Construction Type:", "Cavity", "Record external indicators of Cavity Construction:", "stretcher bond", "Walls - Insulation Type:", "Filled Cavity", "Record indicators of filled cavity:", "evidence of cavity fill drill holes, Scope images", "Page 3", "", "Photo indicators of filled cavity insulation:", "Photo indicators of filled cavity insulation:", "Photo indicators of filled cavity insulation:", "Photo wall thickness:", "Thermal conductivity of wall insulation:", "Unknown", "Wall U-Value known?", "Not Known", "Wall thickness:", "300 mm", "Party wall construction type:", "Unable to determine", "Floor type:", "Ground Floor", "Floor Construction:", "Solid", "Floor Insulation Type:", "As Built", "Floor U-Value known?", "Not Known", "Page 4", "", "Loft insulation:", "Mostly boarded", "Loft insulation:", "Mostly boarded", "Building Measurements", "Area (m2)", "Height (m)", "Heat Loss Perimeter (m)", "PWL (m)", "Main Building", "Floor 1", "41.28", "2.51", "18.62", "7.28", "Floor 0", "41.88", "2.51", "19.8", "7.28", "Roof Space", "Main Building", "Roofs - Construction Type:", "Pitched roof (Slates or tiles), Access to loft", "Roofs - Insulation At:", "Unknown", "Page 5", "", "Loft insulation:", "Mostly boarded", "Loft insulation:", "Mostly boarded", "Loft insulation:", "Mostly boarded", "Loft insulation:", "Mostly boarded", "Page 6", "", "Loft insulation:", "Unknown", "Record indicators of Cavity Wall Construction in roof", "space:", "No indicator of construction visible", "Are there rooms in the roof?", "No", "Windows", "Window 1", "Window location:", "Main Building", "Window wall type:", "External wall", "Glazing Type:", "Double glazing, Unknown install date", "Window type:", "Window", "Window frame type:", "Wooden or PVC", "What size is the glazing gap?", "12 mm", "Is the window draught proofed?", "Yes", "Are there permanent shutters present?", "No", "Window height:", "1.27 m", "Window width:", "2.06 m", "Orientation:", "North West", "Window 2", "Window location:", "Main Building", "Window wall type:", "External wall", "Glazing Type:", "Double glazing, Unknown install date", "Window type:", "Window", "Window frame type:", "Wooden or PVC", "What size is the glazing gap?", "12 mm", "Page 7", "", "Is the window draught proofed?", "Yes", "Are there permanent shutters present?", "No", "Window height:", "0.89 m", "Window width:", "1.45 m", "Orientation:", "North West", "Window 3", "Window location:", "Main Building", "Window wall type:", "External wall", "Glazing Type:", "Double glazing, Unknown install date", "Window type:", "Window", "Window frame type:", "Wooden or PVC", "What size is the glazing gap?", "12 mm", "Is the window draught proofed?", "Yes", "Are there permanent shutters present?", "No", "Window height:", "0.89 m", "Window width:", "0.95 m", "Orientation:", "North West", "Window 4", "Window location:", "Main Building", "Window wall type:", "External wall", "Glazing Type:", "Double glazing, Unknown install date", "Window type:", "Window", "Window frame type:", "Wooden or PVC", "What size is the glazing gap?", "12 mm", "Is the window draught proofed?", "Yes", "Are there permanent shutters present?", "No", "Window height:", "0.89 m", "Window width:", "0.95 m", "Orientation:", "South East", "Window 5", "Window location:", "Main Building", "Window wall type:", "External wall", "Glazing Type:", "Double glazing, Unknown install date", "Window type:", "Window", "Window frame type:", "Wooden or PVC", "What size is the glazing gap?", "12 mm", "Page 8", "", "Is the window draught proofed?", "Yes", "Are there permanent shutters present?", "No", "Window height:", "0.89 m", "Window width:", "1.45 m", "Orientation:", "South East", "Window 6", "Window location:", "Main Building", "Window wall type:", "External wall", "Glazing Type:", "Double glazing, Unknown install date", "Window type:", "Window", "Window frame type:", "Wooden or PVC", "What size is the glazing gap?", "12 mm", "Is the window draught proofed?", "Yes", "Are there permanent shutters present?", "No", "Window height:", "0.89 m", "Window width:", "0.95 m", "Orientation:", "South East", "Window 7", "Window location:", "Main Building", "Window wall type:", "External wall", "Glazing Type:", "Double glazing, Unknown install date", "Window type:", "Window", "Window frame type:", "Wooden or PVC", "What size is the glazing gap?", "12 mm", "Is the window draught proofed?", "Yes", "Are there permanent shutters present?", "No", "Window height:", "1.17 m", "Window width:", "0.46 m", "Orientation:", "South East", "Window 8", "Window location:", "Main Building", "Window wall type:", "External wall", "Glazing Type:", "Double glazing, Unknown install date", "Window type:", "Window", "Window frame type:", "Wooden or PVC", "What size is the glazing gap?", "12 mm", "Page 9", "", "Is the window draught proofed?", "Yes", "Are there permanent shutters present?", "No", "Window height:", "1.15 m", "Window width:", "0.93 m", "Orientation:", "North East", "Page 10", "", "Photo of heating system:", "Heating & Hot Water", "Main Heating Systems", "Main Heating 1", "How would you like to select the Heating System?", "PCDF Search", "System type:", "Boiler with radiators or underfloor heating", "Product Id", "18119", "Manufacturer", "Vaillant", "Model", "ecoTEC sustain 28", "Orig Manuf", "Vaillant", "Fuel", "Mains gas", "S. Efficiency", "0", "Type", "Combi", "Condensing", "Yes", "Year", "2017 - current", "Mount", "Wall", "Open Flue", "Room-sealed", "Fan Assist", "Yes", "Status", "Normal status for an actual product", "Central heating pump age:", "2013 or later", "Controls:", "Programmer, room thermostat and TRVs", "Does the boiler have a Flue Gas Heat Recover", "System (FGHRS)?", "No", "Is there a weather compensator?", "No", "Emitter:", "Radiators", "Emitter Temperature:", "Unknown", "Page 11", "", "Photo of heating system:", "Photo of heating controls:", "Photo of heating controls:", "Photo of secondary heating system", "Secondary Heating System", "Secondary Fuel", "Electricity", "Secondary System:", "Panel, convector or radiant heaters", "Page 12", "", "Water Heating & Cylinder", "Water Heating Type:", "Regular", "Water Heating System:", "From main heating 1", "Cylinder Size:", "No Cylinder", "Ventilation", "Ventilation type:", "Mechanical Extract - Decentralised", "Has fixed air conditioning?", "No", "Is the ventilation in the PCDF database?", "No", "Number of open flues:", "0", "Number of closed flues:", "0", "Number of boiler flues:", "0", "Number of other flues:", "0", "Number of extract fans:", "0", "Number of passive vents:", "0", "Number of flueless gas fires:", "0", "Pressure test:", "No test", "Is there a draught lobby?", "No", "Conservatories", "Is there conservatory?", "No conservatory", "Renewables", "Wind Turbines", "Has wind turbines?", "No", "Solar hot water", "Has solar hot water?", "No", "Photovoltaics", "Has photovoltaic array?", "No", "Number of PV batteries:", "None", "Hydro", "Is the dwelling connected to Hydro?", "No", "Room Count Elements", "Number of habitable rooms?", "4", "Are any of these rooms unheated?", "No", "Number of external doors?", "2", "Number of insulated external doors?", "0", "Number of draughtproofed external doors?", "2", "Page 13", "", "Photo of incandescent bulbs:", "Photo of LED bulbs:", "Photo of LED bulbs:", "Photo of LED bulbs:", "Photo of LED bulbs:", "Number of open chimneys?", "0", "Number of blocked chimneys?", "0", "Number of fixed incandescent bulbs:", "1", "Is the exact number of LED and CFL bulbs known?", "Yes", "Number of fixed LED bulbs:", "7", "Page 14", "", "Photo of LED bulbs:", "Photo of CFL bulbs:", "Photo of CFL bulbs:", "Photo of shower:", "Number of fixed CFL bulbs:", "2", "Are there any waste water heat recovery systems?", "None", "Number of baths:", "1", "How many special features are there at the", "property?", "0", "Showers", "Shower 1", "Shower outlet type:", "Electric Shower", "Page 15", "", "General Photos:", "12mm dg unknown", "External Elevations:", "External Elevations:", "Customer Response", "Customer present?", "Yes", "Customer willing to answer satisfaction survey?", "No", "Addendum + Related Party Disclosure", "Addendum", "None", "Related party disclosure", "No related party", "Hard to treat cavity walls: Property has access", "issues?", "No", "Hard to treat cavity walls: Property has high", "exposure?", "No", "Hard to treat cavity walls: Property has narrow", "cavities?", "No", "Photographs Required", "Page 16", "", "External Elevations:", "Floor plan", "Page 17", ""] \ No newline at end of file diff --git a/backend/documents_parser/tests/test_end_to_end.py b/backend/documents_parser/tests/test_end_to_end.py index 09fcb4db..7b4c7fe9 100644 --- a/backend/documents_parser/tests/test_end_to_end.py +++ b/backend/documents_parser/tests/test_end_to_end.py @@ -340,3 +340,35 @@ class TestPdfToEpcPropertyDataFixture4: 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", "ExampleSiteNotes_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" + ) diff --git a/backend/documents_parser/tests/test_extractor.py b/backend/documents_parser/tests/test_extractor.py index 1a21007f..406e9234 100644 --- a/backend/documents_parser/tests/test_extractor.py +++ b/backend/documents_parser/tests/test_extractor.py @@ -56,6 +56,11 @@ def load_text_fixture_4() -> list[str]: return json.load(f) +def load_text_fixture_5() -> list[str]: + with open(os.path.join(FIXTURES, "site_notes_example_5_text.json")) as f: + return json.load(f) + + class TestInspectionMetadata: def test_full_inspection_metadata(self) -> None: result = PasHubRdSapSiteNotesExtractor(load_text_fixture()).extract_inspection_metadata() @@ -731,3 +736,45 @@ class TestRoofSpaceUnknownInsulation: def test_insulation_thickness_str_none(self, roof_space: RoofSpace) -> None: assert roof_space.main_building.insulation_thickness is None + + +class TestCflBulbCount: + @pytest.fixture + def rce(self) -> RoomCountElements: + return PasHubRdSapSiteNotesExtractor( + load_text_fixture_5() + ).extract_room_count_elements() + + def test_cfl_count(self, rce: RoomCountElements) -> None: + assert rce.number_of_fixed_cfl_bulbs == 2 + + def test_led_count(self, rce: RoomCountElements) -> None: + assert rce.number_of_fixed_led_bulbs == 7 + + def test_incandescent_count(self, rce: RoomCountElements) -> None: + assert rce.number_of_fixed_incandescent_bulbs == 1 + + +class TestSecondaryHeatingPanel: + @pytest.fixture + def hhw(self) -> HeatingAndHotWater: + return PasHubRdSapSiteNotesExtractor( + load_text_fixture_5() + ).extract_heating_and_hot_water() + + def test_secondary_system(self, hhw: HeatingAndHotWater) -> None: + assert hhw.secondary_heating.secondary_system == "Panel, convector or radiant heaters" + + def test_secondary_fuel(self, hhw: HeatingAndHotWater) -> None: + assert hhw.secondary_heating.secondary_fuel == "Electricity" + + +class TestElectricShowerExtraction: + @pytest.fixture + def wu(self) -> WaterUse: + return PasHubRdSapSiteNotesExtractor( + load_text_fixture_5() + ).extract_water_use() + + def test_shower_outlet_type(self, wu: WaterUse) -> None: + assert wu.showers[0].outlet_type == "Electric Shower"