additional fields mapped from pdf 4 🟥

This commit is contained in:
Daniel Roth 2026-04-23 10:36:15 +00:00
parent e406b11be8
commit c5c3f3fc83
5 changed files with 578 additions and 4 deletions

Binary file not shown.

View file

@ -0,0 +1,480 @@
[
"SMART EPC: Record of",
"Inspection & Site Notes",
"Inspection Surveyor:",
"Rebecca Mcwilliam",
"E-Mail Address:",
"rebeccamcdea@gmail.com",
"Report Reference:",
"DA1A93D9-354C-4B4B-A299-3A681231D4B5",
"Created On:",
"15 October 2025",
"Date of Inspection:",
"13 October 2025",
"Property Address:",
"18,",
"Oakfield Close,",
"Wrenbury,",
"CW5 8ET",
"Property Photo",
"Page 1",
"",
"Photo of electricity meter:",
"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:",
"13/10/2025",
"Transaction Type:",
"None of the Above",
"Tenure:",
"Rented Social",
"Type of Property:",
"Bungalow",
"Detachment Type:",
"End-terrace",
"Number of storeys:",
"1 Storey",
"Terrain Type:",
"Suburban",
"Number of Extensions:",
"No Extensions",
"Is an electricity smart meter present?",
"Yes",
"Electric meter type:",
"Single",
"Page 2",
"",
"Photo of electricity meter:",
"Photo of electricity meter:",
"External indicators of Cavity Wall Construction:",
"External indicators of Cavity Wall Construction:",
"Is the dwelling export-capable?",
"Yes",
"Is mains gas available?",
"No",
"Select Measurements Location:",
"Internal",
"Building Construction",
"Main Building",
"Age Range:",
"1950-1966",
"Record indicators of property age:",
"local knowledge, enquiries of owner, period building features",
"Walls - Construction Type:",
"Cavity",
"Record external indicators of Cavity Construction:",
"stretcher bond, wall thickness over 270 mm",
"Walls - Insulation Type:",
"Filled Cavity",
"Record indicators of filled cavity:",
"evidence of cavity fill drill holes, Boroscope",
"Page 3",
"",
"Photo indicators of filled cavity insulation:",
"Photo indicators of filled cavity insulation:",
"Photo indicators of filled cavity insulation:",
"Photo indicators of filled cavity insulation:",
"Photo indicators of filled cavity insulation:",
"Photo indicators of filled cavity insulation:",
"Page 4",
"",
"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 5",
"",
"Loft insulation:",
"Loft insulation:",
"Building Measurements",
"Area (m2)",
"Height (m)",
"Heat Loss Perimeter (m)",
"PWL (m)",
"Main Building",
"Floor 0",
"46.08",
"2.44",
"20.49",
"6.67",
"Roof Space",
"Main Building",
"Roofs - Construction Type:",
"Pitched roof (Slates or tiles), No access",
"Identify the reason for restricted access:",
"access hatch blocked",
"Roofs - Insulation At:",
"Unknown",
"Page 6",
"",
"Loft insulation:",
"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:",
"0.97 m",
"Window width:",
"1.54 m",
"Orientation:",
"North East",
"Page 7",
"",
"Photo of glazing type:",
"Photo of glazing type:",
"Photo of glazing type:",
"Photo of glazing type:",
"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",
"Is the window draught proofed?",
"Yes",
"Are there permanent shutters present?",
"No",
"Window height:",
"1.01 m",
"Window width:",
"1.04 m",
"Orientation:",
"North East",
"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",
"Page 8",
"",
"Photo of glazing type:",
"Photo of glazing type:",
"Photo of glazing type:",
"Photo of glazing type:",
"What size is the glazing gap?",
"12 mm",
"Is the window draught proofed?",
"Yes",
"Are there permanent shutters present?",
"No",
"Window height:",
"0.99 m",
"Window width:",
"1.07 m",
"Orientation:",
"South 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:",
"1.28 m",
"Window width:",
"2.07 m",
"Orientation:",
"South",
"Page 9",
"",
"Photo of heating system:",
"Photo of heating system:",
"Photo of heating system:",
"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:",
"Heat pump with radiators or underfloor heating",
"Product Id",
"102421",
"Manufacturer",
"Daikin Altherma",
"Model",
"EDLQ05CAV3",
"Year",
"2014 - current",
"Fuel",
"Electricity, any tariff",
"Status",
"Normal status for an actual product",
"Central heating pump age:",
"Unknown",
"MCS installed heat pump:",
"Yes",
"Controls:",
"Programmer, room thermostat and TRVs",
"Emitter:",
"Radiators",
"Emitter Temperature:",
"Unknown",
"Page 10",
"",
"Photo of heating controls:",
"Photo of heating controls:",
"Photo of heating controls:",
"Photo of heating controls:",
"Photo of heating controls:",
"Photo of heating controls:",
"Page 11",
"",
"Photo of heating controls:",
"Photo of secondary heating system",
"Secondary Heating System",
"Secondary Fuel",
"Electricity",
"Secondary System:",
"Panel, convector or radiant heaters",
"Water Heating & Cylinder",
"Water Heating Type:",
"Regular",
"Water Heating System:",
"From main heating 1",
"Page 12",
"",
"Photo of water heating system:",
"Photo of water heating system:",
"Photo of water heating system:",
"Cylinder Size:",
"Medium (131-170 litres)",
"What is the cylinder measured heat loss:",
"Not known",
"Insulation Type:",
"Factory fitted",
"Thickness:",
"50 mm",
"Page 13",
"",
"Photo of cylinder and thermostat if present:",
"Photo of cylinder and thermostat if present:",
"Photo of ventilation type:",
"Photo of ventilation type:",
"Has thermostat?",
"Yes",
"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",
"Page 14",
"",
"Photo of extract fans:",
"Number of other flues:",
"0",
"Number of extract fans:",
"1",
"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?",
"2",
"Are any of these rooms unheated?",
"No",
"Page 15",
"",
"Photo of LED bulbs:",
"Photo of LED bulbs:",
"Photo of LED bulbs:",
"Photo of LED bulbs:",
"Photo of LED bulbs:",
"Photo of LED bulbs:",
"Number of external doors?",
"2",
"Number of insulated external doors?",
"0",
"Number of draughtproofed external doors?",
"2",
"Number of open chimneys?",
"0",
"Number of blocked chimneys?",
"0",
"Number of fixed incandescent bulbs:",
"0",
"Is the exact number of LED and CFL bulbs known?",
"Yes",
"Number of fixed LED bulbs:",
"7",
"Page 16",
"",
"Photo of LED bulbs:",
"Photo of shower:",
"Photo of shower:",
"Number of fixed CFL bulbs:",
"0",
"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",
"Customer Response",
"Customer present?",
"Yes",
"Customer willing to answer satisfaction survey?",
"No",
"Addendum + Related Party Disclosure",
"Addendum",
"None",
"Page 17",
"",
"General Photos:",
"General Photos:",
"External Elevations:",
"External Elevations:",
"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 18",
"",
"External Elevations:",
"External Elevations:",
"External Elevations:",
"External Elevations:",
"External Elevations:",
"External Elevations:",
"Page 19",
"",
"External Elevations:",
"External Elevations:",
"External Elevations:",
"External Elevations:",
"Page 20",
"",
"External Elevations:",
"External Elevations:",
"External Elevations:",
"External Elevations:",
"External Elevations:",
"Page 21",
"",
"External Elevations:",
"External Elevations:",
"Page 22",
"",
"Additional Notes",
"Additional Notes",
"The loft hatch is a drop-down hatch with a ladder above. The hatch is broken",
"and it could not be opened. She is about to have a new door made so loft will",
"be able to be accessed at some point soon but on the day I could not.",
"Page 23",
""
]

View file

@ -294,11 +294,49 @@ class TestPdfToEpcPropertyDataFixture3:
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"
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
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"
assert (
result.sap_heating.main_heating_details[0].main_fuel_type == "Electricity"
)
PDF_PATH_4 = os.path.join(
os.path.dirname(__file__), "fixtures", "ExampleSiteNotes_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

View file

@ -51,6 +51,11 @@ def load_text_fixture_3() -> list[str]:
return json.load(f)
def load_text_fixture_4() -> list[str]:
with open(os.path.join(FIXTURES, "site_notes_example_4_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()
@ -675,3 +680,54 @@ class TestSurveyAddendum:
hard_to_treat_cavity_high_exposure=False,
hard_to_treat_cavity_narrow_cavities=False,
)
# --- fixture 4: heat pump, factory-fitted cylinder, blocked loft ---
class TestCylinderInsulationType:
@pytest.fixture
def hhw(self) -> HeatingAndHotWater:
return PasHubRdSapSiteNotesExtractor(
load_text_fixture_4()
).extract_heating_and_hot_water()
def test_insulation_type_extracted(self, hhw: HeatingAndHotWater) -> None:
assert hhw.water_heating.insulation_type == "Factory fitted"
def test_insulation_thickness_mm(self, hhw: HeatingAndHotWater) -> None:
assert hhw.water_heating.insulation_thickness_mm == 50
def test_cylinder_size(self, hhw: HeatingAndHotWater) -> None:
assert hhw.water_heating.cylinder_size == "Medium (131-170 litres)"
class TestHeatPumpFuelExtraction:
@pytest.fixture
def hhw(self) -> HeatingAndHotWater:
return PasHubRdSapSiteNotesExtractor(
load_text_fixture_4()
).extract_heating_and_hot_water()
def test_fuel_raw_value(self, hhw: HeatingAndHotWater) -> None:
assert hhw.main_heating.fuel == "Electricity, any tariff"
def test_system_type(self, hhw: HeatingAndHotWater) -> None:
assert hhw.main_heating.system_type == "Heat pump with radiators or underfloor heating"
class TestRoofSpaceUnknownInsulation:
@pytest.fixture
def roof_space(self) -> RoofSpace:
return PasHubRdSapSiteNotesExtractor(
load_text_fixture_4()
).extract_roof_space()
def test_insulation_at_unknown(self, roof_space: RoofSpace) -> None:
assert roof_space.main_building.insulation_at == "Unknown"
def test_insulation_thickness_mm_none(self, roof_space: RoofSpace) -> None:
assert roof_space.main_building.insulation_thickness_mm is None
def test_insulation_thickness_str_none(self, roof_space: RoofSpace) -> None:
assert roof_space.main_building.insulation_thickness is None

View file

@ -61,7 +61,7 @@ class SapHeating:
water_heating_fuel: Optional[int] = None # TODO: make enum?
immersion_heating_type: Optional[Union[int, str]] = None # TODO: make enum?
shower_outlets: Optional[ShowerOutlets] = None
cylinder_insulation_type: Optional[int] = None
cylinder_insulation_type: Optional[Union[int, str]] = None
cylinder_thermostat: Optional[str] = None
secondary_fuel_type: Optional[int] = None
secondary_heating_type: Optional[Union[int, str]] = None # int from API; str from site notes