import json import os from datetime import date from typing import Any, Dict import pytest from domain.epc.epc_property_data import EpcPropertyData from domain.epc.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__), "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] # --------------------------------------------------------------------------- # 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: assert result.built_form == "2" def test_property_type(self, result: EpcPropertyData) -> None: assert result.property_type == "2" # --------------------------------------------------------------------------- # 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: assert result.built_form == "1" def test_property_type(self, result: EpcPropertyData) -> None: assert result.property_type == "0" # --------------------------------------------------------------------------- # 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: assert result.built_form == "4" def test_property_type(self, result: EpcPropertyData) -> None: assert result.property_type == "0" # --------------------------------------------------------------------------- # 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: assert result.built_form == "2" def test_property_type(self, result: EpcPropertyData) -> None: assert result.property_type == "0" # --------------------------------------------------------------------------- # 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: assert result.built_form == "2" def test_property_type(self, result: EpcPropertyData) -> None: assert result.property_type == "0" # --------------------------------------------------------------------------- # 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: assert result.built_form == "2" def test_property_type(self, result: EpcPropertyData) -> None: assert result.property_type == "0" # --------------------------------------------------------------------------- # Schema 21.0.1 (most comprehensive — full field coverage) # --------------------------------------------------------------------------- class TestFromRdSapSchema21_0_1: @pytest.fixture def schema(self) -> RdSapSchema21_0_1: return from_dict(RdSapSchema21_0_1, load("21_0_1.json")) @pytest.fixture def result(self, schema: RdSapSchema21_0_1) -> EpcPropertyData: return EpcPropertyDataMapper.from_rdsap_schema_21_0_1(schema) # --- general --- 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_property_type(self, result: EpcPropertyData) -> None: assert result.property_type == "0" def test_built_form(self, result: EpcPropertyData) -> None: assert result.built_form == "2" def test_address_line_1(self, result: EpcPropertyData) -> None: assert result.address_line_1 == "1 Some Street" def test_postcode(self, result: EpcPropertyData) -> None: assert result.postcode == "A0 0AA" def test_post_town(self, result: EpcPropertyData) -> None: assert result.post_town == "Whitbury" def test_status(self, result: EpcPropertyData) -> None: assert result.status == "entered" def test_tenure(self, result: EpcPropertyData) -> None: # tenure: 1 — stored as stringified int assert result.tenure == "1" def test_transaction_type(self, result: EpcPropertyData) -> None: # transaction_type: 16 — stored as stringified int assert result.transaction_type == "16" def test_inspection_date(self, result: EpcPropertyData) -> None: assert result.inspection_date == date(2025, 4, 4) def test_total_floor_area(self, result: EpcPropertyData) -> None: assert result.total_floor_area_m2 == 55.0 # --- property flags --- def test_solar_water_heating(self, result: EpcPropertyData) -> None: # solar_water_heating: "N" assert result.solar_water_heating 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_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, result: EpcPropertyData) -> None: assert result.led_fixed_lighting_bulbs_count == 10 def test_cfl_bulbs(self, result: EpcPropertyData) -> None: assert result.cfl_fixed_lighting_bulbs_count == 5 def test_incandescent_bulbs(self, result: EpcPropertyData) -> None: assert result.incandescent_fixed_lighting_bulbs_count == 0 # --- energy elements --- def test_roof_count(self, result: EpcPropertyData) -> None: assert len(result.roofs) == 2 def test_roof_description(self, result: EpcPropertyData) -> None: assert result.roofs[0].description == "Pitched, 25 mm loft insulation" def test_roof_energy_efficiency_rating(self, result: EpcPropertyData) -> None: assert result.roofs[0].energy_efficiency_rating == 2 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 def test_window_frame_material_false(self, result: EpcPropertyData) -> None: # pvc_frame: "false" in fixture → frame_material should be None assert result.sap_windows[0].frame_material is None # --- 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