mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Map to domain from site notes objects 🟥
This commit is contained in:
parent
7523012e24
commit
0fbcacb8cd
2 changed files with 536 additions and 41 deletions
|
|
@ -22,20 +22,20 @@ class InstantaneousWwhrs:
|
|||
@dataclass
|
||||
class MainHeatingDetail:
|
||||
has_fghrs: bool
|
||||
main_fuel_type: int # TODO: make enum?
|
||||
heat_emitter_type: int # TODO: make enum?
|
||||
main_fuel_type: Union[int, str] # int from API, str from site notes
|
||||
heat_emitter_type: Union[int, str] # int from API, str from site notes
|
||||
emitter_temperature: Union[int, str]
|
||||
main_heating_number: int
|
||||
main_heating_control: int
|
||||
main_heating_category: int
|
||||
main_heating_fraction: int
|
||||
main_heating_data_source: int
|
||||
fan_flue_present: bool
|
||||
main_heating_control: Union[int, str] # int from API, str from site notes
|
||||
boiler_flue_type: Optional[int] = None # TODO: make enum?
|
||||
boiler_ignition_type: Optional[int] = None # TODO: make enum?
|
||||
central_heating_pump_age: Optional[int] = None
|
||||
main_heating_index_number: Optional[int] = None
|
||||
sap_main_heating_code: Optional[int] = None # TODO: make enum?
|
||||
main_heating_number: Optional[int] = None
|
||||
main_heating_category: Optional[int] = None
|
||||
main_heating_fraction: Optional[int] = None
|
||||
main_heating_data_source: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -52,13 +52,15 @@ class ShowerOutlets:
|
|||
|
||||
@dataclass
|
||||
class SapHeating:
|
||||
cylinder_size: int
|
||||
water_heating_code: int # TODO: make enum?
|
||||
water_heating_fuel: int # TODO: make enum?
|
||||
instantaneous_wwhrs: InstantaneousWwhrs
|
||||
main_heating_details: List[MainHeatingDetail]
|
||||
immersion_heating_type: Union[int, str]
|
||||
has_fixed_air_conditioning: str
|
||||
cylinder_size: Optional[int] = (
|
||||
None # int code from API; not directly available from site notes
|
||||
)
|
||||
water_heating_code: Optional[int] = None # TODO: make enum?
|
||||
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_thermostat: Optional[str] = None
|
||||
|
|
@ -77,19 +79,19 @@ class WindowTransmissionDetails:
|
|||
@dataclass
|
||||
class SapWindow:
|
||||
pvc_frame: str
|
||||
glazing_gap: int
|
||||
orientation: int
|
||||
window_type: int
|
||||
frame_factor: float
|
||||
glazing_type: int
|
||||
glazing_gap: Union[int, str]
|
||||
orientation: Union[int, str]
|
||||
window_type: Union[int, str]
|
||||
glazing_type: Union[int, str]
|
||||
window_width: float
|
||||
window_height: float
|
||||
draught_proofed: str
|
||||
window_location: int
|
||||
window_wall_type: int
|
||||
permanent_shutters_present: str
|
||||
window_transmission_details: WindowTransmissionDetails
|
||||
permanent_shutters_insulated: str
|
||||
draught_proofed: Union[bool, str] # TODO: make enum/mapping?
|
||||
window_location: Union[int, str] # TODO: make enum/mapping
|
||||
window_wall_type: Union[int, str] # TODO: make enum/mapping
|
||||
permanent_shutters_present: Union[bool, str] # TODO: make enum/mapping
|
||||
frame_factor: Optional[float] = None
|
||||
window_transmission_details: Optional[WindowTransmissionDetails] = None
|
||||
permanent_shutters_insulated: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -170,10 +172,14 @@ class SapBuildingPart:
|
|||
construction_age_band: str
|
||||
|
||||
# Wall
|
||||
wall_construction: int
|
||||
wall_insulation_type: int
|
||||
wall_construction: Union[
|
||||
int, str
|
||||
] # int from API, str from site notes TODO: make enum/mapping?
|
||||
wall_insulation_type: Union[
|
||||
int, str
|
||||
] # int from API, str from site notes TODO: make enum/mapping?
|
||||
wall_thickness_measured: bool
|
||||
party_wall_construction: Union[int, str]
|
||||
party_wall_construction: Union[int, str] # TODO: make enum/mapping?
|
||||
|
||||
# Floor
|
||||
sap_floor_dimensions: List[
|
||||
|
|
@ -192,11 +198,17 @@ class SapBuildingPart:
|
|||
|
||||
floor_heat_loss: Optional[int] = None
|
||||
floor_insulation_thickness: Optional[str] = None
|
||||
flat_roof_insulation_thickness: Optional[Union[str, int]] = None
|
||||
flat_roof_insulation_thickness: Optional[Union[str, int]] = (
|
||||
None # TODO: make enum/mapping?
|
||||
)
|
||||
|
||||
roof_construction: Optional[int] = None
|
||||
roof_insulation_location: Optional[Union[int, str]] = None
|
||||
roof_insulation_thickness: Optional[Union[str, int]] = None
|
||||
roof_insulation_location: Optional[Union[int, str]] = (
|
||||
None # TODO: make enum/mapping?
|
||||
)
|
||||
roof_insulation_thickness: Optional[Union[str, int]] = (
|
||||
None # TODO: make enum/mapping?
|
||||
)
|
||||
sap_room_in_roof: Optional[SapRoomInRoof] = None
|
||||
|
||||
|
||||
|
|
@ -220,26 +232,16 @@ class SapFlatDetails:
|
|||
@dataclass
|
||||
class EpcPropertyData:
|
||||
# General
|
||||
assessment_type: str # TODO: make enum?
|
||||
sap_version: float # Optional?
|
||||
dwelling_type: str # TODO: make enum?
|
||||
uprn: int
|
||||
address_line_1: str
|
||||
postcode: str
|
||||
post_town: str
|
||||
inspection_date: date
|
||||
status: str
|
||||
tenure: int # How does this map to string?
|
||||
transaction_type: int # What is this?
|
||||
tenure: str # str in site notes; stringified int (e.g. "1") from API
|
||||
transaction_type: str # str in site notes; stringified int from API
|
||||
|
||||
# Elements
|
||||
roofs: List[EnergyElement]
|
||||
walls: List[EnergyElement]
|
||||
floors: List[EnergyElement]
|
||||
main_heating: List[EnergyElement]
|
||||
window: EnergyElement
|
||||
lighting: EnergyElement
|
||||
hot_water: EnergyElement
|
||||
door_count: int
|
||||
sap_heating: SapHeating
|
||||
sap_windows: List[SapWindow]
|
||||
|
|
@ -263,9 +265,19 @@ class EpcPropertyData:
|
|||
incandescent_fixed_lighting_bulbs_count: int
|
||||
|
||||
# Measurements
|
||||
total_floor_area_m2: int
|
||||
total_floor_area_m2: float
|
||||
|
||||
# Optional fields
|
||||
assessment_type: Optional[str] = None # not available from site notes
|
||||
sap_version: Optional[float] = None # not available from site notes
|
||||
uprn: Optional[int] = None # not available from site notes
|
||||
address_line_1: Optional[str] = None # not available from site notes
|
||||
postcode: Optional[str] = None # not available from site notes
|
||||
post_town: Optional[str] = None # not available from site notes
|
||||
status: Optional[str] = None # not available from site notes
|
||||
window: Optional[EnergyElement] = None # not available from site notes
|
||||
lighting: Optional[EnergyElement] = None # not available from site notes
|
||||
hot_water: Optional[EnergyElement] = None # not available from site notes
|
||||
schema_type: Optional[str] = None
|
||||
schema_versions_original: Optional[str] = None
|
||||
report_type: Optional[str] = None # TODO: make enum?
|
||||
|
|
@ -283,6 +295,7 @@ class EpcPropertyData:
|
|||
conservatory_type: Optional[int] = (
|
||||
None # What is this? site notes have "has_conservatory" flag
|
||||
)
|
||||
has_conservatory: Optional[bool] = None # mapped directly from site notes
|
||||
has_heated_separate_conservatory: Optional[bool] = None
|
||||
secondary_heating: Optional[EnergyElement] = (
|
||||
None # For site notes, secondary_fuel maps to sap_heating.secondary_fuel_type
|
||||
|
|
|
|||
482
datatypes/epc/domain/tests/test_from_site_notes.py
Normal file
482
datatypes/epc/domain/tests/test_from_site_notes.py
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
import json
|
||||
import os
|
||||
from datetime import date
|
||||
from typing import Any, Dict
|
||||
|
||||
import pytest
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import (
|
||||
EpcPropertyData,
|
||||
InstantaneousWwhrs,
|
||||
MainHeatingDetail,
|
||||
SapBuildingPart,
|
||||
SapEnergySource,
|
||||
SapFloorDimension,
|
||||
SapHeating,
|
||||
SapWindow,
|
||||
)
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
from datatypes.epc.schema.tests.helpers import from_dict
|
||||
from datatypes.epc.surveys.pashub_rdsap_site_notes import PasHubRdSapSiteNotes
|
||||
|
||||
FIXTURES = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"../../surveys/tests/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 TestFromSiteNotesExample1:
|
||||
"""
|
||||
Fixture: pashub_rdsap_site_notes_example1.json
|
||||
No extensions, regular boiler with cylinder, natural ventilation.
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def survey(self) -> PasHubRdSapSiteNotes:
|
||||
return from_dict(PasHubRdSapSiteNotes, load("pashub_rdsap_site_notes_example1.json"))
|
||||
|
||||
@pytest.fixture
|
||||
def result(self, survey: PasHubRdSapSiteNotes) -> EpcPropertyData:
|
||||
return EpcPropertyDataMapper.from_site_notes(survey)
|
||||
|
||||
# --- property details ---
|
||||
|
||||
def test_dwelling_type(self, result: EpcPropertyData) -> None:
|
||||
# general.property_type + general.detachment_type → "Mid-terrace house"
|
||||
assert result.dwelling_type == "Mid-terrace house"
|
||||
|
||||
def test_tenure(self, result: EpcPropertyData) -> None:
|
||||
# general.tenure: "Rented Social"
|
||||
assert result.tenure == "Rented Social"
|
||||
|
||||
def test_transaction_type(self, result: EpcPropertyData) -> None:
|
||||
# general.transaction_type: "None of the Above"
|
||||
assert result.transaction_type == "None of the Above"
|
||||
|
||||
def test_inspection_date(self, result: EpcPropertyData) -> None:
|
||||
# general.inspection_date: "2026-03-31"
|
||||
assert result.inspection_date == date(2026, 3, 31)
|
||||
|
||||
def test_built_form(self, result: EpcPropertyData) -> None:
|
||||
# general.detachment_type: "Mid-terrace"
|
||||
assert result.built_form == "Mid-terrace"
|
||||
|
||||
def test_property_type(self, result: EpcPropertyData) -> None:
|
||||
# general.property_type: "House"
|
||||
assert result.property_type == "House"
|
||||
|
||||
# --- energy elements are not available from site notes ---
|
||||
|
||||
def test_roofs_empty(self, result: EpcPropertyData) -> None:
|
||||
assert result.roofs == []
|
||||
|
||||
def test_walls_empty(self, result: EpcPropertyData) -> None:
|
||||
assert result.walls == []
|
||||
|
||||
def test_floors_empty(self, result: EpcPropertyData) -> None:
|
||||
assert result.floors == []
|
||||
|
||||
def test_main_heating_elements_empty(self, result: EpcPropertyData) -> None:
|
||||
assert result.main_heating == []
|
||||
|
||||
def test_window_element_absent(self, result: EpcPropertyData) -> None:
|
||||
assert result.window is None
|
||||
|
||||
def test_lighting_element_absent(self, result: EpcPropertyData) -> None:
|
||||
assert result.lighting is None
|
||||
|
||||
def test_hot_water_element_absent(self, result: EpcPropertyData) -> None:
|
||||
assert result.hot_water is None
|
||||
|
||||
# --- energy source ---
|
||||
|
||||
def test_mains_gas_available(self, result: EpcPropertyData) -> None:
|
||||
# general.mains_gas_available: true
|
||||
assert result.sap_energy_source.mains_gas is True
|
||||
|
||||
def test_electricity_smart_meter(self, result: EpcPropertyData) -> None:
|
||||
# general.electricity_smart_meter: true
|
||||
assert result.sap_energy_source.electricity_smart_meter_present is True
|
||||
|
||||
def test_gas_smart_meter(self, result: EpcPropertyData) -> None:
|
||||
# general.gas_smart_meter: true
|
||||
assert result.sap_energy_source.gas_smart_meter_present is True
|
||||
|
||||
def test_meter_type(self, result: EpcPropertyData) -> None:
|
||||
# general.electric_meter_type: "Single"
|
||||
assert result.sap_energy_source.meter_type == "Single"
|
||||
|
||||
def test_dwelling_export_capable(self, result: EpcPropertyData) -> None:
|
||||
# general.dwelling_export_capable: true
|
||||
assert result.sap_energy_source.is_dwelling_export_capable is True
|
||||
|
||||
def test_wind_turbines_terrain_type(self, result: EpcPropertyData) -> None:
|
||||
# general.terrain_type: "Suburban"
|
||||
assert result.sap_energy_source.wind_turbines_terrain_type == "Suburban"
|
||||
|
||||
def test_no_wind_turbines(self, result: EpcPropertyData) -> None:
|
||||
# renewables.wind_turbines: false → count 0
|
||||
assert result.sap_energy_source.wind_turbines_count == 0
|
||||
|
||||
def test_no_pv_batteries(self, result: EpcPropertyData) -> None:
|
||||
# renewables.number_of_pv_batteries: 0
|
||||
assert result.sap_energy_source.pv_battery_count == 0
|
||||
|
||||
# --- renewables ---
|
||||
|
||||
def test_no_solar_hot_water(self, result: EpcPropertyData) -> None:
|
||||
# renewables.solar_hot_water: false
|
||||
assert result.solar_water_heating is False
|
||||
|
||||
# --- ventilation ---
|
||||
|
||||
def test_no_fixed_air_conditioning(self, result: EpcPropertyData) -> None:
|
||||
# ventilation.has_fixed_air_conditioning: false
|
||||
assert result.has_fixed_air_conditioning is False
|
||||
|
||||
# --- conservatory ---
|
||||
|
||||
def test_no_conservatory(self, result: EpcPropertyData) -> None:
|
||||
# conservatories.has_conservatory: false
|
||||
assert result.has_conservatory is False
|
||||
|
||||
# --- hot water / cylinder ---
|
||||
|
||||
def test_has_hot_water_cylinder(self, result: EpcPropertyData) -> None:
|
||||
# water_heating.cylinder_size is present → cylinder exists
|
||||
assert result.has_hot_water_cylinder is True
|
||||
|
||||
# --- main heating ---
|
||||
|
||||
def test_main_heating_fuel(self, result: EpcPropertyData) -> None:
|
||||
# heating_and_hot_water.main_heating.fuel: "Mains gas"
|
||||
assert result.sap_heating.main_heating_details[0].main_fuel_type == "Mains gas"
|
||||
|
||||
def test_main_heating_emitter(self, result: EpcPropertyData) -> None:
|
||||
# heating_and_hot_water.main_heating.emitter: "Radiators"
|
||||
assert result.sap_heating.main_heating_details[0].heat_emitter_type == "Radiators"
|
||||
|
||||
def test_main_heating_no_fghrs(self, result: EpcPropertyData) -> None:
|
||||
# heating_and_hot_water.main_heating.flue_gas_heat_recovery_system: false
|
||||
assert result.sap_heating.main_heating_details[0].has_fghrs is False
|
||||
|
||||
def test_main_heating_fan_flue_present(self, result: EpcPropertyData) -> None:
|
||||
# heating_and_hot_water.main_heating.fan_assist: true
|
||||
assert result.sap_heating.main_heating_details[0].fan_flue_present is True
|
||||
|
||||
def test_no_secondary_heating(self, result: EpcPropertyData) -> None:
|
||||
# secondary_heating.secondary_fuel: "No Secondary Heating" → no secondary fuel type
|
||||
assert result.sap_heating.secondary_fuel_type is None
|
||||
|
||||
# --- windows ---
|
||||
|
||||
def test_window_count(self, result: EpcPropertyData) -> None:
|
||||
# 4 windows in fixture
|
||||
assert len(result.sap_windows) == 4
|
||||
|
||||
def test_window_height(self, result: EpcPropertyData) -> None:
|
||||
assert result.sap_windows[0].window_height == 1.36
|
||||
|
||||
def test_window_width(self, result: EpcPropertyData) -> None:
|
||||
assert result.sap_windows[0].window_width == 1.0
|
||||
|
||||
def test_window_draught_proofed(self, result: EpcPropertyData) -> None:
|
||||
# windows[0].draught_proofed: true
|
||||
assert result.sap_windows[0].draught_proofed is True
|
||||
|
||||
def test_window_orientation(self, result: EpcPropertyData) -> None:
|
||||
assert result.sap_windows[0].orientation == "South East"
|
||||
|
||||
def test_window_glazing_type(self, result: EpcPropertyData) -> None:
|
||||
assert result.sap_windows[0].glazing_type == "Double glazing, Unknown install date"
|
||||
|
||||
# --- building parts ---
|
||||
|
||||
def test_building_parts_count(self, result: EpcPropertyData) -> None:
|
||||
# no extensions → one building part for main building
|
||||
assert len(result.sap_building_parts) == 1
|
||||
|
||||
def test_building_part_identifier(self, result: EpcPropertyData) -> None:
|
||||
assert result.sap_building_parts[0].identifier == "main"
|
||||
|
||||
def test_construction_age_band(self, result: EpcPropertyData) -> None:
|
||||
# main_building.age_range: "I: 1996 - 2002" → letter "I"
|
||||
assert result.sap_building_parts[0].construction_age_band == "I"
|
||||
|
||||
def test_wall_construction(self, result: EpcPropertyData) -> None:
|
||||
# main_building.walls_construction_type: "Cavity"
|
||||
assert result.sap_building_parts[0].wall_construction == "Cavity"
|
||||
|
||||
def test_wall_insulation_type(self, result: EpcPropertyData) -> None:
|
||||
# main_building.walls_insulation_type: "As built"
|
||||
assert result.sap_building_parts[0].wall_insulation_type == "As built"
|
||||
|
||||
def test_wall_thickness_measured(self, result: EpcPropertyData) -> None:
|
||||
# main_building.wall_thickness_mm: 280 → thickness was measured
|
||||
assert result.sap_building_parts[0].wall_thickness_measured is True
|
||||
|
||||
def test_wall_thickness_mm(self, result: EpcPropertyData) -> None:
|
||||
assert result.sap_building_parts[0].wall_thickness_mm == 280
|
||||
|
||||
# --- floor dimensions ---
|
||||
|
||||
def test_floor_count(self, result: EpcPropertyData) -> None:
|
||||
# 2 floors in main building
|
||||
assert len(result.sap_building_parts[0].sap_floor_dimensions) == 2
|
||||
|
||||
def test_floor_area(self, result: EpcPropertyData) -> None:
|
||||
# building_measurements.main_building.floors[0].area_m2: 24.78
|
||||
assert result.sap_building_parts[0].sap_floor_dimensions[0].total_floor_area_m2 == 24.78
|
||||
|
||||
def test_floor_height(self, result: EpcPropertyData) -> None:
|
||||
# floors[0].height_m: 2.37
|
||||
assert result.sap_building_parts[0].sap_floor_dimensions[0].room_height_m == 2.37
|
||||
|
||||
def test_heat_loss_perimeter(self, result: EpcPropertyData) -> None:
|
||||
# floors[0].heat_loss_perimeter_m: 14.21
|
||||
assert result.sap_building_parts[0].sap_floor_dimensions[0].heat_loss_perimeter_m == 14.21
|
||||
|
||||
def test_party_wall_length(self, result: EpcPropertyData) -> None:
|
||||
# floors[0].pwl_m: 6.15
|
||||
assert result.sap_building_parts[0].sap_floor_dimensions[0].party_wall_length_m == 6.15
|
||||
|
||||
def test_total_floor_area(self, result: EpcPropertyData) -> None:
|
||||
# sum of all floor areas: 24.78 + 24.78 = 49.56
|
||||
assert result.total_floor_area_m2 == 49.56
|
||||
|
||||
# --- room counts ---
|
||||
|
||||
def test_habitable_rooms(self, result: EpcPropertyData) -> None:
|
||||
# room_count_elements.number_of_habitable_rooms: 2
|
||||
assert result.habitable_rooms_count == 2
|
||||
|
||||
def test_external_doors(self, result: EpcPropertyData) -> None:
|
||||
# room_count_elements.number_of_external_doors: 2
|
||||
assert result.door_count == 2
|
||||
|
||||
def test_open_chimneys(self, result: EpcPropertyData) -> None:
|
||||
# room_count_elements.number_of_open_chimneys: 0
|
||||
assert result.open_chimneys_count == 0
|
||||
|
||||
def test_blocked_chimneys(self, result: EpcPropertyData) -> None:
|
||||
# room_count_elements.number_of_blocked_chimneys: 0
|
||||
assert result.blocked_chimneys_count == 0
|
||||
|
||||
def test_insulated_doors(self, result: EpcPropertyData) -> None:
|
||||
# room_count_elements.number_of_insulated_external_doors: 0
|
||||
assert result.insulated_door_count == 0
|
||||
|
||||
def test_draughtproofed_doors(self, result: EpcPropertyData) -> None:
|
||||
# room_count_elements.number_of_draughtproofed_external_doors: 2
|
||||
assert result.draughtproofed_door_count == 2
|
||||
|
||||
def test_extensions_count(self, result: EpcPropertyData) -> None:
|
||||
# general.number_of_extensions: 0
|
||||
assert result.extensions_count == 0
|
||||
|
||||
def test_heated_rooms_count(self, result: EpcPropertyData) -> None:
|
||||
# room_count_elements.number_of_heated_rooms: 0
|
||||
# no equivalent in site notes defaults to 0
|
||||
assert result.heated_rooms_count == 0
|
||||
|
||||
def test_wet_rooms_count_defaults_to_zero(self, result: EpcPropertyData) -> None:
|
||||
# no equivalent in site notes; mapper must default to 0
|
||||
assert result.wet_rooms_count == 0
|
||||
|
||||
# --- lighting ---
|
||||
|
||||
def test_led_bulbs(self, result: EpcPropertyData) -> None:
|
||||
# room_count_elements.number_of_fixed_led_bulbs: 5
|
||||
assert result.led_fixed_lighting_bulbs_count == 5
|
||||
|
||||
def test_cfl_bulbs(self, result: EpcPropertyData) -> None:
|
||||
# room_count_elements.number_of_fixed_cfl_bulbs: 4
|
||||
assert result.cfl_fixed_lighting_bulbs_count == 4
|
||||
|
||||
def test_incandescent_bulbs(self, result: EpcPropertyData) -> None:
|
||||
# room_count_elements.number_of_fixed_incandescent_bulbs: 0
|
||||
assert result.incandescent_fixed_lighting_bulbs_count == 0
|
||||
|
||||
# --- api-only fields absent ---
|
||||
|
||||
def test_assessment_type_absent(self, result: EpcPropertyData) -> None:
|
||||
assert result.assessment_type is None
|
||||
|
||||
def test_sap_version_absent(self, result: EpcPropertyData) -> None:
|
||||
assert result.sap_version is None
|
||||
|
||||
def test_uprn_absent(self, result: EpcPropertyData) -> None:
|
||||
assert result.uprn is None
|
||||
|
||||
def test_address_absent(self, result: EpcPropertyData) -> None:
|
||||
assert result.address_line_1 is None
|
||||
|
||||
def test_postcode_absent(self, result: EpcPropertyData) -> None:
|
||||
assert result.postcode is None
|
||||
|
||||
def test_post_town_absent(self, result: EpcPropertyData) -> None:
|
||||
assert result.post_town is None
|
||||
|
||||
def test_status_absent(self, result: EpcPropertyData) -> None:
|
||||
assert result.status is None
|
||||
|
||||
# --- full object equality ---
|
||||
|
||||
def test_full_mapping(self, survey: PasHubRdSapSiteNotes) -> None:
|
||||
result = EpcPropertyDataMapper.from_site_notes(survey)
|
||||
expected = EpcPropertyData(
|
||||
# General
|
||||
assessment_type=None,
|
||||
sap_version=None,
|
||||
dwelling_type="Mid-terrace house",
|
||||
uprn=None,
|
||||
address_line_1=None,
|
||||
postcode=None,
|
||||
post_town=None,
|
||||
inspection_date=date(2026, 3, 31),
|
||||
status=None,
|
||||
tenure="Rented Social",
|
||||
transaction_type="None of the Above",
|
||||
# Elements (not available from site notes)
|
||||
roofs=[],
|
||||
walls=[],
|
||||
floors=[],
|
||||
main_heating=[],
|
||||
window=None,
|
||||
lighting=None,
|
||||
hot_water=None,
|
||||
door_count=2,
|
||||
# Heating
|
||||
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",
|
||||
fan_flue_present=True,
|
||||
main_heating_control="Programmer, room thermostat and TRVs",
|
||||
)
|
||||
],
|
||||
has_fixed_air_conditioning="false",
|
||||
),
|
||||
# Windows
|
||||
sap_windows=[
|
||||
SapWindow(
|
||||
pvc_frame="Wooden or PVC",
|
||||
glazing_gap="16 mm or more",
|
||||
orientation="South East",
|
||||
window_type="Window",
|
||||
glazing_type="Double glazing, Unknown install date",
|
||||
window_width=1.0,
|
||||
window_height=1.36,
|
||||
draught_proofed=True,
|
||||
window_location="Main Building",
|
||||
window_wall_type="External wall",
|
||||
permanent_shutters_present=False,
|
||||
),
|
||||
SapWindow(
|
||||
pvc_frame="Wooden or PVC",
|
||||
glazing_gap="16 mm or more",
|
||||
orientation="South East",
|
||||
window_type="Window",
|
||||
glazing_type="Double glazing, Unknown install date",
|
||||
window_width=0.96,
|
||||
window_height=1.33,
|
||||
draught_proofed=True,
|
||||
window_location="Main Building",
|
||||
window_wall_type="External wall",
|
||||
permanent_shutters_present=False,
|
||||
),
|
||||
SapWindow(
|
||||
pvc_frame="Wooden or PVC",
|
||||
glazing_gap="16 mm or more",
|
||||
orientation="North West",
|
||||
window_type="Window",
|
||||
glazing_type="Double glazing, Unknown install date",
|
||||
window_width=0.96,
|
||||
window_height=1.04,
|
||||
draught_proofed=True,
|
||||
window_location="Main Building",
|
||||
window_wall_type="External wall",
|
||||
permanent_shutters_present=False,
|
||||
),
|
||||
SapWindow(
|
||||
pvc_frame="Wooden or PVC",
|
||||
glazing_gap="16 mm or more",
|
||||
orientation="North West",
|
||||
window_type="Window",
|
||||
glazing_type="Double glazing, Unknown install date",
|
||||
window_width=0.97,
|
||||
window_height=1.02,
|
||||
draught_proofed=True,
|
||||
window_location="Main Building",
|
||||
window_wall_type="External wall",
|
||||
permanent_shutters_present=False,
|
||||
),
|
||||
],
|
||||
# Energy source
|
||||
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,
|
||||
),
|
||||
# Building parts
|
||||
sap_building_parts=[
|
||||
SapBuildingPart(
|
||||
identifier="main",
|
||||
construction_age_band="I",
|
||||
wall_construction="Cavity",
|
||||
wall_insulation_type="As built",
|
||||
wall_thickness_measured=True,
|
||||
party_wall_construction="Cavity Masonry, Unfilled",
|
||||
sap_floor_dimensions=[
|
||||
SapFloorDimension(
|
||||
room_height_m=2.37,
|
||||
total_floor_area_m2=24.78,
|
||||
party_wall_length_m=6.15,
|
||||
heat_loss_perimeter_m=14.21,
|
||||
),
|
||||
SapFloorDimension(
|
||||
room_height_m=2.35,
|
||||
total_floor_area_m2=24.78,
|
||||
party_wall_length_m=6.15,
|
||||
heat_loss_perimeter_m=14.21,
|
||||
),
|
||||
],
|
||||
wall_thickness_mm=280,
|
||||
)
|
||||
],
|
||||
solar_water_heating=False,
|
||||
has_hot_water_cylinder=True,
|
||||
has_fixed_air_conditioning=False,
|
||||
# Counts
|
||||
wet_rooms_count=0, # no equivalent in site notes
|
||||
extensions_count=0,
|
||||
heated_rooms_count=0,
|
||||
open_chimneys_count=0,
|
||||
habitable_rooms_count=2,
|
||||
insulated_door_count=0,
|
||||
cfl_fixed_lighting_bulbs_count=4,
|
||||
led_fixed_lighting_bulbs_count=5,
|
||||
incandescent_fixed_lighting_bulbs_count=0,
|
||||
total_floor_area_m2=49.56,
|
||||
# Optional fields populated from site notes
|
||||
built_form="Mid-terrace",
|
||||
property_type="House",
|
||||
has_conservatory=False,
|
||||
blocked_chimneys_count=0,
|
||||
draughtproofed_door_count=2,
|
||||
)
|
||||
assert result == expected
|
||||
Loading…
Add table
Reference in a new issue