diff --git a/backend/onboarders/base.py b/backend/onboarders/base.py index 258784f1..b90f5fc4 100644 --- a/backend/onboarders/base.py +++ b/backend/onboarders/base.py @@ -3,7 +3,39 @@ from utils.s3 import read_from_s3 class OnboarderBase: + # Input dataset to be transformed data: pd.DataFrame | None = None + # Description columns + landlord_wall_construction: str = "landlord_wall_construction" + landlord_roof_construction: str = "landlord_roof_construction" + landlord_floor_construction: str = "landlord_floor_construction" + landlord_windows_construction: str = "landlord_windows_construction" + landlord_heating_construction: str = "landlord_heating_construction" + landlord_fuel_construction: str = "landlord_fuel_construction" + landlord_heating_controls_construction: str = "landlord_heating_controls_construction" + landlord_hot_water_system_construction: str = "landlord_hot_water_system_construction" + + # Efficiency columns + landlord_roof_efficiency: str = "landlord_roof_efficiency" + landlord_windows_efficiency: str = "landlord_windows_efficiency" + landlord_heating_controls_efficiency: str = "landlord_heating_controls_efficiency" + landlord_heating_efficiency: str = "landlord_heating_efficiency" + landlord_hot_water_efficiency: str = "landlord_hot_water_efficiency" + landlord_wall_efficiency: str = "landlord_wall_efficiency" + + # Additional windows features + landlord_multi_glaze_proportion: str = "landlord_multi_glaze_proportion" + landlord_glazed_type: str = "landlord_glazed_type" + landlord_glazed_area: str = "landlord_glazed_area" + + # Additional roof features + landlord_has_sloping_ceiling: str = "landlord_has_sloping_ceiling" + + # Shape, dimensions, age + landlord_total_floor_area_m2: str = "landlord_total_floor_area_m2" + landlord_construction_age_band: str = "landlord_construction_age_band" + landlord_property_type: str = "landlord_property_type" + landlord_built_form: str = "landlord_built_form" def read_s3(self, bucket_name: str, file_name: str): self.data = read_from_s3(bucket_name=bucket_name, s3_file_name=file_name) @@ -27,4 +59,6 @@ class OnboarderBase: assert pd.isnull(data[column]).sum() == 0, f"column {column} contains null values, but should not" def map_construction_age_band(self): - pass + raise NotImplementedError( + "This method should be implemented by subclasses to map construction age bands to descriptions" + ) diff --git a/backend/onboarders/epc_descriptions.py b/backend/onboarders/epc_descriptions.py index bfe6b07f..78cc57c1 100644 --- a/backend/onboarders/epc_descriptions.py +++ b/backend/onboarders/epc_descriptions.py @@ -511,19 +511,3 @@ def resolve_roof_efficiency( except TypeError: # Fallback to (age_band) return rule(age_band) - - -class EpcFloorDescriptions(Enum): - # Solid floor - solid_insulated = "Solid, insulated" - solid_insulated_assumed = "Solid, insulated (assumed)" - solid_no_insulation_assumed = "Solid, no insulation (assumed)" - solid_limited_insulation_assumed = "Solid, limited insulation (assumed)" - - # Suspended floor - suspended_insulated = "Suspended, insulated" - suspended_insulated_assumed = "Suspended, insulated (assumed)" - suspended_no_insulation_assumed = "Suspended, no insulation (assumed)" - suspended_limited_insulation_assumed = "Suspended, limited insulation (assumed)" - - unknown = None # We don't resolve anything diff --git a/backend/onboarders/handler.py b/backend/onboarders/handler.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/onboarders/mappings/age_band.py b/backend/onboarders/mappings/parity/age_band.py similarity index 100% rename from backend/onboarders/mappings/age_band.py rename to backend/onboarders/mappings/parity/age_band.py diff --git a/backend/onboarders/mappings/as_built_floor_classifiers.py b/backend/onboarders/mappings/parity/as_built_floor_classifiers.py similarity index 100% rename from backend/onboarders/mappings/as_built_floor_classifiers.py rename to backend/onboarders/mappings/parity/as_built_floor_classifiers.py diff --git a/backend/onboarders/mappings/as_built_roof_classifiers.py b/backend/onboarders/mappings/parity/as_built_roof_classifiers.py similarity index 100% rename from backend/onboarders/mappings/as_built_roof_classifiers.py rename to backend/onboarders/mappings/parity/as_built_roof_classifiers.py diff --git a/backend/onboarders/mappings/as_built_wall_classifiers.py b/backend/onboarders/mappings/parity/as_built_wall_classifiers.py similarity index 100% rename from backend/onboarders/mappings/as_built_wall_classifiers.py rename to backend/onboarders/mappings/parity/as_built_wall_classifiers.py diff --git a/backend/onboarders/mappings/built_form.py b/backend/onboarders/mappings/parity/built_form.py similarity index 100% rename from backend/onboarders/mappings/built_form.py rename to backend/onboarders/mappings/parity/built_form.py diff --git a/backend/onboarders/mappings/parity/floor.py b/backend/onboarders/mappings/parity/floor.py new file mode 100644 index 00000000..653d4c68 --- /dev/null +++ b/backend/onboarders/mappings/parity/floor.py @@ -0,0 +1,26 @@ +from numpy import nan +from datatypes.epc.floor import EpcFloorDescriptions + +floor_map = { + # Solid floor + ('Solid', 'AsBuilt'): None, # Mapped + ('Solid', 'Unknown'): None, # Mapped + ('Solid', nan): None, # Mapped + ('Solid', 'RetroFitted'): EpcFloorDescriptions.solid_insulated, + + # Suspended floor + ('SuspendedTimber', nan): None, # Mapped suspended_floor_as_built + ('SuspendedTimber', 'AsBuilt'): None, # Mapped suspended_floor_as_built + ('SuspendedTimber', 'RetroFitted'): EpcFloorDescriptions.suspended_insulated, + ('SuspendedTimber', 'Unknown'): None, # Mapped suspended_floor_as_built + ('SuspendedNotTimber', 'RetroFitted'): EpcFloorDescriptions.suspended_insulated, + ('SuspendedNotTimber', nan): None, # Mapped suspended_floor_as_built + ('SuspendedNotTimber', 'Unknown'): None, # Mapped suspended_floor_as_built + ('SuspendedNotTimber', 'AsBuilt'): None, # Mapped suspended_floor_as_built + + # Unknown type - mapped on age + ('Unknown', 'Unknown'): None, # Mapped unknown_floor_as_built + ('Unknown', 'RetroFitted'): None, # Mapped unknown_floor_retrofitted + (nan, nan): None, # No actual information! + ('Unknown', 'AsBuilt'): None, # Mapped unknown_floor_as_built +} diff --git a/backend/onboarders/mappings/parity/glazing.py b/backend/onboarders/mappings/parity/glazing.py new file mode 100644 index 00000000..46c006bd --- /dev/null +++ b/backend/onboarders/mappings/parity/glazing.py @@ -0,0 +1,20 @@ +from datatypes.epc.efficiency import EpcEfficiency + +glazing_map = { + # (description, energy efficiency, multi_glaze_proportion, glazed_type, glazed_area + # For SAP 10 assessments, The glazed type and glazed area are not populated in the EPC API data any more + "Double 2002 or later": ("Fully double glazed", EpcEfficiency.AVERAGE, 1, None, None), + "Double before 2002": ("Fully double glazed", EpcEfficiency.POOR, 1, None, None), + "Double but age unknown": ("Fully double glazed", EpcEfficiency.POOR, 1, None, None), + "Single": ("Single glazed", EpcEfficiency.VERY_POOR, 0, None, None), + # For triple glazing, with age unknown, the performance is only average, whereas if it's a post 2022 + # installation, it's classed as high performance glazing with good efficiency. We'll need to be considerate as to + # how we make updates to the windows data. + # Triple known data is high performance glazing with Good efficiency (at least) + "Triple": ("Fully triple glazed", EpcEfficiency.AVERAGE, 1, None, None), + # This is also classed as high performance glazing + "DoubleKnownData": ("High performance glazing", EpcEfficiency.GOOD, 1, None, None), + # Under SAP 10, secondary glazing is classed as poor efficiency (whereas under SAP 2012 it was generally good) + "Secondary": ("Full secondary glazing", EpcEfficiency.POOR, 1, None, None), + "TripleKnownData": ("High performance glazing", EpcEfficiency.GOOD, 1, None, None), +} diff --git a/backend/onboarders/mappings/parity/heating.py b/backend/onboarders/mappings/parity/heating.py new file mode 100644 index 00000000..aa74834b --- /dev/null +++ b/backend/onboarders/mappings/parity/heating.py @@ -0,0 +1,330 @@ +from datatypes.epc.main_heating import EpcHeatingSystems +from datatypes.epc.efficiency import EpcEfficiency +from datatypes.epc.fuel import EpcFuel +from datatypes.epc.heating_controls import EpcHeatingControls +from datatypes.epc.hotwater import EpcHotWaterSystems + +heating_map = { + # 0 + ('Boilers', 'A', 'ElectricityNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 1 + ('Boilers', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 2 + ('Boilers', 'A', 'ElectricityNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 3 + ('Boilers', 'A', 'LPGNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_and_radiators_lpg, EpcEfficiency.POOR, EpcFuel.lpg_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 4 + ('Boilers', 'A', 'MainsGasNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.VERY_GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 5 + ('Boilers', 'A', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.VERY_GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 6 + ('Boilers', 'A', 'MainsGasNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.VERY_GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 7 + ('Boilers', 'B', 'MainsGasNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 8 + ('Boilers', 'B', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 9 + ('Boilers', 'B', 'MainsGasNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 10 + ('Boilers', 'C', 'ElectricityNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 11 + ('Boilers', 'C', 'ElectricityNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 12 + ('Boilers', 'C', 'ElectricityNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 13 + ('Boilers', 'C', 'LPGNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_and_radiators_lpg, EpcEfficiency.POOR, EpcFuel.lpg_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 14 + ('Boilers', 'C', 'LPGNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_and_radiators_lpg, EpcEfficiency.POOR, EpcFuel.lpg_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 15 + ('Boilers', 'C', 'MainsGasNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 16 + ('Boilers', 'C', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 17 + ('Boilers', 'C', 'MainsGasNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + ('Boilers', 'C', 'OilNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_radiators_oil, EpcEfficiency.AVERAGE, EpcFuel.oil_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 19 + ('Boilers', 'C', 'OilNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_radiators_oil, EpcEfficiency.AVERAGE, EpcFuel.oil_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 20 + ('Boilers', 'C', 'OilNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.boiler_radiators_oil, EpcEfficiency.AVERAGE, EpcFuel.oil_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 21 + ('Boilers', 'D', 'MainsGasNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 22 + ('Boilers', 'D', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 23 + ('Boilers', 'D', 'MainsGasNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 24 + ('Boilers', 'E', 'ElectricityNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 25 + ('Boilers', 'E', 'MainsGasNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 26 + ('Boilers', 'E', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 27 + ('Boilers', 'E', 'MainsGasNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 28 + ('Boilers', 'E', 'OilNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_radiators_oil, EpcEfficiency.AVERAGE, EpcFuel.oil_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 29 + ('Boilers', 'E', 'OilNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_radiators_oil, EpcEfficiency.AVERAGE, EpcFuel.oil_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 30 + ('Boilers', 'F', 'MainsGasNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 31 + ('Boilers', 'F', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 32 + ('Boilers', 'F', 'MainsGasNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 33 + ('Boilers', 'G', 'MainsGasNotCommunity', 'Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 34 + ('Boilers', 'G', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 35 + ('Boilers', 'G', 'MainsGasNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.AVERAGE, EpcFuel.mains_gas_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 36 + ('Electric underfloor', 'A', 'ElectricityNotCommunity', 'Optimal'): ( + EpcHeatingSystems.electric_underfloor_heating, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + # 37 + ('Electric underfloor', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.electric_underfloor_heating, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + # 38 + ('Electric underfloor', 'A', 'ElectricityNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.electric_underfloor_heating, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + # 39 + ('Heat pumps (warm air)', 'A', 'ElectricityNotCommunity', 'Optimal'): ( + EpcHeatingSystems.air_to_air_ashp, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 40 + ('Heat pumps (warm air)', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.air_to_air_ashp, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 41 + ('Heat pumps (wet)', 'A', 'ElectricityNotCommunity', 'Optimal'): ( + EpcHeatingSystems.ashp_radiators_electric, EpcEfficiency.GOOD, EpcFuel.electricity_not_community, + EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 42 + ('Heat pumps (wet)', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.ashp_radiators_electric, EpcEfficiency.GOOD, EpcFuel.electricity_not_community, + EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 43 + ('Heat pumps (wet)', 'A', 'ElectricityNotCommunity', 'Top Spec'): ( + EpcHeatingSystems.ashp_radiators_electric, EpcEfficiency.GOOD, EpcFuel.electricity_not_community, + EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, + EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE + ), + # 44 + ('Room heaters', 'A', 'ElectricityNotCommunity', 'Optimal'): ( + EpcHeatingSystems.room_heaters_electric, EpcEfficiency.POOR, EpcFuel.electricity_not_community, + EpcHeatingControls.programmer_and_appliance_thermostats, EpcEfficiency.GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + # 45 + ('Room heaters', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.room_heaters_electric, EpcEfficiency.POOR, EpcFuel.electricity_not_community, + EpcHeatingControls.appliance_thermostats, EpcEfficiency.GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + # 46 + ('Room heaters', 'C', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.room_heaters_mains_gas, EpcEfficiency.AVERAGE, EpcFuel.mains_gas_not_community, + EpcHeatingControls.appliance_thermostats, EpcEfficiency.GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + # 47 - water done from here + ('Room heaters', 'F', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.room_heaters_mains_gas, EpcEfficiency.POOR, EpcFuel.mains_gas_not_community, + EpcHeatingControls.appliance_thermostats, EpcEfficiency.GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + ('Room heaters', 'G', 'MainsGasNotCommunity', 'Optimal'): ( + EpcHeatingSystems.room_heaters_mains_gas, EpcEfficiency.POOR, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmer_and_appliance_thermostats, EpcEfficiency.GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + ('Room heaters', 'G', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.room_heaters_mains_gas, EpcEfficiency.POOR, EpcFuel.mains_gas_not_community, + EpcHeatingControls.appliance_thermostats, EpcEfficiency.GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + ('Room heaters', 'G', 'SmokelessCoal', 'Sub Optimal'): ( + EpcHeatingSystems.room_heaters_smokeless_fuel, EpcEfficiency.VERY_POOR, EpcFuel.smokeless_coal, + EpcHeatingControls.appliance_thermostats, EpcEfficiency.GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + ('Storage heaters', 'A', 'ElectricityNotCommunity', 'Optimal'): ( + EpcHeatingSystems.electric_storage_heaters, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, + EpcHeatingControls.automatic_charge_control, EpcEfficiency.AVERAGE, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + ('Storage heaters', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.electric_storage_heaters, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, + EpcHeatingControls.manual_charge_control, EpcEfficiency.POOR, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + ('Warm Air (not heat pump)', 'G', 'ElectricityNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.warm_air_electricaire, EpcEfficiency.GOOD, EpcFuel.electricity_not_community, + EpcHeatingControls.programmer_and_atleast_two_room_thermostats, EpcEfficiency.GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ), + ('Warm Air (not heat pump)', 'G', 'MainsGasNotCommunity', 'Sub Optimal'): ( + EpcHeatingSystems.warm_air_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, + EpcHeatingControls.programmer_and_atleast_two_room_thermostats, EpcEfficiency.GOOD, + EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE + ) +} diff --git a/backend/onboarders/mappings/property_type.py b/backend/onboarders/mappings/parity/property_type.py similarity index 100% rename from backend/onboarders/mappings/property_type.py rename to backend/onboarders/mappings/parity/property_type.py diff --git a/backend/onboarders/mappings/parity/roof.py b/backend/onboarders/mappings/parity/roof.py new file mode 100644 index 00000000..14f0c34e --- /dev/null +++ b/backend/onboarders/mappings/parity/roof.py @@ -0,0 +1,103 @@ +from numpy import nan +from datatypes.epc.roof import EpcRoofDescriptions + +roof_map = { + # Dwelling above + ('AnotherDwellingAbove', 'Another Dwelling Above'): EpcRoofDescriptions.another_dwelling_above, + ('SameDwellingAbove', 'Same Dwelling Above'): EpcRoofDescriptions.another_dwelling_above, + # Pitched, normal loft access, with a loft thickness + ('PitchedNormalLoftAccess', 'mm25'): EpcRoofDescriptions.loft_25mm_insulation, + ('PitchedNormalLoftAccess', 'mm50'): EpcRoofDescriptions.loft_50mm_insulation, + ('PitchedNormalLoftAccess', 'mm75'): EpcRoofDescriptions.loft_75mm_insulation, + ('PitchedNormalLoftAccess', 'mm100'): EpcRoofDescriptions.loft_100mm_insulation, + ('PitchedNormalLoftAccess', 'mm150'): EpcRoofDescriptions.loft_150mm_insulation, + ('PitchedNormalLoftAccess', 'mm200'): EpcRoofDescriptions.loft_200mm_insulation, + ('PitchedNormalLoftAccess', 'mm250'): EpcRoofDescriptions.loft_250mm_insulation, + ('PitchedNormalLoftAccess', 'mm270'): EpcRoofDescriptions.loft_270mm_insulation, + ('PitchedNormalLoftAccess', 'mm300'): EpcRoofDescriptions.loft_300mm_insulation, + ('PitchedNormalLoftAccess', 'mm350'): EpcRoofDescriptions.loft_350mm_insulation, + ('PitchedNormalLoftAccess', 'mm400'): EpcRoofDescriptions.loft_400mm_plus_insulation, + + # Pitched, no loft access, with a loft thickness + ('PitchedNormalNoLoftAccess', 'mm25'): EpcRoofDescriptions.loft_25mm_insulation, + ('PitchedNormalNoLoftAccess', 'mm50'): EpcRoofDescriptions.loft_50mm_insulation, + ('PitchedNormalNoLoftAccess', 'mm75'): EpcRoofDescriptions.loft_75mm_insulation, + ('PitchedNormalNoLoftAccess', 'mm100'): EpcRoofDescriptions.loft_100mm_insulation, + ('PitchedNormalNoLoftAccess', 'mm150'): EpcRoofDescriptions.loft_150mm_insulation, + ('PitchedNormalNoLoftAccess', 'mm200'): EpcRoofDescriptions.loft_200mm_insulation, + ('PitchedNormalNoLoftAccess', 'mm250'): EpcRoofDescriptions.loft_250mm_insulation, + ('PitchedNormalNoLoftAccess', 'mm270'): EpcRoofDescriptions.loft_270mm_insulation, + ('PitchedNormalNoLoftAccess', 'mm300'): EpcRoofDescriptions.loft_300mm_insulation, + ('PitchedNormalNoLoftAccess', 'mm350'): EpcRoofDescriptions.loft_350mm_insulation, + ('PitchedNormalNoLoftAccess', 'mm400'): EpcRoofDescriptions.loft_400mm_plus_insulation, + + # All pitched options with asbuilt or unknown got to EpcRoofDescriptions.pitched_insulated_assumed + # With access + ('PitchedNormalLoftAccess', nan): EpcRoofDescriptions.pitched_insulated_assumed, + ('PitchedNormalLoftAccess', 'AsBuilt'): EpcRoofDescriptions.pitched_insulated_assumed, + ('PitchedNormalLoftAccess', 'Unknown'): EpcRoofDescriptions.pitched_insulated_assumed, + # No access + ('PitchedNormalNoLoftAccess', nan): EpcRoofDescriptions.pitched_insulated_assumed, + ('PitchedNormalNoLoftAccess', 'AsBuilt'): EpcRoofDescriptions.pitched_insulated_assumed, + ('PitchedNormalNoLoftAccess', 'Unknown'): EpcRoofDescriptions.pitched_insulated_assumed, + + # Flat + ('Flat', 'NoInsulation'): EpcRoofDescriptions.flat_no_insulation, + # Flat - limited insulation + ('Flat', '12mm'): EpcRoofDescriptions.flat_limited_insulation, + ('Flat', 'mm25'): EpcRoofDescriptions.flat_limited_insulation, + ('Flat', 'mm50'): EpcRoofDescriptions.flat_limited_insulation, + # Flat insulated + ('Flat', 'mm75'): EpcRoofDescriptions.flat_insulated, + ('Flat', 'mm100'): EpcRoofDescriptions.flat_insulated, + ('Flat', 'mm150'): EpcRoofDescriptions.flat_insulated, + ('Flat', 'mm200'): EpcRoofDescriptions.flat_insulated, + ('Flat', 'mm250'): EpcRoofDescriptions.flat_insulated, + ('Flat', 'mm300'): EpcRoofDescriptions.flat_insulated, + ('Flat', 'mm350'): EpcRoofDescriptions.flat_insulated, + ('Flat', 'mm400'): EpcRoofDescriptions.flat_insulated, + # Flat - as built or unknown + ('Flat', 'AsBuilt'): None, # To be classified + ('Flat', nan): None, # To be classified + ('Flat', 'Unknown'): None, # To be classified + + # 12mm = very poor & has limited insulation description + # 25, 50 = poor & has limited insulation description + # 75, 100, 125mm = average (Flat, insulated) + # 150, 175, 200, 225, 250mm = good (Flat, insulated) + # 270mm+ = very good (Flat, insulated) + + # Thatched + ('PitchedThatched', 'mm50'): EpcRoofDescriptions.thatched_with_additional_insulation, + ('PitchedThatched', 'mm150'): EpcRoofDescriptions.thatched_with_additional_insulation, + ('PitchedThatched', 'mm300'): EpcRoofDescriptions.thatched_with_additional_insulation, + ('PitchedThatched', 'Unknown'): EpcRoofDescriptions.thatched, # efficiency classified based on age + + # Sloping: + # Limited (12 very poor, 25-50 poor) + ('PitchedWithSlopingCeiling', 'mm12'): EpcRoofDescriptions.sloping_pitched_limited_insulation, + ('PitchedWithSlopingCeiling', 'mm25'): EpcRoofDescriptions.sloping_pitched_limited_insulation, + ('PitchedWithSlopingCeiling', 'mm50'): EpcRoofDescriptions.sloping_pitched_limited_insulation, + # Insulated 75mm+ (75 - 125 average, 150 - 250 good, 270+ very good) + ('PitchedWithSlopingCeiling', 'mm75'): EpcRoofDescriptions.sloping_pitched_insulated, + ('PitchedWithSlopingCeiling', 'mm100'): EpcRoofDescriptions.sloping_pitched_insulated, + ('PitchedWithSlopingCeiling', 'mm150'): EpcRoofDescriptions.sloping_pitched_insulated, + ('PitchedWithSlopingCeiling', 'mm200'): EpcRoofDescriptions.sloping_pitched_insulated, + ('PitchedWithSlopingCeiling', 'mm250'): EpcRoofDescriptions.sloping_pitched_insulated, + ('PitchedWithSlopingCeiling', 'mm270'): EpcRoofDescriptions.sloping_pitched_insulated, + ('PitchedWithSlopingCeiling', 'mm300'): EpcRoofDescriptions.sloping_pitched_insulated, + ('PitchedWithSlopingCeiling', 'mm350'): EpcRoofDescriptions.sloping_pitched_insulated, + ('PitchedWithSlopingCeiling', 'mm400'): EpcRoofDescriptions.sloping_pitched_insulated, + # As built/unknown + ('PitchedWithSlopingCeiling', 'AsBuilt'): None, # To be classified + ('PitchedWithSlopingCeiling', nan): None, # To be classified + ('PitchedWithSlopingCeiling', 'Unknown'): None, # +} + +roof_unknown_age_fallback = { + "Flat": EpcRoofDescriptions.flat_as_built_unknown, + "PitchedWithSlopingCeiling": EpcRoofDescriptions.sloping_pitched_as_built_unknown, + "PitchedThatched": EpcRoofDescriptions.thatched_as_built_unknown, + "PitchedNormalLoftAccess": EpcRoofDescriptions.loft_as_built_unknown, + "PitchedNormalNoLoftAccess": EpcRoofDescriptions.loft_as_built_unknown, +} diff --git a/backend/onboarders/mappings/parity/walls.py b/backend/onboarders/mappings/parity/walls.py new file mode 100644 index 00000000..b46559b9 --- /dev/null +++ b/backend/onboarders/mappings/parity/walls.py @@ -0,0 +1,56 @@ +from datatypes.epc.walls import EpcWallDescriptions + +# Unique combinations +wall_map = { + # Cavity walls + ('Cavity', 'FilledCavity'): EpcWallDescriptions.cavity_filled_cavity, + ('Cavity', 'Internal'): EpcWallDescriptions.cavity_internal_insulation, + ('Cavity', 'External'): EpcWallDescriptions.cavity_external_insulation, + ('Cavity', 'FilledCavityPlusInternal'): EpcWallDescriptions.cavity_filled_plus_internal, + ('Cavity', 'FilledCavityPlusExternal'): EpcWallDescriptions.cavity_filled_plus_external, + ('Cavity', 'AsBuilt'): None, # To be classified + ('Cavity', 'Unknown'): None, # To be classified + + # System built walls + ('System', 'External'): EpcWallDescriptions.system_external_insulation, + ('System', 'Internal'): EpcWallDescriptions.system_internal_insulation, + ('System', 'AsBuilt'): None, # To be classified + ('System', 'Unknown'): None, + + # Timber Frame walls + ('Timber Frame', 'Internal'): EpcWallDescriptions.timber_frame_internal_insulation, + ('Timber Frame', 'External'): EpcWallDescriptions.timber_frame_external_insulation, + ('Timber Frame', 'AsBuilt'): None, # To be classified + ('Timber Frame', 'Unknown'): None, + + # Solid Brick walls + ('Solid Brick', 'External'): EpcWallDescriptions.solid_brick_external_insulation, + ('Solid Brick', 'Internal'): EpcWallDescriptions.solid_brick_internal_insulation, + ('Solid Brick', 'AsBuilt'): None, # To be classified + ('Solid Brick', 'Unknown'): None, + + # Granite walls + ('Granite', 'External'): EpcWallDescriptions.granite_whinstone_external_insulation, + ("Granite", 'Internal'): EpcWallDescriptions.granite_whinstone_internal_insulation, + ('Granite', 'AsBuilt'): None, + ('Granite', 'Unknown'): None, + + # Sandstone walls + ('Sandstone', 'Internal'): EpcWallDescriptions.sandstone_limestone_internal_insulation, + ('Sandstone', 'External'): EpcWallDescriptions.sandstone_limestone_external_insulation, + ('Sandstone', 'Unknown'): None, + ('Sandstone', 'AsBuilt'): None, + + # Cob walls + ('Cob', 'AsBuilt'): None, +} + +wall_unknown_age_fallback = { + "Cavity": EpcWallDescriptions.cavity_as_built_unknown, + "Solid Brick": EpcWallDescriptions.solid_brick_as_built_unknown, + "Timber Frame": EpcWallDescriptions.timber_frame_as_built_unknown, + "System": EpcWallDescriptions.system_as_built_unknown, + "Granite": EpcWallDescriptions.granite_as_built_unknown, + "Sandstone": EpcWallDescriptions.sandstone_as_built_unknown, + "Cob": EpcWallDescriptions.cob_as_built_unknown, +} diff --git a/backend/onboarders/mappings/walls.py b/backend/onboarders/mappings/walls.py deleted file mode 100644 index 9b70b49c..00000000 --- a/backend/onboarders/mappings/walls.py +++ /dev/null @@ -1,3 +0,0 @@ -parity_map = { - -} diff --git a/backend/onboarders/parity.py b/backend/onboarders/parity.py index c1931437..e820f938 100644 --- a/backend/onboarders/parity.py +++ b/backend/onboarders/parity.py @@ -1,21 +1,24 @@ import re -from numpy import nan from tqdm import tqdm import pandas as pd from backend.onboarders.base import OnboarderBase -from backend.onboarders.mappings.property_type import parity_map as property_map -from backend.onboarders.mappings.age_band import parity_map as age_band_map -from backend.onboarders.mappings.built_form import parity_map as built_form_map +from backend.onboarders.mappings.parity.property_type import parity_map as property_map +from backend.onboarders.mappings.parity.age_band import parity_map as age_band_map +from backend.onboarders.mappings.parity.built_form import parity_map as built_form_map +from backend.onboarders.mappings.parity.walls import wall_map, wall_unknown_age_fallback from backend.onboarders.epc_descriptions import EpcWallDescriptions, EpcConstructionAgeBand, EpcEfficiency, \ - WALL_DESCRIPTION_EFFICIENCIES, EpcRoofDescriptions, resolve_roof_efficiency, EpcFloorDescriptions -from datatypes.epc.fuel import EpcFuel -from datatypes.epc.heating_controls import EpcHeatingControls -from datatypes.epc.main_heating import EpcHeatingSystems -from datatypes.epc.hotwater import EpcHotWaterSystems -from backend.onboarders.mappings.as_built_wall_classifiers import AS_BUILT_WALL_CLASSIFIERS -from backend.onboarders.mappings.as_built_roof_classifiers import AS_BUILT_ROOF_CLASSIFIERS -from backend.onboarders.mappings.as_built_floor_classifiers import unknown_floor_as_built, unknown_floor_retrofitted, \ + WALL_DESCRIPTION_EFFICIENCIES, resolve_roof_efficiency +from backend.onboarders.mappings.parity.as_built_wall_classifiers import AS_BUILT_WALL_CLASSIFIERS +from backend.onboarders.mappings.parity.as_built_roof_classifiers import AS_BUILT_ROOF_CLASSIFIERS +from backend.onboarders.mappings.parity.as_built_floor_classifiers import unknown_floor_as_built, \ + unknown_floor_retrofitted, \ solid_floor_as_built, suspended_floor_as_built +from datatypes.epc.roof import EpcRoofDescriptions +from datatypes.epc.floor import EpcFloorDescriptions +from onboarders.mappings.parity.roof import roof_map, roof_unknown_age_fallback +from onboarders.mappings.parity.floor import floor_map +from onboarders.mappings.parity.heating import heating_map +from onboarders.mappings.parity.glazing import glazing_map tqdm.pandas() @@ -41,811 +44,275 @@ class ParityOnboarder(OnboarderBase): pass def map_construction_age_band(self): - data["construction_age_band"] = data["Construction Years"].map(age_band_map) - self.assert_nulls_only_from_source_nulls(data, "Construction Years", "construction_age_band") + self.data[self.landlord_construction_age_band] = self.data["Construction Years"].map(age_band_map) + self.assert_nulls_only_from_source_nulls( + self.data, "Construction Years", self.landlord_construction_age_band + ) def map_property_type(self): - data["property_type"] = data["Type"].map(property_map) - self.assert_no_nulls(data, "property_type") + self.data[self.landlord_property_type] = self.data["Type"].map(property_map) + self.assert_no_nulls(self.data, self.landlord_property_type) - def process(self): + def map_built_form(self): + self.data[self.landlord_built_form] = self.data["Attachment"].map(built_form_map) + self.assert_no_nulls(self.data, self.landlord_built_form) + + @staticmethod + def _fill_as_built(row: pd.Series) -> EpcWallDescriptions | None: + """ + Utility function, used by map_wall_construction in parity transformation module + :param row: row of input sustainability data, being transformed + :return: EpcWallDescriptions, the as built wall description for the input row, based on the wall construction + type and age band + """ + # Already resolved via direct mapping + if row.landlord_wall_description is not None: + return row.landlord_wall_description + + wall_type = row["Wall Construction"] + + # Missing construction age → conservative fallback + if pd.isnull(row.construction_age_band): + return wall_unknown_age_fallback.get(wall_type) + + classifier = AS_BUILT_WALL_CLASSIFIERS.get(wall_type) + if classifier is None: + return None + + return classifier(row.construction_age_band) + + @staticmethod + def _resolve_wall_efficiency( + description: EpcWallDescriptions, + age_band: EpcConstructionAgeBand | None, + ) -> EpcEfficiency: + # Unknown / holding descriptions → efficiency unknown + if "unknown insulation" in description.value.lower(): + return EpcEfficiency.NA + + rule = WALL_DESCRIPTION_EFFICIENCIES.get(description) + + if rule is None: + return EpcEfficiency.NA + + if isinstance(rule, EpcEfficiency): + return rule + + # Rule needs age band but we don't have one + if age_band is None or pd.isnull(age_band): + return EpcEfficiency.NA + + return rule(age_band) + + def map_wall_construction(self): + self.data[self.landlord_wall_construction] = ( + self.data[["Wall Construction", "Wall Insulation"]] + .apply(tuple, axis=1) + .map(wall_map) + ) + + self.data[self.landlord_wall_construction] = self.data.progress_apply(self._fill_as_built, axis=1) + + # Sanity check + self.assert_no_nulls(self.data, self.landlord_wall_construction) + + self.data[self.landlord_wall_efficiency] = self.data.progress_apply( + lambda row: self._resolve_wall_efficiency( + row.landlord_wall_description, + row.construction_age_band, + ), + axis=1, + ) + # Additional santify check + self.assert_no_nulls(self.data, self.landlord_wall_efficiency) + + @staticmethod + def _fill_roof_as_built(row: pd.Series) -> EpcRoofDescriptions | None: + # Already resolved + if not pd.isnull(row.landlord_roof_description): + return row.landlord_roof_description + + roof_type = row["Roof Construction"] + + classifier = AS_BUILT_ROOF_CLASSIFIERS.get(roof_type) + if classifier is None: + raise NotImplementedError(f"No roof classifier for roof type '{roof_type}'") + + if pd.isnull(row.construction_age_band): + return roof_unknown_age_fallback.get(roof_type) + + output = classifier(row.construction_age_band) + if output is None: + raise NotImplementedError( + f"Roof classification returned None for roof type '{roof_type}'" + ) + + return output + + @staticmethod + def _extract_insulation_thickness(value: str | None) -> int | None: + """ + Extract insulation thickness in mm from a string like 'mm150'. + Returns None if not present or not parseable. + """ + if value is None or pd.isnull(value): + return None + + match = re.search(r"(\d+)", str(value)) + if not match: + return None + + return int(match.group(1)) + + def map_roof_construction(self): + self.data[self.landlord_roof_construction] = ( + self.data[["Roof Construction", "Roof Insulation"]] + .progress_apply(tuple, axis=1) + .map(roof_map) + ) + + self.data[self.landlord_roof_construction] = self.data.progress_apply( + self._fill_roof_as_built, + axis=1, + ) + + # sanity check + self.assert_no_nulls(self.data, self.landlord_roof_construction) + + self.data["roof_insulation_thickness_mm"] = self.data["Roof Insulation"].apply( + self._extract_insulation_thickness + ) + + self.data[self.landlord_roof_efficiency] = self.data.progress_apply( + lambda row: resolve_roof_efficiency( + description=row.landlord_roof_description, + age_band=row.construction_age_band, + insulation_thickness=row.roof_insulation_thickness_mm, + ), + axis=1, + ) + # sanity check + self.assert_no_nulls(self.data, self.landlord_roof_efficiency) + + # Flag sloping ceiling + data[self.landlord_has_sloping_ceiling] = data["Roof Construction"].apply( + lambda x: x == "PitchedWithSlopingCeiling" + ) + + @staticmethod + def _fill_floor_as_built(row: pd.Series): + # 1. Already resolved + if row.landlord_floor_description is not None: + return row.landlord_floor_description + + age_band = row.construction_age_band + floor_type = row["Floor Construction"] + insulation = row["Floor Insulation"] + + # 2. Missing age band → conservative fallback + if pd.isnull(age_band): + return EpcFloorDescriptions.unknown + + # 3. Known floor types + if floor_type == "Solid": + return solid_floor_as_built(age_band) + + if floor_type in {"SuspendedTimber", "SuspendedNotTimber"}: + return suspended_floor_as_built(age_band) + + # 4. Unknown floor type + if floor_type == "Unknown": + if insulation == "RetroFitted": + return unknown_floor_retrofitted(age_band) + return unknown_floor_as_built(age_band) + + # 5. Truly missing / garbage input + return EpcFloorDescriptions.unknown + + def map_floor_construction(self): + self.data[self.landlord_floor_construction] = ( + self.data[["Floor Construction", "Floor Insulation"]] + .progress_apply(tuple, axis=1) + .map(floor_map) + ) + + self.data[self.landlord_floor_construction] = self.data.progress_apply( + self._fill_floor_as_built, + axis=1, + ) + + self.assert_no_nulls(self.data, self.landlord_floor_construction) + + def map_glazing(self): + # TODO: probably doesn't make sense to store multi glazed proportion, glazed type or glazed area. + # There is maybe an argument for landlord_multi_glaze_proportion as this could be variable, + # however + self.data[ + [ + self.landlord_windows_construction, + self.landlord_windows_efficiency, + self.landlord_multi_glaze_proportion, + self.landlord_glazed_type, + self.landlord_glazed_area + ] + ] = self.data["Glazing"].map(glazing_map).progress_apply(pd.Series) + + def map_heating(self): + # TODO - when mapping heating controls, we should check the existing heating controls and the efficiency rating + # For sub optimal heating controls, we're going to make an assumption as to what the heating controls are + # and the energy efficiency rating we prescribe here may not be accurate. We therefore use this as an + # upper limit + # as opposed to a guaranteed efficiency rating. To stress, this is only relevant for sub optimal heating + # controls. E.g. it may be programmer and room thermostat + self.data[ + [ + self.landlord_heating_construction, + self.landlord_heating_efficiency, + self.landlord_fuel_construction, + self.landlord_heating_controls_construction, + self.landlord_heating_controls_efficiency, + self.landlord_hot_water_system_construction, + self.landlord_hot_water_efficiency + ] + ] = self.data[ + [ + "Heating", + "Boiler Efficiency", + "Main Fuel", + "Controls Adequacy" + ] + ].progress_apply(tuple, axis=1).map(heating_map).progress_apply(pd.Series) + + def map_floor_area(self): + # This is just a rename + self.data = self.data.rename( + columns={"Total Floor Area (m2)": self.landlord_total_floor_area_m2} + ) + + def transform(self): # ------------ construction_age_band ------------ self.map_construction_age_band() # ------------ property_type ------------ self.map_property_type() + # ------------ built_form ------------ + self.map_built_form() -# We want to map the parity fields to standard EPC references. This will allow us to -# 1) Estimate EPCs, more accurately -# 2) Patch incorrect EPCs with ease -# 3) Indicate already installed measures + # ------------ Wall Construction ------------ + self.map_wall_construction() + # ------------ Roof Construction ------------ + self.map_roof_construction() -# ------------ built_form ------------ -data["built_form"] = data["Attachment"].map(built_form_map) + # ------------ Floor Construction ------------ + self.map_floor_construction() -assert pd.isnull(data["built_form"]).sum() == 0, "Some built forms were not mapped" + # ------------ Glazing ------------ + self.map_glazing() -# ------------ Wall Construction ------------ + # ------------ Heating, fuel, controls & hot water ------------ + self.map_heating() -# Unique combindations -wall_mapping = { - # Cavity walls - ('Cavity', 'FilledCavity'): EpcWallDescriptions.cavity_filled_cavity, - ('Cavity', 'Internal'): EpcWallDescriptions.cavity_internal_insulation, - ('Cavity', 'External'): EpcWallDescriptions.cavity_external_insulation, - ('Cavity', 'FilledCavityPlusInternal'): EpcWallDescriptions.cavity_filled_plus_internal, - ('Cavity', 'FilledCavityPlusExternal'): EpcWallDescriptions.cavity_filled_plus_external, - ('Cavity', 'AsBuilt'): None, # To be classified - ('Cavity', 'Unknown'): None, # To be classified - - # System built walls - ('System', 'External'): EpcWallDescriptions.system_external_insulation, - ('System', 'Internal'): EpcWallDescriptions.system_internal_insulation, - ('System', 'AsBuilt'): None, # To be classified - ('System', 'Unknown'): None, - - # Timber Frame walls - ('Timber Frame', 'Internal'): EpcWallDescriptions.timber_frame_internal_insulation, - ('Timber Frame', 'External'): EpcWallDescriptions.timber_frame_external_insulation, - ('Timber Frame', 'AsBuilt'): None, # To be classified - ('Timber Frame', 'Unknown'): None, - - # Solid Brick walls - ('Solid Brick', 'External'): EpcWallDescriptions.solid_brick_external_insulation, - ('Solid Brick', 'Internal'): EpcWallDescriptions.solid_brick_internal_insulation, - ('Solid Brick', 'AsBuilt'): None, # To be classified - ('Solid Brick', 'Unknown'): None, - - # Granite walls - ('Granite', 'External'): EpcWallDescriptions.granite_whinstone_external_insulation, - ("Granite", 'Internal'): EpcWallDescriptions.granite_whinstone_internal_insulation, - ('Granite', 'AsBuilt'): None, - ('Granite', 'Unknown'): None, - - # Sandstone walls - ('Sandstone', 'Internal'): EpcWallDescriptions.sandstone_limestone_internal_insulation, - ('Sandstone', 'External'): EpcWallDescriptions.sandstone_limestone_external_insulation, - ('Sandstone', 'Unknown'): None, - ('Sandstone', 'AsBuilt'): None, - - # Cob walls - ('Cob', 'AsBuilt'): None, -} - -WALL_UNKNOWN_AGE_FALLBACK = { - "Cavity": EpcWallDescriptions.cavity_as_built_unknown, - "Solid Brick": EpcWallDescriptions.solid_brick_as_built_unknown, - "Timber Frame": EpcWallDescriptions.timber_frame_as_built_unknown, - "System": EpcWallDescriptions.system_as_built_unknown, - "Granite": EpcWallDescriptions.granite_as_built_unknown, - "Sandstone": EpcWallDescriptions.sandstone_as_built_unknown, - "Cob": EpcWallDescriptions.cob_as_built_unknown, -} - -data["landlord_wall_description"] = ( - data[["Wall Construction", "Wall Insulation"]] - .apply(tuple, axis=1) - .map(wall_mapping) -) - - -def fill_as_built(row): - # Already resolved via direct mapping - if row.landlord_wall_description is not None: - return row.landlord_wall_description - - wall_type = row["Wall Construction"] - - # Missing construction age → conservative fallback - if pd.isnull(row.construction_age_band): - return WALL_UNKNOWN_AGE_FALLBACK.get(wall_type) - - classifier = AS_BUILT_WALL_CLASSIFIERS.get(wall_type) - if classifier is None: - return None - - return classifier(row.construction_age_band) - - -def resolve_wall_efficiency( - description: EpcWallDescriptions, - age_band: EpcConstructionAgeBand | None, -) -> EpcEfficiency: - # Unknown / holding descriptions → efficiency unknown - if "unknown insulation" in description.value.lower(): - return EpcEfficiency.NA - - rule = WALL_DESCRIPTION_EFFICIENCIES.get(description) - - if rule is None: - return EpcEfficiency.NA - - if isinstance(rule, EpcEfficiency): - return rule - - # Rule needs age band but we don't have one - if age_band is None or pd.isnull(age_band): - return EpcEfficiency.NA - - return rule(age_band) - - -data["landlord_wall_description"] = data.progress_apply(fill_as_built, axis=1) - -assert data["landlord_wall_description"].isnull().sum() == 0, ( - "Some wall descriptions could not be resolved" -) - -data["landlord_wall_efficiency"] = data.progress_apply( - lambda row: resolve_wall_efficiency( - row.landlord_wall_description, - row.construction_age_band, - ), - axis=1, -) -# Sanity check -assert data["landlord_wall_efficiency"].isnull().sum() == 0 - -# ------------ Roof Construction ------------ - - -roof_mapping = { - # Dwelling above - ('AnotherDwellingAbove', 'Another Dwelling Above'): EpcRoofDescriptions.another_dwelling_above, - ('SameDwellingAbove', 'Same Dwelling Above'): EpcRoofDescriptions.another_dwelling_above, - # Pitched, normal loft access, with a loft thickness - ('PitchedNormalLoftAccess', 'mm25'): EpcRoofDescriptions.loft_25mm_insulation, - ('PitchedNormalLoftAccess', 'mm50'): EpcRoofDescriptions.loft_50mm_insulation, - ('PitchedNormalLoftAccess', 'mm75'): EpcRoofDescriptions.loft_75mm_insulation, - ('PitchedNormalLoftAccess', 'mm100'): EpcRoofDescriptions.loft_100mm_insulation, - ('PitchedNormalLoftAccess', 'mm150'): EpcRoofDescriptions.loft_150mm_insulation, - ('PitchedNormalLoftAccess', 'mm200'): EpcRoofDescriptions.loft_200mm_insulation, - ('PitchedNormalLoftAccess', 'mm250'): EpcRoofDescriptions.loft_250mm_insulation, - ('PitchedNormalLoftAccess', 'mm270'): EpcRoofDescriptions.loft_270mm_insulation, - ('PitchedNormalLoftAccess', 'mm300'): EpcRoofDescriptions.loft_300mm_insulation, - ('PitchedNormalLoftAccess', 'mm350'): EpcRoofDescriptions.loft_350mm_insulation, - ('PitchedNormalLoftAccess', 'mm400'): EpcRoofDescriptions.loft_400mm_plus_insulation, - - # Pitched, no loft access, with a loft thickness - ('PitchedNormalNoLoftAccess', 'mm25'): EpcRoofDescriptions.loft_25mm_insulation, - ('PitchedNormalNoLoftAccess', 'mm50'): EpcRoofDescriptions.loft_50mm_insulation, - ('PitchedNormalNoLoftAccess', 'mm75'): EpcRoofDescriptions.loft_75mm_insulation, - ('PitchedNormalNoLoftAccess', 'mm100'): EpcRoofDescriptions.loft_100mm_insulation, - ('PitchedNormalNoLoftAccess', 'mm150'): EpcRoofDescriptions.loft_150mm_insulation, - ('PitchedNormalNoLoftAccess', 'mm200'): EpcRoofDescriptions.loft_200mm_insulation, - ('PitchedNormalNoLoftAccess', 'mm250'): EpcRoofDescriptions.loft_250mm_insulation, - ('PitchedNormalNoLoftAccess', 'mm270'): EpcRoofDescriptions.loft_270mm_insulation, - ('PitchedNormalNoLoftAccess', 'mm300'): EpcRoofDescriptions.loft_300mm_insulation, - ('PitchedNormalNoLoftAccess', 'mm350'): EpcRoofDescriptions.loft_350mm_insulation, - ('PitchedNormalNoLoftAccess', 'mm400'): EpcRoofDescriptions.loft_400mm_plus_insulation, - - # All pitched options with asbuilt or unknown got to EpcRoofDescriptions.pitched_insulated_assumed - # With access - ('PitchedNormalLoftAccess', nan): EpcRoofDescriptions.pitched_insulated_assumed, - ('PitchedNormalLoftAccess', 'AsBuilt'): EpcRoofDescriptions.pitched_insulated_assumed, - ('PitchedNormalLoftAccess', 'Unknown'): EpcRoofDescriptions.pitched_insulated_assumed, - # No access - ('PitchedNormalNoLoftAccess', nan): EpcRoofDescriptions.pitched_insulated_assumed, - ('PitchedNormalNoLoftAccess', 'AsBuilt'): EpcRoofDescriptions.pitched_insulated_assumed, - ('PitchedNormalNoLoftAccess', 'Unknown'): EpcRoofDescriptions.pitched_insulated_assumed, - - # Flat - ('Flat', 'NoInsulation'): EpcRoofDescriptions.flat_no_insulation, - # Flat - limited insulation - ('Flat', '12mm'): EpcRoofDescriptions.flat_limited_insulation, - ('Flat', 'mm25'): EpcRoofDescriptions.flat_limited_insulation, - ('Flat', 'mm50'): EpcRoofDescriptions.flat_limited_insulation, - # Flat insulated - ('Flat', 'mm75'): EpcRoofDescriptions.flat_insulated, - ('Flat', 'mm100'): EpcRoofDescriptions.flat_insulated, - ('Flat', 'mm150'): EpcRoofDescriptions.flat_insulated, - ('Flat', 'mm200'): EpcRoofDescriptions.flat_insulated, - ('Flat', 'mm250'): EpcRoofDescriptions.flat_insulated, - ('Flat', 'mm300'): EpcRoofDescriptions.flat_insulated, - ('Flat', 'mm350'): EpcRoofDescriptions.flat_insulated, - ('Flat', 'mm400'): EpcRoofDescriptions.flat_insulated, - # Flat - as built or unknown - ('Flat', 'AsBuilt'): None, # To be classified - ('Flat', nan): None, # To be classified - ('Flat', 'Unknown'): None, # To be classified - - # 12mm = very poor & has limited insulation description - # 25, 50 = poor & has limited insulation description - # 75, 100, 125mm = average (Flat, insulated) - # 150, 175, 200, 225, 250mm = good (Flat, insulated) - # 270mm+ = very good (Flat, insulated) - - # Thatched - ('PitchedThatched', 'mm50'): EpcRoofDescriptions.thatched_with_additional_insulation, - ('PitchedThatched', 'mm150'): EpcRoofDescriptions.thatched_with_additional_insulation, - ('PitchedThatched', 'mm300'): EpcRoofDescriptions.thatched_with_additional_insulation, - ('PitchedThatched', 'Unknown'): EpcRoofDescriptions.thatched, # efficiency classified based on age - - # Sloping: - # Limited (12 very poor, 25-50 poor) - ('PitchedWithSlopingCeiling', 'mm12'): EpcRoofDescriptions.sloping_pitched_limited_insulation, - ('PitchedWithSlopingCeiling', 'mm25'): EpcRoofDescriptions.sloping_pitched_limited_insulation, - ('PitchedWithSlopingCeiling', 'mm50'): EpcRoofDescriptions.sloping_pitched_limited_insulation, - # Insulated 75mm+ (75 - 125 average, 150 - 250 good, 270+ very good) - ('PitchedWithSlopingCeiling', 'mm75'): EpcRoofDescriptions.sloping_pitched_insulated, - ('PitchedWithSlopingCeiling', 'mm100'): EpcRoofDescriptions.sloping_pitched_insulated, - ('PitchedWithSlopingCeiling', 'mm150'): EpcRoofDescriptions.sloping_pitched_insulated, - ('PitchedWithSlopingCeiling', 'mm200'): EpcRoofDescriptions.sloping_pitched_insulated, - ('PitchedWithSlopingCeiling', 'mm250'): EpcRoofDescriptions.sloping_pitched_insulated, - ('PitchedWithSlopingCeiling', 'mm270'): EpcRoofDescriptions.sloping_pitched_insulated, - ('PitchedWithSlopingCeiling', 'mm300'): EpcRoofDescriptions.sloping_pitched_insulated, - ('PitchedWithSlopingCeiling', 'mm350'): EpcRoofDescriptions.sloping_pitched_insulated, - ('PitchedWithSlopingCeiling', 'mm400'): EpcRoofDescriptions.sloping_pitched_insulated, - # As built/unknown - ('PitchedWithSlopingCeiling', 'AsBuilt'): None, # To be classified - ('PitchedWithSlopingCeiling', nan): None, # To be classified - ('PitchedWithSlopingCeiling', 'Unknown'): None, # -} - -ROOF_UNKNOWN_AGE_FALLBACK = { - "Flat": EpcRoofDescriptions.flat_as_built_unknown, - "PitchedWithSlopingCeiling": EpcRoofDescriptions.sloping_pitched_as_built_unknown, - "PitchedThatched": EpcRoofDescriptions.thatched_as_built_unknown, - "PitchedNormalLoftAccess": EpcRoofDescriptions.loft_as_built_unknown, - "PitchedNormalNoLoftAccess": EpcRoofDescriptions.loft_as_built_unknown, -} - - -def fill_roof_as_built(row): - # Already resolved - if not pd.isnull(row.landlord_roof_description): - return row.landlord_roof_description - - roof_type = row["Roof Construction"] - - classifier = AS_BUILT_ROOF_CLASSIFIERS.get(roof_type) - if classifier is None: - raise NotImplementedError(f"No roof classifier for roof type '{roof_type}'") - - if pd.isnull(row.construction_age_band): - return ROOF_UNKNOWN_AGE_FALLBACK.get(roof_type) - - output = classifier(row.construction_age_band) - if output is None: - raise NotImplementedError( - f"Roof classification returned None for roof type '{roof_type}'" - ) - - return output - - -data["landlord_roof_description"] = ( - data[["Roof Construction", "Roof Insulation"]] - .progress_apply(tuple, axis=1) - .map(roof_mapping) -) - -data["landlord_roof_description"] = data.progress_apply( - fill_roof_as_built, - axis=1, -) -# Sanity check -assert data["landlord_roof_description"].isnull().sum() == 0, ( - "Some roof descriptions could not be resolved" -) - - -def extract_insulation_thickness(value: str | None) -> int | None: - """ - Extract insulation thickness in mm from a string like 'mm150'. - Returns None if not present or not parseable. - """ - if value is None or pd.isnull(value): - return None - - match = re.search(r"(\d+)", str(value)) - if not match: - return None - - return int(match.group(1)) - - -data["roof_insulation_thickness_mm"] = data["Roof Insulation"].apply( - extract_insulation_thickness -) - -data["landlord_roof_efficiency"] = data.progress_apply( - lambda row: resolve_roof_efficiency( - description=row.landlord_roof_description, - age_band=row.construction_age_band, - insulation_thickness=row.roof_insulation_thickness_mm, - ), - axis=1, -) - -assert data["landlord_roof_efficiency"].isnull().sum() == 0 - -# Flag sloping ceiling -data["has_sloping_ceiling"] = data["Roof Construction"].apply( - lambda x: x == "PitchedWithSlopingCeiling" -) - -# ------------ Floor Construction ------------ - -floor_mapping = { - # Solid floor - ('Solid', 'AsBuilt'): None, # Mapped - ('Solid', 'Unknown'): None, # Mapped - ('Solid', nan): None, # Mapped - ('Solid', 'RetroFitted'): EpcFloorDescriptions.solid_insulated, - - # Suspended floor - ('SuspendedTimber', nan): None, # Mapped suspended_floor_as_built - ('SuspendedTimber', 'AsBuilt'): None, # Mapped suspended_floor_as_built - ('SuspendedTimber', 'RetroFitted'): EpcFloorDescriptions.suspended_insulated, - ('SuspendedTimber', 'Unknown'): None, # Mapped suspended_floor_as_built - ('SuspendedNotTimber', 'RetroFitted'): EpcFloorDescriptions.suspended_insulated, - ('SuspendedNotTimber', nan): None, # Mapped suspended_floor_as_built - ('SuspendedNotTimber', 'Unknown'): None, # Mapped suspended_floor_as_built - ('SuspendedNotTimber', 'AsBuilt'): None, # Mapped suspended_floor_as_built - - # Unknown type - mapped on age - ('Unknown', 'Unknown'): None, # Mapped unknown_floor_as_built - ('Unknown', 'RetroFitted'): None, # Mapped unknown_floor_retrofitted - (nan, nan): None, # No actual information! - ('Unknown', 'AsBuilt'): None, # Mapped unknown_floor_as_built -} - -data["landlord_floor_description"] = ( - data[["Floor Construction", "Floor Insulation"]] - .progress_apply(tuple, axis=1) - .map(floor_mapping) -) - - -def fill_floor_as_built(row): - # 1. Already resolved - if row.landlord_floor_description is not None: - return row.landlord_floor_description - - age_band = row.construction_age_band - floor_type = row["Floor Construction"] - insulation = row["Floor Insulation"] - - # 2. Missing age band → conservative fallback - if pd.isnull(age_band): - return EpcFloorDescriptions.unknown - - # 3. Known floor types - if floor_type == "Solid": - return solid_floor_as_built(age_band) - - if floor_type in {"SuspendedTimber", "SuspendedNotTimber"}: - return suspended_floor_as_built(age_band) - - # 4. Unknown floor type - if floor_type == "Unknown": - if insulation == "RetroFitted": - return unknown_floor_retrofitted(age_band) - return unknown_floor_as_built(age_band) - - # 5. Truly missing / garbage input - return EpcFloorDescriptions.unknown - - -data["landlord_floor_description"] = data.progress_apply( - fill_floor_as_built, - axis=1, -) - -# All values should be remapped now -assert data["landlord_floor_description"].isnull().sum() == 0, ( - "Some floor descriptions could not be resolved" -) - -# ------------ Glazing ------------ -glazing_map = { - # (description, energy efficiency, multi_glaze_proportion, glazed_type, glazed_area - # For SAP 10 assessments, The glazed type and glazed area are not populated in the EPC API data any more - "Double 2002 or later": ("Fully double glazed", EpcEfficiency.AVERAGE, 1, None, None), - "Double before 2002": ("Fully double glazed", EpcEfficiency.POOR, 1, None, None), - "Double but age unknown": ("Fully double glazed", EpcEfficiency.POOR, 1, None, None), - "Single": ("Single glazed", EpcEfficiency.VERY_POOR, 0, None, None), - # For triple glazing, with age unknown, the performance is only average, whereas if it's a post 2022 - # installation, it's classed as high performance glazing with good efficiency. We'll need to be considerate as to - # how we make updates to the windows data. - # Triple known data is high performance glazing with Good efficiency (at least) - "Triple": ("Fully triple glazed", EpcEfficiency.AVERAGE, 1, None, None), - # This is also classed as high performance glazing - "DoubleKnownData": ("High performance glazing", EpcEfficiency.GOOD, 1, None, None), - # Under SAP 10, secondary glazing is classed as poor efficiency (whereas under SAP 2012 it was generally good) - "Secondary": ("Full secondary glazing", EpcEfficiency.POOR, 1, None, None), - "TripleKnownData": ("High performance glazing", EpcEfficiency.GOOD, 1, None, None), -} - -data[["landlord_windows_description", - "landlord_windows_efficiency", - "landlord_multi_glaze_proportion", - "landlord_glazed_type", - "landlord_glazed_area"]] = data["Glazing"].map(glazing_map).progress_apply(pd.Series) - -# Peform the remapping. The columns we wish to produce are the following: -# 1) landlord_windows_description -# 2) landlord_windows_efficiency -# 3) landlord_multi_glaze_proportion - maybe don't need to store this, same for glazing type and area - - -# ------------ Heating, fuel, controls & hot water ------------ - - -# We map to: -# 1) Heating description -# 2) Heating efficiency -# 3) Fuel type -# 4) Heating controls -# 5) Heating controls efficiency -# 6) Hot water system -# 7) Hot water efficiency - -# TODO - when mapping heating controls, we should check the existing heating controls and the efficiency rating -# For sub optimal heating controls, we're going to make an assumption as to what the heating controls are -# and the energy efficiency rating we prescribe here may not be accurate. We therefore use this as an upper limit -# as opposed to a guaranteed efficiency rating. To stress, this is only relevant for sub optimal heating -# controls. E.g. it may be programmer and room thermostat - -# Boiler ratings based on efficiency -# 90%+ = A -# 86-89.9% = B -> Mapped to good efficiency -# 78 - 85% = C -# 70 - 77.9% = D -# 65 - 69.9% = E -# 60 - 64.9% = F -# <60% = G - -heating_map = { - # 0 - ('Boilers', 'A', 'ElectricityNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 1 - ('Boilers', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 2 - ('Boilers', 'A', 'ElectricityNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 3 - ('Boilers', 'A', 'LPGNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_and_radiators_lpg, EpcEfficiency.POOR, EpcFuel.lpg_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 4 - ('Boilers', 'A', 'MainsGasNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.VERY_GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 5 - ('Boilers', 'A', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.VERY_GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 6 - ('Boilers', 'A', 'MainsGasNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.VERY_GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 7 - ('Boilers', 'B', 'MainsGasNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 8 - ('Boilers', 'B', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 9 - ('Boilers', 'B', 'MainsGasNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 10 - ('Boilers', 'C', 'ElectricityNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 11 - ('Boilers', 'C', 'ElectricityNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 12 - ('Boilers', 'C', 'ElectricityNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 13 - ('Boilers', 'C', 'LPGNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_and_radiators_lpg, EpcEfficiency.POOR, EpcFuel.lpg_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 14 - ('Boilers', 'C', 'LPGNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_and_radiators_lpg, EpcEfficiency.POOR, EpcFuel.lpg_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 15 - ('Boilers', 'C', 'MainsGasNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 16 - ('Boilers', 'C', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 17 - ('Boilers', 'C', 'MainsGasNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - ('Boilers', 'C', 'OilNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_radiators_oil, EpcEfficiency.AVERAGE, EpcFuel.oil_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 19 - ('Boilers', 'C', 'OilNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_radiators_oil, EpcEfficiency.AVERAGE, EpcFuel.oil_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 20 - ('Boilers', 'C', 'OilNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.boiler_radiators_oil, EpcEfficiency.AVERAGE, EpcFuel.oil_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 21 - ('Boilers', 'D', 'MainsGasNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 22 - ('Boilers', 'D', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 23 - ('Boilers', 'D', 'MainsGasNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 24 - ('Boilers', 'E', 'ElectricityNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_and_radiators_electric, EpcEfficiency.VERY_POOR, EpcFuel.electricity_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 25 - ('Boilers', 'E', 'MainsGasNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 26 - ('Boilers', 'E', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 27 - ('Boilers', 'E', 'MainsGasNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 28 - ('Boilers', 'E', 'OilNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_radiators_oil, EpcEfficiency.AVERAGE, EpcFuel.oil_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 29 - ('Boilers', 'E', 'OilNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_radiators_oil, EpcEfficiency.AVERAGE, EpcFuel.oil_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 30 - ('Boilers', 'F', 'MainsGasNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 31 - ('Boilers', 'F', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 32 - ('Boilers', 'F', 'MainsGasNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 33 - ('Boilers', 'G', 'MainsGasNotCommunity', 'Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 34 - ('Boilers', 'G', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 35 - ('Boilers', 'G', 'MainsGasNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.boiler_radiators_mains_gas, EpcEfficiency.AVERAGE, EpcFuel.mains_gas_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 36 - ('Electric underfloor', 'A', 'ElectricityNotCommunity', 'Optimal'): ( - EpcHeatingSystems.electric_underfloor_heating, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - # 37 - ('Electric underfloor', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.electric_underfloor_heating, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - # 38 - ('Electric underfloor', 'A', 'ElectricityNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.electric_underfloor_heating, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - # 39 - ('Heat pumps (warm air)', 'A', 'ElectricityNotCommunity', 'Optimal'): ( - EpcHeatingSystems.air_to_air_ashp, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 40 - ('Heat pumps (warm air)', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.air_to_air_ashp, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 41 - ('Heat pumps (wet)', 'A', 'ElectricityNotCommunity', 'Optimal'): ( - EpcHeatingSystems.ashp_radiators_electric, EpcEfficiency.GOOD, EpcFuel.electricity_not_community, - EpcHeatingControls.programmer_room_thermostat_trvs, EpcEfficiency.GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 42 - ('Heat pumps (wet)', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.ashp_radiators_electric, EpcEfficiency.GOOD, EpcFuel.electricity_not_community, - EpcHeatingControls.programmers_trvs_bypass, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 43 - ('Heat pumps (wet)', 'A', 'ElectricityNotCommunity', 'Top Spec'): ( - EpcHeatingSystems.ashp_radiators_electric, EpcEfficiency.GOOD, EpcFuel.electricity_not_community, - EpcHeatingControls.time_and_temperature_zone_control, EpcEfficiency.VERY_GOOD, - EpcHotWaterSystems.from_main_system, EpcEfficiency.AVERAGE - ), - # 44 - ('Room heaters', 'A', 'ElectricityNotCommunity', 'Optimal'): ( - EpcHeatingSystems.room_heaters_electric, EpcEfficiency.POOR, EpcFuel.electricity_not_community, - EpcHeatingControls.programmer_and_appliance_thermostats, EpcEfficiency.GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - # 45 - ('Room heaters', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.room_heaters_electric, EpcEfficiency.POOR, EpcFuel.electricity_not_community, - EpcHeatingControls.appliance_thermostats, EpcEfficiency.GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - # 46 - ('Room heaters', 'C', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.room_heaters_mains_gas, EpcEfficiency.AVERAGE, EpcFuel.mains_gas_not_community, - EpcHeatingControls.appliance_thermostats, EpcEfficiency.GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - # 47 - water done from here - ('Room heaters', 'F', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.room_heaters_mains_gas, EpcEfficiency.POOR, EpcFuel.mains_gas_not_community, - EpcHeatingControls.appliance_thermostats, EpcEfficiency.GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - ('Room heaters', 'G', 'MainsGasNotCommunity', 'Optimal'): ( - EpcHeatingSystems.room_heaters_mains_gas, EpcEfficiency.POOR, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmer_and_appliance_thermostats, EpcEfficiency.GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - ('Room heaters', 'G', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.room_heaters_mains_gas, EpcEfficiency.POOR, EpcFuel.mains_gas_not_community, - EpcHeatingControls.appliance_thermostats, EpcEfficiency.GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - ('Room heaters', 'G', 'SmokelessCoal', 'Sub Optimal'): ( - EpcHeatingSystems.room_heaters_smokeless_fuel, EpcEfficiency.VERY_POOR, EpcFuel.smokeless_coal, - EpcHeatingControls.appliance_thermostats, EpcEfficiency.GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - ('Storage heaters', 'A', 'ElectricityNotCommunity', 'Optimal'): ( - EpcHeatingSystems.electric_storage_heaters, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, - EpcHeatingControls.automatic_charge_control, EpcEfficiency.AVERAGE, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - ('Storage heaters', 'A', 'ElectricityNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.electric_storage_heaters, EpcEfficiency.AVERAGE, EpcFuel.electricity_not_community, - EpcHeatingControls.manual_charge_control, EpcEfficiency.POOR, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - ('Warm Air (not heat pump)', 'G', 'ElectricityNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.warm_air_electricaire, EpcEfficiency.GOOD, EpcFuel.electricity_not_community, - EpcHeatingControls.programmer_and_atleast_two_room_thermostats, EpcEfficiency.GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ), - ('Warm Air (not heat pump)', 'G', 'MainsGasNotCommunity', 'Sub Optimal'): ( - EpcHeatingSystems.warm_air_mains_gas, EpcEfficiency.GOOD, EpcFuel.mains_gas_not_community, - EpcHeatingControls.programmer_and_atleast_two_room_thermostats, EpcEfficiency.GOOD, - EpcHotWaterSystems.electric_immersion_off_peak, EpcEfficiency.AVERAGE - ) -} - -# Apply the mapping -data[ - [ - "landlord_heating_description", - "landlord_heating_efficiency", - "landlord_fuel_type", - "landlord_heating_controls_description", - "landlord_heating_controls_efficiency", - "landlord_hot_water_system_description", - "landlord_hot_water_efficiency" - ] -] = data[ - [ - "Heating", - "Boiler Efficiency", - "Main Fuel", - "Controls Adequacy" - ] -].progress_apply(tuple, axis=1).map(heating_map).progress_apply(pd.Series) - -# ------------ Floor Area ------------ -# This is just a rename -data = data.rename( - columns={"Total Floor Area (m2)": "landlord_total_floor_area_m2"} -) + # ------------ Floor Area ------------ + self.map_floor_area() diff --git a/datatypes/epc/floor.py b/datatypes/epc/floor.py new file mode 100644 index 00000000..41786101 --- /dev/null +++ b/datatypes/epc/floor.py @@ -0,0 +1,17 @@ +from enum import Enum + + +class EpcFloorDescriptions(Enum): + # Solid floor + solid_insulated = "Solid, insulated" + solid_insulated_assumed = "Solid, insulated (assumed)" + solid_no_insulation_assumed = "Solid, no insulation (assumed)" + solid_limited_insulation_assumed = "Solid, limited insulation (assumed)" + + # Suspended floor + suspended_insulated = "Suspended, insulated" + suspended_insulated_assumed = "Suspended, insulated (assumed)" + suspended_no_insulation_assumed = "Suspended, no insulation (assumed)" + suspended_limited_insulation_assumed = "Suspended, limited insulation (assumed)" + + unknown = None # We don't resolve anything