diff --git a/datatypes/epc/domain/dwelling.py b/datatypes/epc/domain/dwelling.py deleted file mode 100644 index d7faff50..00000000 --- a/datatypes/epc/domain/dwelling.py +++ /dev/null @@ -1,143 +0,0 @@ -from dataclasses import dataclass -from typing import List, Optional - - -@dataclass -class PropertyDetails: - property_type: str # e.g. "House", "Flat" - built_form: str # e.g. "Mid-terrace", "Detached" - tenure: str # e.g. "Owner-occupied", "Rented Social" - number_of_storeys: int - construction_age_band: Optional[str] = None # e.g. "1950-1966", "I: 1996 - 2002" - transaction_type: Optional[str] = None - terrain_type: Optional[str] = None - mains_gas_available: Optional[bool] = None - electricity_smart_meter: Optional[bool] = None - gas_smart_meter: Optional[bool] = None - - -@dataclass -class FloorDimensions: - """Floor area and geometry for one storey of one building part.""" - - total_floor_area_m2: float - height_m: float - heat_loss_perimeter_m: Optional[float] = None - party_wall_length_m: Optional[float] = None - - -@dataclass -class WallDetails: - construction_type: str # e.g. "Cavity", "Solid masonry" - insulation_type: str # e.g. "As built", "Filled cavity", "External" - thickness_mm: Optional[int] = None - party_wall_construction_type: Optional[str] = None - - -@dataclass -class RoofDetails: - construction_type: str # e.g. "Pitched, access to loft", "Flat" - insulation_at: Optional[str] = None # e.g. "Joists", "Rafters" - insulation_thickness_mm: Optional[int] = None - has_rooms_in_roof: bool = False - - -@dataclass -class FloorDetails: - construction_type: str # e.g. "Solid", "Suspended timber" - insulation_type: Optional[str] = None # e.g. "As built", "Insulated" - - -@dataclass -class WindowDetails: - glazing_type: str # e.g. "Double glazing", "Triple glazing" - orientation: Optional[str] = None - frame_type: Optional[str] = None - glazing_gap: Optional[str] = None - draught_proofed: Optional[bool] = None - height_m: Optional[float] = None - width_m: Optional[float] = None - - -@dataclass -class MainHeatingSystem: - fuel: str # e.g. "Mains gas", "Oil", "Electricity" - system_type: str # e.g. "Boiler with radiators or underfloor heating" - boiler_type: Optional[str] = None # e.g. "Regular", "Combi" - manufacturer: Optional[str] = None - model: Optional[str] = None - condensing: Optional[bool] = None - controls: Optional[str] = None - flue_gas_heat_recovery: bool = False - weather_compensator: bool = False - emitter: Optional[str] = None # e.g. "Radiators", "Underfloor" - - -@dataclass -class HotWaterSystem: - source: str # e.g. "From main heating 1", "Immersion heater" - cylinder_size: Optional[str] = None - insulation_type: Optional[str] = None - insulation_thickness_mm: Optional[int] = None - has_thermostat: Optional[bool] = None - - -@dataclass -class SecondaryHeatingSystem: - fuel: str # e.g. "Wood logs", "Electricity" - - -@dataclass -class VentilationDetails: - ventilation_type: str # e.g. "Natural", "Mechanical Extract - Decentralised" - number_of_open_flues: int = 0 - number_of_closed_flues: int = 0 - number_of_boiler_flues: int = 0 - number_of_other_flues: int = 0 - number_of_extract_fans: int = 0 - number_of_passive_vents: int = 0 - number_of_flueless_gas_fires: int = 0 - has_fixed_air_conditioning: bool = False - pressure_test: Optional[str] = None - draught_lobby: Optional[bool] = None - - -@dataclass -class RenewablesDetails: - has_photovoltaic: bool = False - has_solar_hot_water: bool = False - has_wind_turbines: bool = False - has_hydro: bool = False - number_of_pv_batteries: int = 0 - - -@dataclass -class LightingDetails: - number_of_led_bulbs: Optional[int] = None - number_of_cfl_bulbs: Optional[int] = None - number_of_incandescent_bulbs: Optional[int] = None - - -@dataclass -class Dwelling: - property_details: PropertyDetails - # One entry per storey per building part (main building + any extensions, flattened) - floor_dimensions: List[FloorDimensions] - walls: WallDetails - roof: RoofDetails - floor: FloorDetails - windows: List[WindowDetails] - main_heating: MainHeatingSystem - hot_water: HotWaterSystem - ventilation: VentilationDetails - renewables: RenewablesDetails - lighting: LightingDetails - number_of_habitable_rooms: int - number_of_external_doors: int - number_of_open_chimneys: int - has_conservatory: bool = False - secondary_heating: Optional[SecondaryHeatingSystem] = None - number_of_blocked_chimneys: int = 0 - number_of_baths: Optional[int] = None - number_of_showers: Optional[int] = None - waste_water_heat_recovery: Optional[str] = None diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 98d3394d..51f13518 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -1,20 +1,5 @@ -from typing import List, Optional, Union - -from datatypes.epc.domain.dwelling import ( - Dwelling, - FloorDimensions, - FloorDetails, - HotWaterSystem, - LightingDetails, - MainHeatingSystem, - PropertyDetails, - RenewablesDetails, - RoofDetails, - SecondaryHeatingSystem, - VentilationDetails, - WallDetails, - WindowDetails, -) +from typing import Union +from datatypes.epc.domain.epc_property_data import EpcPropertyData from datatypes.epc.schema.rdsap_schema_17_0 import RdSapSchema17_0 from datatypes.epc.schema.rdsap_schema_17_1 import RdSapSchema17_1 from datatypes.epc.schema.rdsap_schema_18_0 import RdSapSchema18_0 @@ -22,10 +7,7 @@ from datatypes.epc.schema.rdsap_schema_19_0 import RdSapSchema19_0 from datatypes.epc.schema.rdsap_schema_20_0_0 import RdSapSchema20_0_0 from datatypes.epc.schema.rdsap_schema_21_0_0 import RdSapSchema21_0_0 from datatypes.epc.schema.rdsap_schema_21_0_1 import RdSapSchema21_0_1 -from datatypes.epc.surveys.pashub_rdsap_site_notes import ( - PasHubRdSapSiteNotes, - Window, -) +from datatypes.epc.surveys.pashub_rdsap_site_notes import PasHubRdSapSiteNotes AnyRdSapSchema = Union[ RdSapSchema17_0, @@ -41,183 +23,9 @@ AnyRdSapSchema = Union[ class DwellingMapper: @staticmethod - def from_site_notes(survey: PasHubRdSapSiteNotes) -> Dwelling: - return Dwelling( - property_details=_sn_property_details(survey), - floor_dimensions=_sn_floor_dimensions(survey), - walls=_sn_walls(survey), - roof=_sn_roof(survey), - floor=_sn_floor(survey), - windows=[_sn_window(w) for w in survey.windows], - main_heating=_sn_main_heating(survey), - hot_water=_sn_hot_water(survey), - ventilation=_sn_ventilation(survey), - renewables=_sn_renewables(survey), - lighting=_sn_lighting(survey), - secondary_heating=_sn_secondary_heating(survey), - number_of_habitable_rooms=survey.room_count_elements.number_of_habitable_rooms, - number_of_external_doors=survey.room_count_elements.number_of_external_doors, - number_of_open_chimneys=survey.room_count_elements.number_of_open_chimneys, - number_of_blocked_chimneys=survey.room_count_elements.number_of_blocked_chimneys, - has_conservatory=survey.conservatories.has_conservatory, - number_of_baths=survey.water_use.number_of_baths, - number_of_showers=len(survey.water_use.showers), - waste_water_heat_recovery=survey.room_count_elements.waste_water_heat_recovery, - ) - - @staticmethod - def from_rdsap_schema(_schema: AnyRdSapSchema) -> Dwelling: + def from_site_notes(survey: PasHubRdSapSiteNotes) -> EpcPropertyData: raise NotImplementedError - -# --------------------------------------------------------------------------- -# Site notes helpers -# --------------------------------------------------------------------------- - -def _sn_property_details(survey: PasHubRdSapSiteNotes) -> PropertyDetails: - return PropertyDetails( - property_type=survey.general.property_type, - built_form=survey.general.detachment_type, - tenure=survey.general.tenure, - number_of_storeys=survey.general.number_of_storeys, - construction_age_band=survey.building_construction.main_building.age_range, - transaction_type=survey.general.transaction_type, - terrain_type=survey.general.terrain_type, - mains_gas_available=survey.general.mains_gas_available, - electricity_smart_meter=survey.general.electricity_smart_meter, - gas_smart_meter=survey.general.gas_smart_meter, - ) - - -def _sn_floor_dimensions(survey: PasHubRdSapSiteNotes) -> List[FloorDimensions]: - dims = [ - FloorDimensions( - total_floor_area_m2=f.area_m2, - height_m=f.height_m, - heat_loss_perimeter_m=f.heat_loss_perimeter_m, - party_wall_length_m=f.pwl_m, - ) - for f in survey.building_measurements.main_building.floors - ] - for ext in survey.building_measurements.extensions or []: - dims.extend( - FloorDimensions( - total_floor_area_m2=f.area_m2, - height_m=f.height_m, - heat_loss_perimeter_m=f.heat_loss_perimeter_m, - party_wall_length_m=f.pwl_m, - ) - for f in ext.floors - ) - return dims - - -def _sn_walls(survey: PasHubRdSapSiteNotes) -> WallDetails: - mb = survey.building_construction.main_building - return WallDetails( - construction_type=mb.walls_construction_type, - insulation_type=mb.walls_insulation_type, - thickness_mm=mb.wall_thickness_mm, - party_wall_construction_type=mb.party_wall_construction_type, - ) - - -def _sn_roof(survey: PasHubRdSapSiteNotes) -> RoofDetails: - mb = survey.roof_space.main_building - return RoofDetails( - construction_type=mb.construction_type, - insulation_at=mb.insulation_at, - insulation_thickness_mm=mb.insulation_thickness_mm, - has_rooms_in_roof=mb.rooms_in_roof, - ) - - -def _sn_floor(survey: PasHubRdSapSiteNotes) -> FloorDetails: - f = survey.building_construction.floor - return FloorDetails( - construction_type=f.floor_construction, - insulation_type=f.floor_insulation_type, - ) - - -def _sn_window(w: Window) -> WindowDetails: - return WindowDetails( - glazing_type=w.glazing_type, - orientation=w.orientation, - frame_type=w.frame_type, - glazing_gap=w.glazing_gap, - draught_proofed=w.draught_proofed, - height_m=w.height_m, - width_m=w.width_m, - ) - - -def _sn_main_heating(survey: PasHubRdSapSiteNotes) -> MainHeatingSystem: - mh = survey.heating_and_hot_water.main_heating - return MainHeatingSystem( - fuel=mh.fuel, - system_type=mh.system_type, - boiler_type=mh.type, - manufacturer=mh.manufacturer, - model=mh.model, - condensing=mh.condensing, - controls=mh.controls, - flue_gas_heat_recovery=mh.flue_gas_heat_recovery_system, - weather_compensator=mh.weather_compensator, - emitter=mh.emitter, - ) - - -def _sn_hot_water(survey: PasHubRdSapSiteNotes) -> HotWaterSystem: - wh = survey.heating_and_hot_water.water_heating - return HotWaterSystem( - source=wh.system, - cylinder_size=wh.cylinder_size, - insulation_type=wh.insulation_type, - insulation_thickness_mm=wh.insulation_thickness_mm, - has_thermostat=wh.has_thermostat, - ) - - -def _sn_secondary_heating(survey: PasHubRdSapSiteNotes) -> Optional[SecondaryHeatingSystem]: - fuel = survey.heating_and_hot_water.secondary_heating.secondary_fuel - if fuel == "No Secondary Heating": - return None - return SecondaryHeatingSystem(fuel=fuel) - - -def _sn_ventilation(survey: PasHubRdSapSiteNotes) -> VentilationDetails: - v = survey.ventilation - return VentilationDetails( - ventilation_type=v.ventilation_type, - number_of_open_flues=v.number_of_open_flues, - number_of_closed_flues=v.number_of_closed_flues, - number_of_boiler_flues=v.number_of_boiler_flues, - number_of_other_flues=v.number_of_other_flues, - number_of_extract_fans=v.number_of_extract_fans, - number_of_passive_vents=v.number_of_passive_vents, - number_of_flueless_gas_fires=v.number_of_flueless_gas_fires, - has_fixed_air_conditioning=v.has_fixed_air_conditioning, - pressure_test=v.pressure_test, - draught_lobby=v.draught_lobby, - ) - - -def _sn_renewables(survey: PasHubRdSapSiteNotes) -> RenewablesDetails: - r = survey.renewables - return RenewablesDetails( - has_photovoltaic=r.photovoltaic_array, - has_solar_hot_water=r.solar_hot_water, - has_wind_turbines=r.wind_turbines, - has_hydro=r.hydro, - number_of_pv_batteries=r.number_of_pv_batteries, - ) - - -def _sn_lighting(survey: PasHubRdSapSiteNotes) -> LightingDetails: - rc = survey.room_count_elements - return LightingDetails( - number_of_led_bulbs=rc.number_of_fixed_led_bulbs, - number_of_cfl_bulbs=rc.number_of_fixed_cfl_bulbs, - number_of_incandescent_bulbs=rc.number_of_fixed_incandescent_bulbs, - ) + @staticmethod + def from_rdsap_schema(_schema: AnyRdSapSchema) -> EpcPropertyData: + raise NotImplementedError diff --git a/datatypes/epc/domain/tests/test_from_site_notes.py b/datatypes/epc/domain/tests/test_from_site_notes.py deleted file mode 100644 index c065085d..00000000 --- a/datatypes/epc/domain/tests/test_from_site_notes.py +++ /dev/null @@ -1,558 +0,0 @@ -import json -import os -from typing import Any, Dict - -import pytest - -from datatypes.epc.domain import ( - Dwelling, - FloorDimensions, - FloorDetails, - HotWaterSystem, - LightingDetails, - MainHeatingSystem, - PropertyDetails, - RenewablesDetails, - RoofDetails, - VentilationDetails, - WallDetails, - WindowDetails, -) -from datatypes.epc.domain.mapper import DwellingMapper -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] - - -def survey(filename: str) -> PasHubRdSapSiteNotes: - return from_dict(PasHubRdSapSiteNotes, load(filename)) - - -class TestFromExample1: - """No extensions; regular boiler with cylinder; natural ventilation.""" - - @pytest.fixture - def dwelling(self) -> Dwelling: - return DwellingMapper.from_site_notes(survey("pashub_rdsap_site_notes_example1.json")) - - # --- property_details --- - - def test_property_type(self, dwelling: Dwelling) -> None: - assert dwelling.property_details.property_type == "House" - - def test_built_form(self, dwelling: Dwelling) -> None: - assert dwelling.property_details.built_form == "Mid-terrace" - - def test_tenure(self, dwelling: Dwelling) -> None: - assert dwelling.property_details.tenure == "Rented Social" - - def test_number_of_storeys(self, dwelling: Dwelling) -> None: - assert dwelling.property_details.number_of_storeys == 2 - - def test_construction_age_band(self, dwelling: Dwelling) -> None: - assert dwelling.property_details.construction_age_band == "I: 1996 - 2002" - - def test_transaction_type(self, dwelling: Dwelling) -> None: - assert dwelling.property_details.transaction_type == "None of the Above" - - def test_terrain_type(self, dwelling: Dwelling) -> None: - assert dwelling.property_details.terrain_type == "Suburban" - - def test_mains_gas_available(self, dwelling: Dwelling) -> None: - assert dwelling.property_details.mains_gas_available is True - - def test_electricity_smart_meter(self, dwelling: Dwelling) -> None: - assert dwelling.property_details.electricity_smart_meter is True - - def test_gas_smart_meter(self, dwelling: Dwelling) -> None: - assert dwelling.property_details.gas_smart_meter is True - - # --- floor_dimensions --- - - def test_floor_count(self, dwelling: Dwelling) -> None: - # 2 floors from main building, no extensions - assert len(dwelling.floor_dimensions) == 2 - - def test_floor_area(self, dwelling: Dwelling) -> None: - assert dwelling.floor_dimensions[0].total_floor_area_m2 == 24.78 - - def test_floor_height(self, dwelling: Dwelling) -> None: - assert dwelling.floor_dimensions[0].height_m == 2.37 - - def test_heat_loss_perimeter(self, dwelling: Dwelling) -> None: - assert dwelling.floor_dimensions[0].heat_loss_perimeter_m == 14.21 - - def test_party_wall_length(self, dwelling: Dwelling) -> None: - assert dwelling.floor_dimensions[0].party_wall_length_m == 6.15 - - # --- walls --- - - def test_wall_construction_type(self, dwelling: Dwelling) -> None: - assert dwelling.walls.construction_type == "Cavity" - - def test_wall_insulation_type(self, dwelling: Dwelling) -> None: - assert dwelling.walls.insulation_type == "As built" - - def test_wall_thickness(self, dwelling: Dwelling) -> None: - assert dwelling.walls.thickness_mm == 280 - - def test_party_wall_construction_type(self, dwelling: Dwelling) -> None: - assert dwelling.walls.party_wall_construction_type == "Cavity Masonry, Unfilled" - - # --- roof --- - - def test_roof_construction_type(self, dwelling: Dwelling) -> None: - assert dwelling.roof.construction_type == "Pitched roof (Slates or tiles), Access to loft" - - def test_roof_insulation_at(self, dwelling: Dwelling) -> None: - assert dwelling.roof.insulation_at == "Joists" - - def test_roof_insulation_thickness(self, dwelling: Dwelling) -> None: - assert dwelling.roof.insulation_thickness_mm == 100 - - def test_roof_no_rooms_in_roof(self, dwelling: Dwelling) -> None: - assert dwelling.roof.has_rooms_in_roof is False - - # --- floor --- - - def test_floor_construction_type(self, dwelling: Dwelling) -> None: - assert dwelling.floor.construction_type == "Suspended, not timber" - - def test_floor_insulation_type(self, dwelling: Dwelling) -> None: - assert dwelling.floor.insulation_type == "As Built" - - # --- windows --- - - def test_window_count(self, dwelling: Dwelling) -> None: - assert len(dwelling.windows) == 4 - - def test_window_glazing_type(self, dwelling: Dwelling) -> None: - assert dwelling.windows[0].glazing_type == "Double glazing, Unknown install date" - - def test_window_orientation(self, dwelling: Dwelling) -> None: - assert dwelling.windows[0].orientation == "South East" - - def test_window_frame_type(self, dwelling: Dwelling) -> None: - assert dwelling.windows[0].frame_type == "Wooden or PVC" - - def test_window_glazing_gap(self, dwelling: Dwelling) -> None: - assert dwelling.windows[0].glazing_gap == "16 mm or more" - - def test_window_draught_proofed(self, dwelling: Dwelling) -> None: - assert dwelling.windows[0].draught_proofed is True - - def test_window_dimensions(self, dwelling: Dwelling) -> None: - assert dwelling.windows[0].height_m == 1.36 - assert dwelling.windows[0].width_m == 1.0 - - # --- main_heating --- - - def test_main_heating_fuel(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.fuel == "Mains gas" - - def test_main_heating_system_type(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.system_type == "Boiler with radiators or underfloor heating" - - def test_main_heating_boiler_type(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.boiler_type == "Regular" - - def test_main_heating_manufacturer(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.manufacturer == "Vaillant" - - def test_main_heating_model(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.model == "ecoFIT sustain 415" - - def test_main_heating_condensing(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.condensing is True - - def test_main_heating_controls(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.controls == "Programmer, room thermostat and TRVs" - - def test_main_heating_fghr(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.flue_gas_heat_recovery is False - - def test_main_heating_weather_compensator(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.weather_compensator is False - - def test_main_heating_emitter(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.emitter == "Radiators" - - # --- hot_water --- - - def test_hot_water_source(self, dwelling: Dwelling) -> None: - assert dwelling.hot_water.source == "From main heating 1" - - def test_hot_water_cylinder_size(self, dwelling: Dwelling) -> None: - assert dwelling.hot_water.cylinder_size == "Normal (90-130 litres)" - - def test_hot_water_insulation_type(self, dwelling: Dwelling) -> None: - assert dwelling.hot_water.insulation_type == "Factory fitted" - - def test_hot_water_insulation_thickness(self, dwelling: Dwelling) -> None: - assert dwelling.hot_water.insulation_thickness_mm == 12 - - def test_hot_water_thermostat(self, dwelling: Dwelling) -> None: - assert dwelling.hot_water.has_thermostat is True - - # --- secondary_heating --- - - def test_secondary_heating_absent(self, dwelling: Dwelling) -> None: - # "No Secondary Heating" maps to None - assert dwelling.secondary_heating is None - - # --- ventilation --- - - def test_ventilation_type(self, dwelling: Dwelling) -> None: - assert dwelling.ventilation.ventilation_type == "Natural" - - def test_ventilation_extract_fans(self, dwelling: Dwelling) -> None: - assert dwelling.ventilation.number_of_extract_fans == 2 - - def test_ventilation_no_air_conditioning(self, dwelling: Dwelling) -> None: - assert dwelling.ventilation.has_fixed_air_conditioning is False - - def test_ventilation_pressure_test(self, dwelling: Dwelling) -> None: - assert dwelling.ventilation.pressure_test == "No test" - - def test_ventilation_draught_lobby(self, dwelling: Dwelling) -> None: - assert dwelling.ventilation.draught_lobby is False - - # --- renewables --- - - def test_no_photovoltaic(self, dwelling: Dwelling) -> None: - assert dwelling.renewables.has_photovoltaic is False - - def test_no_solar_hot_water(self, dwelling: Dwelling) -> None: - assert dwelling.renewables.has_solar_hot_water is False - - def test_no_wind_turbines(self, dwelling: Dwelling) -> None: - assert dwelling.renewables.has_wind_turbines is False - - def test_pv_batteries_count(self, dwelling: Dwelling) -> None: - assert dwelling.renewables.number_of_pv_batteries == 0 - - # --- lighting --- - - def test_led_bulbs(self, dwelling: Dwelling) -> None: - assert dwelling.lighting.number_of_led_bulbs == 5 - - def test_cfl_bulbs(self, dwelling: Dwelling) -> None: - assert dwelling.lighting.number_of_cfl_bulbs == 4 - - def test_incandescent_bulbs(self, dwelling: Dwelling) -> None: - assert dwelling.lighting.number_of_incandescent_bulbs == 0 - - # --- dwelling-level counts --- - - def test_habitable_rooms(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_habitable_rooms == 2 - - def test_external_doors(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_external_doors == 2 - - def test_open_chimneys(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_open_chimneys == 0 - - def test_blocked_chimneys(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_blocked_chimneys == 0 - - def test_no_conservatory(self, dwelling: Dwelling) -> None: - assert dwelling.has_conservatory is False - - def test_baths(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_baths == 1 - - def test_showers(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_showers == 1 - - def test_waste_water_heat_recovery(self, dwelling: Dwelling) -> None: - assert dwelling.waste_water_heat_recovery == "None" - - def test_full_mapping(self, dwelling: Dwelling) -> None: - assert dwelling == Dwelling( - property_details=PropertyDetails( - property_type="House", - built_form="Mid-terrace", - tenure="Rented Social", - number_of_storeys=2, - construction_age_band="I: 1996 - 2002", - transaction_type="None of the Above", - terrain_type="Suburban", - mains_gas_available=True, - electricity_smart_meter=True, - gas_smart_meter=True, - ), - floor_dimensions=[ - FloorDimensions(total_floor_area_m2=24.78, height_m=2.37, heat_loss_perimeter_m=14.21, party_wall_length_m=6.15), - FloorDimensions(total_floor_area_m2=24.78, height_m=2.35, heat_loss_perimeter_m=14.21, party_wall_length_m=6.15), - ], - walls=WallDetails( - construction_type="Cavity", - insulation_type="As built", - thickness_mm=280, - party_wall_construction_type="Cavity Masonry, Unfilled", - ), - roof=RoofDetails( - construction_type="Pitched roof (Slates or tiles), Access to loft", - insulation_at="Joists", - insulation_thickness_mm=100, - has_rooms_in_roof=False, - ), - floor=FloorDetails( - construction_type="Suspended, not timber", - insulation_type="As Built", - ), - windows=[ - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="South East", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=1.36, width_m=1.0), - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="South East", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=1.33, width_m=0.96), - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="North West", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=1.04, width_m=0.96), - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="North West", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=1.02, width_m=0.97), - ], - main_heating=MainHeatingSystem( - fuel="Mains gas", - system_type="Boiler with radiators or underfloor heating", - boiler_type="Regular", - manufacturer="Vaillant", - model="ecoFIT sustain 415", - condensing=True, - controls="Programmer, room thermostat and TRVs", - flue_gas_heat_recovery=False, - weather_compensator=False, - emitter="Radiators", - ), - hot_water=HotWaterSystem( - source="From main heating 1", - cylinder_size="Normal (90-130 litres)", - insulation_type="Factory fitted", - insulation_thickness_mm=12, - has_thermostat=True, - ), - secondary_heating=None, - ventilation=VentilationDetails( - ventilation_type="Natural", - 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, - has_fixed_air_conditioning=False, - pressure_test="No test", - draught_lobby=False, - ), - renewables=RenewablesDetails( - has_photovoltaic=False, - has_solar_hot_water=False, - has_wind_turbines=False, - has_hydro=False, - number_of_pv_batteries=0, - ), - lighting=LightingDetails( - number_of_led_bulbs=5, - number_of_cfl_bulbs=4, - number_of_incandescent_bulbs=0, - ), - number_of_habitable_rooms=2, - number_of_external_doors=2, - number_of_open_chimneys=0, - number_of_blocked_chimneys=0, - has_conservatory=False, - number_of_baths=1, - number_of_showers=1, - waste_water_heat_recovery="None", - ) - - -class TestFromExample2: - """With extensions; combi boiler (no cylinder); mechanical extract ventilation.""" - - @pytest.fixture - def dwelling(self) -> Dwelling: - return DwellingMapper.from_site_notes(survey("pashub_rdsap_site_notes_example2.json")) - - # --- floor_dimensions: main building + extension floors flattened --- - - def test_floor_count_includes_extensions(self, dwelling: Dwelling) -> None: - # 2 main building floors + 1 extension floor = 3 - assert len(dwelling.floor_dimensions) == 3 - - def test_extension_floor_area(self, dwelling: Dwelling) -> None: - # Extension floor is last; area 3.8 m² - assert dwelling.floor_dimensions[2].total_floor_area_m2 == 3.8 - - def test_extension_floor_party_wall_length(self, dwelling: Dwelling) -> None: - assert dwelling.floor_dimensions[2].party_wall_length_m == 0.0 - - # --- walls: from main building --- - - def test_wall_insulation_type_filled_cavity(self, dwelling: Dwelling) -> None: - assert dwelling.walls.insulation_type == "Filled Cavity" - - def test_wall_thickness(self, dwelling: Dwelling) -> None: - assert dwelling.walls.thickness_mm == 310 - - # --- roof: from main building --- - - def test_roof_insulation_thickness(self, dwelling: Dwelling) -> None: - assert dwelling.roof.insulation_thickness_mm == 100 - - # --- windows: all 8, location info discarded --- - - def test_window_count(self, dwelling: Dwelling) -> None: - assert len(dwelling.windows) == 8 - - # --- main_heating --- - - def test_main_heating_boiler_type_combi(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.boiler_type == "Combi" - - def test_main_heating_model(self, dwelling: Dwelling) -> None: - assert dwelling.main_heating.model == "ecoTEC pro 28" - - # --- hot_water: combi has no cylinder --- - - def test_hot_water_source(self, dwelling: Dwelling) -> None: - assert dwelling.hot_water.source == "From main heating 1" - - def test_hot_water_cylinder_size_no_cylinder(self, dwelling: Dwelling) -> None: - assert dwelling.hot_water.cylinder_size == "No Cylinder" - - def test_hot_water_insulation_type_none(self, dwelling: Dwelling) -> None: - assert dwelling.hot_water.insulation_type is None - - def test_hot_water_thermostat_none(self, dwelling: Dwelling) -> None: - assert dwelling.hot_water.has_thermostat is None - - # --- ventilation --- - - def test_ventilation_type_mechanical(self, dwelling: Dwelling) -> None: - assert dwelling.ventilation.ventilation_type == "Mechanical Extract - Decentralised" - - def test_ventilation_extract_fans_zero(self, dwelling: Dwelling) -> None: - assert dwelling.ventilation.number_of_extract_fans == 0 - - # --- lighting --- - - def test_incandescent_bulbs(self, dwelling: Dwelling) -> None: - assert dwelling.lighting.number_of_incandescent_bulbs == 4 - - def test_led_bulbs_zero(self, dwelling: Dwelling) -> None: - assert dwelling.lighting.number_of_led_bulbs == 0 - - # --- counts --- - - def test_habitable_rooms(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_habitable_rooms == 3 - - def test_showers(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_showers == 1 - - def test_full_mapping(self, dwelling: Dwelling) -> None: - assert dwelling == Dwelling( - property_details=PropertyDetails( - property_type="House", - built_form="Mid-terrace", - tenure="Rented Social", - number_of_storeys=2, - construction_age_band="1950-1966", - transaction_type="Grant-Scheme (ECO, RHI, etc.)", - terrain_type="Suburban", - mains_gas_available=True, - electricity_smart_meter=True, - gas_smart_meter=True, - ), - floor_dimensions=[ - FloorDimensions(total_floor_area_m2=35.68, height_m=2.19, heat_loss_perimeter_m=13.44, party_wall_length_m=10.62), - FloorDimensions(total_floor_area_m2=35.68, height_m=2.17, heat_loss_perimeter_m=11.0, party_wall_length_m=10.62), - FloorDimensions(total_floor_area_m2=3.8, height_m=2.0, heat_loss_perimeter_m=5.7, party_wall_length_m=0.0), - ], - walls=WallDetails( - construction_type="Cavity", - insulation_type="Filled Cavity", - thickness_mm=310, - party_wall_construction_type="Cavity Masonry, Filled", - ), - roof=RoofDetails( - construction_type="Pitched roof (Slates or tiles), Access to loft", - insulation_at="Joists", - insulation_thickness_mm=100, - has_rooms_in_roof=False, - ), - floor=FloorDetails( - construction_type="Solid", - insulation_type="As Built", - ), - windows=[ - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="North West", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=1.2, width_m=2.3), - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="North West", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=1.2, width_m=1.0), - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="North East", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=0.9, width_m=1.0), - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="North", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=0.9, width_m=1.0), - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="North East", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=0.9, width_m=1.7), - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="North West", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=0.9, width_m=2.3), - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="North West", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=1.0, width_m=1.2), - WindowDetails(glazing_type="Double glazing, Unknown install date", orientation="North East", frame_type="Wooden or PVC", glazing_gap="16 mm or more", draught_proofed=True, height_m=0.9, width_m=1.0), - ], - main_heating=MainHeatingSystem( - fuel="Mains gas", - system_type="Boiler with radiators or underfloor heating", - boiler_type="Combi", - manufacturer="Vaillant", - model="ecoTEC pro 28", - condensing=True, - controls="Programmer, room thermostat and TRVs", - flue_gas_heat_recovery=False, - weather_compensator=False, - emitter="Radiators", - ), - hot_water=HotWaterSystem( - source="From main heating 1", - cylinder_size="No Cylinder", - insulation_type=None, - insulation_thickness_mm=None, - has_thermostat=None, - ), - secondary_heating=None, - ventilation=VentilationDetails( - ventilation_type="Mechanical Extract - Decentralised", - 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, - has_fixed_air_conditioning=False, - pressure_test="No test", - draught_lobby=False, - ), - renewables=RenewablesDetails( - has_photovoltaic=False, - has_solar_hot_water=False, - has_wind_turbines=False, - has_hydro=False, - number_of_pv_batteries=0, - ), - lighting=LightingDetails( - number_of_led_bulbs=0, - number_of_cfl_bulbs=1, - number_of_incandescent_bulbs=4, - ), - number_of_habitable_rooms=3, - number_of_external_doors=2, - number_of_open_chimneys=0, - number_of_blocked_chimneys=0, - has_conservatory=False, - number_of_baths=1, - number_of_showers=1, - waste_water_heat_recovery="None", - )