diff --git a/datatypes/epc/schema/__init__.py b/datatypes/epc/schema/__init__.py new file mode 100644 index 00000000..e49f2317 --- /dev/null +++ b/datatypes/epc/schema/__init__.py @@ -0,0 +1,17 @@ +from .rdsap_schema_17_0 import RdSapSchema17_0 +from .rdsap_schema_17_1 import RdSapSchema17_1 +from .rdsap_schema_18_0 import RdSapSchema18_0 +from .rdsap_schema_19_0 import RdSapSchema19_0 +from .rdsap_schema_20_0_0 import RdSapSchema20_0_0 +from .rdsap_schema_21_0_0 import RdSapSchema21_0_0 +from .rdsap_schema_21_0_1 import RdSapSchema21_0_1 + +__all__ = [ + "RdSapSchema17_0", + "RdSapSchema17_1", + "RdSapSchema18_0", + "RdSapSchema19_0", + "RdSapSchema20_0_0", + "RdSapSchema21_0_0", + "RdSapSchema21_0_1", +] diff --git a/datatypes/epc/schema/common.py b/datatypes/epc/schema/common.py new file mode 100644 index 00000000..aa9ea512 --- /dev/null +++ b/datatypes/epc/schema/common.py @@ -0,0 +1,22 @@ +from dataclasses import dataclass + + +@dataclass +class Measurement: + """A numeric value with a physical unit, e.g. {"value": 2.4, "quantity": "metres"}.""" + value: float + quantity: str + + +@dataclass +class DescriptionV1: + """Localised description object used in schemas 17.x, 18.x, 19.x, and 21.0.1.""" + value: str + language: str + + +@dataclass +class CostAmount: + """Monetary amount used in schemas 17.x, 18.x, and 19.x.""" + value: int + currency: str diff --git a/datatypes/epc/schema/rdsap_schema_17_0.py b/datatypes/epc/schema/rdsap_schema_17_0.py new file mode 100644 index 00000000..22aaded4 --- /dev/null +++ b/datatypes/epc/schema/rdsap_schema_17_0.py @@ -0,0 +1,222 @@ +from dataclasses import dataclass +from typing import List, Optional, Union + +from .common import CostAmount, DescriptionV1, Measurement + + +@dataclass +class EnergyElement: + description: DescriptionV1 + energy_efficiency_rating: int + environmental_efficiency_rating: int + + +@dataclass +class InstantaneousWwhrs: + rooms_with_bath_and_or_shower: int + rooms_with_mixer_shower_no_bath: int + rooms_with_bath_and_mixer_shower: int + + +@dataclass +class MainHeatingDetail: + has_fghrs: str + main_fuel_type: int + heat_emitter_type: int + 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 + sap_main_heating_code: Optional[int] = None + + +@dataclass +class SapHeating: + cylinder_size: int + water_heating_code: int + water_heating_fuel: int + instantaneous_wwhrs: InstantaneousWwhrs + main_heating_details: List[MainHeatingDetail] + immersion_heating_type: Union[int, str] + cylinder_insulation_type: int + has_fixed_air_conditioning: str + + +@dataclass +class PhotovoltaicSupplyNoneOrNoDetails: + pv_connection: int + percent_roof_area: int + + +@dataclass +class PhotovoltaicSupply: + none_or_no_details: PhotovoltaicSupplyNoneOrNoDetails + + +@dataclass +class SapEnergySource: + mains_gas: str + meter_type: int + photovoltaic_supply: PhotovoltaicSupply + wind_turbines_count: int + wind_turbines_terrain_type: int + + +@dataclass +class SapFloorDimension: + floor: int + room_height: Measurement + total_floor_area: Measurement + party_wall_length: Measurement + heat_loss_perimeter: Measurement + + +@dataclass +class SapBuildingPart: + identifier: str + wall_dry_lined: str + wall_thickness: int + floor_heat_loss: int + roof_construction: int + wall_construction: int + building_part_number: int + sap_floor_dimensions: List[SapFloorDimension] + wall_insulation_type: int + construction_age_band: str + party_wall_construction: Union[int, str] + wall_thickness_measured: str + roof_insulation_location: Union[int, str] + roof_insulation_thickness: str + wall_insulation_thickness: Optional[str] = None + + +@dataclass +class SapFlatDetails: + level: int + top_storey: str + flat_location: int + heat_loss_corridor: int + + +@dataclass +class ImprovementDetails: + improvement_number: int + + +@dataclass +class SuggestedImprovement: + sequence: int + typical_saving: CostAmount + indicative_cost: str + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class AlternativeImprovement: + sequence: int + typical_saving: CostAmount + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class RenewableHeatIncentive: + water_heating: int + space_heating_existing_dwelling: int + impact_of_loft_insulation: Optional[int] = None + impact_of_solid_wall_insulation: Optional[int] = None + + +@dataclass +class RdSapSchema17_0: + uprn: int + roofs: List[EnergyElement] + walls: List[EnergyElement] + floors: List[EnergyElement] + status: str + tenure: int + window: EnergyElement + lighting: EnergyElement + postcode: str + hot_water: EnergyElement + post_town: str + built_form: int + door_count: int + glazed_area: int + glazing_gap: str + region_code: int + report_type: int + sap_heating: SapHeating + sap_version: float + schema_type: str + uprn_source: str + country_code: str + main_heating: List[EnergyElement] + dwelling_type: DescriptionV1 + language_code: int + property_type: int + address_line_1: str + assessment_type: str + completion_date: str + inspection_date: str + extensions_count: int + measurement_type: int + total_floor_area: int + transaction_type: int + conservatory_type: int + heated_room_count: int + pvc_window_frames: str + registration_date: str + sap_energy_source: SapEnergySource + secondary_heating: EnergyElement + lzc_energy_sources: List[int] + sap_building_parts: List[SapBuildingPart] + low_energy_lighting: int + solar_water_heating: str + habitable_room_count: int + heating_cost_current: CostAmount + insulated_door_count: int + co2_emissions_current: float + energy_rating_average: int + energy_rating_current: int + lighting_cost_current: CostAmount + main_heating_controls: List[EnergyElement] + multiple_glazing_type: int + open_fireplaces_count: int + has_hot_water_cylinder: str + heating_cost_potential: CostAmount + hot_water_cost_current: CostAmount + mechanical_ventilation: int + percent_draughtproofed: int + suggested_improvements: List[SuggestedImprovement] + co2_emissions_potential: float + energy_rating_potential: int + lighting_cost_potential: CostAmount + schema_version_original: str + hot_water_cost_potential: CostAmount + renewable_heat_incentive: RenewableHeatIncentive + energy_consumption_current: int + has_fixed_air_conditioning: str + multiple_glazed_proportion: int + calculation_software_version: str + energy_consumption_potential: int + environmental_impact_current: int + fixed_lighting_outlets_count: int + current_energy_efficiency_band: str + environmental_impact_potential: int + has_heated_separate_conservatory: str + potential_energy_efficiency_band: str + co2_emissions_current_per_floor_area: int + low_energy_fixed_lighting_outlets_count: int + sap_flat_details: Optional[SapFlatDetails] = None + address_line_2: Optional[str] = None + alternative_improvements: Optional[List[AlternativeImprovement]] = None diff --git a/datatypes/epc/schema/rdsap_schema_17_1.py b/datatypes/epc/schema/rdsap_schema_17_1.py new file mode 100644 index 00000000..a4c007ed --- /dev/null +++ b/datatypes/epc/schema/rdsap_schema_17_1.py @@ -0,0 +1,234 @@ +from dataclasses import dataclass +from typing import List, Optional, Union + +from .common import CostAmount, DescriptionV1, Measurement + + +@dataclass +class EnergyElement: + description: DescriptionV1 + energy_efficiency_rating: int + environmental_efficiency_rating: int + + +@dataclass +class InstantaneousWwhrs: + rooms_with_bath_and_or_shower: int + rooms_with_mixer_shower_no_bath: int + rooms_with_bath_and_mixer_shower: int + + +@dataclass +class MainHeatingDetail: + has_fghrs: str + main_fuel_type: int + heat_emitter_type: int + 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 + boiler_flue_type: Optional[int] = None + fan_flue_present: Optional[str] = None + mcs_installed_heat_pump: Optional[str] = None + main_heating_index_number: Optional[int] = None + sap_main_heating_code: Optional[int] = None + + +@dataclass +class SapHeating: + cylinder_size: int + water_heating_code: int + water_heating_fuel: int + instantaneous_wwhrs: InstantaneousWwhrs + main_heating_details: List[MainHeatingDetail] + immersion_heating_type: Union[int, str] + cylinder_insulation_type: int + has_fixed_air_conditioning: str + cylinder_thermostat: Optional[str] = None + secondary_fuel_type: Optional[int] = None + secondary_heating_type: Optional[int] = None + cylinder_insulation_thickness: Optional[int] = None + + +@dataclass +class PhotovoltaicSupplyNoneOrNoDetails: + pv_connection: int + percent_roof_area: int + + +@dataclass +class PhotovoltaicSupply: + none_or_no_details: PhotovoltaicSupplyNoneOrNoDetails + + +@dataclass +class SapEnergySource: + mains_gas: str + meter_type: int + photovoltaic_supply: PhotovoltaicSupply + wind_turbines_count: int + wind_turbines_terrain_type: int + + +@dataclass +class SapFloorDimension: + floor: int + room_height: Measurement + total_floor_area: Measurement + # Can be a Measurement object or 0 (int) for party walls of zero length + party_wall_length: Union[Measurement, int] + heat_loss_perimeter: Measurement + floor_insulation: Optional[int] = None + floor_construction: Optional[int] = None + + +@dataclass +class SapBuildingPart: + identifier: str + wall_dry_lined: str + wall_thickness: int + floor_heat_loss: int + roof_construction: int + wall_construction: int + building_part_number: int + sap_floor_dimensions: List[SapFloorDimension] + wall_insulation_type: int + construction_age_band: str + party_wall_construction: Union[int, str] + wall_thickness_measured: str + roof_insulation_location: Union[int, str] + # Can be a thickness string (e.g. "100mm") or 0 for uninsulated flat roofs + roof_insulation_thickness: Union[str, int] + wall_insulation_thickness: Optional[str] = None + + +@dataclass +class SapFlatDetails: + level: int + top_storey: str + flat_location: int + heat_loss_corridor: int + + +@dataclass +class ImprovementDetails: + improvement_number: int + + +@dataclass +class SuggestedImprovement: + sequence: int + typical_saving: CostAmount + indicative_cost: str + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class AlternativeImprovement: + sequence: int + typical_saving: CostAmount + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class RenewableHeatIncentive: + water_heating: int + space_heating_existing_dwelling: int + impact_of_loft_insulation: Optional[int] = None + impact_of_solid_wall_insulation: Optional[int] = None + + +@dataclass +class RdSapSchema17_1: + uprn: int + roofs: List[EnergyElement] + walls: List[EnergyElement] + floors: List[EnergyElement] + status: str + tenure: int + window: EnergyElement + lighting: EnergyElement + postcode: str + hot_water: EnergyElement + post_town: str + built_form: int + door_count: int + glazed_area: int + glazing_gap: str + region_code: int + report_type: int + sap_heating: SapHeating + sap_version: float + schema_type: str + uprn_source: str + country_code: str + main_heating: List[EnergyElement] + dwelling_type: DescriptionV1 + language_code: int + property_type: int + address_line_1: str + assessment_type: str + completion_date: str + inspection_date: str + extensions_count: int + measurement_type: int + total_floor_area: int + transaction_type: int + conservatory_type: int + heated_room_count: int + pvc_window_frames: str + registration_date: str + sap_energy_source: SapEnergySource + secondary_heating: EnergyElement + lzc_energy_sources: List[int] + sap_building_parts: List[SapBuildingPart] + low_energy_lighting: int + solar_water_heating: str + habitable_room_count: int + heating_cost_current: CostAmount + insulated_door_count: int + co2_emissions_current: float + energy_rating_average: int + energy_rating_current: int + lighting_cost_current: CostAmount + main_heating_controls: List[EnergyElement] + multiple_glazing_type: int + open_fireplaces_count: int + has_hot_water_cylinder: str + heating_cost_potential: CostAmount + hot_water_cost_current: CostAmount + mechanical_ventilation: int + percent_draughtproofed: int + suggested_improvements: List[SuggestedImprovement] + co2_emissions_potential: float + energy_rating_potential: int + lighting_cost_potential: CostAmount + schema_version_original: str + hot_water_cost_potential: CostAmount + renewable_heat_incentive: RenewableHeatIncentive + energy_consumption_current: int + has_fixed_air_conditioning: str + multiple_glazed_proportion: int + calculation_software_version: str + energy_consumption_potential: int + environmental_impact_current: int + fixed_lighting_outlets_count: int + current_energy_efficiency_band: str + environmental_impact_potential: int + has_heated_separate_conservatory: str + potential_energy_efficiency_band: str + co2_emissions_current_per_floor_area: int + low_energy_fixed_lighting_outlets_count: int + sap_flat_details: Optional[SapFlatDetails] = None + address_line_2: Optional[str] = None + alternative_improvements: Optional[List[AlternativeImprovement]] = None diff --git a/datatypes/epc/schema/rdsap_schema_18_0.py b/datatypes/epc/schema/rdsap_schema_18_0.py new file mode 100644 index 00000000..a038dc9b --- /dev/null +++ b/datatypes/epc/schema/rdsap_schema_18_0.py @@ -0,0 +1,245 @@ +from dataclasses import dataclass +from typing import List, Optional, Union + +from .common import CostAmount, DescriptionV1, Measurement + + +@dataclass +class EnergyElement: + description: DescriptionV1 + energy_efficiency_rating: int + environmental_efficiency_rating: int + + +@dataclass +class InstantaneousWwhrs: + rooms_with_bath_and_or_shower: int + rooms_with_mixer_shower_no_bath: int + rooms_with_bath_and_mixer_shower: int + + +@dataclass +class MainHeatingDetail: + has_fghrs: str + main_fuel_type: int + heat_emitter_type: int + 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 + boiler_flue_type: Optional[int] = None + fan_flue_present: Optional[str] = None + central_heating_pump_age: Optional[int] = None + main_heating_index_number: Optional[int] = None + sap_main_heating_code: Optional[int] = None + + +@dataclass +class SapHeating: + cylinder_size: int + water_heating_code: int + water_heating_fuel: int + instantaneous_wwhrs: InstantaneousWwhrs + main_heating_details: List[MainHeatingDetail] + immersion_heating_type: Union[int, str] + has_fixed_air_conditioning: str + cylinder_insulation_type: Optional[int] = None + cylinder_thermostat: Optional[str] = None + secondary_fuel_type: Optional[int] = None + secondary_heating_type: Optional[int] = None + cylinder_insulation_thickness: Optional[int] = None + + +@dataclass +class PhotovoltaicSupplyNoneOrNoDetails: + pv_connection: int + percent_roof_area: int + + +@dataclass +class PhotovoltaicSupply: + none_or_no_details: PhotovoltaicSupplyNoneOrNoDetails + + +@dataclass +class SapEnergySource: + mains_gas: str + meter_type: int + photovoltaic_supply: PhotovoltaicSupply + wind_turbines_count: int + wind_turbines_terrain_type: int + + +@dataclass +class SapFloorDimension: + floor: int + room_height: Measurement + total_floor_area: Measurement + party_wall_length: Union[Measurement, int] + heat_loss_perimeter: Measurement + floor_insulation: Optional[int] = None + floor_construction: Optional[int] = None + + +@dataclass +class SapRoomInRoof: + """Room-in-roof details. floor_area is a Measurement object in schema 18.0.""" + floor_area: Measurement + insulation: str + roof_room_connected: str + construction_age_band: str + + +@dataclass +class SapBuildingPart: + identifier: str + wall_dry_lined: str + wall_thickness: int + floor_heat_loss: int + roof_construction: int + wall_construction: int + building_part_number: int + sap_floor_dimensions: List[SapFloorDimension] + wall_insulation_type: int + construction_age_band: str + party_wall_construction: Union[int, str] + wall_thickness_measured: str + roof_insulation_location: Union[int, str] + roof_insulation_thickness: Union[str, int] + sap_room_in_roof: Optional[SapRoomInRoof] = None + wall_insulation_thickness: Optional[str] = None + floor_insulation_thickness: Optional[str] = None + flat_roof_insulation_thickness: Optional[Union[str, int]] = None + + +@dataclass +class SapFlatDetails: + level: int + top_storey: str + flat_location: int + heat_loss_corridor: int + + +@dataclass +class ImprovementDetails: + improvement_number: int + + +@dataclass +class SuggestedImprovement: + sequence: int + typical_saving: CostAmount + indicative_cost: str + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class AlternativeImprovement: + sequence: int + typical_saving: CostAmount + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class RenewableHeatIncentive: + water_heating: int + space_heating_existing_dwelling: int + impact_of_loft_insulation: Optional[int] = None + impact_of_solid_wall_insulation: Optional[int] = None + + +@dataclass +class RdSapSchema18_0: + uprn: int + roofs: List[EnergyElement] + walls: List[EnergyElement] + floors: List[EnergyElement] + status: str + tenure: int + window: EnergyElement + lighting: EnergyElement + postcode: str + hot_water: EnergyElement + post_town: str + built_form: int + door_count: int + glazed_area: int + # glazing_gap is an integer in 18.0 (e.g. 12 mm), unlike 17.x where it was a string + glazing_gap: int + region_code: int + report_type: int + sap_heating: SapHeating + sap_version: float + schema_type: str + uprn_source: str + country_code: str + main_heating: List[EnergyElement] + dwelling_type: DescriptionV1 + language_code: int + property_type: int + address_line_1: str + assessment_type: str + completion_date: str + inspection_date: str + extensions_count: int + measurement_type: int + total_floor_area: int + transaction_type: int + conservatory_type: int + heated_room_count: int + pvc_window_frames: str + registration_date: str + sap_energy_source: SapEnergySource + secondary_heating: EnergyElement + lzc_energy_sources: List[int] + sap_building_parts: List[SapBuildingPart] + low_energy_lighting: int + solar_water_heating: str + habitable_room_count: int + heating_cost_current: CostAmount + insulated_door_count: int + co2_emissions_current: float + energy_rating_average: int + energy_rating_current: int + lighting_cost_current: CostAmount + main_heating_controls: List[EnergyElement] + multiple_glazing_type: int + open_fireplaces_count: int + has_hot_water_cylinder: str + heating_cost_potential: CostAmount + hot_water_cost_current: CostAmount + mechanical_ventilation: int + percent_draughtproofed: int + suggested_improvements: List[SuggestedImprovement] + co2_emissions_potential: float + energy_rating_potential: int + lighting_cost_potential: CostAmount + schema_version_original: str + hot_water_cost_potential: CostAmount + renewable_heat_incentive: RenewableHeatIncentive + energy_consumption_current: int + has_fixed_air_conditioning: str + multiple_glazed_proportion: int + calculation_software_version: str + energy_consumption_potential: int + environmental_impact_current: int + fixed_lighting_outlets_count: int + current_energy_efficiency_band: str + environmental_impact_potential: int + has_heated_separate_conservatory: str + potential_energy_efficiency_band: str + co2_emissions_current_per_floor_area: int + low_energy_fixed_lighting_outlets_count: int + sap_flat_details: Optional[SapFlatDetails] = None + address_line_2: Optional[str] = None + alternative_improvements: Optional[List[AlternativeImprovement]] = None diff --git a/datatypes/epc/schema/rdsap_schema_19_0.py b/datatypes/epc/schema/rdsap_schema_19_0.py new file mode 100644 index 00000000..b94d9bb3 --- /dev/null +++ b/datatypes/epc/schema/rdsap_schema_19_0.py @@ -0,0 +1,251 @@ +from dataclasses import dataclass +from typing import List, Optional, Union + +from .common import CostAmount, DescriptionV1, Measurement + + +@dataclass +class EnergyElement: + description: DescriptionV1 + energy_efficiency_rating: int + environmental_efficiency_rating: int + + +@dataclass +class InstantaneousWwhrs: + rooms_with_bath_and_or_shower: int + rooms_with_mixer_shower_no_bath: int + rooms_with_bath_and_mixer_shower: int + + +@dataclass +class MainHeatingDetail: + has_fghrs: str + main_fuel_type: int + heat_emitter_type: int + 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 + boiler_flue_type: Optional[int] = None + fan_flue_present: Optional[str] = None + central_heating_pump_age: Optional[int] = None + main_heating_index_number: Optional[int] = None + sap_main_heating_code: Optional[int] = None + + +@dataclass +class SapHeating: + cylinder_size: int + water_heating_code: int + water_heating_fuel: int + instantaneous_wwhrs: InstantaneousWwhrs + main_heating_details: List[MainHeatingDetail] + immersion_heating_type: Union[int, str] + has_fixed_air_conditioning: str + cylinder_insulation_type: Optional[int] = None + cylinder_thermostat: Optional[str] = None + secondary_fuel_type: Optional[int] = None + secondary_heating_type: Optional[int] = None + cylinder_insulation_thickness: Optional[int] = None + + +@dataclass +class PhotovoltaicSupplyNoneOrNoDetails: + pv_connection: int + percent_roof_area: int + + +@dataclass +class PhotovoltaicSupply: + none_or_no_details: PhotovoltaicSupplyNoneOrNoDetails + + +@dataclass +class SapEnergySource: + mains_gas: str + meter_type: int + photovoltaic_supply: PhotovoltaicSupply + wind_turbines_count: int + wind_turbines_terrain_type: int + + +@dataclass +class SapFloorDimension: + floor: int + room_height: Measurement + total_floor_area: Measurement + party_wall_length: Union[Measurement, int] + heat_loss_perimeter: Measurement + floor_insulation: Optional[int] = None + floor_construction: Optional[int] = None + + +@dataclass +class SapRoomInRoof: + floor_area: Measurement + insulation: str + roof_room_connected: str + construction_age_band: str + + +@dataclass +class SapBuildingPart: + identifier: str + wall_dry_lined: str + wall_thickness: int + floor_heat_loss: int + roof_construction: int + wall_construction: int + building_part_number: int + sap_floor_dimensions: List[SapFloorDimension] + wall_insulation_type: int + construction_age_band: str + party_wall_construction: Union[int, str] + wall_thickness_measured: str + roof_insulation_location: Union[int, str] + roof_insulation_thickness: Union[str, int] + sap_room_in_roof: Optional[SapRoomInRoof] = None + wall_insulation_thickness: Optional[str] = None + floor_insulation_thickness: Optional[str] = None + flat_roof_insulation_thickness: Optional[Union[str, int]] = None + + +@dataclass +class SapFlatDetails: + level: int + top_storey: str + flat_location: int + heat_loss_corridor: int + + +@dataclass +class WindowsTransmissionDetails: + u_value: float + data_source: int + solar_transmittance: float + + +@dataclass +class ImprovementDetails: + improvement_number: int + + +@dataclass +class SuggestedImprovement: + sequence: int + typical_saving: CostAmount + indicative_cost: str + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class AlternativeImprovement: + sequence: int + typical_saving: CostAmount + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class RenewableHeatIncentive: + water_heating: int + space_heating_existing_dwelling: int + impact_of_loft_insulation: Optional[int] = None + impact_of_solid_wall_insulation: Optional[int] = None + + +@dataclass +class RdSapSchema19_0: + uprn: int + roofs: List[EnergyElement] + walls: List[EnergyElement] + floors: List[EnergyElement] + status: str + tenure: int + window: EnergyElement + lighting: EnergyElement + postcode: str + hot_water: EnergyElement + post_town: str + built_form: int + door_count: int + glazed_area: int + region_code: int + report_type: int + sap_heating: SapHeating + sap_version: float + schema_type: str + uprn_source: str + country_code: str + main_heating: List[EnergyElement] + dwelling_type: DescriptionV1 + language_code: int + property_type: int + address_line_1: str + assessment_type: str + completion_date: str + inspection_date: str + extensions_count: int + measurement_type: int + total_floor_area: int + transaction_type: int + conservatory_type: int + heated_room_count: int + pvc_window_frames: str + registration_date: str + sap_energy_source: SapEnergySource + secondary_heating: EnergyElement + lzc_energy_sources: List[int] + sap_building_parts: List[SapBuildingPart] + low_energy_lighting: int + solar_water_heating: str + habitable_room_count: int + heating_cost_current: CostAmount + insulated_door_count: int + co2_emissions_current: float + energy_rating_average: int + energy_rating_current: int + lighting_cost_current: CostAmount + main_heating_controls: List[EnergyElement] + multiple_glazing_type: int + open_fireplaces_count: int + has_hot_water_cylinder: str + heating_cost_potential: CostAmount + hot_water_cost_current: CostAmount + mechanical_ventilation: int + percent_draughtproofed: int + suggested_improvements: List[SuggestedImprovement] + co2_emissions_potential: float + energy_rating_potential: int + lighting_cost_potential: CostAmount + schema_version_original: str + hot_water_cost_potential: CostAmount + renewable_heat_incentive: RenewableHeatIncentive + windows_transmission_details: WindowsTransmissionDetails + energy_consumption_current: int + has_fixed_air_conditioning: str + multiple_glazed_proportion: int + calculation_software_version: str + energy_consumption_potential: int + environmental_impact_current: int + fixed_lighting_outlets_count: int + current_energy_efficiency_band: str + environmental_impact_potential: int + has_heated_separate_conservatory: str + potential_energy_efficiency_band: str + co2_emissions_current_per_floor_area: int + low_energy_fixed_lighting_outlets_count: int + sap_flat_details: Optional[SapFlatDetails] = None + address_line_2: Optional[str] = None + glazing_gap: Optional[Union[str, int]] = None + alternative_improvements: Optional[List[AlternativeImprovement]] = None diff --git a/datatypes/epc/schema/rdsap_schema_20_0_0.py b/datatypes/epc/schema/rdsap_schema_20_0_0.py new file mode 100644 index 00000000..8f3986a2 --- /dev/null +++ b/datatypes/epc/schema/rdsap_schema_20_0_0.py @@ -0,0 +1,282 @@ +from dataclasses import dataclass +from typing import List, Optional, Union + +from .common import Measurement + + +@dataclass +class EnergyElement: + # description is a plain string in schema 20.0.0 onwards (no longer a localised object) + description: str + energy_efficiency_rating: int + environmental_efficiency_rating: int + + +@dataclass +class Addendum: + addendum_numbers: List[int] + stone_walls: Optional[str] = None + system_build: Optional[str] = None + + +@dataclass +class InstantaneousWwhrs: + rooms_with_bath_and_or_shower: int + rooms_with_mixer_shower_no_bath: int + rooms_with_bath_and_mixer_shower: int + + +@dataclass +class MainHeatingDetail: + has_fghrs: str + main_fuel_type: int + heat_emitter_type: int + 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 + boiler_flue_type: Optional[int] = None + fan_flue_present: Optional[str] = None + central_heating_pump_age: Optional[int] = None + main_heating_index_number: Optional[int] = None + sap_main_heating_code: Optional[int] = None + + +@dataclass +class SapHeating: + cylinder_size: int + water_heating_code: int + water_heating_fuel: int + instantaneous_wwhrs: InstantaneousWwhrs + main_heating_details: List[MainHeatingDetail] + immersion_heating_type: Union[int, str] + has_fixed_air_conditioning: str + cylinder_insulation_type: Optional[int] = None + cylinder_thermostat: Optional[str] = None + secondary_fuel_type: Optional[int] = None + secondary_heating_type: Optional[int] = None + cylinder_insulation_thickness: Optional[int] = None + + +@dataclass +class PhotovoltaicSupplyNoneOrNoDetails: + pv_connection: int + percent_roof_area: int + + +@dataclass +class PhotovoltaicSupply: + none_or_no_details: PhotovoltaicSupplyNoneOrNoDetails + + +@dataclass +class SapEnergySource: + mains_gas: str + meter_type: int + photovoltaic_supply: PhotovoltaicSupply + wind_turbines_count: int + wind_turbines_terrain_type: int + + +@dataclass +class SapWindow: + orientation: int + window_area: float + window_type: int + glazing_type: int + window_location: int + + +@dataclass +class SapFloorDimension: + floor: int + room_height: Measurement + total_floor_area: Measurement + party_wall_length: Union[Measurement, int] + heat_loss_perimeter: Measurement + floor_insulation: Optional[int] = None + floor_construction: Optional[int] = None + + +@dataclass +class SapRoomInRoof: + """Room-in-roof details. floor_area is a plain number in schema 20.0.0 (not a Measurement object).""" + floor_area: Union[int, float] + insulation: str + roof_room_connected: str + construction_age_band: str + + +@dataclass +class SapAlternativeWall: + wall_area: float + wall_dry_lined: str + wall_construction: int + wall_insulation_type: int + wall_thickness_measured: str + wall_insulation_thickness: Optional[str] = None + + +@dataclass +class SapBuildingPart: + identifier: str + wall_dry_lined: str + floor_heat_loss: int + roof_construction: int + wall_construction: int + building_part_number: int + sap_floor_dimensions: List[SapFloorDimension] + wall_insulation_type: int + construction_age_band: str + party_wall_construction: Union[int, str] + wall_thickness_measured: str + roof_insulation_location: Union[int, str] + roof_insulation_thickness: Union[str, int] + sap_room_in_roof: Optional[SapRoomInRoof] = None + wall_thickness: Optional[int] = None + wall_insulation_thickness: Optional[str] = None + floor_insulation_thickness: Optional[str] = None + flat_roof_insulation_thickness: Optional[Union[str, int]] = None + + +@dataclass +class SapFlatDetails: + level: int + top_storey: str + flat_location: int + heat_loss_corridor: int + storey_count: Optional[int] = None + unheated_corridor_length: Optional[int] = None + + +@dataclass +class WindowsTransmissionDetails: + u_value: float + data_source: int + solar_transmittance: float + + +@dataclass +class ImprovementTexts: + improvement_description: str + improvement_summary: Optional[str] = None + + +@dataclass +class ImprovementDetails: + improvement_number: Optional[int] = None + improvement_texts: Optional[ImprovementTexts] = None + + +@dataclass +class SuggestedImprovement: + sequence: int + # typical_saving is a plain number in schema 20.0.0 (not a CostAmount object) + typical_saving: float + # indicative_cost can be a formatted string (e.g. "£100 - £350") or a plain integer + indicative_cost: Union[str, int] + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class RenewableHeatIncentive: + water_heating: int + space_heating_existing_dwelling: int + impact_of_loft_insulation: Optional[int] = None + impact_of_cavity_insulation: Optional[int] = None + impact_of_solid_wall_insulation: Optional[int] = None + + +@dataclass +class RdSapSchema20_0_0: + uprn: int + roofs: List[EnergyElement] + walls: List[EnergyElement] + floors: List[EnergyElement] + status: str + tenure: int + window: EnergyElement + lighting: EnergyElement + postcode: str + hot_water: EnergyElement + post_town: str + built_form: int + door_count: int + glazed_area: int + region_code: int + report_type: int + sap_heating: SapHeating + sap_version: float + sap_windows: List[SapWindow] + schema_type: str + uprn_source: str + country_code: str + main_heating: List[EnergyElement] + # dwelling_type is a plain string in schema 20.0.0 onwards + dwelling_type: str + language_code: int + property_type: int + address_line_1: str + assessment_type: str + completion_date: str + inspection_date: str + extensions_count: int + measurement_type: int + total_floor_area: int + transaction_type: int + conservatory_type: int + heated_room_count: int + registration_date: str + sap_energy_source: SapEnergySource + secondary_heating: EnergyElement + lzc_energy_sources: List[int] + sap_building_parts: List[SapBuildingPart] + low_energy_lighting: int + solar_water_heating: str + habitable_room_count: int + heating_cost_current: float + insulated_door_count: int + co2_emissions_current: float + energy_rating_average: int + energy_rating_current: int + lighting_cost_current: float + main_heating_controls: List[EnergyElement] + multiple_glazing_type: int + open_fireplaces_count: int + heating_cost_potential: float + hot_water_cost_current: float + insulated_door_u_value: float + mechanical_ventilation: int + percent_draughtproofed: int + suggested_improvements: List[SuggestedImprovement] + co2_emissions_potential: float + energy_rating_potential: int + lighting_cost_potential: float + schema_version_original: str + hot_water_cost_potential: float + renewable_heat_incentive: RenewableHeatIncentive + windows_transmission_details: WindowsTransmissionDetails + energy_consumption_current: int + multiple_glazed_proportion: int + calculation_software_version: str + energy_consumption_potential: int + environmental_impact_current: int + fixed_lighting_outlets_count: int + multiple_glazed_proportion_nr: Optional[str] + current_energy_efficiency_band: str + environmental_impact_potential: int + potential_energy_efficiency_band: str + co2_emissions_current_per_floor_area: int + low_energy_fixed_lighting_outlets_count: int + sap_flat_details: Optional[SapFlatDetails] = None + addendum: Optional[Addendum] = None + address_line_2: Optional[str] = None + has_hot_water_cylinder: Optional[str] = None + has_fixed_air_conditioning: Optional[str] = None + has_heated_separate_conservatory: Optional[str] = None diff --git a/datatypes/epc/schema/rdsap_schema_21_0_0.py b/datatypes/epc/schema/rdsap_schema_21_0_0.py new file mode 100644 index 00000000..eee00cb8 --- /dev/null +++ b/datatypes/epc/schema/rdsap_schema_21_0_0.py @@ -0,0 +1,339 @@ +from dataclasses import dataclass +from typing import List, Optional, Union + +from .common import Measurement + + +@dataclass +class EnergyElement: + # description is a plain string in schema 21.0.0 (no longer a localised object) + description: str + energy_efficiency_rating: int + environmental_efficiency_rating: int + + +@dataclass +class Addendum: + addendum_numbers: List[int] + stone_walls: Optional[str] = None + system_build: Optional[str] = None + + +@dataclass +class ShowerOutlet: + shower_wwhrs: int + shower_outlet_type: int + + +@dataclass +class ShowerOutlets: + shower_outlet: ShowerOutlet + + +@dataclass +class InstantaneousWwhrs: + """Changed in 21.0.0: references WWHRS product index numbers instead of room counts.""" + wwhrs_index_number1: Optional[int] = None + wwhrs_index_number2: Optional[int] = None + + +@dataclass +class MainHeatingDetail: + has_fghrs: str + main_fuel_type: int + heat_emitter_type: int + 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 + boiler_flue_type: Optional[int] = None + fan_flue_present: Optional[str] = None + boiler_ignition_type: Optional[int] = None + central_heating_pump_age: Optional[int] = None + main_heating_index_number: Optional[int] = None + sap_main_heating_code: Optional[int] = None + + +@dataclass +class SapHeating: + cylinder_size: int + water_heating_code: int + water_heating_fuel: int + instantaneous_wwhrs: InstantaneousWwhrs + main_heating_details: List[MainHeatingDetail] + immersion_heating_type: Union[int, str] + has_fixed_air_conditioning: str + shower_outlets: Optional[ShowerOutlets] = None + cylinder_insulation_type: Optional[int] = None + cylinder_thermostat: Optional[str] = None + secondary_fuel_type: Optional[int] = None + secondary_heating_type: Optional[int] = None + cylinder_insulation_thickness: Optional[int] = None + + +@dataclass +class PvBattery: + battery_capacity: float + + +@dataclass +class PvBatteries: + pv_battery: PvBattery + + +@dataclass +class WindTurbineDetails: + hub_height: float + rotor_diameter: float + + +@dataclass +class PhotovoltaicSupplyNoneOrNoDetails: + percent_roof_area: int + + +@dataclass +class PhotovoltaicSupply: + none_or_no_details: PhotovoltaicSupplyNoneOrNoDetails + + +@dataclass +class SapEnergySource: + mains_gas: str + meter_type: int + pv_connection: int + pv_battery_count: int + photovoltaic_supply: PhotovoltaicSupply + wind_turbines_count: int + wind_turbine_details: WindTurbineDetails + gas_smart_meter_present: str + is_dwelling_export_capable: str + wind_turbines_terrain_type: int + electricity_smart_meter_present: str + pv_batteries: Optional[PvBatteries] = None + + +@dataclass +class WindowTransmissionDetails: + u_value: float + data_source: int + solar_transmittance: float + + +@dataclass +class SapWindow: + pvc_frame: str + glazing_gap: int + orientation: int + window_type: int + frame_factor: float + glazing_type: int + 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 + + +@dataclass +class SapFloorDimension: + floor: int + room_height: Measurement + total_floor_area: Measurement + party_wall_length: Union[Measurement, int] + heat_loss_perimeter: Measurement + floor_insulation: Optional[int] = None + floor_construction: Optional[int] = None + + +@dataclass +class SapRoomInRoof: + """Room-in-roof details. insulation and roof_room_connected removed in schema 21.0.0.""" + floor_area: Union[int, float] + construction_age_band: str + + +@dataclass +class SapAlternativeWall: + wall_area: float + wall_dry_lined: str + wall_construction: int + wall_insulation_type: int + wall_thickness_measured: str + wall_insulation_thickness: Optional[str] = None + + +@dataclass +class SapBuildingPart: + identifier: str + wall_dry_lined: str + floor_heat_loss: int + roof_construction: int + wall_construction: int + building_part_number: int + sap_floor_dimensions: List[SapFloorDimension] + wall_insulation_type: int + construction_age_band: str + party_wall_construction: Union[int, str] + wall_thickness_measured: str + roof_insulation_location: Union[int, str] + roof_insulation_thickness: Union[str, int] + sap_room_in_roof: Optional[SapRoomInRoof] = None + sap_alternative_wall_1: Optional[SapAlternativeWall] = None + sap_alternative_wall_2: Optional[SapAlternativeWall] = None + wall_thickness: Optional[int] = None + wall_insulation_thickness: Optional[str] = None + floor_insulation_thickness: Optional[str] = None + flat_roof_insulation_thickness: Optional[Union[str, int]] = None + + +@dataclass +class SapFlatDetails: + level: int + top_storey: str + flat_location: int + heat_loss_corridor: int + storey_count: Optional[int] = None + unheated_corridor_length: Optional[int] = None + + +@dataclass +class WindowsTransmissionDetails: + u_value: float + data_source: int + solar_transmittance: float + + +@dataclass +class ImprovementTexts: + improvement_description: str + improvement_summary: Optional[str] = None + + +@dataclass +class ImprovementDetails: + improvement_number: Optional[int] = None + improvement_texts: Optional[ImprovementTexts] = None + + +@dataclass +class SuggestedImprovement: + sequence: int + typical_saving: float + indicative_cost: Union[str, int] + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class RenewableHeatIncentive: + water_heating: int + space_heating_existing_dwelling: int + impact_of_loft_insulation: Optional[int] = None + impact_of_cavity_insulation: Optional[int] = None + impact_of_solid_wall_insulation: Optional[int] = None + + +@dataclass +class RdSapSchema21_0_0: + uprn: int + roofs: List[EnergyElement] + walls: List[EnergyElement] + floors: List[EnergyElement] + status: str + tenure: int + window: EnergyElement + lighting: EnergyElement + postcode: str + hot_water: EnergyElement + post_town: str + built_form: int + door_count: int + region_code: int + report_type: int + sap_heating: SapHeating + sap_version: float + sap_windows: List[SapWindow] + schema_type: str + uprn_source: str + country_code: str + main_heating: List[EnergyElement] + dwelling_type: str + language_code: int + pressure_test: int + property_type: int + address_line_1: str + assessment_type: str + completion_date: str + inspection_date: str + wet_rooms_count: int + extensions_count: int + measurement_type: int + total_floor_area: int + transaction_type: int + conservatory_type: int + heated_room_count: int + registration_date: str + sap_energy_source: SapEnergySource + secondary_heating: EnergyElement + sap_building_parts: List[SapBuildingPart] + open_chimneys_count: int + solar_water_heating: str + habitable_room_count: int + heating_cost_current: float + insulated_door_count: int + co2_emissions_current: float + energy_rating_average: int + energy_rating_current: int + lighting_cost_current: float + main_heating_controls: List[EnergyElement] + has_hot_water_cylinder: str + heating_cost_potential: float + hot_water_cost_current: float + insulated_door_u_value: float + mechanical_ventilation: int + percent_draughtproofed: int + suggested_improvements: List[SuggestedImprovement] + co2_emissions_potential: float + energy_rating_potential: int + lighting_cost_potential: float + schema_version_original: str + hot_water_cost_potential: float + renewable_heat_incentive: RenewableHeatIncentive + draughtproofed_door_count: int + mechanical_vent_duct_type: int + windows_transmission_details: WindowsTransmissionDetails + cfl_fixed_lighting_bulbs_count: int + energy_consumption_current: int + has_fixed_air_conditioning: str + multiple_glazed_proportion: int + calculation_software_version: str + energy_consumption_potential: int + environmental_impact_current: int + led_fixed_lighting_bulbs_count: int + mechanical_vent_duct_placement: int + mechanical_vent_duct_insulation: int + potential_energy_efficiency_band: str + pressure_test_certificate_number: int + mechanical_ventilation_index_number: int + co2_emissions_current_per_floor_area: int + current_energy_efficiency_band: str + environmental_impact_potential: int + low_energy_fixed_lighting_bulbs_count: int + mechanical_vent_duct_insulation_level: int + mechanical_vent_measured_installation: str + incandescent_fixed_lighting_bulbs_count: int + sap_flat_details: Optional[SapFlatDetails] = None + addendum: Optional[Addendum] = None + address_line_2: Optional[str] = None + has_heated_separate_conservatory: Optional[str] = None + fixed_lighting_outlets_count: Optional[int] = None + low_energy_fixed_lighting_outlets_count: Optional[int] = None diff --git a/datatypes/epc/schema/rdsap_schema_21_0_1.py b/datatypes/epc/schema/rdsap_schema_21_0_1.py new file mode 100644 index 00000000..046e4fec --- /dev/null +++ b/datatypes/epc/schema/rdsap_schema_21_0_1.py @@ -0,0 +1,340 @@ +from dataclasses import dataclass +from typing import List, Optional, Union + +from .common import DescriptionV1, Measurement + + +@dataclass +class EnergyElement: + # Descriptions revert to localised objects in schema 21.0.1 (were plain strings in 21.0.0) + description: DescriptionV1 + energy_efficiency_rating: int + environmental_efficiency_rating: int + + +@dataclass +class Addendum: + addendum_numbers: List[int] + stone_walls: Optional[str] = None + system_build: Optional[str] = None + + +@dataclass +class ShowerOutlet: + shower_wwhrs: int + shower_outlet_type: int + + +@dataclass +class ShowerOutlets: + shower_outlet: ShowerOutlet + + +@dataclass +class InstantaneousWwhrs: + """References WWHRS product index numbers (introduced in 21.0.0).""" + wwhrs_index_number1: Optional[int] = None + wwhrs_index_number2: Optional[int] = None + + +@dataclass +class MainHeatingDetail: + has_fghrs: str + main_fuel_type: int + heat_emitter_type: int + 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 + boiler_flue_type: Optional[int] = None + fan_flue_present: Optional[str] = None + boiler_ignition_type: Optional[int] = None + central_heating_pump_age: Optional[int] = None + main_heating_index_number: Optional[int] = None + sap_main_heating_code: Optional[int] = None + + +@dataclass +class SapHeating: + cylinder_size: int + water_heating_code: int + water_heating_fuel: int + instantaneous_wwhrs: InstantaneousWwhrs + main_heating_details: List[MainHeatingDetail] + immersion_heating_type: Union[int, str] + has_fixed_air_conditioning: str + shower_outlets: Optional[ShowerOutlets] = None + cylinder_insulation_type: Optional[int] = None + cylinder_thermostat: Optional[str] = None + secondary_fuel_type: Optional[int] = None + secondary_heating_type: Optional[int] = None + cylinder_insulation_thickness: Optional[int] = None + + +@dataclass +class PvBattery: + battery_capacity: float + + +@dataclass +class PvBatteries: + pv_battery: PvBattery + + +@dataclass +class WindTurbineDetails: + hub_height: float + rotor_diameter: float + + +@dataclass +class PhotovoltaicSupplyNoneOrNoDetails: + percent_roof_area: int + + +@dataclass +class PhotovoltaicSupply: + none_or_no_details: PhotovoltaicSupplyNoneOrNoDetails + + +@dataclass +class SapEnergySource: + mains_gas: str + meter_type: int + pv_connection: int + pv_battery_count: int + photovoltaic_supply: PhotovoltaicSupply + wind_turbines_count: int + wind_turbine_details: WindTurbineDetails + gas_smart_meter_present: str + is_dwelling_export_capable: str + wind_turbines_terrain_type: int + electricity_smart_meter_present: str + pv_batteries: Optional[PvBatteries] = None + + +@dataclass +class WindowTransmissionDetails: + u_value: float + data_source: int + solar_transmittance: float + + +@dataclass +class SapWindow: + pvc_frame: str + glazing_gap: int + orientation: int + window_type: int + frame_factor: float + glazing_type: int + 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 + + +@dataclass +class SapFloorDimension: + floor: int + room_height: Measurement + total_floor_area: Measurement + party_wall_length: Union[Measurement, int] + heat_loss_perimeter: Measurement + floor_insulation: Optional[int] = None + floor_construction: Optional[int] = None + + +@dataclass +class SapRoomInRoof: + floor_area: Union[int, float] + construction_age_band: str + + +@dataclass +class SapAlternativeWall: + wall_area: float + wall_dry_lined: str + wall_construction: int + wall_insulation_type: int + wall_thickness_measured: str + wall_insulation_thickness: Optional[str] = None + + +@dataclass +class SapBuildingPart: + identifier: str + wall_dry_lined: str + floor_heat_loss: int + roof_construction: int + wall_construction: int + building_part_number: int + sap_floor_dimensions: List[SapFloorDimension] + wall_insulation_type: int + construction_age_band: str + party_wall_construction: Union[int, str] + wall_thickness_measured: str + roof_insulation_location: Union[int, str] + roof_insulation_thickness: Union[str, int] + sap_room_in_roof: Optional[SapRoomInRoof] = None + sap_alternative_wall_1: Optional[SapAlternativeWall] = None + sap_alternative_wall_2: Optional[SapAlternativeWall] = None + wall_thickness: Optional[int] = None + wall_insulation_thickness: Optional[str] = None + floor_insulation_thickness: Optional[str] = None + flat_roof_insulation_thickness: Optional[Union[str, int]] = None + + +@dataclass +class SapFlatDetails: + level: int + top_storey: str + flat_location: int + heat_loss_corridor: int + storey_count: Optional[int] = None + # Changed from plain int in 21.0.0 to a Measurement object in 21.0.1 + unheated_corridor_length: Optional[Union[Measurement, int]] = None + + +@dataclass +class WindowsTransmissionDetails: + u_value: float + data_source: int + solar_transmittance: float + + +@dataclass +class ImprovementTexts: + improvement_description: str + improvement_summary: Optional[str] = None + + +@dataclass +class ImprovementDetails: + improvement_number: Optional[int] = None + improvement_texts: Optional[ImprovementTexts] = None + + +@dataclass +class SuggestedImprovement: + sequence: int + typical_saving: float + indicative_cost: Union[str, int] + improvement_type: str + improvement_details: ImprovementDetails + improvement_category: int + energy_performance_rating: int + environmental_impact_rating: int + + +@dataclass +class RenewableHeatIncentive: + water_heating: int + space_heating_existing_dwelling: int + impact_of_loft_insulation: Optional[int] = None + impact_of_cavity_insulation: Optional[int] = None + impact_of_solid_wall_insulation: Optional[int] = None + + +@dataclass +class RdSapSchema21_0_1: + uprn: int + roofs: List[EnergyElement] + walls: List[EnergyElement] + floors: List[EnergyElement] + status: str + tenure: int + window: EnergyElement + lighting: EnergyElement + postcode: str + hot_water: EnergyElement + post_town: str + built_form: int + door_count: int + region_code: int + report_type: int + sap_heating: SapHeating + sap_version: float + sap_windows: List[SapWindow] + schema_type: str + uprn_source: str + country_code: str + main_heating: List[EnergyElement] + # dwelling_type remains a plain string (not reverted to DescriptionV1) in 21.0.1 + dwelling_type: str + language_code: int + pressure_test: int + property_type: int + address_line_1: str + assessment_type: str + completion_date: str + inspection_date: str + wet_rooms_count: int + extensions_count: int + measurement_type: int + total_floor_area: int + transaction_type: int + conservatory_type: int + heated_room_count: int + registration_date: str + sap_energy_source: SapEnergySource + secondary_heating: EnergyElement + sap_building_parts: List[SapBuildingPart] + open_chimneys_count: int + solar_water_heating: str + habitable_room_count: int + heating_cost_current: float + insulated_door_count: int + co2_emissions_current: float + energy_rating_average: int + energy_rating_current: int + lighting_cost_current: float + main_heating_controls: List[EnergyElement] + has_hot_water_cylinder: str + heating_cost_potential: float + hot_water_cost_current: float + insulated_door_u_value: float + mechanical_ventilation: int + percent_draughtproofed: int + suggested_improvements: List[SuggestedImprovement] + co2_emissions_potential: float + energy_rating_potential: int + lighting_cost_potential: float + schema_version_original: str + hot_water_cost_potential: float + renewable_heat_incentive: RenewableHeatIncentive + draughtproofed_door_count: int + mechanical_vent_duct_type: int + windows_transmission_details: WindowsTransmissionDetails + cfl_fixed_lighting_bulbs_count: int + energy_consumption_current: int + has_fixed_air_conditioning: str + multiple_glazed_proportion: int + calculation_software_version: str + energy_consumption_potential: int + environmental_impact_current: int + led_fixed_lighting_bulbs_count: int + mechanical_vent_duct_placement: int + mechanical_vent_duct_insulation: int + potential_energy_efficiency_band: str + pressure_test_certificate_number: int + mechanical_ventilation_index_number: int + co2_emissions_current_per_floor_area: int + current_energy_efficiency_band: str + environmental_impact_potential: int + low_energy_fixed_lighting_bulbs_count: int + mechanical_vent_duct_insulation_level: int + mechanical_vent_measured_installation: str + incandescent_fixed_lighting_bulbs_count: int + sap_flat_details: Optional[SapFlatDetails] = None + addendum: Optional[Addendum] = None + address_line_2: Optional[str] = None + has_heated_separate_conservatory: Optional[str] = None + fixed_lighting_outlets_count: Optional[int] = None + low_energy_fixed_lighting_outlets_count: Optional[int] = None diff --git a/datatypes/epc/schema/tests/__init__.py b/datatypes/epc/schema/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/datatypes/epc/schema/tests/fixtures/17_0.json b/datatypes/epc/schema/tests/fixtures/17_0.json new file mode 100644 index 00000000..b17ca7f3 --- /dev/null +++ b/datatypes/epc/schema/tests/fixtures/17_0.json @@ -0,0 +1,218 @@ +{ + "uprn": 12457, + "roofs": [ + { + "description": {"value": "(another dwelling above)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "walls": [ + { + "description": {"value": "System built, with internal insulation", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "floors": [ + { + "description": {"value": "(another dwelling below)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "status": "entered", + "tenure": 2, + "window": { + "description": {"value": "Fully double glazed", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "lighting": { + "description": {"value": "Low energy lighting in 57% of fixed outlets", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "postcode": "PT5 4RZ", + "hot_water": { + "description": {"value": "Electric immersion, off-peak", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 2 + }, + "post_town": "POSTTOWN", + "built_form": 2, + "door_count": 2, + "glazed_area": 1, + "glazing_gap": "16+", + "region_code": 3, + "report_type": 2, + "sap_heating": { + "cylinder_size": 2, + "water_heating_code": 903, + "water_heating_fuel": 29, + "instantaneous_wwhrs": { + "rooms_with_bath_and_or_shower": 1, + "rooms_with_mixer_shower_no_bath": 0, + "rooms_with_bath_and_mixer_shower": 0 + }, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 29, + "heat_emitter_type": 0, + "emitter_temperature": "NA", + "main_heating_number": 1, + "main_heating_control": 2401, + "main_heating_category": 7, + "main_heating_fraction": 1, + "sap_main_heating_code": 402, + "main_heating_data_source": 2 + } + ], + "immersion_heating_type": 1, + "cylinder_insulation_type": 0, + "has_fixed_air_conditioning": "false" + }, + "sap_version": 9.92, + "schema_type": "RdSAP-Schema-17.0", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + { + "description": {"value": "Electric storage heaters", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 1 + } + ], + "dwelling_type": {"value": "Mid-floor flat", "language": "1"}, + "language_code": 1, + "property_type": 2, + "address_line_1": "42, Moria Mines Lane", + "assessment_type": "RdSAP", + "completion_date": "2016-01-12", + "inspection_date": "2016-01-12", + "extensions_count": 0, + "measurement_type": 1, + "sap_flat_details": { + "level": 2, + "top_storey": "N", + "flat_location": 1, + "heat_loss_corridor": 0 + }, + "total_floor_area": 55, + "transaction_type": 8, + "conservatory_type": 1, + "heated_room_count": 1, + "pvc_window_frames": "true", + "registration_date": "2016-01-12", + "sap_energy_source": { + "mains_gas": "N", + "meter_type": 1, + "photovoltaic_supply": { + "none_or_no_details": {"pv_connection": 0, "percent_roof_area": 0} + }, + "wind_turbines_count": 0, + "wind_turbines_terrain_type": 2 + }, + "secondary_heating": { + "description": {"value": "Portable electric heaters (assumed)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "lzc_energy_sources": [11], + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "wall_thickness": 240, + "floor_heat_loss": 6, + "roof_construction": 3, + "wall_construction": 8, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.4, "quantity": "metres"}, + "total_floor_area": {"value": 54.6, "quantity": "square metres"}, + "party_wall_length": {"value": 7.3, "quantity": "metres"}, + "heat_loss_perimeter": {"value": 23.3, "quantity": "metres"} + } + ], + "wall_insulation_type": 3, + "construction_age_band": "D", + "party_wall_construction": 0, + "wall_thickness_measured": "Y", + "roof_insulation_location": "ND", + "roof_insulation_thickness": "ND", + "wall_insulation_thickness": "50mm" + } + ], + "low_energy_lighting": 57, + "solar_water_heating": "N", + "habitable_room_count": 3, + "heating_cost_current": {"value": 214, "currency": "GBP"}, + "insulated_door_count": 0, + "co2_emissions_current": 3.9, + "energy_rating_average": 60, + "energy_rating_current": 66, + "lighting_cost_current": {"value": 61, "currency": "GBP"}, + "main_heating_controls": [ + { + "description": {"value": "Manual charge control", "language": "1"}, + "energy_efficiency_rating": 2, + "environmental_efficiency_rating": 2 + } + ], + "multiple_glazing_type": 3, + "open_fireplaces_count": 0, + "has_hot_water_cylinder": "true", + "heating_cost_potential": {"value": 216, "currency": "GBP"}, + "hot_water_cost_current": {"value": 396, "currency": "GBP"}, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": {"value": 158, "currency": "GBP"}, + "indicative_cost": "£15 - £30", + "improvement_type": "C", + "improvement_details": {"improvement_number": 1}, + "improvement_category": 5, + "energy_performance_rating": 74, + "environmental_impact_rating": 60 + } + ], + "co2_emissions_potential": 2.5, + "energy_rating_potential": 79, + "lighting_cost_potential": {"value": 42, "currency": "GBP"}, + "schema_version_original": "LIG-17.0", + "alternative_improvements": [ + { + "sequence": 1, + "typical_saving": {"value": 141, "currency": "GBP"}, + "improvement_type": "J2", + "improvement_details": {"improvement_number": 54}, + "improvement_category": 6, + "energy_performance_rating": 81, + "environmental_impact_rating": 96 + } + ], + "hot_water_cost_potential": {"value": 154, "currency": "GBP"}, + "renewable_heat_incentive": { + "water_heating": 4818, + "space_heating_existing_dwelling": 2415 + }, + "energy_consumption_current": 427, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "9.0.0", + "energy_consumption_potential": 267, + "environmental_impact_current": 48, + "fixed_lighting_outlets_count": 7, + "current_energy_efficiency_band": "D", + "environmental_impact_potential": 66, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "C", + "co2_emissions_current_per_floor_area": 72, + "low_energy_fixed_lighting_outlets_count": 4 +} diff --git a/datatypes/epc/schema/tests/fixtures/17_1.json b/datatypes/epc/schema/tests/fixtures/17_1.json new file mode 100644 index 00000000..ef0613d1 --- /dev/null +++ b/datatypes/epc/schema/tests/fixtures/17_1.json @@ -0,0 +1,243 @@ +{ + "uprn": 12457, + "roofs": [ + { + "description": {"value": "Pitched, 100 mm loft insulation", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + { + "description": {"value": "Pitched, insulated (assumed)", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "walls": [ + { + "description": {"value": "Cavity wall, filled cavity", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "floors": [ + { + "description": {"value": "Solid, no insulation (assumed)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "status": "entered", + "tenure": 1, + "window": { + "description": {"value": "Fully double glazed", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "lighting": { + "description": {"value": "Low energy lighting in 23% of fixed outlets", "language": "1"}, + "energy_efficiency_rating": 2, + "environmental_efficiency_rating": 2 + }, + "postcode": "PT42 5HL", + "hot_water": { + "description": {"value": "From main system", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "post_town": "POSTTOWN", + "built_form": 1, + "door_count": 4, + "glazed_area": 1, + "glazing_gap": "16+", + "region_code": 17, + "report_type": 2, + "sap_heating": { + "cylinder_size": 2, + "water_heating_code": 901, + "water_heating_fuel": 28, + "cylinder_thermostat": "Y", + "instantaneous_wwhrs": { + "rooms_with_bath_and_or_shower": 1, + "rooms_with_mixer_shower_no_bath": 0, + "rooms_with_bath_and_mixer_shower": 1 + }, + "secondary_fuel_type": 29, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 28, + "boiler_flue_type": 1, + "fan_flue_present": "N", + "heat_emitter_type": 1, + "emitter_temperature": "NA", + "main_heating_number": 1, + "main_heating_control": 2104, + "main_heating_category": 2, + "main_heating_fraction": 1, + "mcs_installed_heat_pump": "false", + "main_heating_data_source": 1, + "main_heating_index_number": 9049 + } + ], + "immersion_heating_type": "NA", + "secondary_heating_type": 691, + "cylinder_insulation_type": 1, + "has_fixed_air_conditioning": "false", + "cylinder_insulation_thickness": 12 + }, + "sap_version": 9.92, + "schema_type": "RdSAP-Schema-17.1", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + { + "description": {"value": "Boiler and radiators, oil", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + } + ], + "dwelling_type": {"value": "Detached house", "language": "1"}, + "language_code": 1, + "property_type": 0, + "address_line_1": "15, Hedge Lane", + "address_line_2": "Lower Moria", + "assessment_type": "RdSAP", + "completion_date": "2018-05-29", + "inspection_date": "2018-05-29", + "extensions_count": 1, + "measurement_type": 2, + "total_floor_area": 101, + "transaction_type": 1, + "conservatory_type": 1, + "heated_room_count": 7, + "pvc_window_frames": "true", + "registration_date": "2018-05-27", + "sap_energy_source": { + "mains_gas": "N", + "meter_type": 2, + "photovoltaic_supply": { + "none_or_no_details": {"pv_connection": 0, "percent_roof_area": 0} + }, + "wind_turbines_count": 0, + "wind_turbines_terrain_type": 2 + }, + "secondary_heating": { + "description": {"value": "Room heaters, electric", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "lzc_energy_sources": [11], + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "wall_thickness": 270, + "floor_heat_loss": 7, + "roof_construction": 4, + "wall_construction": 4, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.4, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 48.45, "quantity": "square metres"}, + "party_wall_length": {"value": 0, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 22.3, "quantity": "metres"} + } + ], + "wall_insulation_type": 2, + "construction_age_band": "G", + "party_wall_construction": "NA", + "wall_thickness_measured": "Y", + "roof_insulation_location": 2, + "roof_insulation_thickness": "100mm" + }, + { + "identifier": "Extension 1", + "wall_dry_lined": "N", + "wall_thickness": 260, + "floor_heat_loss": 7, + "roof_construction": 5, + "wall_construction": 4, + "building_part_number": 2, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.4, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 21.84, "quantity": "square metres"}, + "party_wall_length": 0, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 16.6, "quantity": "metres"} + } + ], + "wall_insulation_type": 4, + "construction_age_band": "H", + "party_wall_construction": "NA", + "wall_thickness_measured": "Y", + "roof_insulation_location": 4, + "roof_insulation_thickness": 0, + "wall_insulation_thickness": "NI" + } + ], + "low_energy_lighting": 23, + "solar_water_heating": "N", + "habitable_room_count": 7, + "heating_cost_current": {"value": 659, "currency": "GBP"}, + "insulated_door_count": 0, + "co2_emissions_current": 5.8, + "energy_rating_average": 60, + "energy_rating_current": 53, + "lighting_cost_current": {"value": 115, "currency": "GBP"}, + "main_heating_controls": [ + { + "description": {"value": "Programmer and room thermostat", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + } + ], + "multiple_glazing_type": 3, + "open_fireplaces_count": 0, + "has_hot_water_cylinder": "true", + "heating_cost_potential": {"value": 470, "currency": "GBP"}, + "hot_water_cost_current": {"value": 161, "currency": "GBP"}, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": {"value": 25, "currency": "GBP"}, + "indicative_cost": "£100 - £350", + "improvement_type": "A", + "improvement_details": {"improvement_number": 5}, + "improvement_category": 5, + "energy_performance_rating": 54, + "environmental_impact_rating": 47 + } + ], + "co2_emissions_potential": 2.7, + "energy_rating_potential": 78, + "lighting_cost_potential": {"value": 65, "currency": "GBP"}, + "schema_version_original": "LIG-17.1", + "hot_water_cost_potential": {"value": 77, "currency": "GBP"}, + "renewable_heat_incentive": { + "water_heating": 3301, + "impact_of_loft_insulation": -565, + "space_heating_existing_dwelling": 11351 + }, + "energy_consumption_current": 234, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "v92.0.1.1", + "energy_consumption_potential": 95, + "environmental_impact_current": 46, + "fixed_lighting_outlets_count": 13, + "current_energy_efficiency_band": "E", + "environmental_impact_potential": 72, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "C", + "co2_emissions_current_per_floor_area": 57, + "low_energy_fixed_lighting_outlets_count": 3 +} diff --git a/datatypes/epc/schema/tests/fixtures/18_0.json b/datatypes/epc/schema/tests/fixtures/18_0.json new file mode 100644 index 00000000..502ac329 --- /dev/null +++ b/datatypes/epc/schema/tests/fixtures/18_0.json @@ -0,0 +1,251 @@ +{ + "uprn": 12457, + "roofs": [ + { + "description": {"value": "Pitched, 100 mm loft insulation", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + { + "description": {"value": "Flat, insulated", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + { + "description": {"value": "Roof room(s), insulated (assumed)", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "walls": [ + { + "description": {"value": "Solid brick, as built, no insulation (assumed)", "language": "1"}, + "energy_efficiency_rating": 1, + "environmental_efficiency_rating": 1 + } + ], + "floors": [ + { + "description": {"value": "Solid, no insulation (assumed)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "status": "entered", + "tenure": 1, + "window": { + "description": {"value": "Fully double glazed", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "lighting": { + "description": {"value": "Low energy lighting in 67% of fixed outlets", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "postcode": "PT11 4RF", + "hot_water": { + "description": {"value": "From main system", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "post_town": "POSTTOWN", + "built_form": 4, + "door_count": 2, + "glazed_area": 1, + "glazing_gap": 12, + "region_code": 1, + "report_type": 2, + "sap_heating": { + "cylinder_size": 1, + "water_heating_code": 901, + "water_heating_fuel": 26, + "instantaneous_wwhrs": { + "rooms_with_bath_and_or_shower": 1, + "rooms_with_mixer_shower_no_bath": 0, + "rooms_with_bath_and_mixer_shower": 0 + }, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "heat_emitter_type": 1, + "emitter_temperature": 0, + "main_heating_number": 1, + "main_heating_control": 2106, + "main_heating_category": 2, + "main_heating_fraction": 1, + "central_heating_pump_age": 0, + "main_heating_data_source": 1, + "main_heating_index_number": 16137 + } + ], + "immersion_heating_type": "NA", + "has_fixed_air_conditioning": "false" + }, + "sap_version": 9.92, + "schema_type": "RdSAP-Schema-18.0", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + { + "description": {"value": "Boiler and radiators, mains gas", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "dwelling_type": {"value": "Mid-terrace house", "language": "1"}, + "language_code": 1, + "property_type": 0, + "address_line_1": "1, Bagshot Lane", + "address_line_2": "Village", + "assessment_type": "RdSAP", + "completion_date": "2017-03-19", + "inspection_date": "2017-03-19", + "extensions_count": 1, + "measurement_type": 1, + "total_floor_area": 93, + "transaction_type": 5, + "conservatory_type": 1, + "heated_room_count": 5, + "pvc_window_frames": "true", + "registration_date": "2017-03-19", + "sap_energy_source": { + "mains_gas": "Y", + "meter_type": 1, + "photovoltaic_supply": { + "none_or_no_details": {"pv_connection": 0, "percent_roof_area": 0} + }, + "wind_turbines_count": 0, + "wind_turbines_terrain_type": 2 + }, + "secondary_heating": { + "description": {"value": "None", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "lzc_energy_sources": [11], + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "wall_thickness": 330, + "floor_heat_loss": 7, + "sap_room_in_roof": { + "floor_area": {"value": 9, "quantity": "square metres"}, + "insulation": "AB", + "roof_room_connected": "N", + "construction_age_band": "G" + }, + "roof_construction": 4, + "wall_construction": 3, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.4, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 29.12, "quantity": "square metres"}, + "party_wall_length": {"value": 11.2, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 5.2, "quantity": "metres"} + } + ], + "wall_insulation_type": 4, + "construction_age_band": "C", + "party_wall_construction": 0, + "wall_thickness_measured": "Y", + "roof_insulation_location": 2, + "roof_insulation_thickness": "100mm", + "wall_insulation_thickness": "NI" + }, + { + "identifier": "Extension", + "wall_dry_lined": "N", + "wall_thickness": 290, + "floor_heat_loss": 7, + "roof_construction": 1, + "wall_construction": 4, + "building_part_number": 2, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.4, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 15.6, "quantity": "square metres"}, + "party_wall_length": {"value": 6, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 5.2, "quantity": "metres"} + } + ], + "wall_insulation_type": 4, + "construction_age_band": "G", + "party_wall_construction": 0, + "wall_thickness_measured": "Y", + "roof_insulation_location": 6, + "roof_insulation_thickness": "NI", + "wall_insulation_thickness": "NI", + "flat_roof_insulation_thickness": "NI" + } + ], + "low_energy_lighting": 67, + "solar_water_heating": "N", + "habitable_room_count": 5, + "heating_cost_current": {"value": 619, "currency": "GBP"}, + "insulated_door_count": 0, + "co2_emissions_current": 3.4, + "energy_rating_average": 60, + "energy_rating_current": 69, + "lighting_cost_current": {"value": 81, "currency": "GBP"}, + "main_heating_controls": [ + { + "description": {"value": "Programmer, room thermostat and TRVs", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "multiple_glazing_type": 3, + "open_fireplaces_count": 0, + "has_hot_water_cylinder": "false", + "heating_cost_potential": {"value": 534, "currency": "GBP"}, + "hot_water_cost_current": {"value": 100, "currency": "GBP"}, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": {"value": 88, "currency": "GBP"}, + "indicative_cost": "£4,000 - £14,000", + "improvement_type": "Q", + "improvement_details": {"improvement_number": 7}, + "improvement_category": 5, + "energy_performance_rating": 72, + "environmental_impact_rating": 71 + } + ], + "co2_emissions_potential": 1.7, + "energy_rating_potential": 85, + "lighting_cost_potential": {"value": 61, "currency": "GBP"}, + "schema_version_original": "LIG-17.0", + "hot_water_cost_potential": {"value": 68, "currency": "GBP"}, + "renewable_heat_incentive": { + "water_heating": 2087, + "impact_of_loft_insulation": -214, + "impact_of_solid_wall_insulation": -1864, + "space_heating_existing_dwelling": 10483 + }, + "energy_consumption_current": 230, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "2.0.0.0", + "energy_consumption_potential": 115, + "environmental_impact_current": 66, + "fixed_lighting_outlets_count": 9, + "current_energy_efficiency_band": "C", + "environmental_impact_potential": 83, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "B", + "co2_emissions_current_per_floor_area": 41, + "low_energy_fixed_lighting_outlets_count": 6 +} diff --git a/datatypes/epc/schema/tests/fixtures/19_0.json b/datatypes/epc/schema/tests/fixtures/19_0.json new file mode 100644 index 00000000..f9995286 --- /dev/null +++ b/datatypes/epc/schema/tests/fixtures/19_0.json @@ -0,0 +1,213 @@ +{ + "uprn": 12457, + "roofs": [ + { + "description": {"value": "Pitched, 150 mm loft insulation", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "walls": [ + { + "description": {"value": "Cavity wall, filled cavity", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + } + ], + "floors": [ + { + "description": {"value": "Solid, no insulation (assumed)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + } + ], + "status": "entered", + "tenure": 3, + "window": { + "description": {"value": "Fully double glazed", "language": "1"}, + "energy_efficiency_rating": 3, + "environmental_efficiency_rating": 3 + }, + "lighting": { + "description": {"value": "Low energy lighting in 87% of fixed outlets", "language": "1"}, + "energy_efficiency_rating": 5, + "environmental_efficiency_rating": 5 + }, + "postcode": "A1 1AA", + "hot_water": { + "description": {"value": "From main system", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + }, + "post_town": "Town", + "built_form": 2, + "door_count": 1, + "glazed_area": 1, + "region_code": 19, + "report_type": 2, + "sap_heating": { + "cylinder_size": 1, + "water_heating_code": 901, + "water_heating_fuel": 26, + "instantaneous_wwhrs": { + "rooms_with_bath_and_or_shower": 1, + "rooms_with_mixer_shower_no_bath": 0, + "rooms_with_bath_and_mixer_shower": 0 + }, + "secondary_fuel_type": 9, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "heat_emitter_type": 1, + "emitter_temperature": 0, + "main_heating_number": 1, + "main_heating_control": 2106, + "main_heating_category": 2, + "main_heating_fraction": 1, + "central_heating_pump_age": 0, + "main_heating_data_source": 1, + "main_heating_index_number": 15274 + } + ], + "immersion_heating_type": "NA", + "secondary_heating_type": 631, + "has_fixed_air_conditioning": "false" + }, + "sap_version": 9.94, + "schema_type": "RdSAP-Schema-19.0", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + { + "description": {"value": "Boiler and radiators, mains gas", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "dwelling_type": {"value": "Semi-detached house", "language": "1"}, + "language_code": 1, + "property_type": 0, + "address_line_1": "15, Address Lane", + "address_line_2": "New Town", + "assessment_type": "RdSAP", + "completion_date": "2020-06-04", + "inspection_date": "2020-06-03", + "extensions_count": 1, + "measurement_type": 1, + "total_floor_area": 94, + "transaction_type": 8, + "conservatory_type": 2, + "heated_room_count": 5, + "pvc_window_frames": "false", + "registration_date": "2020-06-04", + "sap_energy_source": { + "mains_gas": "Y", + "meter_type": 3, + "photovoltaic_supply": { + "none_or_no_details": {"pv_connection": 0, "percent_roof_area": 0} + }, + "wind_turbines_count": 0, + "wind_turbines_terrain_type": 2 + }, + "secondary_heating": { + "description": {"value": "Room heaters, dual fuel (mineral and wood)", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "lzc_energy_sources": [11, 9], + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "wall_thickness": 300, + "floor_heat_loss": 7, + "roof_construction": 4, + "wall_construction": 4, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.47, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 39.91, "quantity": "square metres"}, + "party_wall_length": {"value": 8.81, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 13.65, "quantity": "metres"} + } + ], + "wall_insulation_type": 2, + "construction_age_band": "D", + "party_wall_construction": 1, + "wall_thickness_measured": "Y", + "roof_insulation_location": 2, + "roof_insulation_thickness": "150mm", + "wall_insulation_thickness": "NI", + "floor_insulation_thickness": "NI" + } + ], + "low_energy_lighting": 87, + "solar_water_heating": "N", + "habitable_room_count": 5, + "heating_cost_current": {"value": 666, "currency": "GBP"}, + "insulated_door_count": 0, + "co2_emissions_current": 3.8, + "energy_rating_average": 60, + "energy_rating_current": 66, + "lighting_cost_current": {"value": 81, "currency": "GBP"}, + "main_heating_controls": [ + { + "description": {"value": "Programmer, room thermostat and TRVs", "language": "1"}, + "energy_efficiency_rating": 4, + "environmental_efficiency_rating": 4 + } + ], + "multiple_glazing_type": 3, + "open_fireplaces_count": 0, + "has_hot_water_cylinder": "false", + "heating_cost_potential": {"value": 615, "currency": "GBP"}, + "hot_water_cost_current": {"value": 107, "currency": "GBP"}, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": {"value": 51, "currency": "GBP"}, + "indicative_cost": "£4,000 - £6,000", + "improvement_type": "W2", + "improvement_details": {"improvement_number": 58}, + "improvement_category": 5, + "energy_performance_rating": 68, + "environmental_impact_rating": 65 + } + ], + "co2_emissions_potential": 2.4, + "energy_rating_potential": 79, + "lighting_cost_potential": {"value": 81, "currency": "GBP"}, + "schema_version_original": "LIG-19.0", + "hot_water_cost_potential": {"value": 74, "currency": "GBP"}, + "renewable_heat_incentive": { + "water_heating": 2207, + "impact_of_loft_insulation": -394, + "space_heating_existing_dwelling": 9825 + }, + "energy_consumption_current": 222, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "2.1.1.0", + "energy_consumption_potential": 137, + "environmental_impact_current": 62, + "fixed_lighting_outlets_count": 15, + "windows_transmission_details": { + "u_value": 3.1, + "data_source": 2, + "solar_transmittance": 0.76 + }, + "current_energy_efficiency_band": "D", + "environmental_impact_potential": 75, + "has_heated_separate_conservatory": "false", + "potential_energy_efficiency_band": "C", + "co2_emissions_current_per_floor_area": 41, + "low_energy_fixed_lighting_outlets_count": 13 +} diff --git a/datatypes/epc/schema/tests/fixtures/20_0_0.json b/datatypes/epc/schema/tests/fixtures/20_0_0.json new file mode 100644 index 00000000..397c2758 --- /dev/null +++ b/datatypes/epc/schema/tests/fixtures/20_0_0.json @@ -0,0 +1,225 @@ +{ + "uprn": 12457, + "roofs": [ + {"description": "Pitched, 25 mm loft insulation", "energy_efficiency_rating": 2, "environmental_efficiency_rating": 2}, + {"description": "Pitched, 250 mm loft insulation", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "walls": [ + {"description": "Solid brick, as built, no insulation (assumed)", "energy_efficiency_rating": 1, "environmental_efficiency_rating": 1}, + {"description": "Cavity wall, as built, insulated (assumed)", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "floors": [ + {"description": "Suspended, no insulation (assumed)", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0}, + {"description": "Solid, insulated (assumed)", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0} + ], + "status": "entered", + "tenure": 1, + "window": {"description": "Fully double glazed", "energy_efficiency_rating": 3, "environmental_efficiency_rating": 3}, + "addendum": { + "stone_walls": "true", + "system_build": "true", + "addendum_numbers": [1, 8] + }, + "lighting": {"description": "Low energy lighting in 50% of fixed outlets", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "postcode": "A0 0AA", + "hot_water": {"description": "From main system", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "post_town": "Whitbury", + "built_form": 2, + "door_count": 2, + "glazed_area": 1, + "region_code": 1, + "report_type": 2, + "sap_heating": { + "cylinder_size": 1, + "water_heating_code": 901, + "water_heating_fuel": 26, + "instantaneous_wwhrs": { + "rooms_with_bath_and_or_shower": 1, + "rooms_with_mixer_shower_no_bath": 0, + "rooms_with_bath_and_mixer_shower": 0 + }, + "secondary_fuel_type": 25, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "fan_flue_present": "N", + "heat_emitter_type": 1, + "emitter_temperature": 0, + "main_heating_number": 1, + "main_heating_control": 2106, + "main_heating_category": 2, + "main_heating_fraction": 1, + "sap_main_heating_code": 101, + "central_heating_pump_age": 0, + "main_heating_data_source": 1, + "main_heating_index_number": 17507 + } + ], + "immersion_heating_type": "NA", + "has_fixed_air_conditioning": "false" + }, + "sap_version": 9.8, + "sap_windows": [ + {"orientation": 1, "window_area": 200.1, "window_type": 2, "glazing_type": 1, "window_location": 0}, + {"orientation": 2, "window_area": 180.2, "window_type": 1, "glazing_type": 2, "window_location": 1} + ], + "schema_type": "RdSAP-Schema-20.0.0", + "uprn_source": "Energy Assessor", + "country_code": "EAW", + "main_heating": [ + {"description": "Boiler and radiators, anthracite", "energy_efficiency_rating": 3, "environmental_efficiency_rating": 1}, + {"description": "Boiler and radiators, mains gas", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "dwelling_type": "Mid-terrace house", + "language_code": 1, + "property_type": 0, + "address_line_1": "1 Some Street", + "assessment_type": "RdSAP", + "completion_date": "2020-05-04", + "inspection_date": "2020-05-04", + "extensions_count": 0, + "measurement_type": 1, + "sap_flat_details": { + "level": 1, + "top_storey": "N", + "storey_count": 3, + "flat_location": 1, + "heat_loss_corridor": 2, + "unheated_corridor_length": 10 + }, + "total_floor_area": 55, + "transaction_type": 1, + "conservatory_type": 1, + "heated_room_count": 5, + "registration_date": "2020-05-04", + "sap_energy_source": { + "mains_gas": "Y", + "meter_type": 2, + "photovoltaic_supply": { + "none_or_no_details": {"pv_connection": 0, "percent_roof_area": 50} + }, + "wind_turbines_count": 0, + "wind_turbines_terrain_type": 2 + }, + "secondary_heating": {"description": "Room heaters, electric", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0}, + "lzc_energy_sources": [11], + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "floor_heat_loss": 7, + "sap_room_in_roof": { + "floor_area": 100, + "insulation": "AB", + "roof_room_connected": "N", + "construction_age_band": "B" + }, + "roof_construction": 4, + "wall_construction": 4, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.45, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 45.82, "quantity": "square metres"}, + "party_wall_length": {"value": 7.9, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 19.5, "quantity": "metres"} + } + ], + "wall_insulation_type": 2, + "construction_age_band": "K", + "party_wall_construction": 0, + "wall_thickness_measured": "N", + "roof_insulation_location": 2, + "roof_insulation_thickness": "200mm", + "wall_insulation_thickness": "NI", + "floor_insulation_thickness": "NI" + } + ], + "low_energy_lighting": 100, + "solar_water_heating": "N", + "habitable_room_count": 5, + "heating_cost_current": 365.98, + "insulated_door_count": 2, + "co2_emissions_current": 2.4, + "energy_rating_average": 60, + "energy_rating_current": 50, + "lighting_cost_current": 123.45, + "main_heating_controls": [ + {"description": "Programmer, room thermostat and TRVs", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + {"description": "Time and temperature zone control", "energy_efficiency_rating": 5, "environmental_efficiency_rating": 5} + ], + "multiple_glazing_type": 2, + "open_fireplaces_count": 0, + "heating_cost_potential": 250.34, + "hot_water_cost_current": 200.4, + "insulated_door_u_value": 3, + "mechanical_ventilation": 0, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": 360, + "indicative_cost": "£100 - £350", + "improvement_type": "Z3", + "improvement_details": {"improvement_number": 5}, + "improvement_category": 6, + "energy_performance_rating": 50, + "environmental_impact_rating": 50 + }, + { + "sequence": 2, + "typical_saving": 99, + "indicative_cost": 2000, + "improvement_type": "Z2", + "improvement_details": {"improvement_number": 1}, + "improvement_category": 2, + "energy_performance_rating": 60, + "environmental_impact_rating": 64 + }, + { + "sequence": 3, + "typical_saving": 99, + "indicative_cost": 1000, + "improvement_type": "Z2", + "improvement_details": { + "improvement_texts": { + "improvement_summary": "An improvement summary", + "improvement_description": "An improvement desc" + } + }, + "improvement_category": 2, + "energy_performance_rating": 60, + "environmental_impact_rating": 64 + } + ], + "co2_emissions_potential": 1.4, + "energy_rating_potential": 72, + "lighting_cost_potential": 84.23, + "schema_version_original": "SAP-19.0", + "hot_water_cost_potential": 180.43, + "renewable_heat_incentive": { + "water_heating": 2285, + "impact_of_loft_insulation": -2114, + "impact_of_cavity_insulation": -122, + "impact_of_solid_wall_insulation": -3560, + "space_heating_existing_dwelling": 13120 + }, + "energy_consumption_current": 230, + "multiple_glazed_proportion": 100, + "calculation_software_version": "13.05r16", + "energy_consumption_potential": 88, + "environmental_impact_current": 52, + "fixed_lighting_outlets_count": 16, + "windows_transmission_details": {"u_value": 2, "data_source": 2, "solar_transmittance": 0.72}, + "multiple_glazed_proportion_nr": "NR", + "current_energy_efficiency_band": "E", + "environmental_impact_potential": 74, + "potential_energy_efficiency_band": "C", + "co2_emissions_current_per_floor_area": 20, + "low_energy_fixed_lighting_outlets_count": 16 +} diff --git a/datatypes/epc/schema/tests/fixtures/21_0_0.json b/datatypes/epc/schema/tests/fixtures/21_0_0.json new file mode 100644 index 00000000..6781ac6e --- /dev/null +++ b/datatypes/epc/schema/tests/fixtures/21_0_0.json @@ -0,0 +1,245 @@ +{ + "uprn": 12457, + "roofs": [ + {"description": "Pitched, 25 mm loft insulation", "energy_efficiency_rating": 2, "environmental_efficiency_rating": 2}, + {"description": "Pitched, 250 mm loft insulation", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "walls": [ + {"description": "Solid brick, as built, no insulation (assumed)", "energy_efficiency_rating": 1, "environmental_efficiency_rating": 1}, + {"description": "Cavity wall, as built, insulated (assumed)", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "floors": [ + {"description": "Suspended, no insulation (assumed)", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0}, + {"description": "Solid, insulated (assumed)", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0} + ], + "status": "entered", + "tenure": 1, + "window": {"description": "Fully double glazed", "energy_efficiency_rating": 3, "environmental_efficiency_rating": 3}, + "addendum": {"stone_walls": "true", "system_build": "true", "addendum_numbers": [1, 8]}, + "lighting": {"description": "Low energy lighting in 50% of fixed outlets", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "postcode": "A0 0AA", + "hot_water": {"description": "From main system", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "post_town": "Whitbury", + "built_form": 2, + "door_count": 3, + "region_code": 1, + "report_type": 2, + "sap_heating": { + "cylinder_size": 1, + "shower_outlets": { + "shower_outlet": {"shower_wwhrs": 1, "shower_outlet_type": 1} + }, + "water_heating_code": 901, + "water_heating_fuel": 26, + "instantaneous_wwhrs": {"wwhrs_index_number1": 1, "wwhrs_index_number2": 2}, + "secondary_fuel_type": 25, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "fan_flue_present": "N", + "heat_emitter_type": 1, + "emitter_temperature": 0, + "main_heating_number": 1, + "boiler_ignition_type": 1, + "main_heating_control": 2106, + "main_heating_category": 2, + "main_heating_fraction": 1, + "sap_main_heating_code": 101, + "central_heating_pump_age": 0, + "main_heating_data_source": 1, + "main_heating_index_number": 17507 + } + ], + "immersion_heating_type": "NA", + "has_fixed_air_conditioning": "false" + }, + "sap_version": 10.2, + "sap_windows": [ + { + "pvc_frame": "false", + "glazing_gap": 6, + "orientation": 1, + "window_type": 2, + "frame_factor": 1.0, + "glazing_type": 14, + "window_width": 1.2, + "window_height": 2.0, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 6, + "permanent_shutters_present": "N", + "window_transmission_details": {"u_value": 1.0, "data_source": 2, "solar_transmittance": 1.0}, + "permanent_shutters_insulated": "N" + } + ], + "schema_type": "RdSAP-Schema-21.0.0", + "uprn_source": "Energy Assessor", + "country_code": "ENG", + "main_heating": [ + {"description": "Boiler and radiators, anthracite", "energy_efficiency_rating": 3, "environmental_efficiency_rating": 1}, + {"description": "Boiler and radiators, mains gas", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "dwelling_type": "Mid-terrace house", + "language_code": 1, + "pressure_test": 6, + "property_type": 0, + "address_line_1": "1 Some Street", + "assessment_type": "RdSAP", + "completion_date": "2023-12-01", + "inspection_date": "2023-12-01", + "wet_rooms_count": 0, + "extensions_count": 0, + "measurement_type": 1, + "sap_flat_details": { + "level": 1, + "top_storey": "N", + "storey_count": 3, + "flat_location": 1, + "heat_loss_corridor": 2, + "unheated_corridor_length": 10 + }, + "total_floor_area": 55, + "transaction_type": 16, + "conservatory_type": 1, + "heated_room_count": 5, + "registration_date": "2023-12-01", + "sap_energy_source": { + "mains_gas": "Y", + "meter_type": 2, + "pv_batteries": {"pv_battery": {"battery_capacity": 5}}, + "pv_connection": 0, + "pv_battery_count": 1, + "photovoltaic_supply": {"none_or_no_details": {"percent_roof_area": 0}}, + "wind_turbines_count": 0, + "wind_turbine_details": {"hub_height": 0, "rotor_diameter": 0}, + "gas_smart_meter_present": "false", + "is_dwelling_export_capable": "false", + "wind_turbines_terrain_type": 4, + "electricity_smart_meter_present": "true" + }, + "secondary_heating": {"description": "Room heaters, electric", "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0}, + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "floor_heat_loss": 7, + "sap_room_in_roof": {"floor_area": 100, "construction_age_band": "B"}, + "roof_construction": 4, + "wall_construction": 4, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.45, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 45.82, "quantity": "square metres"}, + "party_wall_length": {"value": 7.9, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 19.5, "quantity": "metres"} + } + ], + "wall_insulation_type": 2, + "construction_age_band": "M", + "sap_alternative_wall_1": { + "wall_area": 10.4, + "wall_dry_lined": "N", + "wall_construction": 4, + "wall_insulation_type": 2, + "wall_thickness_measured": "N" + }, + "sap_alternative_wall_2": { + "wall_area": 10.8, + "wall_dry_lined": "N", + "wall_construction": 4, + "wall_insulation_type": 2, + "wall_thickness_measured": "N" + }, + "party_wall_construction": 0, + "wall_thickness_measured": "N", + "roof_insulation_location": 2, + "roof_insulation_thickness": "200mm", + "wall_insulation_thickness": "NI", + "floor_insulation_thickness": "NI" + } + ], + "open_chimneys_count": 1, + "solar_water_heating": "N", + "habitable_room_count": 5, + "heating_cost_current": 365.98, + "insulated_door_count": 2, + "co2_emissions_current": 2.4, + "energy_rating_average": 60, + "energy_rating_current": 50, + "lighting_cost_current": 123.45, + "main_heating_controls": [ + {"description": "Programmer, room thermostat and TRVs", "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + {"description": "Time and temperature zone control", "energy_efficiency_rating": 5, "environmental_efficiency_rating": 5} + ], + "has_hot_water_cylinder": "true", + "heating_cost_potential": 250.34, + "hot_water_cost_current": 200.4, + "insulated_door_u_value": 3, + "mechanical_ventilation": 6, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": 360, + "indicative_cost": "£100 - £350", + "improvement_type": "Z3", + "improvement_details": {"improvement_number": 5}, + "improvement_category": 6, + "energy_performance_rating": 50, + "environmental_impact_rating": 50 + }, + { + "sequence": 3, + "typical_saving": 99, + "indicative_cost": 1000, + "improvement_type": "Z2", + "improvement_details": { + "improvement_texts": {"improvement_description": "Improvement desc"} + }, + "improvement_category": 2, + "energy_performance_rating": 60, + "environmental_impact_rating": 64 + } + ], + "co2_emissions_potential": 1.4, + "energy_rating_potential": 72, + "lighting_cost_potential": 84.23, + "schema_version_original": "SAP-19.0", + "hot_water_cost_potential": 180.43, + "renewable_heat_incentive": { + "water_heating": 2285, + "impact_of_loft_insulation": -2114, + "impact_of_cavity_insulation": -122, + "impact_of_solid_wall_insulation": -3560, + "space_heating_existing_dwelling": 13120 + }, + "draughtproofed_door_count": 1, + "mechanical_vent_duct_type": 3, + "energy_consumption_current": 230, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "13.05r16", + "energy_consumption_potential": 88, + "environmental_impact_current": 52, + "windows_transmission_details": {"u_value": 2, "data_source": 2, "solar_transmittance": 0.72}, + "cfl_fixed_lighting_bulbs_count": 5, + "current_energy_efficiency_band": "E", + "environmental_impact_potential": 74, + "led_fixed_lighting_bulbs_count": 10, + "mechanical_vent_duct_placement": 2, + "mechanical_vent_duct_insulation": 2, + "potential_energy_efficiency_band": "C", + "pressure_test_certificate_number": 0, + "mechanical_ventilation_index_number": 12, + "co2_emissions_current_per_floor_area": 20, + "low_energy_fixed_lighting_bulbs_count": 16, + "mechanical_vent_duct_insulation_level": 2, + "mechanical_vent_measured_installation": "false", + "incandescent_fixed_lighting_bulbs_count": 5 +} diff --git a/datatypes/epc/schema/tests/fixtures/21_0_1.json b/datatypes/epc/schema/tests/fixtures/21_0_1.json new file mode 100644 index 00000000..45361227 --- /dev/null +++ b/datatypes/epc/schema/tests/fixtures/21_0_1.json @@ -0,0 +1,222 @@ +{ + "uprn": 12457, + "roofs": [ + {"description": {"value": "Pitched, 25 mm loft insulation", "language": "1"}, "energy_efficiency_rating": 2, "environmental_efficiency_rating": 2}, + {"description": {"value": "Pitched, 250 mm loft insulation", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "walls": [ + {"description": {"value": "Solid brick, as built, no insulation (assumed)", "language": "1"}, "energy_efficiency_rating": 1, "environmental_efficiency_rating": 1}, + {"description": {"value": "Cavity wall, as built, insulated (assumed)", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "floors": [ + {"description": {"value": "Suspended, no insulation (assumed)", "language": "1"}, "energy_efficiency_rating": 0, "environmental_efficiency_rating": 0} + ], + "status": "entered", + "tenure": 1, + "window": {"description": {"value": "Fully double glazed", "language": "1"}, "energy_efficiency_rating": 3, "environmental_efficiency_rating": 3}, + "addendum": {"stone_walls": "true", "system_build": "true", "addendum_numbers": [1, 13]}, + "lighting": {"description": {"value": "Low energy lighting in 50% of fixed outlets", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "postcode": "A0 0AA", + "hot_water": {"description": {"value": "From main system", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + "post_town": "Whitbury", + "built_form": 2, + "door_count": 3, + "region_code": 1, + "report_type": 2, + "sap_heating": { + "cylinder_size": 1, + "shower_outlets": {"shower_outlet": {"shower_wwhrs": 1, "shower_outlet_type": 1}}, + "water_heating_code": 901, + "water_heating_fuel": 26, + "instantaneous_wwhrs": {"wwhrs_index_number1": 1, "wwhrs_index_number2": 2}, + "secondary_fuel_type": 25, + "main_heating_details": [ + { + "has_fghrs": "N", + "main_fuel_type": 26, + "boiler_flue_type": 2, + "fan_flue_present": "N", + "heat_emitter_type": 1, + "emitter_temperature": 0, + "main_heating_number": 1, + "boiler_ignition_type": 1, + "main_heating_control": 2106, + "main_heating_category": 2, + "main_heating_fraction": 1, + "sap_main_heating_code": 101, + "central_heating_pump_age": 0, + "main_heating_data_source": 1, + "main_heating_index_number": 17507 + } + ], + "immersion_heating_type": "NA", + "has_fixed_air_conditioning": "false" + }, + "sap_version": 10.2, + "sap_windows": [ + { + "pvc_frame": "false", + "glazing_gap": 6, + "orientation": 1, + "window_type": 2, + "frame_factor": 1.0, + "glazing_type": 14, + "window_width": 1.2, + "window_height": 2.0, + "draught_proofed": "true", + "window_location": 0, + "window_wall_type": 6, + "permanent_shutters_present": "N", + "window_transmission_details": {"u_value": 1.0, "data_source": 2, "solar_transmittance": 1.0}, + "permanent_shutters_insulated": "N" + } + ], + "schema_type": "RdSAP-Schema-21.0.1", + "uprn_source": "Energy Assessor", + "country_code": "ENG", + "main_heating": [ + {"description": {"value": "Boiler and radiators, anthracite", "language": "1"}, "energy_efficiency_rating": 3, "environmental_efficiency_rating": 1}, + {"description": {"value": "Boiler and radiators, mains gas", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4} + ], + "dwelling_type": "Mid-terrace house", + "language_code": 1, + "pressure_test": 6, + "property_type": 0, + "address_line_1": "1 Some Street", + "assessment_type": "RdSAP", + "completion_date": "2025-04-04", + "inspection_date": "2025-04-04", + "wet_rooms_count": 0, + "extensions_count": 0, + "measurement_type": 1, + "sap_flat_details": { + "level": 1, + "top_storey": "N", + "storey_count": 3, + "flat_location": 1, + "heat_loss_corridor": 2, + "unheated_corridor_length": {"value": 10, "quantity": "metres"} + }, + "total_floor_area": 55, + "transaction_type": 16, + "conservatory_type": 1, + "heated_room_count": 5, + "registration_date": "2025-04-04", + "sap_energy_source": { + "mains_gas": "Y", + "meter_type": 2, + "pv_batteries": {"pv_battery": {"battery_capacity": 5}}, + "pv_connection": 0, + "pv_battery_count": 1, + "photovoltaic_supply": {"none_or_no_details": {"percent_roof_area": 0}}, + "wind_turbines_count": 0, + "wind_turbine_details": {"hub_height": 0, "rotor_diameter": 0}, + "gas_smart_meter_present": "false", + "is_dwelling_export_capable": "false", + "wind_turbines_terrain_type": 4, + "electricity_smart_meter_present": "true" + }, + "secondary_heating": { + "description": {"value": "Room heaters, electric", "language": "1"}, + "energy_efficiency_rating": 0, + "environmental_efficiency_rating": 0 + }, + "sap_building_parts": [ + { + "identifier": "Main Dwelling", + "wall_dry_lined": "N", + "floor_heat_loss": 7, + "sap_room_in_roof": {"floor_area": 100, "construction_age_band": "B"}, + "roof_construction": 4, + "wall_construction": 4, + "building_part_number": 1, + "sap_floor_dimensions": [ + { + "floor": 0, + "room_height": {"value": 2.45, "quantity": "metres"}, + "floor_insulation": 1, + "total_floor_area": {"value": 45.82, "quantity": "square metres"}, + "party_wall_length": {"value": 7.9, "quantity": "metres"}, + "floor_construction": 1, + "heat_loss_perimeter": {"value": 19.5, "quantity": "metres"} + } + ], + "wall_insulation_type": 2, + "construction_age_band": "M", + "sap_alternative_wall_1": {"wall_area": 10.4, "wall_dry_lined": "N", "wall_construction": 4, "wall_insulation_type": 2, "wall_thickness_measured": "N"}, + "sap_alternative_wall_2": {"wall_area": 10.8, "wall_dry_lined": "N", "wall_construction": 4, "wall_insulation_type": 2, "wall_thickness_measured": "N"}, + "party_wall_construction": 0, + "wall_thickness_measured": "N", + "roof_insulation_location": 2, + "roof_insulation_thickness": "200mm", + "wall_insulation_thickness": "NI", + "floor_insulation_thickness": "NI" + } + ], + "open_chimneys_count": 1, + "solar_water_heating": "N", + "habitable_room_count": 5, + "heating_cost_current": 365.98, + "insulated_door_count": 2, + "co2_emissions_current": 2.4, + "energy_rating_average": 60, + "energy_rating_current": 50, + "lighting_cost_current": 123.45, + "main_heating_controls": [ + {"description": {"value": "Programmer, room thermostat and TRVs", "language": "1"}, "energy_efficiency_rating": 4, "environmental_efficiency_rating": 4}, + {"description": {"value": "Time and temperature zone control", "language": "1"}, "energy_efficiency_rating": 5, "environmental_efficiency_rating": 5} + ], + "has_hot_water_cylinder": "true", + "heating_cost_potential": 250.34, + "hot_water_cost_current": 200.4, + "insulated_door_u_value": 3, + "mechanical_ventilation": 6, + "percent_draughtproofed": 100, + "suggested_improvements": [ + { + "sequence": 1, + "typical_saving": 139, + "indicative_cost": "£220 - £250", + "improvement_type": "G", + "improvement_details": {"improvement_number": 66}, + "improvement_category": 5, + "energy_performance_rating": 70, + "environmental_impact_rating": 70 + } + ], + "co2_emissions_potential": 1.4, + "energy_rating_potential": 72, + "lighting_cost_potential": 84.23, + "schema_version_original": "SAP-19.0", + "hot_water_cost_potential": 180.43, + "renewable_heat_incentive": { + "water_heating": 2285, + "impact_of_loft_insulation": -2114, + "impact_of_cavity_insulation": -122, + "impact_of_solid_wall_insulation": -3560, + "space_heating_existing_dwelling": 13120 + }, + "draughtproofed_door_count": 1, + "mechanical_vent_duct_type": 3, + "energy_consumption_current": 230, + "has_fixed_air_conditioning": "false", + "multiple_glazed_proportion": 100, + "calculation_software_version": "13.05r16", + "energy_consumption_potential": 88, + "environmental_impact_current": 52, + "windows_transmission_details": {"u_value": 2, "data_source": 2, "solar_transmittance": 0.72}, + "cfl_fixed_lighting_bulbs_count": 5, + "current_energy_efficiency_band": "E", + "environmental_impact_potential": 74, + "led_fixed_lighting_bulbs_count": 10, + "mechanical_vent_duct_placement": 2, + "mechanical_vent_duct_insulation": 2, + "potential_energy_efficiency_band": "C", + "pressure_test_certificate_number": 0, + "mechanical_ventilation_index_number": 12, + "co2_emissions_current_per_floor_area": 20, + "low_energy_fixed_lighting_bulbs_count": 16, + "mechanical_vent_duct_insulation_level": 2, + "mechanical_vent_measured_installation": "false", + "incandescent_fixed_lighting_bulbs_count": 0 +} diff --git a/datatypes/epc/schema/tests/helpers.py b/datatypes/epc/schema/tests/helpers.py new file mode 100644 index 00000000..677bd8b7 --- /dev/null +++ b/datatypes/epc/schema/tests/helpers.py @@ -0,0 +1,73 @@ +import dataclasses +import typing +from typing import Any, Dict, Type, TypeVar + +T = TypeVar("T") + + +def from_dict(cls: Type[T], data: Dict[str, Any]) -> T: + """ + Recursively convert a plain dict (e.g. from json.loads) into the given + dataclass type, using the field type hints to convert nested structures. + + Handles: + - Nested dataclasses + - List[SomeDataclass] + - Optional[X] / Union[X, None] + - Union[DataclassType, primitive] (e.g. Union[Measurement, int]) + - Primitive pass-through for Union[str, int] etc. + """ + return _from_dict_impl(cls, data) # type: ignore[return-value] + + +def _from_dict_impl(cls: Any, data: Any) -> Any: + hints = typing.get_type_hints(cls) + kwargs: Dict[str, Any] = {} + + for field in dataclasses.fields(cls): # type: ignore[arg-type] + has_default = ( + field.default is not dataclasses.MISSING + or field.default_factory is not dataclasses.MISSING # type: ignore[misc] + ) + if field.name not in data: + if has_default: + continue + raise ValueError(f"{cls.__name__}: missing required field '{field.name}'") + + kwargs[field.name] = _coerce(data[field.name], hints[field.name]) + + return cls(**kwargs) + + +def _coerce(value: Any, hint: Any) -> Any: + if value is None: + return None + + origin = typing.get_origin(hint) + args = typing.get_args(hint) + + # Union (includes Optional[X] which is Union[X, None]) + if origin is typing.Union: + if value is None: + return None + non_none_args = [a for a in args if a is not type(None)] + if len(non_none_args) == 1: + # Optional[X] — recurse so List[X] and nested dataclasses are handled + return _coerce(value, non_none_args[0]) + # Multi-type Union (e.g. Union[Measurement, int]): try dataclasses first + for arg in non_none_args: + if dataclasses.is_dataclass(arg) and isinstance(value, dict): + return _from_dict_impl(arg, value) + # All remaining args are primitives — return value as-is + return value + + # List[X] + if origin is list: + item_hint = args[0] + return [_coerce(item, item_hint) for item in value] + + # Plain dataclass + if dataclasses.is_dataclass(hint) and isinstance(value, dict): + return _from_dict_impl(hint, value) + + return value diff --git a/datatypes/epc/schema/tests/test_schema_loading.py b/datatypes/epc/schema/tests/test_schema_loading.py new file mode 100644 index 00000000..dc80ddc0 --- /dev/null +++ b/datatypes/epc/schema/tests/test_schema_loading.py @@ -0,0 +1,380 @@ +import json +import os +from typing import Any, Dict + +import pytest + +from datatypes.epc.schema import ( + RdSapSchema17_0, + RdSapSchema17_1, + RdSapSchema18_0, + RdSapSchema19_0, + RdSapSchema20_0_0, + RdSapSchema21_0_0, + RdSapSchema21_0_1, +) +from datatypes.epc.schema.common import CostAmount, DescriptionV1, Measurement +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] + + +class TestRdSapSchema17_0: + + @pytest.fixture + def epc(self) -> RdSapSchema17_0: + return from_dict(RdSapSchema17_0, load("17_0.json")) + + def test_schema_type(self, epc: RdSapSchema17_0) -> None: + assert epc.schema_type == "RdSAP-Schema-17.0" + + def test_uprn(self, epc: RdSapSchema17_0) -> None: + assert epc.uprn == 12457 + + def test_description_is_localised_object(self, epc: RdSapSchema17_0) -> None: + assert isinstance(epc.roofs[0].description, DescriptionV1) + assert epc.roofs[0].description.value == "(another dwelling above)" + assert epc.roofs[0].description.language == "1" + + def test_dwelling_type_is_localised_object(self, epc: RdSapSchema17_0) -> None: + assert isinstance(epc.dwelling_type, DescriptionV1) + assert epc.dwelling_type.value == "Mid-floor flat" + + def test_costs_are_cost_amount_objects(self, epc: RdSapSchema17_0) -> None: + assert isinstance(epc.heating_cost_current, CostAmount) + assert epc.heating_cost_current.value == 214 + assert epc.heating_cost_current.currency == "GBP" + + def test_suggested_improvement_saving_is_cost_amount(self, epc: RdSapSchema17_0) -> None: + assert isinstance(epc.suggested_improvements[0].typical_saving, CostAmount) + assert epc.suggested_improvements[0].typical_saving.value == 158 + + def test_flat_details_populated(self, epc: RdSapSchema17_0) -> None: + assert epc.sap_flat_details is not None + assert epc.sap_flat_details.level == 2 + + def test_floor_dimensions_use_measurement(self, epc: RdSapSchema17_0) -> None: + dim = epc.sap_building_parts[0].sap_floor_dimensions[0] + assert isinstance(dim.room_height, Measurement) + assert dim.room_height.value == 2.4 + assert dim.room_height.quantity == "metres" + + def test_alternative_improvements_present(self, epc: RdSapSchema17_0) -> None: + assert epc.alternative_improvements is not None + assert len(epc.alternative_improvements) == 1 + assert epc.alternative_improvements[0].improvement_type == "J2" + + def test_renewable_heat_incentive(self, epc: RdSapSchema17_0) -> None: + assert epc.renewable_heat_incentive.water_heating == 4818 + assert epc.renewable_heat_incentive.space_heating_existing_dwelling == 2415 + assert epc.renewable_heat_incentive.impact_of_loft_insulation is None + + def test_glazing_gap_is_string(self, epc: RdSapSchema17_0) -> None: + assert epc.glazing_gap == "16+" + + def test_lzc_energy_sources(self, epc: RdSapSchema17_0) -> None: + assert epc.lzc_energy_sources == [11] + + +class TestRdSapSchema17_1: + + @pytest.fixture + def epc(self) -> RdSapSchema17_1: + return from_dict(RdSapSchema17_1, load("17_1.json")) + + def test_schema_type(self, epc: RdSapSchema17_1) -> None: + assert epc.schema_type == "RdSAP-Schema-17.1" + + def test_description_is_localised_object(self, epc: RdSapSchema17_1) -> None: + assert isinstance(epc.roofs[0].description, DescriptionV1) + assert epc.roofs[0].description.value == "Pitched, 100 mm loft insulation" + + def test_address_line_2(self, epc: RdSapSchema17_1) -> None: + assert epc.address_line_2 == "Lower Moria" + + def test_cylinder_thermostat(self, epc: RdSapSchema17_1) -> None: + assert epc.sap_heating.cylinder_thermostat == "Y" + + def test_cylinder_insulation_thickness(self, epc: RdSapSchema17_1) -> None: + assert epc.sap_heating.cylinder_insulation_thickness == 12 + + def test_boiler_flue_type_in_heating_detail(self, epc: RdSapSchema17_1) -> None: + detail = epc.sap_heating.main_heating_details[0] + assert detail.boiler_flue_type == 1 + assert detail.main_heating_index_number == 9049 + + def test_extension_party_wall_length_is_int(self, epc: RdSapSchema17_1) -> None: + # Extension floor dimension has party_wall_length as a plain int (0) + extension = epc.sap_building_parts[1] + assert extension.identifier == "Extension 1" + dim = extension.sap_floor_dimensions[0] + assert dim.party_wall_length == 0 + + def test_extension_roof_insulation_thickness_is_int(self, epc: RdSapSchema17_1) -> None: + extension = epc.sap_building_parts[1] + assert extension.roof_insulation_thickness == 0 + + def test_renewable_heat_incentive_has_loft_impact(self, epc: RdSapSchema17_1) -> None: + assert epc.renewable_heat_incentive.impact_of_loft_insulation == -565 + + def test_multiple_roofs(self, epc: RdSapSchema17_1) -> None: + assert len(epc.roofs) == 2 + + +class TestRdSapSchema18_0: + + @pytest.fixture + def epc(self) -> RdSapSchema18_0: + return from_dict(RdSapSchema18_0, load("18_0.json")) + + def test_schema_type(self, epc: RdSapSchema18_0) -> None: + assert epc.schema_type == "RdSAP-Schema-18.0" + + def test_glazing_gap_is_integer(self, epc: RdSapSchema18_0) -> None: + assert epc.glazing_gap == 12 + assert isinstance(epc.glazing_gap, int) + + def test_room_in_roof_present(self, epc: RdSapSchema18_0) -> None: + main = epc.sap_building_parts[0] + assert main.sap_room_in_roof is not None + assert main.sap_room_in_roof.insulation == "AB" + assert main.sap_room_in_roof.construction_age_band == "G" + + def test_room_in_roof_floor_area_is_measurement(self, epc: RdSapSchema18_0) -> None: + room_in_roof = epc.sap_building_parts[0].sap_room_in_roof + assert room_in_roof is not None + assert isinstance(room_in_roof.floor_area, Measurement) + assert room_in_roof.floor_area.value == 9.0 + assert room_in_roof.floor_area.quantity == "square metres" + + def test_flat_roof_insulation_on_extension(self, epc: RdSapSchema18_0) -> None: + extension = epc.sap_building_parts[1] + assert extension.flat_roof_insulation_thickness == "NI" + + def test_description_is_localised_object(self, epc: RdSapSchema18_0) -> None: + assert isinstance(epc.walls[0].description, DescriptionV1) + + def test_three_roofs(self, epc: RdSapSchema18_0) -> None: + assert len(epc.roofs) == 3 + + def test_renewable_heat_incentive_has_solid_wall_impact(self, epc: RdSapSchema18_0) -> None: + assert epc.renewable_heat_incentive.impact_of_solid_wall_insulation == -1864 + + +class TestRdSapSchema19_0: + + @pytest.fixture + def epc(self) -> RdSapSchema19_0: + return from_dict(RdSapSchema19_0, load("19_0.json")) + + def test_schema_type(self, epc: RdSapSchema19_0) -> None: + assert epc.schema_type == "RdSAP-Schema-19.0" + + def test_windows_transmission_details(self, epc: RdSapSchema19_0) -> None: + wtd = epc.windows_transmission_details + assert wtd.u_value == 3.1 + assert wtd.data_source == 2 + assert wtd.solar_transmittance == 0.76 + + def test_description_is_localised_object(self, epc: RdSapSchema19_0) -> None: + assert isinstance(epc.roofs[0].description, DescriptionV1) + assert epc.roofs[0].description.value == "Pitched, 150 mm loft insulation" + + def test_glazing_gap_absent(self, epc: RdSapSchema19_0) -> None: + assert epc.glazing_gap is None + + def test_secondary_heating_type(self, epc: RdSapSchema19_0) -> None: + assert epc.sap_heating.secondary_heating_type == 631 + + def test_multiple_lzc_sources(self, epc: RdSapSchema19_0) -> None: + assert epc.lzc_energy_sources == [11, 9] + + def test_floor_insulation_thickness_on_building_part(self, epc: RdSapSchema19_0) -> None: + assert epc.sap_building_parts[0].floor_insulation_thickness == "NI" + + +class TestRdSapSchema20_0_0: + + @pytest.fixture + def epc(self) -> RdSapSchema20_0_0: + return from_dict(RdSapSchema20_0_0, load("20_0_0.json")) + + def test_schema_type(self, epc: RdSapSchema20_0_0) -> None: + assert epc.schema_type == "RdSAP-Schema-20.0.0" + + def test_description_is_plain_string(self, epc: RdSapSchema20_0_0) -> None: + assert isinstance(epc.roofs[0].description, str) + assert epc.roofs[0].description == "Pitched, 25 mm loft insulation" + + def test_dwelling_type_is_plain_string(self, epc: RdSapSchema20_0_0) -> None: + assert isinstance(epc.dwelling_type, str) + assert epc.dwelling_type == "Mid-terrace house" + + def test_costs_are_floats(self, epc: RdSapSchema20_0_0) -> None: + assert isinstance(epc.heating_cost_current, float) + assert epc.heating_cost_current == pytest.approx(365.98) + + def test_suggested_improvement_saving_is_numeric(self, epc: RdSapSchema20_0_0) -> None: + assert epc.suggested_improvements[0].typical_saving == 360 + + def test_indicative_cost_can_be_int(self, epc: RdSapSchema20_0_0) -> None: + # Second improvement has indicative_cost as a plain integer + assert epc.suggested_improvements[1].indicative_cost == 2000 + + def test_improvement_details_with_texts(self, epc: RdSapSchema20_0_0) -> None: + # Third improvement uses improvement_texts instead of improvement_number + third = epc.suggested_improvements[2] + assert third.improvement_details.improvement_number is None + assert third.improvement_details.improvement_texts is not None + assert third.improvement_details.improvement_texts.improvement_summary == "An improvement summary" + + def test_sap_windows_present(self, epc: RdSapSchema20_0_0) -> None: + assert len(epc.sap_windows) == 2 + assert epc.sap_windows[0].window_area == pytest.approx(200.1) + assert epc.sap_windows[0].glazing_type == 1 + + def test_addendum(self, epc: RdSapSchema20_0_0) -> None: + assert epc.addendum is not None + assert epc.addendum.stone_walls == "true" + assert epc.addendum.addendum_numbers == [1, 8] + + def test_flat_details_has_storey_count(self, epc: RdSapSchema20_0_0) -> None: + assert epc.sap_flat_details is not None + assert epc.sap_flat_details.storey_count == 3 + assert epc.sap_flat_details.unheated_corridor_length == 10 + + def test_room_in_roof_floor_area_is_plain_number(self, epc: RdSapSchema20_0_0) -> None: + room_in_roof = epc.sap_building_parts[0].sap_room_in_roof + assert room_in_roof is not None + assert room_in_roof.floor_area == 100 + assert not isinstance(room_in_roof.floor_area, Measurement) + + def test_renewable_heat_incentive_has_cavity_impact(self, epc: RdSapSchema20_0_0) -> None: + assert epc.renewable_heat_incentive.impact_of_cavity_insulation == -122 + + def test_multiple_glazed_proportion_nr(self, epc: RdSapSchema20_0_0) -> None: + assert epc.multiple_glazed_proportion_nr == "NR" + + +class TestRdSapSchema21_0_0: + + @pytest.fixture + def epc(self) -> RdSapSchema21_0_0: + return from_dict(RdSapSchema21_0_0, load("21_0_0.json")) + + def test_schema_type(self, epc: RdSapSchema21_0_0) -> None: + assert epc.schema_type == "RdSAP-Schema-21.0.0" + + def test_description_is_plain_string(self, epc: RdSapSchema21_0_0) -> None: + assert isinstance(epc.roofs[0].description, str) + + def test_pressure_test(self, epc: RdSapSchema21_0_0) -> None: + assert epc.pressure_test == 6 + + def test_wet_rooms_count(self, epc: RdSapSchema21_0_0) -> None: + assert epc.wet_rooms_count == 0 + + def test_instantaneous_wwhrs_uses_index_numbers(self, epc: RdSapSchema21_0_0) -> None: + wwhrs = epc.sap_heating.instantaneous_wwhrs + assert wwhrs.wwhrs_index_number1 == 1 + assert wwhrs.wwhrs_index_number2 == 2 + + def test_shower_outlets(self, epc: RdSapSchema21_0_0) -> None: + outlets = epc.sap_heating.shower_outlets + assert outlets is not None + assert outlets.shower_outlet.shower_wwhrs == 1 + assert outlets.shower_outlet.shower_outlet_type == 1 + + def test_boiler_ignition_type(self, epc: RdSapSchema21_0_0) -> None: + assert epc.sap_heating.main_heating_details[0].boiler_ignition_type == 1 + + def test_smart_meters(self, epc: RdSapSchema21_0_0) -> None: + src = epc.sap_energy_source + assert src.electricity_smart_meter_present == "true" + assert src.gas_smart_meter_present == "false" + + def test_pv_batteries(self, epc: RdSapSchema21_0_0) -> None: + assert epc.sap_energy_source.pv_batteries is not None + assert epc.sap_energy_source.pv_batteries.pv_battery.battery_capacity == 5 + + def test_alternative_walls(self, epc: RdSapSchema21_0_0) -> None: + part = epc.sap_building_parts[0] + assert part.sap_alternative_wall_1 is not None + assert part.sap_alternative_wall_1.wall_area == pytest.approx(10.4) + assert part.sap_alternative_wall_2 is not None + assert part.sap_alternative_wall_2.wall_area == pytest.approx(10.8) + + def test_detailed_sap_windows(self, epc: RdSapSchema21_0_0) -> None: + win = epc.sap_windows[0] + assert win.glazing_gap == 6 + assert win.window_width == pytest.approx(1.2) + assert win.window_transmission_details.u_value == pytest.approx(1.0) + + def test_mechanical_vent_fields(self, epc: RdSapSchema21_0_0) -> None: + assert epc.mechanical_vent_duct_type == 3 + assert epc.mechanical_ventilation_index_number == 12 + + def test_lighting_bulb_counts(self, epc: RdSapSchema21_0_0) -> None: + assert epc.led_fixed_lighting_bulbs_count == 10 + assert epc.cfl_fixed_lighting_bulbs_count == 5 + assert epc.incandescent_fixed_lighting_bulbs_count == 5 + + def test_open_chimneys_count(self, epc: RdSapSchema21_0_0) -> None: + assert epc.open_chimneys_count == 1 + + def test_room_in_roof_has_no_insulation_field(self, epc: RdSapSchema21_0_0) -> None: + room_in_roof = epc.sap_building_parts[0].sap_room_in_roof + assert room_in_roof is not None + assert room_in_roof.construction_age_band == "B" + assert not hasattr(room_in_roof, "insulation") + + +class TestRdSapSchema21_0_1: + + @pytest.fixture + def epc(self) -> RdSapSchema21_0_1: + return from_dict(RdSapSchema21_0_1, load("21_0_1.json")) + + def test_schema_type(self, epc: RdSapSchema21_0_1) -> None: + assert epc.schema_type == "RdSAP-Schema-21.0.1" + + def test_description_reverts_to_localised_object(self, epc: RdSapSchema21_0_1) -> None: + # Descriptions on energy elements revert to DescriptionV1 in 21.0.1 + assert isinstance(epc.roofs[0].description, DescriptionV1) + assert epc.roofs[0].description.value == "Pitched, 25 mm loft insulation" + + def test_main_heating_description_is_localised_object(self, epc: RdSapSchema21_0_1) -> None: + assert isinstance(epc.main_heating[0].description, DescriptionV1) + assert epc.main_heating[0].description.value == "Boiler and radiators, anthracite" + + def test_dwelling_type_remains_plain_string(self, epc: RdSapSchema21_0_1) -> None: + # dwelling_type does NOT revert — stays as plain str in 21.0.1 + assert isinstance(epc.dwelling_type, str) + assert epc.dwelling_type == "Mid-terrace house" + + def test_unheated_corridor_length_is_measurement(self, epc: RdSapSchema21_0_1) -> None: + # Changed from plain int (21.0.0) to Measurement object in 21.0.1 + assert epc.sap_flat_details is not None + corridor_length = epc.sap_flat_details.unheated_corridor_length + assert isinstance(corridor_length, Measurement) + assert corridor_length.value == 10 + assert corridor_length.quantity == "metres" + + def test_completion_date(self, epc: RdSapSchema21_0_1) -> None: + assert epc.completion_date == "2025-04-04" + + def test_addendum_numbers(self, epc: RdSapSchema21_0_1) -> None: + assert epc.addendum is not None + assert epc.addendum.addendum_numbers == [1, 13] + + def test_pv_battery_capacity(self, epc: RdSapSchema21_0_1) -> None: + assert epc.sap_energy_source.pv_batteries is not None + assert epc.sap_energy_source.pv_batteries.pv_battery.battery_capacity == 5 + + def test_incandescent_bulb_count(self, epc: RdSapSchema21_0_1) -> None: + assert epc.incandescent_fixed_lighting_bulbs_count == 0 diff --git a/pytest.ini b/pytest.ini index 792b27e0..4a5327c1 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,6 +3,6 @@ pythonpath = . log_cli = true log_cli_level = INFO addopts = --cov-report term-missing --cov=etl/epc --cov=recommendations --cov=backend --cov=etl/epc_clean --cov=etl/spatial -testpaths = recommendations/tests backend/tests etl/epc/tests etl/epc_clean/tests etl/spatial/tests backend/condition/tests backend/address2UPRN/tests backend/onboarders/tests backend/categorisation/tests backend/export/tests etl/hubspot/tests backend/hubspot_trigger_orchestrator/tests +testpaths = recommendations/tests backend/tests etl/epc/tests etl/epc_clean/tests etl/spatial/tests backend/condition/tests backend/address2UPRN/tests backend/onboarders/tests backend/categorisation/tests backend/export/tests etl/hubspot/tests backend/hubspot_trigger_orchestrator/tests datatypes/epc/schema/tests markers = integration: mark a test as an integration test