From e6349337688409ce7ec3616a935c34744dccbeef Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 14 Apr 2026 09:55:16 +0000 Subject: [PATCH] =?UTF-8?q?Map=20to=20domain=20from=20site=20notes=20?= =?UTF-8?q?=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- datatypes/epc/domain/mapper.py | 26 +- .../domain/tests/test_from_rdsap_schema.py | 629 ++++++++++++++---- 2 files changed, 518 insertions(+), 137 deletions(-) diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 70b5616f..2171275a 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -113,7 +113,31 @@ class EpcPropertyDataMapper: ) @staticmethod - def from_rdsap_schema(_schema: AnyRdSapSchema) -> EpcPropertyData: + def from_rdsap_schema_17_0(_schema: RdSapSchema17_0) -> EpcPropertyData: + raise NotImplementedError + + @staticmethod + def from_rdsap_schema_17_1(_schema: RdSapSchema17_1) -> EpcPropertyData: + raise NotImplementedError + + @staticmethod + def from_rdsap_schema_18_0(_schema: RdSapSchema18_0) -> EpcPropertyData: + raise NotImplementedError + + @staticmethod + def from_rdsap_schema_19_0(_schema: RdSapSchema19_0) -> EpcPropertyData: + raise NotImplementedError + + @staticmethod + def from_rdsap_schema_20_0_0(_schema: RdSapSchema20_0_0) -> EpcPropertyData: + raise NotImplementedError + + @staticmethod + def from_rdsap_schema_21_0_0(_schema: RdSapSchema21_0_0) -> EpcPropertyData: + raise NotImplementedError + + @staticmethod + def from_rdsap_schema_21_0_1(_schema: RdSapSchema21_0_1) -> EpcPropertyData: raise NotImplementedError diff --git a/datatypes/epc/domain/tests/test_from_rdsap_schema.py b/datatypes/epc/domain/tests/test_from_rdsap_schema.py index dcea42c2..5fb6a310 100644 --- a/datatypes/epc/domain/tests/test_from_rdsap_schema.py +++ b/datatypes/epc/domain/tests/test_from_rdsap_schema.py @@ -1,18 +1,22 @@ import json import os +from datetime import date from typing import Any, Dict import pytest -from datatypes.epc.domain import Dwelling +from datatypes.epc.domain.epc_property_data import EpcPropertyData from datatypes.epc.domain.mapper import EpcPropertyDataMapper +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 +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.schema.tests.helpers import from_dict -FIXTURES = os.path.join( - os.path.dirname(__file__), - "../../schema/tests/fixtures", -) +FIXTURES = os.path.join(os.path.dirname(__file__), "../../schema/tests/fixtures") def load(filename: str) -> Dict[str, Any]: @@ -20,168 +24,521 @@ def load(filename: str) -> Dict[str, Any]: return json.load(f) # type: ignore[no-any-return] +# --------------------------------------------------------------------------- +# Schema 17.0 +# --------------------------------------------------------------------------- + + +class TestFromRdSapSchema17_0: + + @pytest.fixture + def result(self) -> EpcPropertyData: + schema = from_dict(RdSapSchema17_0, load("17_0.json")) + return EpcPropertyDataMapper.from_rdsap_schema_17_0(schema) + + def test_uprn(self, result: EpcPropertyData) -> None: + assert result.uprn == 12457 + + def test_assessment_type(self, result: EpcPropertyData) -> None: + assert result.assessment_type == "RdSAP" + + def test_sap_version(self, result: EpcPropertyData) -> None: + assert result.sap_version == 9.92 + + def test_dwelling_type(self, result: EpcPropertyData) -> None: + # dwelling_type is a localised object in 17.0; mapper extracts the string value + assert result.dwelling_type == "Mid-floor flat" + + def test_tenure(self, result: EpcPropertyData) -> None: + # tenure: 2 — stored as stringified int + assert result.tenure == "2" + + def test_door_count(self, result: EpcPropertyData) -> None: + assert result.door_count == 2 + + def test_built_form(self, result: EpcPropertyData) -> None: + # built_form: 2 → "Semi-detached" + assert result.built_form == "Semi-detached" + + def test_property_type(self, result: EpcPropertyData) -> None: + # property_type: 2 → "Flat" + assert result.property_type == "Flat" + + +# --------------------------------------------------------------------------- +# Schema 17.1 +# --------------------------------------------------------------------------- + + +class TestFromRdSapSchema17_1: + + @pytest.fixture + def result(self) -> EpcPropertyData: + schema = from_dict(RdSapSchema17_1, load("17_1.json")) + return EpcPropertyDataMapper.from_rdsap_schema_17_1(schema) + + def test_uprn(self, result: EpcPropertyData) -> None: + assert result.uprn == 12457 + + def test_assessment_type(self, result: EpcPropertyData) -> None: + assert result.assessment_type == "RdSAP" + + def test_sap_version(self, result: EpcPropertyData) -> None: + assert result.sap_version == 9.92 + + def test_dwelling_type(self, result: EpcPropertyData) -> None: + # dwelling_type is a localised object in 17.1; mapper extracts the string value + assert result.dwelling_type == "Detached house" + + def test_tenure(self, result: EpcPropertyData) -> None: + # tenure: 1 + assert result.tenure == "1" + + def test_door_count(self, result: EpcPropertyData) -> None: + assert result.door_count == 4 + + def test_built_form(self, result: EpcPropertyData) -> None: + # built_form: 1 → "Detached" + assert result.built_form == "Detached" + + def test_property_type(self, result: EpcPropertyData) -> None: + # property_type: 0 → "House" + assert result.property_type == "House" + + +# --------------------------------------------------------------------------- +# Schema 18.0 +# --------------------------------------------------------------------------- + + +class TestFromRdSapSchema18_0: + + @pytest.fixture + def result(self) -> EpcPropertyData: + schema = from_dict(RdSapSchema18_0, load("18_0.json")) + return EpcPropertyDataMapper.from_rdsap_schema_18_0(schema) + + def test_uprn(self, result: EpcPropertyData) -> None: + assert result.uprn == 12457 + + def test_assessment_type(self, result: EpcPropertyData) -> None: + assert result.assessment_type == "RdSAP" + + def test_sap_version(self, result: EpcPropertyData) -> None: + assert result.sap_version == 9.92 + + def test_dwelling_type(self, result: EpcPropertyData) -> None: + # dwelling_type is a localised object in 18.0; mapper extracts the string value + assert result.dwelling_type == "Mid-terrace house" + + def test_tenure(self, result: EpcPropertyData) -> None: + assert result.tenure == "1" + + def test_door_count(self, result: EpcPropertyData) -> None: + assert result.door_count == 2 + + def test_built_form(self, result: EpcPropertyData) -> None: + # built_form: 4 → "Mid-terrace" + assert result.built_form == "Mid-terrace" + + def test_property_type(self, result: EpcPropertyData) -> None: + # property_type: 0 → "House" + assert result.property_type == "House" + + +# --------------------------------------------------------------------------- +# Schema 19.0 +# --------------------------------------------------------------------------- + + +class TestFromRdSapSchema19_0: + + @pytest.fixture + def result(self) -> EpcPropertyData: + schema = from_dict(RdSapSchema19_0, load("19_0.json")) + return EpcPropertyDataMapper.from_rdsap_schema_19_0(schema) + + def test_uprn(self, result: EpcPropertyData) -> None: + assert result.uprn == 12457 + + def test_assessment_type(self, result: EpcPropertyData) -> None: + assert result.assessment_type == "RdSAP" + + def test_sap_version(self, result: EpcPropertyData) -> None: + assert result.sap_version == 9.94 + + def test_dwelling_type(self, result: EpcPropertyData) -> None: + # dwelling_type is a localised object in 19.0; mapper extracts the string value + assert result.dwelling_type == "Semi-detached house" + + def test_tenure(self, result: EpcPropertyData) -> None: + # tenure: 3 + assert result.tenure == "3" + + def test_door_count(self, result: EpcPropertyData) -> None: + assert result.door_count == 1 + + def test_built_form(self, result: EpcPropertyData) -> None: + # built_form: 2 → "Semi-detached" + assert result.built_form == "Semi-detached" + + def test_property_type(self, result: EpcPropertyData) -> None: + # property_type: 0 → "House" + assert result.property_type == "House" + + +# --------------------------------------------------------------------------- +# Schema 20.0.0 +# --------------------------------------------------------------------------- + + +class TestFromRdSapSchema20_0_0: + + @pytest.fixture + def result(self) -> EpcPropertyData: + schema = from_dict(RdSapSchema20_0_0, load("20_0_0.json")) + return EpcPropertyDataMapper.from_rdsap_schema_20_0_0(schema) + + def test_uprn(self, result: EpcPropertyData) -> None: + assert result.uprn == 12457 + + def test_assessment_type(self, result: EpcPropertyData) -> None: + assert result.assessment_type == "RdSAP" + + def test_sap_version(self, result: EpcPropertyData) -> None: + assert result.sap_version == 9.8 + + def test_dwelling_type(self, result: EpcPropertyData) -> None: + # dwelling_type is a plain string from 20.0.0 onwards + assert result.dwelling_type == "Mid-terrace house" + + def test_tenure(self, result: EpcPropertyData) -> None: + assert result.tenure == "1" + + def test_door_count(self, result: EpcPropertyData) -> None: + assert result.door_count == 2 + + def test_built_form(self, result: EpcPropertyData) -> None: + # built_form: 2 → "Semi-detached" + assert result.built_form == "Semi-detached" + + def test_property_type(self, result: EpcPropertyData) -> None: + # property_type: 0 → "House" + assert result.property_type == "House" + + +# --------------------------------------------------------------------------- +# Schema 21.0.0 +# --------------------------------------------------------------------------- + + +class TestFromRdSapSchema21_0_0: + + @pytest.fixture + def result(self) -> EpcPropertyData: + schema = from_dict(RdSapSchema21_0_0, load("21_0_0.json")) + return EpcPropertyDataMapper.from_rdsap_schema_21_0_0(schema) + + def test_uprn(self, result: EpcPropertyData) -> None: + assert result.uprn == 12457 + + def test_assessment_type(self, result: EpcPropertyData) -> None: + assert result.assessment_type == "RdSAP" + + def test_sap_version(self, result: EpcPropertyData) -> None: + assert result.sap_version == 10.2 + + def test_dwelling_type(self, result: EpcPropertyData) -> None: + assert result.dwelling_type == "Mid-terrace house" + + def test_tenure(self, result: EpcPropertyData) -> None: + assert result.tenure == "1" + + def test_door_count(self, result: EpcPropertyData) -> None: + assert result.door_count == 3 + + def test_built_form(self, result: EpcPropertyData) -> None: + # built_form: 2 → "Semi-detached" + assert result.built_form == "Semi-detached" + + def test_property_type(self, result: EpcPropertyData) -> None: + # property_type: 0 → "House" + assert result.property_type == "House" + + +# --------------------------------------------------------------------------- +# Schema 21.0.1 (most comprehensive — full field coverage) +# --------------------------------------------------------------------------- + + class TestFromRdSapSchema21_0_1: @pytest.fixture - def dwelling(self) -> Dwelling: - schema = from_dict(RdSapSchema21_0_1, load("21_0_1.json")) - return EpcPropertyDataMapper.from_rdsap_schema(schema) + def schema(self) -> RdSapSchema21_0_1: + return from_dict(RdSapSchema21_0_1, load("21_0_1.json")) - # --- property_details --- + @pytest.fixture + def result(self, schema: RdSapSchema21_0_1) -> EpcPropertyData: + return EpcPropertyDataMapper.from_rdsap_schema_21_0_1(schema) - def test_property_type(self, dwelling: Dwelling) -> None: - # property_type: 0 → House - assert dwelling.property_details.property_type == "House" + # --- general --- - def test_built_form(self, dwelling: Dwelling) -> None: - # built_form: 2 → Semi-detached - assert dwelling.property_details.built_form == "Semi-detached" + def test_uprn(self, result: EpcPropertyData) -> None: + assert result.uprn == 12457 - def test_tenure(self, dwelling: Dwelling) -> None: - # tenure: 1 → Owner-occupied - assert dwelling.property_details.tenure == "Owner-occupied" + def test_assessment_type(self, result: EpcPropertyData) -> None: + assert result.assessment_type == "RdSAP" - def test_construction_age_band(self, dwelling: Dwelling) -> None: - # taken directly from sap_building_parts[0].construction_age_band - assert dwelling.property_details.construction_age_band == "M" + def test_sap_version(self, result: EpcPropertyData) -> None: + assert result.sap_version == 10.2 - def test_mains_gas_available(self, dwelling: Dwelling) -> None: - # sap_energy_source.mains_gas: "Y" - assert dwelling.property_details.mains_gas_available is True + def test_dwelling_type(self, result: EpcPropertyData) -> None: + assert result.dwelling_type == "Mid-terrace house" - def test_electricity_smart_meter(self, dwelling: Dwelling) -> None: - # sap_energy_source.electricity_smart_meter_present: "true" - assert dwelling.property_details.electricity_smart_meter is True + def test_property_type(self, result: EpcPropertyData) -> None: + # property_type: 0 → "House" + assert result.property_type == "House" - def test_gas_smart_meter(self, dwelling: Dwelling) -> None: - # sap_energy_source.gas_smart_meter_present: "false" - assert dwelling.property_details.gas_smart_meter is False + def test_built_form(self, result: EpcPropertyData) -> None: + # built_form: 2 → "Semi-detached" + assert result.built_form == "Semi-detached" - # --- floor_dimensions --- + def test_address_line_1(self, result: EpcPropertyData) -> None: + assert result.address_line_1 == "1 Some Street" - def test_floor_count(self, dwelling: Dwelling) -> None: - # one SapFloorDimension in the fixture - assert len(dwelling.floor_dimensions) == 1 + def test_postcode(self, result: EpcPropertyData) -> None: + assert result.postcode == "A0 0AA" - def test_floor_area(self, dwelling: Dwelling) -> None: - # total_floor_area.value: 45.82 - assert dwelling.floor_dimensions[0].total_floor_area_m2 == 45.82 + def test_post_town(self, result: EpcPropertyData) -> None: + assert result.post_town == "Whitbury" - def test_floor_height(self, dwelling: Dwelling) -> None: - # room_height.value: 2.45 - assert dwelling.floor_dimensions[0].height_m == 2.45 + def test_status(self, result: EpcPropertyData) -> None: + assert result.status == "entered" - def test_heat_loss_perimeter(self, dwelling: Dwelling) -> None: - # heat_loss_perimeter.value: 19.5 - assert dwelling.floor_dimensions[0].heat_loss_perimeter_m == 19.5 + def test_tenure(self, result: EpcPropertyData) -> None: + # tenure: 1 — stored as stringified int + assert result.tenure == "1" - def test_party_wall_length(self, dwelling: Dwelling) -> None: - # party_wall_length.value: 7.9 - assert dwelling.floor_dimensions[0].party_wall_length_m == 7.9 + def test_transaction_type(self, result: EpcPropertyData) -> None: + # transaction_type: 16 — stored as stringified int + assert result.transaction_type == "16" - # --- walls --- + def test_inspection_date(self, result: EpcPropertyData) -> None: + assert result.inspection_date == date(2025, 4, 4) - def test_wall_construction_type(self, dwelling: Dwelling) -> None: - # wall_construction: 4 → Cavity wall - assert dwelling.walls.construction_type == "Cavity wall" + def test_total_floor_area(self, result: EpcPropertyData) -> None: + assert result.total_floor_area_m2 == 55.0 - def test_wall_insulation_type(self, dwelling: Dwelling) -> None: - # wall_insulation_type: 2 → As built, insulated (assumed) - assert dwelling.walls.insulation_type == "As built, insulated (assumed)" + # --- property flags --- - def test_wall_thickness(self, dwelling: Dwelling) -> None: - # wall_thickness_measured: "N" → thickness unknown - assert dwelling.walls.thickness_mm is None - - # --- roof --- - - def test_roof_insulation_thickness(self, dwelling: Dwelling) -> None: - # roof_insulation_thickness: "200mm" - assert dwelling.roof.insulation_thickness_mm == 200 - - def test_roof_has_rooms_in_roof(self, dwelling: Dwelling) -> None: - # sap_room_in_roof is present in the fixture - assert dwelling.roof.has_rooms_in_roof is True - - # --- windows --- - - def test_window_count(self, dwelling: Dwelling) -> None: - assert len(dwelling.windows) == 1 - - def test_window_height(self, dwelling: Dwelling) -> None: - assert dwelling.windows[0].height_m == 2.0 - - def test_window_width(self, dwelling: Dwelling) -> None: - assert dwelling.windows[0].width_m == 1.2 - - def test_window_draught_proofed(self, dwelling: Dwelling) -> None: - # draught_proofed: "true" - assert dwelling.windows[0].draught_proofed is True - - # --- main_heating --- - - def test_main_heating_fuel(self, dwelling: Dwelling) -> None: - # main_fuel_type: 26 → Mains gas - assert dwelling.main_heating.fuel == "Mains gas" - - def test_main_heating_flue_gas_heat_recovery(self, dwelling: Dwelling) -> None: - # has_fghrs: "N" - assert dwelling.main_heating.flue_gas_heat_recovery is False - - # --- hot_water --- - - def test_hot_water_cylinder_present(self, dwelling: Dwelling) -> None: - # has_hot_water_cylinder: "true" - assert dwelling.hot_water.cylinder_size is not None - - # --- secondary_heating --- - - def test_secondary_heating_present(self, dwelling: Dwelling) -> None: - # secondary_fuel_type: 25 → electricity - assert dwelling.secondary_heating is not None - - # --- ventilation --- - - def test_no_fixed_air_conditioning(self, dwelling: Dwelling) -> None: - # has_fixed_air_conditioning: "false" - assert dwelling.ventilation.has_fixed_air_conditioning is False - - # --- renewables --- - - def test_no_solar_hot_water(self, dwelling: Dwelling) -> None: + def test_solar_water_heating(self, result: EpcPropertyData) -> None: # solar_water_heating: "N" - assert dwelling.renewables.has_solar_hot_water is False + assert result.solar_water_heating is False - def test_no_wind_turbines(self, dwelling: Dwelling) -> None: - # wind_turbines_count: 0 - assert dwelling.renewables.has_wind_turbines is False + def test_has_hot_water_cylinder(self, result: EpcPropertyData) -> None: + # has_hot_water_cylinder: "true" + assert result.has_hot_water_cylinder is True - def test_pv_battery_count(self, dwelling: Dwelling) -> None: - # pv_battery_count: 1 - assert dwelling.renewables.number_of_pv_batteries == 1 + def test_has_fixed_air_conditioning(self, result: EpcPropertyData) -> None: + # has_fixed_air_conditioning: "false" + assert result.has_fixed_air_conditioning is False + + def test_no_conservatory(self, result: EpcPropertyData) -> None: + # conservatory_type: 1 → no conservatory + assert result.has_conservatory is False + + # --- counts --- + + def test_door_count(self, result: EpcPropertyData) -> None: + assert result.door_count == 3 + + def test_habitable_rooms(self, result: EpcPropertyData) -> None: + assert result.habitable_rooms_count == 5 + + def test_heated_rooms(self, result: EpcPropertyData) -> None: + assert result.heated_rooms_count == 5 + + def test_wet_rooms(self, result: EpcPropertyData) -> None: + assert result.wet_rooms_count == 0 + + def test_extensions_count(self, result: EpcPropertyData) -> None: + assert result.extensions_count == 0 + + def test_open_chimneys(self, result: EpcPropertyData) -> None: + assert result.open_chimneys_count == 1 + + def test_insulated_doors(self, result: EpcPropertyData) -> None: + assert result.insulated_door_count == 2 + + def test_draughtproofed_doors(self, result: EpcPropertyData) -> None: + assert result.draughtproofed_door_count == 1 # --- lighting --- - def test_led_bulbs(self, dwelling: Dwelling) -> None: - assert dwelling.lighting.number_of_led_bulbs == 10 + def test_led_bulbs(self, result: EpcPropertyData) -> None: + assert result.led_fixed_lighting_bulbs_count == 10 - def test_cfl_bulbs(self, dwelling: Dwelling) -> None: - assert dwelling.lighting.number_of_cfl_bulbs == 5 + def test_cfl_bulbs(self, result: EpcPropertyData) -> None: + assert result.cfl_fixed_lighting_bulbs_count == 5 - def test_incandescent_bulbs(self, dwelling: Dwelling) -> None: - assert dwelling.lighting.number_of_incandescent_bulbs == 0 + def test_incandescent_bulbs(self, result: EpcPropertyData) -> None: + assert result.incandescent_fixed_lighting_bulbs_count == 0 - # --- dwelling-level counts --- + # --- energy elements --- - def test_habitable_rooms(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_habitable_rooms == 5 + def test_roof_count(self, result: EpcPropertyData) -> None: + assert len(result.roofs) == 2 - def test_external_doors(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_external_doors == 3 + def test_roof_description(self, result: EpcPropertyData) -> None: + assert result.roofs[0].description == "Pitched, 25 mm loft insulation" - def test_open_chimneys(self, dwelling: Dwelling) -> None: - assert dwelling.number_of_open_chimneys == 1 + def test_roof_energy_efficiency_rating(self, result: EpcPropertyData) -> None: + assert result.roofs[0].energy_efficiency_rating == 2 - def test_no_conservatory(self, dwelling: Dwelling) -> None: - # conservatory_type: 1 → no conservatory - assert dwelling.has_conservatory is False + def test_wall_count(self, result: EpcPropertyData) -> None: + assert len(result.walls) == 2 + + def test_window_element_description(self, result: EpcPropertyData) -> None: + assert result.window is not None + assert result.window.description == "Fully double glazed" + + def test_window_element_rating(self, result: EpcPropertyData) -> None: + assert result.window is not None + assert result.window.energy_efficiency_rating == 3 + + def test_lighting_element_description(self, result: EpcPropertyData) -> None: + assert result.lighting is not None + assert result.lighting.description == "Low energy lighting in 50% of fixed outlets" + + def test_hot_water_element_description(self, result: EpcPropertyData) -> None: + assert result.hot_water is not None + assert result.hot_water.description == "From main system" + + def test_secondary_heating_element(self, result: EpcPropertyData) -> None: + assert result.secondary_heating is not None + assert result.secondary_heating.description == "Room heaters, electric" + + def test_main_heating_element_count(self, result: EpcPropertyData) -> None: + assert len(result.main_heating) == 2 + + def test_main_heating_element_description(self, result: EpcPropertyData) -> None: + assert result.main_heating[0].description == "Boiler and radiators, anthracite" + + # --- sap energy source --- + + def test_mains_gas(self, result: EpcPropertyData) -> None: + # mains_gas: "Y" + assert result.sap_energy_source.mains_gas is True + + def test_electricity_smart_meter(self, result: EpcPropertyData) -> None: + # electricity_smart_meter_present: "true" + assert result.sap_energy_source.electricity_smart_meter_present is True + + def test_gas_smart_meter(self, result: EpcPropertyData) -> None: + # gas_smart_meter_present: "false" + assert result.sap_energy_source.gas_smart_meter_present is False + + def test_pv_battery_count(self, result: EpcPropertyData) -> None: + assert result.sap_energy_source.pv_battery_count == 1 + + def test_wind_turbines_count(self, result: EpcPropertyData) -> None: + assert result.sap_energy_source.wind_turbines_count == 0 + + # --- sap heating --- + + def test_cylinder_size(self, result: EpcPropertyData) -> None: + assert result.sap_heating.cylinder_size == 1 + + def test_water_heating_code(self, result: EpcPropertyData) -> None: + assert result.sap_heating.water_heating_code == 901 + + def test_water_heating_fuel(self, result: EpcPropertyData) -> None: + assert result.sap_heating.water_heating_fuel == 26 + + def test_secondary_fuel_type(self, result: EpcPropertyData) -> None: + # secondary_fuel_type: 25 + assert result.sap_heating.secondary_fuel_type == 25 + + def test_main_heating_no_fghrs(self, result: EpcPropertyData) -> None: + # has_fghrs: "N" + assert result.sap_heating.main_heating_details[0].has_fghrs is False + + def test_main_heating_fuel_type(self, result: EpcPropertyData) -> None: + # main_fuel_type: 26 + assert result.sap_heating.main_heating_details[0].main_fuel_type == 26 + + def test_main_heating_fan_flue(self, result: EpcPropertyData) -> None: + # fan_flue_present: "N" + assert result.sap_heating.main_heating_details[0].fan_flue_present is False + + def test_main_heating_control(self, result: EpcPropertyData) -> None: + assert result.sap_heating.main_heating_details[0].main_heating_control == 2106 + + def test_main_heating_category(self, result: EpcPropertyData) -> None: + assert result.sap_heating.main_heating_details[0].main_heating_category == 2 + + def test_main_heating_number(self, result: EpcPropertyData) -> None: + assert result.sap_heating.main_heating_details[0].main_heating_number == 1 + + # --- sap windows --- + + def test_window_count(self, result: EpcPropertyData) -> None: + assert len(result.sap_windows) == 1 + + def test_window_height(self, result: EpcPropertyData) -> None: + assert result.sap_windows[0].window_height == 2.0 + + def test_window_width(self, result: EpcPropertyData) -> None: + assert result.sap_windows[0].window_width == 1.2 + + def test_window_draught_proofed(self, result: EpcPropertyData) -> None: + # draught_proofed: "true" + assert result.sap_windows[0].draught_proofed is True + + # --- sap building parts --- + + def test_building_part_count(self, result: EpcPropertyData) -> None: + assert len(result.sap_building_parts) == 1 + + def test_construction_age_band(self, result: EpcPropertyData) -> None: + assert result.sap_building_parts[0].construction_age_band == "M" + + def test_wall_construction(self, result: EpcPropertyData) -> None: + # wall_construction: 4 (int preserved from API) + assert result.sap_building_parts[0].wall_construction == 4 + + def test_wall_insulation_type(self, result: EpcPropertyData) -> None: + # wall_insulation_type: 2 (int preserved from API) + assert result.sap_building_parts[0].wall_insulation_type == 2 + + def test_wall_thickness_not_measured(self, result: EpcPropertyData) -> None: + # wall_thickness_measured: "N" + assert result.sap_building_parts[0].wall_thickness_measured is False + + def test_wall_thickness_mm_absent(self, result: EpcPropertyData) -> None: + assert result.sap_building_parts[0].wall_thickness_mm is None + + def test_roof_insulation_thickness(self, result: EpcPropertyData) -> None: + # roof_insulation_thickness: "200mm" — preserved as-is from schema + assert result.sap_building_parts[0].roof_insulation_thickness == "200mm" + + def test_room_in_roof_present(self, result: EpcPropertyData) -> None: + # sap_room_in_roof is present in the fixture + assert result.sap_building_parts[0].sap_room_in_roof is not None + + # --- floor dimensions --- + + def test_floor_count(self, result: EpcPropertyData) -> None: + assert len(result.sap_building_parts[0].sap_floor_dimensions) == 1 + + def test_floor_area(self, result: EpcPropertyData) -> None: + assert result.sap_building_parts[0].sap_floor_dimensions[0].total_floor_area_m2 == 45.82 + + def test_floor_height(self, result: EpcPropertyData) -> None: + assert result.sap_building_parts[0].sap_floor_dimensions[0].room_height_m == 2.45 + + def test_heat_loss_perimeter(self, result: EpcPropertyData) -> None: + assert result.sap_building_parts[0].sap_floor_dimensions[0].heat_loss_perimeter_m == 19.5 + + def test_party_wall_length(self, result: EpcPropertyData) -> None: + assert result.sap_building_parts[0].sap_floor_dimensions[0].party_wall_length_m == 7.9