From 04df5743c239f0bcd695311326be29cf4eb76e9e Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 30 Sep 2024 14:55:13 +0100 Subject: [PATCH] deleted irrelevant tests for solar pv costs --- .../tests/test_air_source_heat_pump.py | 944 ------------------ recommendations/tests/test_costs.py | 336 +------ 2 files changed, 51 insertions(+), 1229 deletions(-) delete mode 100644 recommendations/tests/test_air_source_heat_pump.py diff --git a/recommendations/tests/test_air_source_heat_pump.py b/recommendations/tests/test_air_source_heat_pump.py deleted file mode 100644 index 0d69b10d..00000000 --- a/recommendations/tests/test_air_source_heat_pump.py +++ /dev/null @@ -1,944 +0,0 @@ -import pandas as pd -import msgpack -from datetime import datetime - -from utils.s3 import read_dataframe_from_s3_parquet, read_from_s3 -from backend.Property import Property -from recommendations.HeatingRecommender import HeatingRecommender -from recommendations.Recommendations import Recommendations -from etl.epc.Record import EPCRecord -from etl.solar.SolarPhotoSupply import SolarPhotoSupply -from backend.ml_models.api import ModelApi - - -def find_examples(): - """ Some scrappy helper code to find EPC examples""" - # Let's look for some testing data, where the only thing different pre and post is the installation of an - # air source heat pump - data = read_dataframe_from_s3_parquet( - bucket_name="retrofit-data-dev", - file_key="sap_change_model/2024-03-24-15-51-13/dataset_no_cleaning.parquet" - ) - - # Firstly, take records where before there was no air source heat pump and afterwards there was - data = data[ - data["has_air_source_heat_pump_ending"] & ~data["has_air_source_heat_pump"] - ] - - # Start with a property that has a boiler - data = data[data["has_boiler"]] - - static_columns = [ - # Walls - 'walls_thermal_transmittance_ending', - 'is_filled_cavity_ending', - 'is_park_home_ending', - 'walls_insulation_thickness_ending', - 'external_insulation_ending', - 'internal_insulation_ending', - # Floors - # 'floor_thermal_transmittance_ending', # Don't subset on this, because it changes based on floor area - 'floor_insulation_thickness_ending', - # Roof - 'roof_thermal_transmittance_ending', - 'is_at_rafters_ending', - 'roof_insulation_thickness_ending', - # Hot water - air source heat pump will shange the hot water system (probably from whatever it was -> main) - # 'heater_type_ending', - # 'system_type_ending', - # 'thermostat_characteristics_ending', - # 'heating_scope_ending', - # 'energy_recovery_ending', - # 'hotwater_tariff_type_ending', - # 'extra_features_ending', - # 'chp_systems_ending', - # 'distribution_system_ending', - # 'no_system_present_ending', - # 'appliance_ending', - # Heating - Will change when installing an ASHP - # 'has_radiators_ending', - # 'has_fan_coil_units_ending', - # 'has_pipes_in_screed_above_insulation_ending', - # 'has_pipes_in_insulated_timber_floor_ending', - # 'has_pipes_in_concrete_slab_ending', - # 'has_boiler_ending', - # 'has_air_source_heat_pump_ending', # We want the air source heat pump to change - # 'has_room_heaters_ending', - # 'has_electric_storage_heaters_ending', - # 'has_warm_air_ending', - # 'has_electric_underfloor_heating_ending', - # 'has_electric_ceiling_heating_ending', - # 'has_community_scheme_ending', - # 'has_ground_source_heat_pump_ending', - # 'has_no_system_present_ending', - # 'has_portable_electric_heaters_ending', - # 'has_water_source_heat_pump_ending', - # 'has_electric_heat_pump_ending', - # 'has_micro-cogeneration_ending', - # 'has_solar_assisted_heat_pump_ending', - # 'has_exhaust_source_heat_pump_ending', - # 'has_community_heat_pump_ending', - # 'has_electric_ending', - # 'has_mains_gas_ending', - # 'has_wood_logs_ending', 'has_coal_ending', 'has_oil_ending', - # 'has_wood_pellets_ending', 'has_anthracite_ending', 'has_dual_fuel_mineral_and_wood_ending', - # 'has_smokeless_fuel_ending', 'has_lpg_ending', 'has_b30k_ending', 'has_electricaire_ending', - # 'has_assumed_for_most_rooms_ending', 'has_underfloor_heating_ending', - # 'thermostatic_control_ending', - # 'charging_system_ending', - # 'switch_system_ending', - # 'no_control_ending', - # 'dhw_control_ending', - # 'community_heating_ending', - # 'multiple_room_thermostats_ending', - # 'auxiliary_systems_ending', - # 'trvs_ending', - # 'rate_control_ending', - # Window - 'glazing_type_ending', - # Fuel - could change with ASHP - # 'fuel_type_ending', - # 'main-fuel_tariff_type_ending', - # 'is_community_ending', - # 'no_individual_heating_or_community_network_ending', - # 'complex_fuel_type_ending', - - 'mechanical_ventilation_ending', 'secondheat_description_ending', 'glazed_type_ending', - 'multi_glaze_proportion_ending', 'low_energy_lighting_ending', 'number_open_fireplaces_ending', - 'solar_water_heating_flag_ending', - 'photo_supply_ending', - 'energy_tariff_ending', - 'extension_count_ending', - 'total_floor_area_ending', - # 'hot_water_energy_eff_ending', - 'floor_energy_eff_ending', - 'windows_energy_eff_ending', - 'walls_energy_eff_ending', - 'sheating_energy_eff_ending', - 'roof_energy_eff_ending', - # 'mainheat_energy_eff_ending', - # 'mainheatc_energy_eff_ending', - 'lighting_energy_eff_ending', - 'number_habitable_rooms_ending', - 'number_heated_rooms_ending', - ] - - for col in static_columns: - - base_starting = col.split("_ending")[0] - if base_starting + "_starting" in data.columns: - starting_col = base_starting + "_starting" - else: - starting_col = base_starting - # Filter - print("Column: %s" % col) - print("Starting size: %s" % data.shape[0]) - data = data[data[starting_col] == data[col]] - print("Ending size: %s" % data.shape[0]) - - z = data[['uprn', col, starting_col]] - - # Great example UPRNs - # 100030969273 - # 10034685399 - Completely transforms the heating and hot water systems in the home (goes from oil -> electricity) - # 100091200828 - goes from a liquid petroleum gas boiler to ashp - - # Look for starting with a gas boiler - data[ - data["has_boiler"] & data["has_radiators"] & data["has_mains_gas"] & ~data["has_boiler_ending"] - ] - - # UPRN: 100011776843 - - -class TestAirSourceHeatPump: - - def test_eligible(self): - # This tests a house, which will be suitable for an air source heat pump - epc_record = EPCRecord() - epc_record.prepared_epc = { - "county": "Broxbourne", - "mainheat-energy-eff": "Good", - "hot-water-energy-eff": "Good", - "mainheatc-energy-eff": "Good", - "number-heated-rooms": 5, - "property-type": "House", - "built-form": "Semi-Detached" - } - - property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record) - property_instance.main_heating = { - 'original_description': 'Boiler and radiators, mains gas', - "clean_description": "Boiler and radiators, mains gas", - 'has_radiators': True, - 'has_fan_coil_units': False, 'has_pipes_in_screed_above_insulation': False, - 'has_pipes_in_insulated_timber_floor': False, 'has_pipes_in_concrete_slab': False, 'has_boiler': True, - 'has_air_source_heat_pump': False, - 'has_room_heaters': False, 'has_electric_storage_heaters': False, - 'has_warm_air': False, - 'has_electric_underfloor_heating': False, - 'has_electric_ceiling_heating': False, 'has_community_scheme': False, - 'has_ground_source_heat_pump': False, 'has_no_system_present': False, - 'has_portable_electric_heaters': False, - 'has_water_source_heat_pump': False, 'has_electric': False, - 'has_mains_gas': True, 'has_wood_logs': False, - 'has_coal': False, 'has_oil': False, 'has_wood_pellets': False, - 'has_anthracite': False, - 'has_dual_fuel_mineral_and_wood': False, 'has_smokeless_fuel': False, - 'has_lpg': False, 'has_assumed': False, - 'has_electricaire': False, 'has_assumed_for_most_rooms': False, - 'has_underfloor_heating': False, - "has_electric_heat_pumps": False, - "has_micro-cogeneration": False - } - property_instance.main_fuel = { - 'original_description': 'mains gas (not community)', 'fuel_type': 'mains gas', - 'tariff_type': None, - 'is_community': False, 'no_individual_heating_or_community_network': False, - 'complex_fuel_type': None - } - property_instance.hotwater = { - 'original_description': 'From main system', - 'clean_description': 'From main system', - 'heater_type': None, - 'system_type': 'from main system', - 'thermostat_characteristics': None, 'heating_scope': None, - 'energy_recovery': None, 'tariff_type': None, - 'extra_features': None, 'chp_systems': None, 'distribution_system': None, - 'no_system_present': None, - 'assumed': False, "appliance": None - } - property_instance.main_heating_controls = { - 'original_description': 'Programmer, room thermostat and TRVs', - 'thermostatic_control': 'room thermostat', 'charging_system': None, 'switch_system': 'programmer', - 'no_control': None, 'dhw_control': None, 'community_heating': None, 'multiple_room_thermostats': False, - 'auxiliary_systems': None, 'trvs': 'trvs', 'rate_control': None - - } - - recommender = HeatingRecommender(property_instance=property_instance) - - assert not recommender.heating_recommendations - - recommender.recommend(phase=0) - - assert recommender.recommendation is None - - def test_air_source_heat_pump_gas_boiler_starting(self): - starting_epc = { - 'low-energy-fixed-light-count': '', 'address': '430 Gidlow Lane', 'uprn-source': 'Energy Assessor', - 'floor-height': '2.62', 'heating-cost-potential': '599', 'unheated-corridor-length': '', - 'hot-water-cost-potential': '67', 'construction-age-band': 'England and Wales: 1950-1966', - 'potential-energy-rating': 'C', 'mainheat-energy-eff': 'Good', 'windows-env-eff': 'Good', - 'lighting-energy-eff': 'Very Good', 'environment-impact-potential': '72', - 'glazed-type': 'double glazing installed during or after 2002', 'heating-cost-current': '913', - 'address3': '', 'mainheatcont-description': 'Programmer, no room thermostat', 'sheating-energy-eff': 'N/A', - 'property-type': 'House', 'local-authority-label': 'Wigan', 'fixed-lighting-outlets-count': '9', - 'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '210', - 'county': '', 'postcode': 'WN6 8RG', 'solar-water-heating-flag': 'N', 'constituency': 'E14001039', - 'co2-emissions-potential': '2.6', 'number-heated-rooms': '4', - 'floor-description': 'Solid, no insulation (assumed)', 'energy-consumption-potential': '180', - 'local-authority': 'E08000010', 'built-form': 'Mid-Terrace', 'number-open-fireplaces': '0', - 'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2022-02-15', - 'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '78', 'address1': '430 Gidlow Lane', - 'heat-loss-corridor': '', 'flat-storey-count': '', 'constituency-label': 'Wigan', - 'roof-energy-eff': 'Very Poor', 'total-floor-area': '80.0', 'building-reference-number': '10002334112', - 'environment-impact-current': '38', 'co2-emissions-current': '6.2', - 'roof-description': 'Pitched, no insulation (assumed)', 'floor-energy-eff': 'N/A', - 'number-habitable-rooms': '4', 'address2': '', 'hot-water-env-eff': 'Poor', 'posttown': 'WIGAN', - 'mainheatc-energy-eff': 'Very Poor', 'main-fuel': 'mains gas (not community)', - 'lighting-env-eff': 'Very Good', 'windows-energy-eff': 'Good', 'floor-env-eff': 'N/A', - 'sheating-env-eff': 'N/A', 'lighting-description': 'Low energy lighting in all fixed outlets', - 'roof-env-eff': 'Very Poor', 'walls-energy-eff': 'Average', 'photo-supply': '0.0', - 'lighting-cost-potential': '67', 'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100', - 'main-heating-controls': '', 'lodgement-datetime': '2022-02-23 16:39:41', 'flat-top-storey': '', - 'current-energy-rating': 'E', 'secondheat-description': 'Room heaters, mains gas', - 'walls-env-eff': 'Average', 'transaction-type': 'ECO assessment', 'uprn': '100011776843', - 'current-energy-efficiency': '45', 'energy-consumption-current': '441', - 'mainheat-description': 'Boiler and radiators, mains gas', 'lighting-cost-current': '67', - 'lodgement-date': '2022-02-23', 'extension-count': '1', 'mainheatc-env-eff': 'Very Poor', - 'lmk-key': '46cb404438a6d88ddff8965cab8b3027ec15c32d93e0b6a5f0381a5109b9bb0d', 'wind-turbine-count': '0', - 'tenure': 'Owner-occupied', 'floor-level': '', 'potential-energy-efficiency': '77', - 'hot-water-energy-eff': 'Poor', 'low-energy-lighting': '100', - 'walls-description': 'Cavity wall, filled cavity', - 'hotwater-description': 'From main system, no cylinder thermostat' - } - - ending_epc = { - 'low-energy-fixed-light-count': '', 'address': '430 Gidlow Lane', 'uprn-source': 'Energy Assessor', - 'floor-height': '2.62', 'heating-cost-potential': '803', 'unheated-corridor-length': '', - 'hot-water-cost-potential': '292', 'construction-age-band': 'England and Wales: 1950-1966', - 'potential-energy-rating': 'C', 'mainheat-energy-eff': 'Very Good', 'windows-env-eff': 'Good', - 'lighting-energy-eff': 'Very Good', 'environment-impact-potential': '78', - 'glazed-type': 'double glazing installed during or after 2002', 'heating-cost-current': '861', - 'address3': '', 'mainheatcont-description': 'Time and temperature zone control', - 'sheating-energy-eff': 'N/A', 'property-type': 'House', 'local-authority-label': 'Wigan', - 'fixed-lighting-outlets-count': '9', 'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', - 'hot-water-cost-current': '434', 'county': '', 'postcode': 'WN6 8RG', 'solar-water-heating-flag': 'N', - 'constituency': 'E14001039', 'co2-emissions-potential': '2.0', 'number-heated-rooms': '4', - 'floor-description': 'Solid, no insulation (assumed)', 'energy-consumption-potential': '147', - 'local-authority': 'E08000010', 'built-form': 'Mid-Terrace', 'number-open-fireplaces': '0', - 'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2022-05-11', - 'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '43', 'address1': '430 Gidlow Lane', - 'heat-loss-corridor': '', 'flat-storey-count': '', 'constituency-label': 'Wigan', - 'roof-energy-eff': 'Very Poor', 'total-floor-area': '80.0', 'building-reference-number': '10002334112', - 'environment-impact-current': '63', 'co2-emissions-current': '3.4', - 'roof-description': 'Pitched, no insulation (assumed)', 'floor-energy-eff': 'N/A', - 'number-habitable-rooms': '4', 'address2': '', 'hot-water-env-eff': 'Poor', 'posttown': 'WIGAN', - 'mainheatc-energy-eff': 'Very Good', 'main-fuel': 'electricity (not community)', - 'lighting-env-eff': 'Very Good', 'windows-energy-eff': 'Good', 'floor-env-eff': 'N/A', - 'sheating-env-eff': 'N/A', 'lighting-description': 'Low energy lighting in all fixed outlets', - 'roof-env-eff': 'Very Poor', 'walls-energy-eff': 'Average', 'photo-supply': '0.0', - 'lighting-cost-potential': '67', 'mainheat-env-eff': 'Very Good', 'multi-glaze-proportion': '100', - 'main-heating-controls': '', 'lodgement-datetime': '2022-06-06 13:01:20', 'flat-top-storey': '', - 'current-energy-rating': 'E', 'secondheat-description': 'Room heaters, mains gas', - 'walls-env-eff': 'Average', 'transaction-type': 'ECO assessment', 'uprn': '100011776843', - 'current-energy-efficiency': '53', 'energy-consumption-current': '252', - 'mainheat-description': 'Air source heat pump, radiators, electric', 'lighting-cost-current': '67', - 'lodgement-date': '2022-06-06', 'extension-count': '1', 'mainheatc-env-eff': 'Very Good', - 'lmk-key': '672d5947f3d4a55d97255af71651d6127a939418fa66a687070af77e0ba90df2', 'wind-turbine-count': '0', - 'tenure': 'Owner-occupied', 'floor-level': '', 'potential-energy-efficiency': '70', - 'hot-water-energy-eff': 'Very Poor', 'low-energy-lighting': '100', - 'walls-description': 'Cavity wall, filled cavity', 'hotwater-description': 'From main system' - } - - # differences = [] - # for k, v in ending_epc.items(): - # if v != starting_epc[k]: - # differences.append( - # { - # "variable": k, - # "starting_value": starting_epc[k], - # "ending_value": v - # } - # ) - # differences = pd.DataFrame(differences) - # - # diffs = differences[ - # differences["variable"].isin( - # [ - # "mainheat-energy-eff", - # "mainheatcont-description", - # "mainheatc-energy-eff", - # "main-fuel", - # "mainheat-env-eff", - # "mainheat-description", - # "hot-water-energy-eff", - # "hotwater-description" - # ] - # ) - # ] - - cleaning_data = read_dataframe_from_s3_parquet( - bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet", - ) - - cleaned = read_from_s3( - s3_file_name="cleaned_epc_data/cleaned.bson", - bucket_name="retrofit-data-dev" - ) - cleaned = msgpack.unpackb(cleaned, raw=False) - - photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev") - - epc = EPCRecord( - epc_records={ - 'original_epc': starting_epc, - 'full_sap_epc': {}, - 'old_data': [] - }, - run_mode="newdata", - cleaning_data=cleaning_data - ) - - home = Property( - id=0, - address="", - postcode="", - epc_record=epc, - already_installed={}, - non_invasive_recommendations={}, - ) - home.in_conservation_area = False - home.is_listed = False - home.is_heritage = False - home.restricted_measures = True - home.get_components( - cleaned=cleaned, - photo_supply_lookup=photo_supply_lookup, - floor_area_decile_thresholds=floor_area_decile_thresholds - ) - - recommender = HeatingRecommender(property_instance=home) - recommender.recommend_air_source_heat_pump(phase=0, has_cavity_or_loft_recommendations=False) - - # Patch - for this property, the hot water energy efficiency is very poor. it's not clear why this is, - # but we insert this for this test - recommender.heating_recommendations[0]["simulation_config"]["hot_water_energy_eff_ending"] = "Very Poor" - - property_recommendations = Recommendations.insert_temp_recommendation_id([recommender.heating_recommendations]) - - assert len(recommender.heating_recommendations) == 1 - - home.create_base_difference_epc_record(cleaned_lookup=cleaned) - home.adjust_difference_record_with_recommendations( - property_recommendations, [] - ) - - scoring_data = pd.DataFrame(home.recommendations_scoring_data).drop( - columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending", - "carbon_ending"] - ) - - model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat()) - model_api.MODEL_PREFIXES = ["sap_change_predictions"] - - predictions_dict = model_api.predict_all( - df=scoring_data, - bucket="retrofit-data-dev", - prediction_buckets={ - "sap_change_predictions": "retrofit-sap-predictions-dev", - } - ) - assert predictions_dict["sap_change_predictions"]["predictions"].values[0] == 52.2 - - def test_air_source_heat_pump_gas_boiler_starting_2(self): - """ - This property seems to have miniscule movement in SAP - just 2 poins - :return: - """ - - starting_epc = { - 'low-energy-fixed-light-count': '', 'address': '31 Whinney Hill Park', 'uprn-source': 'Energy Assessor', - 'floor-height': '2.3', 'heating-cost-potential': '394', 'unheated-corridor-length': '', - 'hot-water-cost-potential': '48', 'construction-age-band': 'England and Wales: 1967-1975', - 'potential-energy-rating': 'B', 'mainheat-energy-eff': 'Good', 'windows-env-eff': 'Average', - 'lighting-energy-eff': 'Good', 'environment-impact-potential': '87', - 'glazed-type': 'double glazing, unknown install date', 'heating-cost-current': '487', 'address3': '', - 'mainheatcont-description': 'Programmer, room thermostat and TRVs', 'sheating-energy-eff': 'N/A', - 'property-type': 'Bungalow', 'local-authority-label': 'Calderdale', 'fixed-lighting-outlets-count': '5', - 'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '86', - 'county': '', 'postcode': 'HD6 2PX', 'solar-water-heating-flag': 'N', 'constituency': 'E14000614', - 'co2-emissions-potential': '0.8', 'number-heated-rooms': '2', - 'floor-description': 'Solid, no insulation (assumed)', 'energy-consumption-potential': '105', - 'local-authority': 'E08000033', 'built-form': 'End-Terrace', 'number-open-fireplaces': '0', - 'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2021-11-25', - 'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '56', 'address1': '31 Whinney Hill Park', - 'heat-loss-corridor': '', 'flat-storey-count': '', 'constituency-label': 'Calder Valley', - 'roof-energy-eff': 'Good', 'total-floor-area': '44.0', 'building-reference-number': '10001772583', - 'environment-impact-current': '62', 'co2-emissions-current': '2.5', - 'roof-description': 'Pitched, 250 mm loft insulation', 'floor-energy-eff': 'N/A', - 'number-habitable-rooms': '2', 'address2': '', 'hot-water-env-eff': 'Good', 'posttown': 'BRIGHOUSE', - 'mainheatc-energy-eff': 'Good', 'main-fuel': 'mains gas (not community)', 'lighting-env-eff': 'Good', - 'windows-energy-eff': 'Average', 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A', - 'lighting-description': 'Low energy lighting in 60% of fixed outlets', 'roof-env-eff': 'Good', - 'walls-energy-eff': 'Average', 'photo-supply': '0.0', 'lighting-cost-potential': '40', - 'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '', - 'lodgement-datetime': '2021-11-25 11:39:35', 'flat-top-storey': '', 'current-energy-rating': 'D', - 'secondheat-description': 'Room heaters, electric', 'walls-env-eff': 'Average', - 'transaction-type': 'rental', 'uprn': '100051304421', 'current-energy-efficiency': '62', - 'energy-consumption-current': '322', 'mainheat-description': 'Boiler and radiators, mains gas', - 'lighting-cost-current': '56', 'lodgement-date': '2021-11-25', 'extension-count': '0', - 'mainheatc-env-eff': 'Good', 'lmk-key': '077f70657e9c3f1f0ce5392798398398616b159493b2a8ca2338961596631c27', - 'wind-turbine-count': '0', 'tenure': 'Rented (social)', 'floor-level': '', - 'potential-energy-efficiency': '86', 'hot-water-energy-eff': 'Good', 'low-energy-lighting': '60', - 'walls-description': 'Cavity wall, filled cavity', 'hotwater-description': 'From main system' - } - - ending_epc = { - 'low-energy-fixed-light-count': '', 'address': '31 Whinney Hill Park', - 'uprn-source': 'Energy Assessor', 'floor-height': '2.3', 'heating-cost-potential': '277', - 'unheated-corridor-length': '', 'hot-water-cost-potential': '266', - 'construction-age-band': 'England and Wales: 1967-1975', 'potential-energy-rating': 'B', - 'mainheat-energy-eff': 'Very Good', 'windows-env-eff': 'Average', 'lighting-energy-eff': 'Good', - 'environment-impact-potential': '90', 'glazed-type': 'double glazing, unknown install date', - 'heating-cost-current': '331', 'address3': '', - 'mainheatcont-description': 'Programmer and room thermostat', 'sheating-energy-eff': 'N/A', - 'property-type': 'Bungalow', 'local-authority-label': 'Calderdale', - 'fixed-lighting-outlets-count': '5', 'energy-tariff': 'Single', - 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '404', 'county': '', - 'postcode': 'HD6 2PX', 'solar-water-heating-flag': 'N', 'constituency': 'E14000614', - 'co2-emissions-potential': '0.7', 'number-heated-rooms': '2', - 'floor-description': 'Solid, no insulation (assumed)', 'energy-consumption-potential': '92', - 'local-authority': 'E08000033', 'built-form': 'End-Terrace', 'number-open-fireplaces': '0', - 'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', - 'inspection-date': '2021-11-25', 'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '48', - 'address1': '31 Whinney Hill Park', 'heat-loss-corridor': '', 'flat-storey-count': '', - 'constituency-label': 'Calder Valley', 'roof-energy-eff': 'Good', 'total-floor-area': '44.0', - 'building-reference-number': '10001772583', 'environment-impact-current': '68', - 'co2-emissions-current': '2.1', 'roof-description': 'Pitched, 250 mm loft insulation', - 'floor-energy-eff': 'N/A', 'number-habitable-rooms': '2', 'address2': '', - 'hot-water-env-eff': 'Poor', 'posttown': 'BRIGHOUSE', 'mainheatc-energy-eff': 'Average', - 'main-fuel': 'electricity (not community)', 'lighting-env-eff': 'Good', - 'windows-energy-eff': 'Average', 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A', - 'lighting-description': 'Low energy lighting in 60% of fixed outlets', 'roof-env-eff': 'Good', - 'walls-energy-eff': 'Average', 'photo-supply': '0.0', 'lighting-cost-potential': '40', - 'mainheat-env-eff': 'Very Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '', - 'lodgement-datetime': '2022-03-23 16:06:21', 'flat-top-storey': '', 'current-energy-rating': 'D', - 'secondheat-description': 'Room heaters, electric', 'walls-env-eff': 'Average', - 'transaction-type': 'rental', 'uprn': '100051304421', 'current-energy-efficiency': '64', - 'energy-consumption-current': '283', - 'mainheat-description': 'Air source heat pump, radiators, electric', - 'lighting-cost-current': '57', 'lodgement-date': '2022-03-23', 'extension-count': '0', - 'mainheatc-env-eff': 'Average', - 'lmk-key': '6296248141447b53426a40f1c39da17dad5f4786485db55ee38737891111a4d4', - 'wind-turbine-count': '0', 'tenure': 'Rented (social)', 'floor-level': '', - 'potential-energy-efficiency': '89', 'hot-water-energy-eff': 'Very Poor', - 'low-energy-lighting': '60', 'walls-description': 'Cavity wall, filled cavity', - 'hotwater-description': 'From main system' - } - - # differences = [] - # for k, v in ending_epc.items(): - # if v != starting_epc[k]: - # differences.append( - # { - # "variable": k, - # "starting_value": starting_epc[k], - # "ending_value": v - # } - # ) - # differences = pd.DataFrame(differences) - # - # diffs = differences[ - # differences["variable"].isin( - # [ - # "mainheat-energy-eff", - # "mainheatcont-description", - # "mainheatc-energy-eff", - # "main-fuel", - # "mainheat-env-eff", - # "mainheat-description", - # "hot-water-energy-eff", - # "hotwater-description" - # ] - # ) - # ] - - cleaning_data = read_dataframe_from_s3_parquet( - bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet", - ) - - cleaned = read_from_s3( - s3_file_name="cleaned_epc_data/cleaned.bson", - bucket_name="retrofit-data-dev" - ) - cleaned = msgpack.unpackb(cleaned, raw=False) - - photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev") - - epc = EPCRecord( - epc_records={ - 'original_epc': starting_epc, - 'full_sap_epc': {}, - 'old_data': [] - }, - run_mode="newdata", - cleaning_data=cleaning_data - ) - - home = Property( - id=0, - address="", - postcode="", - epc_record=epc, - already_installed={}, - non_invasive_recommendations={}, - ) - home.in_conservation_area = False - home.is_listed = False - home.is_heritage = False - home.restricted_measures = True - home.get_components( - cleaned=cleaned, - photo_supply_lookup=photo_supply_lookup, - floor_area_decile_thresholds=floor_area_decile_thresholds - ) - - recommender = HeatingRecommender(property_instance=home) - recommender.recommend_air_source_heat_pump(phase=0, has_cavity_or_loft_recommendations=False) - property_recommendations = Recommendations.insert_temp_recommendation_id([recommender.heating_recommendations]) - - assert len(recommender.heating_recommendations) == 1 - - home.create_base_difference_epc_record(cleaned_lookup=cleaned) - home.adjust_difference_record_with_recommendations( - property_recommendations, [] - ) - - scoring_data = pd.DataFrame(home.recommendations_scoring_data).drop( - columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending", - "carbon_ending"] - ) - - model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat()) - model_api.MODEL_PREFIXES = ["sap_change_predictions"] - - predictions_dict = model_api.predict_all( - df=scoring_data, - bucket="retrofit-data-dev", - prediction_buckets={ - "sap_change_predictions": "retrofit-sap-predictions-dev", - } - ) - assert predictions_dict["sap_change_predictions"]["predictions"].values[0] == 69.3 - - # In actuality with this property, the heating controls get downgraded, so we test a manual patch of this - patched_simulation_config = { - 'mainheat_energy_eff_ending': "Very Good", - 'hot_water_energy_eff_ending': 'Very Poor', - 'has_boiler_ending': False, - 'has_air_source_heat_pump_ending': True, - 'has_electric_ending': True, - 'has_mains_gas_ending': False, - 'fuel_type_ending': 'electricity', - 'trvs_ending': None, - "mainheatc_energy_eff_ending": 'Average' - } - - # PATCHING - property_recommendations_patch = Recommendations.insert_temp_recommendation_id( - [recommender.heating_recommendations] - ) - property_recommendations_patch[0][0]["simulation_config"] = patched_simulation_config - - home.create_base_difference_epc_record(cleaned_lookup=cleaned) - home.adjust_difference_record_with_recommendations( - property_recommendations_patch, [] - ) - - scoring_data_patch = pd.DataFrame(home.recommendations_scoring_data).drop( - columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending", - "carbon_ending"] - ) - - model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat()) - model_api.MODEL_PREFIXES = ["sap_change_predictions"] - - predictions_dict_patch = model_api.predict_all( - df=scoring_data_patch, - bucket="retrofit-data-dev", - prediction_buckets={ - "sap_change_predictions": "retrofit-sap-predictions-dev", - } - ) - # The error is only 0.3, so the model is working - assert predictions_dict_patch["sap_change_predictions"]["predictions"].values[0] == 64.3 - assert ending_epc["current-energy-efficiency"] == '64' - - def test_air_source_heat_pump_lpg_boiler(self): - starting_epc = { - 'low-energy-fixed-light-count': '', 'address': 'Holly Lodge, The Drive, Perry', - 'uprn-source': 'Energy Assessor', 'floor-height': '2.8', 'heating-cost-potential': '1628', - 'unheated-corridor-length': '', 'hot-water-cost-potential': '175', - 'construction-age-band': 'England and Wales: 1950-1966', 'potential-energy-rating': 'D', - 'mainheat-energy-eff': 'Poor', 'windows-env-eff': 'Average', 'lighting-energy-eff': 'Average', - 'environment-impact-potential': '70', 'glazed-type': 'double glazing, unknown install date', - 'heating-cost-current': '2158', 'address3': 'Perry', - 'mainheatcont-description': 'No time or thermostatic control of room temperature', - 'sheating-energy-eff': 'N/A', 'property-type': 'Bungalow', 'local-authority-label': 'Huntingdonshire', - 'fixed-lighting-outlets-count': '12', 'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', - 'hot-water-cost-current': '257', 'county': 'Cambridgeshire', 'postcode': 'PE28 0SX', - 'solar-water-heating-flag': 'N', 'constituency': 'E14000757', 'co2-emissions-potential': '3.3', - 'number-heated-rooms': '5', 'floor-description': 'Solid, no insulation (assumed)', - 'energy-consumption-potential': '128', 'local-authority': 'E07000011', 'built-form': 'Semi-Detached', - 'number-open-fireplaces': '0', 'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', - 'inspection-date': '2023-08-31', 'mains-gas-flag': 'N', 'co2-emiss-curr-per-floor-area': '51', - 'address1': 'Holly Lodge', 'heat-loss-corridor': '', 'flat-storey-count': '', - 'constituency-label': 'Huntingdon', 'roof-energy-eff': 'Good', 'total-floor-area': '117.0', - 'building-reference-number': '10005199915', 'environment-impact-current': '50', - 'co2-emissions-current': '5.9', 'roof-description': 'Pitched, 270 mm loft insulation', - 'floor-energy-eff': 'N/A', 'number-habitable-rooms': '5', 'address2': 'The Drive', - 'hot-water-env-eff': 'Good', 'posttown': 'HUNTINGDON', 'mainheatc-energy-eff': 'Very Poor', - 'main-fuel': 'LPG (not community)', 'lighting-env-eff': 'Average', 'windows-energy-eff': 'Average', - 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A', - 'lighting-description': 'Low energy lighting in 33% of fixed outlets', 'roof-env-eff': 'Good', - 'walls-energy-eff': 'Average', 'photo-supply': '0.0', 'lighting-cost-potential': '166', - 'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '', - 'lodgement-datetime': '2023-10-30 13:46:54', 'flat-top-storey': '', 'current-energy-rating': 'F', - 'secondheat-description': 'Room heaters, electric', 'walls-env-eff': 'Average', - 'transaction-type': 'ECO assessment', 'uprn': '100091200828', 'current-energy-efficiency': '32', - 'energy-consumption-current': '243', 'mainheat-description': 'Boiler and radiators, LPG', - 'lighting-cost-current': '277', 'lodgement-date': '2023-10-30', 'extension-count': '0', - 'mainheatc-env-eff': 'Very Poor', - 'lmk-key': 'f1d3bd4b8b50bc9b006231ccb158537c408523b748b3f4ef7e98cd03b144afa5', 'wind-turbine-count': '0', - 'tenure': 'Owner-occupied', 'floor-level': '', 'potential-energy-efficiency': '56', - 'hot-water-energy-eff': 'Poor', 'low-energy-lighting': '33', - 'walls-description': 'Cavity wall, filled cavity', 'hotwater-description': 'From main system' - } - - ending_epc = { - 'low-energy-fixed-light-count': '', 'address': 'Holly Lodge, The Drive, Perry', - 'uprn-source': 'Energy Assessor', 'floor-height': '2.8', 'heating-cost-potential': '917', - 'unheated-corridor-length': '', 'hot-water-cost-potential': '328', - 'construction-age-band': 'England and Wales: 1950-1966', 'potential-energy-rating': 'A', - 'mainheat-energy-eff': 'Very Good', 'windows-env-eff': 'Average', 'lighting-energy-eff': 'Average', - 'environment-impact-potential': '96', 'glazed-type': 'double glazing, unknown install date', - 'heating-cost-current': '1098', 'address3': 'Perry', - 'mainheatcont-description': 'Programmer, TRVs and bypass', 'sheating-energy-eff': 'N/A', - 'property-type': 'Bungalow', 'local-authority-label': 'Huntingdonshire', - 'fixed-lighting-outlets-count': '12', 'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', - 'hot-water-cost-current': '328', 'county': 'Cambridgeshire', 'postcode': 'PE28 0SX', - 'solar-water-heating-flag': 'N', 'constituency': 'E14000757', 'co2-emissions-potential': '0.3', - 'number-heated-rooms': '5', 'floor-description': 'Solid, no insulation (assumed)', - 'energy-consumption-potential': '16', 'local-authority': 'E07000011', 'built-form': 'Semi-Detached', - 'number-open-fireplaces': '0', 'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', - 'inspection-date': '2023-10-05', 'mains-gas-flag': 'N', 'co2-emiss-curr-per-floor-area': '6', - 'address1': 'Holly Lodge', 'heat-loss-corridor': '', 'flat-storey-count': '', - 'constituency-label': 'Huntingdon', 'roof-energy-eff': 'Good', 'total-floor-area': '117.0', - 'building-reference-number': '10005199915', 'environment-impact-current': '92', - 'co2-emissions-current': '0.7', 'roof-description': 'Pitched, 270 mm loft insulation', - 'floor-energy-eff': 'N/A', 'number-habitable-rooms': '5', 'address2': 'The Drive', - 'hot-water-env-eff': 'Very Good', 'posttown': 'HUNTINGDON', 'mainheatc-energy-eff': 'Average', - 'main-fuel': 'electricity (not community)', 'lighting-env-eff': 'Average', 'windows-energy-eff': 'Average', - 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A', - 'lighting-description': 'Low energy lighting in 33% of fixed outlets', 'roof-env-eff': 'Good', - 'walls-energy-eff': 'Average', 'photo-supply': '', 'lighting-cost-potential': '166', - 'mainheat-env-eff': 'Very Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '', - 'lodgement-datetime': '2023-11-01 16:29:16', 'flat-top-storey': '', 'current-energy-rating': 'A', - 'secondheat-description': 'Room heaters, electric', 'walls-env-eff': 'Average', - 'transaction-type': 'ECO assessment', 'uprn': '100091200828', 'current-energy-efficiency': '92', - 'energy-consumption-current': '37', 'mainheat-description': 'Air source heat pump, radiators, electric', - 'lighting-cost-current': '277', 'lodgement-date': '2023-11-01', 'extension-count': '0', - 'mainheatc-env-eff': 'Average', - 'lmk-key': 'cb7f2838b727907767c8c2a385cd22f722b1e4745463391d910d228e52124515', 'wind-turbine-count': '0', - 'tenure': 'Owner-occupied', 'floor-level': '', 'potential-energy-efficiency': '95', - 'hot-water-energy-eff': 'Good', 'low-energy-lighting': '33', - 'walls-description': 'Cavity wall, filled cavity', 'hotwater-description': 'From main system' - } - - cleaning_data = read_dataframe_from_s3_parquet( - bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet", - ) - - cleaned = read_from_s3( - s3_file_name="cleaned_epc_data/cleaned.bson", - bucket_name="retrofit-data-dev" - ) - cleaned = msgpack.unpackb(cleaned, raw=False) - - photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev") - - epc = EPCRecord( - epc_records={ - 'original_epc': starting_epc, - 'full_sap_epc': {}, - 'old_data': [] - }, - run_mode="newdata", - cleaning_data=cleaning_data - ) - - home = Property( - id=0, - address="", - postcode="", - epc_record=epc, - already_installed={}, - non_invasive_recommendations={}, - ) - home.in_conservation_area = False - home.is_listed = False - home.is_heritage = False - home.restricted_measures = True - home.get_components( - cleaned=cleaned, - photo_supply_lookup=photo_supply_lookup, - floor_area_decile_thresholds=floor_area_decile_thresholds - ) - - recommender = HeatingRecommender(property_instance=home) - recommender.recommend_air_source_heat_pump(phase=0, has_cavity_or_loft_recommendations=False) - property_recommendations = Recommendations.insert_temp_recommendation_id([recommender.heating_recommendations]) - - assert len(recommender.heating_recommendations) == 1 - - home.create_base_difference_epc_record(cleaned_lookup=cleaned) - home.adjust_difference_record_with_recommendations( - property_recommendations, [] - ) - - scoring_data = pd.DataFrame(home.recommendations_scoring_data).drop( - columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending", - "carbon_ending"] - ) - - model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat()) - model_api.MODEL_PREFIXES = ["sap_change_predictions"] - - predictions_dict = model_api.predict_all( - df=scoring_data, - bucket="retrofit-data-dev", - prediction_buckets={ - "sap_change_predictions": "retrofit-sap-predictions-dev", - } - ) - # We predict a huge uplift but not quite as much as the EPC, due to some distinct differences between our - # recommendation and the EPC - assert predictions_dict["sap_change_predictions"]["predictions"].values[0] == 81.3 - assert ending_epc['current-energy-efficiency'] == '92' - - # PATCH - # We patch the simulation config, to reflect the ending EPC, to see if we get the ending EPC's config - patched_simulation_config = { - 'mainheat_energy_eff_ending': "Very Good", - 'hot_water_energy_eff_ending': 'Good', - 'has_boiler_ending': False, - 'has_air_source_heat_pump_ending': True, - 'has_electric_ending': True, - 'has_lpg_ending': False, - 'fuel_type_ending': 'electricity', - 'switch_system_ending': 'programmer', - 'no_control_ending': None, - 'auxiliary_systems_ending': 'bypass', - 'trvs_ending': 'trvs', - "mainheatc_energy_eff_ending": 'Average' - } - - # PATCHING - property_recommendations_patch = Recommendations.insert_temp_recommendation_id( - [recommender.heating_recommendations] - ) - property_recommendations_patch[0][0]["simulation_config"] = patched_simulation_config - - home.create_base_difference_epc_record(cleaned_lookup=cleaned) - home.adjust_difference_record_with_recommendations( - property_recommendations_patch, [] - ) - - scoring_data_patch = pd.DataFrame(home.recommendations_scoring_data).drop( - columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending", - "carbon_ending"] - ) - - model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat()) - model_api.MODEL_PREFIXES = ["sap_change_predictions"] - - predictions_dict_patch = model_api.predict_all( - df=scoring_data_patch, - bucket="retrofit-data-dev", - prediction_buckets={ - "sap_change_predictions": "retrofit-sap-predictions-dev", - } - ) - - assert predictions_dict_patch["sap_change_predictions"]["predictions"].values[0] == 88.9 - # We still underpredict but the improvement is notable - - def test_offgrid(self): - """ - We test on a property we've worked with before, where we compare two options - a) Upgrading to a boiler - b) Upgrading to a heat pump - :return: - """ - - starting_epc = { - 'low-energy-fixed-light-count': '', 'address': '6 Beech Road', 'uprn-source': 'Energy Assessor', - 'floor-height': '2.4', 'heating-cost-potential': '612', 'unheated-corridor-length': '', - 'hot-water-cost-potential': '123', 'construction-age-band': 'England and Wales: 1930-1949', - 'potential-energy-rating': 'B', 'mainheat-energy-eff': 'Very Poor', 'windows-env-eff': 'Good', - 'lighting-energy-eff': 'Good', 'environment-impact-potential': '87', - 'glazed-type': 'double glazing installed during or after 2002', 'heating-cost-current': '2278', - 'address3': '', 'mainheatcont-description': 'Appliance thermostats', 'sheating-energy-eff': 'N/A', - 'property-type': 'House', 'local-authority-label': 'Dudley', 'fixed-lighting-outlets-count': '9', - 'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '604', - 'county': '', 'postcode': 'DY1 4BP', 'solar-water-heating-flag': 'N', 'constituency': 'E14000671', - 'co2-emissions-potential': '1.0', 'number-heated-rooms': '4', - 'floor-description': 'Solid, no insulation (assumed)', 'energy-consumption-potential': '93', - 'local-authority': 'E08000027', 'built-form': 'End-Terrace', 'number-open-fireplaces': '0', - 'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2024-03-13', - 'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '83', 'address1': '6 Beech Road', - 'heat-loss-corridor': '', 'flat-storey-count': '', 'constituency-label': 'Dudley North', - 'roof-energy-eff': 'Very Poor', 'total-floor-area': '60.0', 'building-reference-number': '10005780080', - 'environment-impact-current': '41', 'co2-emissions-current': '5.0', - 'roof-description': 'Pitched, 12 mm loft insulation', 'floor-energy-eff': 'N/A', - 'number-habitable-rooms': '4', 'address2': '', 'hot-water-env-eff': 'Poor', 'posttown': 'DUDLEY', - 'mainheatc-energy-eff': 'Good', 'main-fuel': 'electricity (not community)', 'lighting-env-eff': 'Good', - 'windows-energy-eff': 'Good', 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A', - 'lighting-description': 'Low energy lighting in 67% of fixed outlets', 'roof-env-eff': 'Very Poor', - 'walls-energy-eff': 'Average', 'photo-supply': '0.0', 'lighting-cost-potential': '113', - 'mainheat-env-eff': 'Poor', 'multi-glaze-proportion': '100', 'main-heating-controls': '', - 'lodgement-datetime': '2024-03-13 11:29:11', 'flat-top-storey': '', 'current-energy-rating': 'F', - 'secondheat-description': 'None', 'walls-env-eff': 'Average', 'transaction-type': 'rental', - 'uprn': '90055152', 'current-energy-efficiency': '32', 'energy-consumption-current': '491', - 'mainheat-description': 'Room heaters, electric', 'lighting-cost-current': '113', - 'lodgement-date': '2024-03-13', 'extension-count': '1', 'mainheatc-env-eff': 'Good', - 'lmk-key': '78ddf851b660e599a0894924d0e6b503980f5e0ad1aa711f8411718dc2989c44', 'wind-turbine-count': '0', - 'tenure': 'Rented (social)', 'floor-level': '', 'potential-energy-efficiency': '87', - 'hot-water-energy-eff': 'Very Poor', 'low-energy-lighting': '67', - 'walls-description': 'Cavity wall, filled cavity', - 'hotwater-description': 'Electric immersion, standard tariff' - } - - cleaning_data = read_dataframe_from_s3_parquet( - bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet", - ) - - cleaned = read_from_s3( - s3_file_name="cleaned_epc_data/cleaned.bson", - bucket_name="retrofit-data-dev" - ) - cleaned = msgpack.unpackb(cleaned, raw=False) - - photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev") - - epc = EPCRecord( - epc_records={ - 'original_epc': starting_epc, - 'full_sap_epc': {}, - 'old_data': [] - }, - run_mode="newdata", - cleaning_data=cleaning_data - ) - - home = Property( - id=0, - address="", - postcode="", - epc_record=epc, - already_installed={}, - non_invasive_recommendations={}, - ) - home.in_conservation_area = False - home.is_listed = False - home.is_heritage = False - home.restricted_measures = True - home.get_components( - cleaned=cleaned, - photo_supply_lookup=photo_supply_lookup, - floor_area_decile_thresholds=floor_area_decile_thresholds - ) - - recommender = HeatingRecommender(property_instance=home) - recommender.recommend_air_source_heat_pump(phase=0, has_cavity_or_loft_recommendations=False) - recommender.recommend_boiler_upgrades(phase=0, system_change=True, exising_room_heaters=False) - - assert len(recommender.heating_recommendations) == 3 - - property_recommendations = Recommendations.insert_temp_recommendation_id([recommender.heating_recommendations]) - - home.create_base_difference_epc_record(cleaned_lookup=cleaned) - home.adjust_difference_record_with_recommendations( - property_recommendations, [] - ) - - scoring_data = pd.DataFrame(home.recommendations_scoring_data).drop( - columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending", - "carbon_ending"] - ) - - model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat()) - model_api.MODEL_PREFIXES = ["sap_change_predictions"] - - predictions_dict = model_api.predict_all( - df=scoring_data, - bucket="retrofit-data-dev", - prediction_buckets={ - "sap_change_predictions": "retrofit-sap-predictions-dev", - } - ) - - # The ASHP isn't better under SAP, compared to a gas boiler with good heat controls - assert predictions_dict["sap_change_predictions"]["predictions"].tolist() == [66.9, 65.5, 65.9] diff --git a/recommendations/tests/test_costs.py b/recommendations/tests/test_costs.py index 402e38eb..74a210c1 100644 --- a/recommendations/tests/test_costs.py +++ b/recommendations/tests/test_costs.py @@ -18,10 +18,9 @@ class TestCosts: "description": "cwi", "depth": 75, "thermal_conductivity": 0.037, - "prime_cost": 5.17, - "material_cost": 5.62, - "labour_cost": 1.125, + "total_cost": 14, "labour_hours_per_unit": 0.065, + "is_installer_quote": True } cwi_results = costs.cavity_wall_insulation( @@ -29,12 +28,7 @@ class TestCosts: material=cwi_material, ) - assert cwi_results == { - 'total': 1065.0661223512907, 'subtotal': 887.5551019594088, 'vat': 177.51102039188177, - 'contingency': 63.396792997100626, 'preliminaries': 63.396792997100626, 'material': 539.0166061175574, - 'profit': 126.79358599420125, 'labour_hours': 6.234177828761786, 'labour_cost': 94.95132385344874, - 'labour_days': 0.38963611429761164 - } + assert cwi_results == {'total': 1342.7459938871539, 'labour_hours': 8, 'labour_days': 1} def test_loft_insulation(self): mock_property = Mock() @@ -47,22 +41,17 @@ class TestCosts: "description": "Crown Loft Roll 44 glass fibre roll", "depth": 270, "thermal_conductivity": 0.044, - "prime_cost": None, - "material_cost": 5.91938, - "labour_cost": 1.96, - "labour_hours_per_unit": 0.11 + "total_cost": 11, + "labour_hours_per_unit": 0.11, + "is_installer_quote": True, } - loft_results = costs.loft_insulation( + loft_results = costs.loft_and_flat_insulation( floor_area=33.5, material=loft_material, ) - assert loft_results == { - 'total': 639.4133610000001, 'subtotal': 532.8444675000001, 'vat': 106.56889350000002, - 'contingency': 71.045929, 'preliminaries': 35.5229645, 'material': 297.448845, 'profit': 71.045929, - 'labour_hours': 3.685, 'labour_cost': 57.7808, 'labour_days': 0.460625 - } + assert loft_results == {'total': 368.5, 'labour_hours': 8, 'labour_days': 1} def test_internal_wall_insulation(self): mock_property = Mock() @@ -71,87 +60,6 @@ class TestCosts: } costs = Costs(mock_property) - iwi_non_insulation_materials = [ - {'type': 'iwi_wall_demolition', - 'description': 'Solid & Dry Lined walls: Hack of wall finishes with chipping hammer; plaster to walls.', - 'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0, - 'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 10.27, - 'labour_hours_per_unit': 0.33, 'plant_cost': 1.28, 'total_cost': 11.55, 'link': 'SPONs', 'Notes': 0.0}, - {'type': 'iwi_wall_demolition', - 'description': 'Stud walls: Remove wall linings including battening behind; plasterboard and skim', - 'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0, - 'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 6.23, - 'labour_hours_per_unit': 0.2, 'plant_cost': 1.25, 'total_cost': 7.48, 'link': 'SPONs', 'Notes': 0.0}, - {'type': 'iwi_wall_demolition', - 'description': 'Lathe and Plaster walls: Remove wall linings including battening behind; wood lath and ' - 'plaster', - 'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0, - 'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 6.85, - 'labour_hours_per_unit': 0.22, 'plant_cost': 2.09, 'total_cost': 8.94, 'link': 'SPONs', 'Notes': 0.0}, - {'Notes': "", - 'cost_unit': "", - 'depth': "", - 'depth_unit': "", - 'description': 'Visqueen High Performance Vapour Barrier', - 'labour_cost': 0.48, - 'labour_hours_per_unit': 0.02, - 'link': 'SPONs', - 'material_cost': 1.21, - 'plant_cost': 0, - 'prime_material_cost': 0.58, - 'thermal_conductivity': "", - 'thermal_conductivity_unit': "", - 'total_cost': 1.69, - 'type': 'iwi_vapour_barrier'}, - {'Notes': "", - 'cost_unit': "", - 'depth': "", - 'depth_unit': "", - 'description': 'Plaster; one coat Thistle board finish or other equal; steel trowelled; 3 mm thick work ' - 'to walls or ceilings; one coat; to plasterboard base; over 600mm wide', - 'labour_cost': 6.58, - 'labour_hours_per_unit': 0.25, - 'link': "", - 'material_cost': 0.06, - 'plant_cost': 0, - 'prime_material_cost': 0.0, - 'thermal_conductivity': "", - 'thermal_conductivity_unit': "", - 'total_cost': 6.64, - 'type': 'iwi_redecoration'}, - {'Notes': "", - 'cost_unit': "", - 'depth': "", - 'depth_unit': "", - 'description': 'Two coats emulsion paint on plaster, over 40mm girth; 3.5m - ' - '5m high', - 'labour_cost': 0.0, - 'labour_hours_per_unit': 0.21, - 'link': "", - 'material_cost': 0.41, - 'plant_cost': 0, - 'prime_material_cost': "", - 'thermal_conductivity': "", - 'thermal_conductivity_unit': "", - 'total_cost': 4.34, - 'type': 'iwi_redecoration'}, - {'Notes': "", - 'cost_unit': "", - 'depth': "", - 'depth_unit': "", - 'description': 'Fitting existing softwood skirting or architrave to new ' - 'frames; 150mm high', - 'labour_cost': 4.87, - 'labour_hours_per_unit': 0.01, - 'link': "", - 'material_cost': 4.86, - 'plant_cost': 0, - 'prime_material_cost': "", - 'thermal_conductivity': "", - 'thermal_conductivity_unit': "", - 'total_cost': 4.88, - 'type': 'iwi_redecoration'} - ] iwi_material = { "type": "internal_wall_insulation", @@ -161,26 +69,19 @@ class TestCosts: "cost_unit": "gbp_per_m2", "thermal_conductivity": 0.022, "thermal_conductivity_unit": "watt_per_meter_kelvin", - "prime_material_cost": "", - "material_cost": 11.68, - "labour_cost": 3.12, "labour_hours_per_unit": 0.18, - "plant_cost": "", - "total_cost": 14.8, - "link": "SPONs" + "total_cost": 200, + "link": "link", + "is_installer_quote": True } - iwi_results = costs.internal_wall_insulation( + iwi_results = costs.solid_wall_insulation( wall_area=95.9104281347967, material=iwi_material, - non_insulation_materials=iwi_non_insulation_materials ) assert iwi_results == { - 'total': 6880.2304726777775, 'subtotal': 5733.525393898148, 'vat': 1146.7050787796295, - 'contingency': 764.470052519753, 'preliminaries': 382.2350262598765, 'material': 1747.488000615996, - 'profit': 764.470052519753, 'labour_hours': 88.23759388401297, 'labour_days': 2.757424808875405, - 'labour_cost': 1927.1602026551818 + 'total': 19182.085626959342, 'labour_hours': 17.263877064263404, 'labour_days': 0.5394961582582314 } def test_suspended_floor_insulation(self): @@ -201,7 +102,8 @@ class TestCosts: 'total_cost': 13.46, 'link': 'SPONs', 'Notes': 'Spons did not contain labour costs so we use values for similar insulations. ' 'We use the ' - 'same values as in Crown loft roll 44, since it is also an insulation roll' + 'same values as in Crown loft roll 44, since it is also an insulation roll', + "is_installer_quote": False } sus_floor_non_insulation_materials = [ @@ -256,7 +158,7 @@ class TestCosts: 'depth': 100.0, 'depth_unit': 'mm', 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.033, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'prime_material_cost': 0, 'material_cost': 12.02, 'labour_cost': 4.4, 'labour_hours_per_unit': 0.19, 'plant_cost': 0, - 'total_cost': 16.42, 'link': 'SPONs', 'Notes': 0 + 'total_cost': 16.42, 'link': 'SPONs', 'Notes': 0, "is_installer_quote": False } sol_floor_non_insulation_materials = [ @@ -342,81 +244,18 @@ class TestCosts: ewi_material = { 'type': 'external_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board', 'depth': 150.0, 'depth_unit': 'mm', 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.022, - 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'prime_material_cost': 23.53, - 'material_cost': 34.62, 'labour_cost': 33.06, 'labour_hours_per_unit': 1.4, 'plant_cost': 0, - 'total_cost': 67.68, 'link': 'SPONs', 'Notes': 0 + 'thermal_conductivity_unit': 'watt_per_meter_kelvin', + 'labour_hours_per_unit': 1.4, + 'total_cost': 300, 'link': 'SPONs', 'Notes': 0, "is_installer_quote": True } - ewi_non_insulation_materials = [ - {'type': 'ewi_wall_demolition', - 'description': 'Solid & Dry Lined walls: Hack of wall finishes with chipping ' - 'hammer; plaster to walls.', - 'depth': 0, 'depth_unit': 0, 'cost_unit': 'gbp_per_m2', - 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0, - 'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 10.27, - 'labour_hours_per_unit': 0.33, 'plant_cost': 1.28, 'total_cost': 11.55, - 'link': 'SPONs', 'Notes': 0}, {'type': 'ewi_wall_demolition', - 'description': 'Stud walls: Remove wall linings ' - 'including battening behind; ' - 'plasterboard and skim', - 'depth': 0, 'depth_unit': 0, - 'cost_unit': 'gbp_per_m2', - 'thermal_conductivity': 0, - 'thermal_conductivity_unit': 0, - 'prime_material_cost': 0, 'material_cost': 0, - 'labour_cost': 6.23, 'labour_hours_per_unit': 0.2, - 'plant_cost': 1.25, 'total_cost': 7.48, - 'link': 'SPONs', 'Notes': 0}, - {'type': 'ewi_wall_demolition', - 'description': 'Lathe and Plaster walls: Remove wall linings including battening ' - 'behind; wood lath and plaster', - 'depth': 0, 'depth_unit': 0, 'cost_unit': 'gbp_per_m2', - 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0, - 'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 6.85, - 'labour_hours_per_unit': 0.22, 'plant_cost': 2.09, 'total_cost': 8.94, - 'link': 'SPONs', 'Notes': 0}, {'type': 'ewi_wall_preparation', - 'description': 'Clean and prepare surfaces, ' - 'one coat Keim dilution, ' - 'one coat primer and two coats ' - 'of Keim Ecosil paint; Brick or ' - 'block walls; over 300 mm girth', - 'depth': 0, 'depth_unit': 0, 'cost_unit': 0, - 'thermal_conductivity': 0, - 'thermal_conductivity_unit': 0, - 'prime_material_cost': 0, 'material_cost': 7.3, - 'labour_cost': 5.62, 'labour_hours_per_unit': 0.3, - 'plant_cost': 0, 'total_cost': 12.92, - 'link': 'SPONs', - 'Notes': 'This work covers the preparation and ' - 'priming of the wall before insulating'}, - {'type': 'ewi_wall_redecoration', - 'description': 'EPS insulation fixed with adhesive to SFS structure (measured ' - 'separately) with horizontal PVC intermediate track and vertical ' - 'T-spines; with glassfibre mesh reinforcement embedded in Sto ' - 'Armat Classic Basecoat Render and Stolit K 1.5 Decorative ' - 'Topcoat Render (white)', - 'depth': 0, 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, - 'thermal_conductivity_unit': 0, 'prime_material_cost': 0, 'material_cost': 0, - 'labour_cost': 0, 'labour_hours_per_unit': 0, 'plant_cost': 0, - 'total_cost': 69.94, 'link': 'SPONs', - 'Notes': 'This material in SPONs is for 70mm EPS insulation, which comes in at a ' - 'cost of 99.17 per meter square. This includes the cost of insulation. ' - 'To get the costing for just the works and not the insulation, ' - 'we subtract the cost of EPS insulation, using Ravathem 75mm insulation ' - 'as an example, which costs £29.23 per meter square, giving us the cost ' - 'of the remaining works without insulation. This material gives us a ' - 'cost for basecoat, mesh application and a render finish'}] - ewi_results = costs.external_wall_insulation( + ewi_results = costs.solid_wall_insulation( wall_area=95.9104281347967, material=ewi_material, - non_insulation_materials=ewi_non_insulation_materials ) assert ewi_results == { - 'total': 15047.078622131372, 'subtotal': 12539.232185109477, 'vat': 2507.8464370218953, - 'contingency': 808.9827216199662, 'preliminaries': 2022.4568040499155, 'material': 4020.565147410677, - 'profit': 1617.9654432399325, 'labour_hours': 187.02533486285358, 'labour_days': 5.8445417144641745, - 'labour_cost': 3921.5600094613983 + 'total': 28773.12844043901, 'labour_hours': 134.2745993887154, 'labour_days': 4.196081230897356 } def test_flat_roof_insulation(self): @@ -426,120 +265,47 @@ class TestCosts: } costs = Costs(mock_property) - flat_roof_material = {'id': 1225, 'type': 'flat_roof_insulation', - 'description': 'Kingspan Thermaroof TR21 zero OPD ' - 'urethene insulation board', - 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, - 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04, - 'r_value_unit': 'square_meter_kelvin_per_watt', - 'thermal_conductivity': 0.025, - 'thermal_conductivity_unit': 'watt_per_meter_kelvin', - 'link': 'SPONs', - 'created_at': "now", 'is_active': True, - 'prime_material_cost': None, 'material_cost': 50.95, - 'labour_cost': 10.66, 'labour_hours_per_unit': 0.48, - 'plant_cost': 0.0, 'total_cost': 61.61, - 'notes': "SPONs didn't have a labour hours so we use " - "0.48 which is similar to other materials"} + flat_roof_material = { + 'id': 1225, 'type': 'flat_roof_insulation', + 'description': 'Kingspan Thermaroof TR21 zero OPD ' + 'urethene insulation board', + 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, + 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04, + 'r_value_unit': 'square_meter_kelvin_per_watt', + 'thermal_conductivity': 0.025, + 'thermal_conductivity_unit': 'watt_per_meter_kelvin', + 'link': 'SPONs', + 'created_at': "now", 'is_active': True, + 'prime_material_cost': None, 'material_cost': 50.95, + 'labour_cost': 10.66, 'labour_hours_per_unit': 0.48, + 'plant_cost': 0.0, 'total_cost': 61.61, + 'notes': "SPONs didn't have a labour hours so we use " + "0.48 which is similar to other materials", + "is_installer_quote": False + } - flat_roof_non_insulation_materials = [ - {'id': 17, 'type': 'mechanical_ventilation', 'description': 'Mechanical Extract Ventilation', 'depth': None, - 'depth_unit': None, 'cost': 500, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': None, 'r_value_unit': None, - 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': None, - 'created_at': datetime.datetime(2023, 10, 18, 16, 39, 9, 827188), 'is_active': True, - 'prime_material_cost': None, - 'material_cost': None, 'labour_cost': None, 'labour_hours_per_unit': None, 'plant_cost': None, - 'total_cost': None, - 'notes': None}, - {'id': 1221, 'type': 'flat_roof_preparation', - 'description': 'clean surface to receive new damp-proof membrane', - 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None, - 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, - 'thermal_conductivity_unit': None, - 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, - 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 4.36, 'labour_hours_per_unit': 0.14, - 'plant_cost': 0.0, 'total_cost': 4.36, - 'notes': 'This data is based on concrete however forms a decent baseline for a Bituminous Felt flat roof'}, - {'id': 1223, 'type': 'flat_roof_preparation', - 'description': 'One coat primer; on wood surfaces before fixing; General surfaces; over 300 mm girth', - 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None, - 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, - 'thermal_conductivity_unit': None, - 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, - 'prime_material_cost': None, 'material_cost': 2.49, 'labour_cost': 1.5, 'labour_hours_per_unit': 0.08, - 'plant_cost': 0.0, 'total_cost': 3.99, 'notes': 'SPONs data gives us a baseline for a wood surface'}, - {'id': 1224, 'type': 'flat_roof_vapour_barrier', 'description': 'Visqueen High Performance Vapour Barrier', - 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None, - 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, - 'thermal_conductivity_unit': None, - 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, - 'prime_material_cost': 0.58, 'material_cost': 1.21, 'labour_cost': 0.48, 'labour_hours_per_unit': 0.02, - 'plant_cost': 0.0, 'total_cost': 1.69, 'notes': None}, - {'id': 1234, 'type': 'flat_roof_waterproofing', - 'description': '20 mm thick two coat coverings; felt isolating membrane; to concrete (or ' - 'timber) base; flat or to falls or slopes not exceeding 10° from horizontal', - 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None, - 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, - 'thermal_conductivity_unit': None, 'link': 'SPONs', - 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, - 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 0.0, - 'labour_hours_per_unit': 0.5, 'plant_cost': 0.0, 'total_cost': 31.13, 'notes': None} - ] - - flat_roof_floor_results = costs.flat_roof_insulation( + flat_roof_floor_results = costs.loft_and_flat_insulation( floor_area=33.5, material=flat_roof_material, - non_insulation_materials=flat_roof_non_insulation_materials ) - assert flat_roof_floor_results == {'total': 5325.327767999999, 'subtotal': 4437.773139999999, - 'vat': 887.5546279999999, 'contingency': 459.07998, - 'preliminaries': 306.05332, 'material': 1830.775, 'profit': 612.10664, - 'labour_hours': 24.79, 'labour_days': 1.549375, 'labour_cost': 186.9032} + assert flat_roof_floor_results == { + 'total': 2063.935, 'subtotal': 1719.9458333333334, 'vat': 343.9891666666665, 'labour_hours': 8, + 'labour_days': 1 + } assert costs.labour_adjustment_factor == 0.88 - # Mock property instance for regional tests - @pytest.fixture(params=[ - ("Northamptonshire", "East Midlands", 7927.44), - ("Greater London Authority", "Inner London", 10475.0), - ("Adur", "South East England", 8333.32), - ("Bournemouth", "South West England", 8452), - ("Basildon", "East of England", 7895.44), - ("Birmingham", "West Midlands", 7706.2), - ("County Durham", "North East England", 8113.96), - ("Allerdale", "North West England", 6481.68), - ("York", "Yorkshire and the Humber", 8243.6), - ("Cardiff", "Wales", 7595.32), - ("Glasgow City", "Scotland", 7871.88), - ("Belfast", "Northern Ireland", 8504.36) - ]) - def mock_property_with_region(self, request): - county, region, expected_cost = request.param - mock_property = Mock() - mock_property.data = {"county": county} - return mock_property, region, expected_cost - # Test for different wattages - @pytest.mark.parametrize("wattage, expected_cost", [ - (3000, 5945.58), - (4000, 7927.44), - (5000, 9909.3), - (6000, 11891.16), + @pytest.mark.parametrize("n_panels, expected_cost", [ + (7, 4055.0), + (10, 4540.0), + (12, 4863.0), + (15, 5707.0), ]) - def test_solar_pv_different_wattages(self, wattage, expected_cost): + def test_solar_pv_different_wattages(self, n_panels, expected_cost): mock_property = Mock() mock_property.data = {"county": "Mansfield"} costs = Costs(mock_property) - result = costs.solar_pv(wattage) - assert result['total'] == pytest.approx(expected_cost, rel=0.01) - - def test_solar_pv_regional_variation(self, mock_property_with_region): - # Test for regional cost variations - property_instance, expected_region, expected_cost = mock_property_with_region - costs = Costs(property_instance) - - assert costs.region == expected_region - - result = costs.solar_pv(4000) # Testing with a fixed wattage of 4000 + result = costs.solar_pv(n_panels) assert result['total'] == pytest.approx(expected_cost, rel=0.01)