Model/backend/documents_parser/tests/test_extractor.py
2026-04-16 14:15:11 +00:00

338 lines
12 KiB
Python

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",
)
],
)
class TestWindows:
@pytest.fixture
def windows(self) -> list:
return PasHubRdSapSiteNotesExtractor(load_text_fixture()).extract_windows()
def test_window_count(self, windows: list) -> None:
assert len(windows) == 8
def test_ids_are_sequential(self, windows: list) -> None:
assert [w.id for w in windows] == list(range(1, 9))
def test_first_window_location(self, windows: list) -> None:
assert windows[0].location == "Main Building"
def test_extension_window_location(self, windows: list) -> None:
assert windows[3].location == "Extension 1"
def test_height_parses_to_float(self, windows: list) -> None:
assert windows[0].height_m == 1.2
def test_draught_proofed_true(self, windows: list) -> None:
assert windows[0].draught_proofed is True
def test_permanent_shutters_false(self, windows: list) -> None:
assert windows[0].permanent_shutters is False
def test_first_window_full(self, windows: list) -> None:
from datatypes.epc.surveys.pashub_rdsap_site_notes import Window
assert windows[0] == Window(
id=1,
location="Main Building",
wall_type="External wall",
glazing_type="Double glazing, Unknown install date",
window_type="Window",
frame_type="Wooden or PVC",
glazing_gap="16 mm or more",
draught_proofed=True,
permanent_shutters=False,
height_m=1.2,
width_m=2.3,
orientation="North West",
)