pashub rdsap sitenotes pdf output to dataclasses

This commit is contained in:
Daniel Roth 2026-04-13 11:35:02 +00:00
parent 6f8a304568
commit a7d460a2d4
8 changed files with 1221 additions and 3 deletions

View file

@ -42,8 +42,8 @@ logger = setup_logger()
def run_job() -> None:
username: str = "" # TODO: get from github secrets
password: str = ""
username: str = "joseph.westwood@domna.homes" # TODO: get from github secrets
password: str = "DomnaTest123!"
property_list_file: str = (
"hubspot-crm-exports-southern-ra-lite-programme-3103-2026-03-31-2.xlsx"

View file

@ -0,0 +1,3 @@
from .pashub_rdsap_site_notes import PasHubRdSapSiteNotes
__all__ = ["PasHubRdSapSiteNotes"]

View file

@ -0,0 +1,291 @@
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class InspectionMetadata:
inspection_surveyor: str
email_address: str
report_reference: str
created_on: str
date_of_inspection: str
property_address: str
property_photo: Optional[bool] = None
@dataclass
class General:
epc_checked_before_assessment: bool
epc_exists_at_point_of_assessment: bool
inspection_date: str
transaction_type: str
tenure: str
property_type: str
detachment_type: str
number_of_storeys: int
terrain_type: str
number_of_extensions: int
electricity_smart_meter: bool
electric_meter_type: str
dwelling_export_capable: bool
mains_gas_available: bool
gas_smart_meter: bool
gas_meter_accessible: bool
measurements_location: str
@dataclass
class MainBuildingConstruction:
age_range: str
age_indicators: str
walls_construction_type: str
cavity_construction_indicators: str
walls_insulation_type: str
thermal_conductivity_of_wall_insulation: str
wall_u_value_known: bool
wall_thickness_mm: int
party_wall_construction_type: str
filled_cavity_indicators: Optional[str] = None
@dataclass
class ExtensionConstruction:
id: int
age_range: str
age_indicators: str
walls_construction_type: str
cavity_construction_indicators: str
walls_insulation_type: str
thermal_conductivity_of_wall_insulation: str
wall_u_value_known: bool
wall_thickness_mm: int
party_wall_construction_type: str
filled_cavity_indicators: Optional[str] = None
@dataclass
class FloorConstruction:
floor_type: str
floor_construction: str
floor_insulation_type: str
floor_u_value_known: bool
@dataclass
class BuildingConstruction:
main_building: MainBuildingConstruction
floor: FloorConstruction
extensions: Optional[List[ExtensionConstruction]] = None
@dataclass
class FloorMeasurement:
name: str
area_m2: float
height_m: float
heat_loss_perimeter_m: float
pwl_m: float
@dataclass
class MainBuildingMeasurements:
floors: List[FloorMeasurement]
@dataclass
class ExtensionMeasurements:
id: int
floors: List[FloorMeasurement]
@dataclass
class BuildingMeasurements:
main_building: MainBuildingMeasurements
extensions: Optional[List[ExtensionMeasurements]] = None
@dataclass
class RoofSpaceDetail:
construction_type: str
insulation_at: str
roof_u_value_known: bool
cavity_wall_construction_indicators: str
rooms_in_roof: bool
# Numeric thickness (mm) when known; string (e.g. "As built") when not measured
insulation_thickness_mm: Optional[int] = None
insulation_thickness: Optional[str] = None
@dataclass
class ExtensionRoofSpace:
id: int
construction_type: str
insulation_at: str
roof_u_value_known: bool
cavity_wall_construction_indicators: str
rooms_in_roof: bool
insulation_thickness_mm: Optional[int] = None
insulation_thickness: Optional[str] = None
@dataclass
class RoofSpace:
main_building: RoofSpaceDetail
extensions: Optional[List[ExtensionRoofSpace]] = None
@dataclass
class Window:
id: int
location: str
wall_type: str
glazing_type: str
window_type: str
frame_type: str
glazing_gap: str
draught_proofed: bool
permanent_shutters: bool
height_m: float
width_m: float
orientation: str
@dataclass
class MainHeating:
selection_method: str
system_type: str
product_id: int
manufacturer: str
model: str
orig_manufacturer: str
fuel: str
summer_efficiency: float
type: str
condensing: bool
year: str
mount: str
open_flue: str
fan_assist: bool
status: str
central_heating_pump_age: str
controls: str
flue_gas_heat_recovery_system: bool
weather_compensator: bool
emitter: str
emitter_temperature: str
@dataclass
class SecondaryHeating:
secondary_fuel: str
@dataclass
class WaterHeating:
type: str
system: str
cylinder_size: str
cylinder_measured_heat_loss: Optional[str] = None
insulation_type: Optional[str] = None
insulation_thickness_mm: Optional[int] = None
has_thermostat: Optional[bool] = None
@dataclass
class HeatingAndHotWater:
main_heating: MainHeating
secondary_heating: SecondaryHeating
water_heating: WaterHeating
@dataclass
class Ventilation:
ventilation_type: str
has_fixed_air_conditioning: bool
number_of_open_flues: int
number_of_closed_flues: int
number_of_boiler_flues: int
number_of_other_flues: int
number_of_extract_fans: int
number_of_passive_vents: int
number_of_flueless_gas_fires: int
pressure_test: str
draught_lobby: bool
ventilation_in_pcdf_database: Optional[bool] = None
@dataclass
class Conservatories:
has_conservatory: bool
@dataclass
class Renewables:
wind_turbines: bool
solar_hot_water: bool
photovoltaic_array: bool
number_of_pv_batteries: int
hydro: bool
@dataclass
class RoomCountElements:
number_of_habitable_rooms: int
any_unheated_rooms: bool
number_of_external_doors: int
number_of_insulated_external_doors: int
number_of_draughtproofed_external_doors: int
number_of_open_chimneys: int
number_of_blocked_chimneys: int
number_of_fixed_incandescent_bulbs: int
exact_led_cfl_count_known: bool
number_of_fixed_led_bulbs: int
number_of_fixed_cfl_bulbs: int
waste_water_heat_recovery: str
number_of_heated_rooms: Optional[int] = None
@dataclass
class Shower:
id: int
outlet_type: str
@dataclass
class WaterUse:
number_of_baths: int
number_of_special_features: int
showers: List[Shower]
@dataclass
class CustomerResponse:
customer_present: bool
willing_to_answer_satisfaction_survey: bool
@dataclass
class SurveyAddendum:
addendum: str
related_party_disclosure: str
hard_to_treat_cavity_access_issues: bool
hard_to_treat_cavity_high_exposure: bool
hard_to_treat_cavity_narrow_cavities: bool
@dataclass
class PasHubRdSapSiteNotes:
inspection_metadata: InspectionMetadata
general: General
building_construction: BuildingConstruction
building_measurements: BuildingMeasurements
roof_space: RoofSpace
windows: List[Window]
heating_and_hot_water: HeatingAndHotWater
ventilation: Ventilation
conservatories: Conservatories
renewables: Renewables
room_count_elements: RoomCountElements
water_use: WaterUse
customer_response: CustomerResponse
addendum: SurveyAddendum

View file

View file

@ -0,0 +1,232 @@
{
"inspection_metadata": {
"inspection_surveyor": "test",
"email_address": "test@test.com",
"report_reference": "49D422A9-0779-44DD-9665-464D35DFF1A8",
"created_on": "2026-03-31",
"date_of_inspection": "2026-03-31",
"property_address": "test"
},
"general": {
"epc_checked_before_assessment": true,
"epc_exists_at_point_of_assessment": false,
"inspection_date": "2026-03-31",
"transaction_type": "None of the Above",
"tenure": "Rented Social",
"property_type": "House",
"detachment_type": "Mid-terrace",
"number_of_storeys": 2,
"terrain_type": "Suburban",
"number_of_extensions": 0,
"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"
},
"building_construction": {
"main_building": {
"age_range": "I: 1996 - 2002",
"age_indicators": "local knowledge",
"walls_construction_type": "Cavity",
"cavity_construction_indicators": "stretcher bond",
"walls_insulation_type": "As built",
"thermal_conductivity_of_wall_insulation": "Unknown",
"wall_u_value_known": false,
"wall_thickness_mm": 280,
"party_wall_construction_type": "Cavity Masonry, Unfilled"
},
"floor": {
"floor_type": "Ground Floor",
"floor_construction": "Suspended, not timber",
"floor_insulation_type": "As Built",
"floor_u_value_known": false
}
},
"building_measurements": {
"main_building": {
"floors": [
{
"name": "Floor 1",
"area_m2": 24.78,
"height_m": 2.37,
"heat_loss_perimeter_m": 14.21,
"pwl_m": 6.15
},
{
"name": "Floor 0",
"area_m2": 24.78,
"height_m": 2.35,
"heat_loss_perimeter_m": 14.21,
"pwl_m": 6.15
}
]
}
},
"roof_space": {
"main_building": {
"construction_type": "Pitched roof (Slates or tiles), Access to loft",
"insulation_at": "Joists",
"roof_u_value_known": false,
"insulation_thickness_mm": 100,
"cavity_wall_construction_indicators": "No indicator of construction visible",
"rooms_in_roof": false
}
},
"windows": [
{
"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.36,
"width_m": 1.0,
"orientation": "South East"
},
{
"id": 2,
"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.33,
"width_m": 0.96,
"orientation": "South East"
},
{
"id": 3,
"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.04,
"width_m": 0.96,
"orientation": "North West"
},
{
"id": 4,
"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.02,
"width_m": 0.97,
"orientation": "North West"
}
],
"heating_and_hot_water": {
"main_heating": {
"selection_method": "PCDF Search",
"system_type": "Boiler with radiators or underfloor heating",
"product_id": 18400,
"manufacturer": "Vaillant",
"model": "ecoFIT sustain 415",
"orig_manufacturer": "Vaillant",
"fuel": "Mains gas",
"summer_efficiency": 0,
"type": "Regular",
"condensing": true,
"year": "2018 - current",
"mount": "Wall",
"open_flue": "Room-sealed",
"fan_assist": true,
"status": "Normal status for an actual product",
"central_heating_pump_age": "Unknown",
"controls": "Programmer, room thermostat and TRVs",
"flue_gas_heat_recovery_system": false,
"weather_compensator": false,
"emitter": "Radiators",
"emitter_temperature": "Unknown"
},
"secondary_heating": {
"secondary_fuel": "No Secondary Heating"
},
"water_heating": {
"type": "Regular",
"system": "From main heating 1",
"cylinder_size": "Normal (90-130 litres)",
"cylinder_measured_heat_loss": "Not known",
"insulation_type": "Factory fitted",
"insulation_thickness_mm": 12,
"has_thermostat": true
}
},
"ventilation": {
"ventilation_type": "Natural",
"has_fixed_air_conditioning": false,
"number_of_open_flues": 0,
"number_of_closed_flues": 0,
"number_of_boiler_flues": 0,
"number_of_other_flues": 0,
"number_of_extract_fans": 2,
"number_of_passive_vents": 0,
"number_of_flueless_gas_fires": 0,
"pressure_test": "No test",
"draught_lobby": false
},
"conservatories": {
"has_conservatory": false
},
"renewables": {
"wind_turbines": false,
"solar_hot_water": false,
"photovoltaic_array": false,
"number_of_pv_batteries": 0,
"hydro": false
},
"room_count_elements": {
"number_of_habitable_rooms": 2,
"any_unheated_rooms": true,
"number_of_heated_rooms": 0,
"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,
"exact_led_cfl_count_known": true,
"number_of_fixed_led_bulbs": 5,
"number_of_fixed_cfl_bulbs": 4,
"waste_water_heat_recovery": "None"
},
"water_use": {
"number_of_baths": 1,
"number_of_special_features": 0,
"showers": [
{
"id": 1,
"outlet_type": "Non-Electric Shower"
}
]
},
"customer_response": {
"customer_present": true,
"willing_to_answer_satisfaction_survey": false
},
"addendum": {
"addendum": "PV Recommended",
"related_party_disclosure": "No related party",
"hard_to_treat_cavity_access_issues": false,
"hard_to_treat_cavity_high_exposure": false,
"hard_to_treat_cavity_narrow_cavities": false
}
}

View file

@ -0,0 +1,330 @@
{
"inspection_metadata": {
"inspection_surveyor": "test",
"email_address": "test@test.com",
"report_reference": "6EA2A86D-94CE-4792-8D49-AB495C744EDD",
"created_on": "2025-11-10",
"date_of_inspection": "2025-09-25",
"property_address": "test",
"property_photo": true
},
"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"
},
"building_construction": {
"main_building": {
"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"
},
"extensions": [
{
"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"
}
],
"floor": {
"floor_type": "Ground Floor",
"floor_construction": "Solid",
"floor_insulation_type": "As Built",
"floor_u_value_known": false
}
},
"building_measurements": {
"main_building": {
"floors": [
{
"name": "Floor 1",
"area_m2": 35.68,
"height_m": 2.19,
"heat_loss_perimeter_m": 13.44,
"pwl_m": 10.62
},
{
"name": "Floor 0",
"area_m2": 35.68,
"height_m": 2.17,
"heat_loss_perimeter_m": 11.0,
"pwl_m": 10.62
}
]
},
"extensions": [
{
"id": 1,
"floors": [
{
"name": "Floor 0",
"area_m2": 3.8,
"height_m": 2.0,
"heat_loss_perimeter_m": 5.7,
"pwl_m": 0.0
}
]
}
]
},
"roof_space": {
"main_building": {
"construction_type": "Pitched roof (Slates or tiles), Access to loft",
"insulation_at": "Joists",
"roof_u_value_known": false,
"insulation_thickness_mm": 100,
"cavity_wall_construction_indicators": "cavity visible in roof space",
"rooms_in_roof": false
},
"extensions": [
{
"id": 1,
"construction_type": "Pitched roof, Sloping ceiling",
"insulation_at": "Sloping ceiling insulation",
"roof_u_value_known": false,
"insulation_thickness": "As built",
"cavity_wall_construction_indicators": "No indicator of construction visible",
"rooms_in_roof": false
}
]
},
"windows": [
{
"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"
},
{
"id": 2,
"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": 1.0,
"orientation": "North West"
},
{
"id": 3,
"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": 0.9,
"width_m": 1.0,
"orientation": "North East"
},
{
"id": 4,
"location": "Extension 1",
"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": 0.9,
"width_m": 1.0,
"orientation": "North"
},
{
"id": 5,
"location": "Extension 1",
"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": 0.9,
"width_m": 1.7,
"orientation": "North East"
},
{
"id": 6,
"location": "Extension 1",
"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": 0.9,
"width_m": 2.3,
"orientation": "North West"
},
{
"id": 7,
"location": "Extension 1",
"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.0,
"width_m": 1.2,
"orientation": "North West"
},
{
"id": 8,
"location": "Extension 1",
"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": 0.9,
"width_m": 1.0,
"orientation": "North East"
}
],
"heating_and_hot_water": {
"main_heating": {
"selection_method": "PCDF Search",
"system_type": "Boiler with radiators or underfloor heating",
"product_id": 16839,
"manufacturer": "Vaillant",
"model": "ecoTEC pro 28",
"orig_manufacturer": "Vaillant",
"fuel": "Mains gas",
"summer_efficiency": 0,
"type": "Combi",
"condensing": true,
"year": "2005 - 2015",
"mount": "Wall",
"open_flue": "Room-sealed",
"fan_assist": true,
"status": "Normal status for an actual product",
"central_heating_pump_age": "Unknown",
"controls": "Programmer, room thermostat and TRVs",
"flue_gas_heat_recovery_system": false,
"weather_compensator": false,
"emitter": "Radiators",
"emitter_temperature": "Unknown"
},
"secondary_heating": {
"secondary_fuel": "No Secondary Heating"
},
"water_heating": {
"type": "Regular",
"system": "From main heating 1",
"cylinder_size": "No Cylinder",
"cylinder_measured_heat_loss": null,
"insulation_type": null,
"insulation_thickness_mm": null,
"has_thermostat": null
}
},
"ventilation": {
"ventilation_type": "Mechanical Extract - Decentralised",
"ventilation_in_pcdf_database": false,
"has_fixed_air_conditioning": false,
"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",
"draught_lobby": false
},
"conservatories": {
"has_conservatory": false
},
"renewables": {
"wind_turbines": false,
"solar_hot_water": false,
"photovoltaic_array": false,
"number_of_pv_batteries": 0,
"hydro": false
},
"room_count_elements": {
"number_of_habitable_rooms": 3,
"any_unheated_rooms": false,
"number_of_heated_rooms": null,
"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": 4,
"exact_led_cfl_count_known": true,
"number_of_fixed_led_bulbs": 0,
"number_of_fixed_cfl_bulbs": 1,
"waste_water_heat_recovery": "None"
},
"water_use": {
"number_of_baths": 1,
"number_of_special_features": 0,
"showers": [
{
"id": 1,
"outlet_type": "Non-Electric Shower"
}
]
},
"customer_response": {
"customer_present": true,
"willing_to_answer_satisfaction_survey": false
},
"addendum": {
"addendum": "None",
"related_party_disclosure": "No related party",
"hard_to_treat_cavity_access_issues": false,
"hard_to_treat_cavity_high_exposure": false,
"hard_to_treat_cavity_narrow_cavities": false
}
}

View file

@ -0,0 +1,362 @@
import json
import os
from typing import Any, Dict
import pytest
from datatypes.epc.schema.tests.helpers import from_dict
from datatypes.epc.surveys.pashub_rdsap_site_notes import (
ExtensionConstruction,
ExtensionMeasurements,
ExtensionRoofSpace,
PasHubRdSapSiteNotes,
)
FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures")
def load(filename: str) -> Dict[str, Any]:
with open(os.path.join(FIXTURES, filename)) as f:
return json.load(f) # type: ignore[no-any-return]
class TestExample1:
"""No extensions; regular boiler with hot water cylinder; natural ventilation."""
@pytest.fixture
def survey(self) -> PasHubRdSapSiteNotes:
return from_dict(PasHubRdSapSiteNotes, load("example1.json"))
# --- inspection_metadata ---
def test_report_reference(self, survey: PasHubRdSapSiteNotes) -> None:
assert (
survey.inspection_metadata.report_reference
== "49D422A9-0779-44DD-9665-464D35DFF1A8"
)
def test_created_on(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.inspection_metadata.created_on == "2026-03-31"
def test_property_photo_absent(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.inspection_metadata.property_photo is None
# --- general ---
def test_transaction_type(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.general.transaction_type == "None of the Above"
def test_number_of_extensions(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.general.number_of_extensions == 0
def test_smart_meters(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.general.electricity_smart_meter is True
assert survey.general.gas_smart_meter is True
# --- building_construction ---
def test_main_building_wall_thickness(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.building_construction.main_building.wall_thickness_mm == 280
def test_main_building_walls_insulation_type(
self, survey: PasHubRdSapSiteNotes
) -> None:
assert (
survey.building_construction.main_building.walls_insulation_type
== "As built"
)
def test_filled_cavity_indicators_absent(
self, survey: PasHubRdSapSiteNotes
) -> None:
assert (
survey.building_construction.main_building.filled_cavity_indicators is None
)
def test_no_extensions_in_construction(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.building_construction.extensions is None
def test_floor_construction(self, survey: PasHubRdSapSiteNotes) -> None:
assert (
survey.building_construction.floor.floor_construction
== "Suspended, not timber"
)
# --- building_measurements ---
def test_main_building_floor_count(self, survey: PasHubRdSapSiteNotes) -> None:
assert len(survey.building_measurements.main_building.floors) == 2
def test_floor_area(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.building_measurements.main_building.floors[0].area_m2 == 24.78
def test_no_extension_measurements(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.building_measurements.extensions is None
# --- roof_space ---
def test_roof_insulation_thickness_mm(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.roof_space.main_building.insulation_thickness_mm == 100
def test_roof_insulation_thickness_string_absent(
self, survey: PasHubRdSapSiteNotes
) -> None:
assert survey.roof_space.main_building.insulation_thickness is None
def test_no_extension_roof_spaces(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.roof_space.extensions is None
def test_rooms_in_roof_false(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.roof_space.main_building.rooms_in_roof is False
# --- windows ---
def test_window_count(self, survey: PasHubRdSapSiteNotes) -> None:
assert len(survey.windows) == 4
def test_window_dimensions(self, survey: PasHubRdSapSiteNotes) -> None:
w = survey.windows[0]
assert w.height_m == 1.36
assert w.width_m == 1.0
def test_window_orientation(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.windows[0].orientation == "South East"
assert survey.windows[2].orientation == "North West"
def test_window_glazing_gap(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.windows[0].glazing_gap == "16 mm or more"
# --- heating_and_hot_water ---
def test_main_heating_manufacturer(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.heating_and_hot_water.main_heating.manufacturer == "Vaillant"
def test_main_heating_model(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.heating_and_hot_water.main_heating.model == "ecoFIT sustain 415"
def test_main_heating_product_id(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.heating_and_hot_water.main_heating.product_id == 18400
def test_main_heating_type_regular(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.heating_and_hot_water.main_heating.type == "Regular"
def test_water_heating_cylinder_present(self, survey: PasHubRdSapSiteNotes) -> None:
wh = survey.heating_and_hot_water.water_heating
assert wh.cylinder_size == "Normal (90-130 litres)"
assert wh.insulation_type == "Factory fitted"
assert wh.insulation_thickness_mm == 12
assert wh.has_thermostat is True
def test_secondary_heating_none(self, survey: PasHubRdSapSiteNotes) -> None:
assert (
survey.heating_and_hot_water.secondary_heating.secondary_fuel
== "No Secondary Heating"
)
# --- ventilation ---
def test_ventilation_type(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.ventilation.ventilation_type == "Natural"
def test_ventilation_pcdf_absent(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.ventilation.ventilation_in_pcdf_database is None
def test_extract_fans(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.ventilation.number_of_extract_fans == 2
# --- room_count_elements ---
def test_habitable_rooms(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.room_count_elements.number_of_habitable_rooms == 2
def test_heated_rooms_zero(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.room_count_elements.number_of_heated_rooms == 0
def test_led_bulbs(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.room_count_elements.number_of_fixed_led_bulbs == 5
def test_cfl_bulbs(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.room_count_elements.number_of_fixed_cfl_bulbs == 4
# --- water_use ---
def test_shower_outlet_type(self, survey: PasHubRdSapSiteNotes) -> None:
assert len(survey.water_use.showers) == 1
assert survey.water_use.showers[0].outlet_type == "Non-Electric Shower"
# --- addendum ---
def test_addendum_value(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.addendum.addendum == "PV Recommended"
def test_related_party_disclosure(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.addendum.related_party_disclosure == "No related party"
def test_hard_to_treat_flags_false(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.addendum.hard_to_treat_cavity_access_issues is False
assert survey.addendum.hard_to_treat_cavity_high_exposure is False
assert survey.addendum.hard_to_treat_cavity_narrow_cavities is False
class TestExample2:
"""With extensions; combi boiler (no cylinder); mechanical extract ventilation."""
@pytest.fixture
def survey(self) -> PasHubRdSapSiteNotes:
return from_dict(PasHubRdSapSiteNotes, load("example2.json"))
# --- inspection_metadata ---
def test_report_reference(self, survey: PasHubRdSapSiteNotes) -> None:
assert (
survey.inspection_metadata.report_reference
== "6EA2A86D-94CE-4792-8D49-AB495C744EDD"
)
def test_property_photo_present(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.inspection_metadata.property_photo is True
def test_created_on_differs_from_inspection_date(
self, survey: PasHubRdSapSiteNotes
) -> None:
assert survey.inspection_metadata.created_on == "2025-11-10"
assert survey.inspection_metadata.date_of_inspection == "2025-09-25"
# --- general ---
def test_number_of_extensions(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.general.number_of_extensions == 1
def test_transaction_type(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.general.transaction_type == "Grant-Scheme (ECO, RHI, etc.)"
# --- building_construction ---
def test_main_building_filled_cavity_indicators(
self, survey: PasHubRdSapSiteNotes
) -> None:
assert (
survey.building_construction.main_building.filled_cavity_indicators
== "evidence of cavity fill drill holes"
)
def test_extension_construction_present(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.building_construction.extensions is not None
assert len(survey.building_construction.extensions) == 1
def test_extension_construction_type(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.building_construction.extensions is not None
ext: ExtensionConstruction = survey.building_construction.extensions[0]
assert ext.id == 1
assert ext.walls_insulation_type == "As built"
assert ext.wall_thickness_mm == 310
def test_extension_no_filled_cavity_indicators(
self, survey: PasHubRdSapSiteNotes
) -> None:
assert survey.building_construction.extensions is not None
assert (
survey.building_construction.extensions[0].filled_cavity_indicators is None
)
# --- building_measurements ---
def test_main_building_floor_count(self, survey: PasHubRdSapSiteNotes) -> None:
assert len(survey.building_measurements.main_building.floors) == 2
def test_extension_measurements_present(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.building_measurements.extensions is not None
assert len(survey.building_measurements.extensions) == 1
def test_extension_floor_area(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.building_measurements.extensions is not None
ext: ExtensionMeasurements = survey.building_measurements.extensions[0]
assert ext.id == 1
assert len(ext.floors) == 1
assert ext.floors[0].area_m2 == 3.8
# --- roof_space ---
def test_main_roof_insulation_thickness_mm(
self, survey: PasHubRdSapSiteNotes
) -> None:
assert survey.roof_space.main_building.insulation_thickness_mm == 100
def test_extension_roof_spaces_present(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.roof_space.extensions is not None
assert len(survey.roof_space.extensions) == 1
def test_extension_roof_uses_string_thickness(
self, survey: PasHubRdSapSiteNotes
) -> None:
assert survey.roof_space.extensions is not None
ext: ExtensionRoofSpace = survey.roof_space.extensions[0]
assert ext.insulation_thickness == "As built"
assert ext.insulation_thickness_mm is None
def test_extension_roof_construction_type(
self, survey: PasHubRdSapSiteNotes
) -> None:
assert survey.roof_space.extensions is not None
assert (
survey.roof_space.extensions[0].construction_type
== "Pitched roof, Sloping ceiling"
)
# --- windows ---
def test_window_count(self, survey: PasHubRdSapSiteNotes) -> None:
assert len(survey.windows) == 8
def test_extension_windows(self, survey: PasHubRdSapSiteNotes) -> None:
extension_windows = [w for w in survey.windows if w.location == "Extension 1"]
assert len(extension_windows) == 5
def test_window_ids_sequential(self, survey: PasHubRdSapSiteNotes) -> None:
ids = [w.id for w in survey.windows]
assert ids == list(range(1, 9))
# --- heating_and_hot_water ---
def test_main_heating_type_combi(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.heating_and_hot_water.main_heating.type == "Combi"
def test_main_heating_model(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.heating_and_hot_water.main_heating.model == "ecoTEC pro 28"
def test_water_heating_no_cylinder(self, survey: PasHubRdSapSiteNotes) -> None:
wh = survey.heating_and_hot_water.water_heating
assert wh.cylinder_size == "No Cylinder"
assert wh.cylinder_measured_heat_loss is None
assert wh.insulation_type is None
assert wh.insulation_thickness_mm is None
assert wh.has_thermostat is None
# --- ventilation ---
def test_ventilation_type(self, survey: PasHubRdSapSiteNotes) -> None:
assert (
survey.ventilation.ventilation_type == "Mechanical Extract - Decentralised"
)
def test_ventilation_in_pcdf_database(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.ventilation.ventilation_in_pcdf_database is False
def test_no_extract_fans_for_mev(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.ventilation.number_of_extract_fans == 0
# --- room_count_elements ---
def test_habitable_rooms(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.room_count_elements.number_of_habitable_rooms == 3
def test_heated_rooms_null(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.room_count_elements.number_of_heated_rooms is None
def test_incandescent_bulbs(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.room_count_elements.number_of_fixed_incandescent_bulbs == 4
# --- addendum ---
def test_addendum_none(self, survey: PasHubRdSapSiteNotes) -> None:
assert survey.addendum.addendum == "None"

View file

@ -3,6 +3,6 @@ pythonpath = .
log_cli = true
log_cli_level = INFO
addopts = --cov-report term-missing --cov=etl/epc --cov=recommendations --cov=backend --cov=etl/epc_clean --cov=etl/spatial
testpaths = recommendations/tests backend/tests etl/epc/tests etl/epc_clean/tests etl/spatial/tests backend/condition/tests backend/address2UPRN/tests backend/onboarders/tests backend/categorisation/tests backend/export/tests etl/hubspot/tests backend/hubspot_trigger_orchestrator/tests datatypes/epc/schema/tests
testpaths = recommendations/tests backend/tests etl/epc/tests etl/epc_clean/tests etl/spatial/tests backend/condition/tests backend/address2UPRN/tests backend/onboarders/tests backend/categorisation/tests backend/export/tests etl/hubspot/tests backend/hubspot_trigger_orchestrator/tests datatypes/epc/schema/tests datatypes/epc/surveys/tests
markers =
integration: mark a test as an integration test