import json import pandas as pd from tqdm import tqdm from utils.s3 import read_dataframe_from_s3_parquet, save_data_to_s3, save_dataframe_to_s3_parquet from backend.Property import Property # This is the github pr number MODEL_VERSION = "101" def app(): dataset = read_dataframe_from_s3_parquet( bucket_name="retrofit-data-dev", file_key="sap_change_model/dataset.parquet" ) thresholds = dataset["total_floor_area_starting"].quantile( [0.3, 0.6, 0.9] ).values dataset["floor_area_quantile"] = pd.cut( dataset["total_floor_area_starting"], bins=[0] + list(thresholds) + [float('inf')], labels=False, include_lowest=True ) # We want to set up some tests to deduce the following: # For different property types, of various sizes, what is the impact of the various measures that we recommend # 1) Insulating the loft. We test the impact of bringing the loft to 270mm insulation and 300mm insulation property_types = dataset[ ["property_type", "built_form", "floor_area_quantile", "construction_age_band"] ].drop_duplicates() property_types = property_types.sort_values( ["property_type", "built_form", "floor_area_quantile", "construction_age_band"] ) # For each property type congifuration, we take an example property with different starting loft thresholds. We take # the value with the lowest U-value, since when simulating, we often work with particularly low u-values # TODOS # 1) When simulating with loft insulation, make sure is_loft is definitely true, because the roof could start as # pitched, but is_loft false # TODO: We have a description: "Pitched, loft insulation", which seems to have its insulation thickness set to # "none" # Example UPRN: 100021359753, 10001204228 # TODO: For windows, we have glazing_type and glazed_type. When simulating, we don't set glazed_type_ending which # could be set to "double glazing installed during or after 2002" (THIS HAS BEEN ADDED!) # TODO: When simulating external wall insulation vs internal wall insulation, I need to set the external_insulation # or internal_insulation boolean values to true (THIS HAS BEEN ADDED!) # TODO: We could probably re-map some of the values of glazed_type_ending # For simulating # 1) loft insulation - we take the lowest u-value when loft insulation is 270mm and 300mm, the values we most # commonly simulate to - For loft insulation, these values are in-line with best_270mm_uvalue = dataset[dataset["roof_insulation_thickness"] == "270"]["roof_thermal_transmittance"].min() best_300mm_uvalue = dataset[dataset["roof_insulation_thickness"] == "300"]["roof_thermal_transmittance"].min() # 2) Intenal wall insulation - we take the lowest u-value when simulating internal wall insulation best_internal_wall_uvalue = dataset[ dataset["internal_insulation"] & dataset["is_solid_brick"] ]["walls_thermal_transmittance"].min() # 3) External wall insulation - we take the lowest u-value when simulating external wall insulation best_external_wall_uvalue = dataset[ dataset["external_insulation"] & dataset["is_solid_brick"] ]["walls_thermal_transmittance"].min() # 4) Cavity wall insulation - we take the lowest u-value when simulating cavity wall insulation # This is 0.28, which is a sufficiently low value best_cavity_wall_uvalue = dataset[ dataset["is_cavity_wall"] & dataset["is_filled_cavity"] & (~dataset["external_insulation"]) & ( ~dataset["internal_insulation"]) ]["walls_thermal_transmittance"].min() ending_colums = [col for col in dataset.columns if col.endswith("_ending")] # For the purpose of scoring, we want to simulate JUST the impact of the measure we're testing. We therefore # need to make sure that every "_ending" column is equal to its starting value column_config = {} for ending_col in ending_colums: base_col = ending_col.replace("_ending", "") # We check if the starting column ends with _starting or is just the base col if base_col + "_starting" in dataset.columns: column_config[ending_col] = base_col + "_starting" elif base_col in dataset.columns: column_config[ending_col] = base_col else: raise ValueError("something went wrong") loft_insulation_testing_data = [] solid_wall_testing_data = [] cavity_wall_testing_data = [] solid_floor_testing_data = [] suspended_floor_testing_data = [] single_glazed_testing_data = [] partial_double_glazed_testing_data = [] partial_secondary_glazed_testing_data = [] pitched_roof_solar = [] flat_roof_solar = [] for property_config in tqdm(property_types.itertuples(), total=property_types.shape[0]): config_hash = hash(str(property_config)) # Take a sample row population = dataset[ (dataset["property_type"] == property_config.property_type) & (dataset["built_form"] == property_config.built_form) & (dataset["floor_area_quantile"] == property_config.floor_area_quantile) & (dataset["construction_age_band"] == property_config.construction_age_band) ].copy() # Re-set all of the ending columns for col in ending_colums: population[col] = population[column_config[col]] # 1) Loft insulation # For loft insulation, there are two scenarios we test. # 1) Loft insulation to 270mm # 2) Lost insulation to 300mm for insulation_thickness in ["none", "12", "50", "75", "100", "150", "200", "250"]: if insulation_thickness == "none": row = population[ (population["roof_insulation_thickness"] == "none") & (population["is_pitched"]) ] else: row = population[ (population["roof_insulation_thickness"] == insulation_thickness) & (population["is_pitched"]) ] if row.empty: continue row = row.sample(1) loft_insulation_270mm_simulation = Property.create_recommendation_scoring_data( property_id=row["uprn"].values[0], recommendation_record=row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"loft_insulation_{insulation_thickness}_270mm_{config_hash}", "type": "loft_insulation", "new_u_value": best_270mm_uvalue, "parts": [ {"depth": 270} ] } ) loft_insulation_300mm_simulation = Property.create_recommendation_scoring_data( property_id=row["uprn"].values[0], recommendation_record=row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"loft_insulation_{insulation_thickness}_300mm_{config_hash}", "type": "loft_insulation", "new_u_value": best_300mm_uvalue, "parts": [ {"depth": 300} ] } ) # Insert simulation specific configuration details loft_insulation_270mm_simulation = { "simulation_ending_insulation_thickness": "270", "simulation_starting_insulation_thickness": insulation_thickness, **loft_insulation_270mm_simulation } loft_insulation_300mm_simulation = { "simulation_ending_insulation_thickness": "300", "simulation_starting_insulation_thickness": insulation_thickness, **loft_insulation_300mm_simulation } loft_insulation_testing_data.append(loft_insulation_270mm_simulation) loft_insulation_testing_data.append(loft_insulation_300mm_simulation) # 2) Solid wall insulation solid_wall_sample = population[ population["is_solid_brick"] & (population["walls_insulation_thickness"] == "none") ] # We take 1 sample for each value of walls_thermal_transmittance for uvalue in solid_wall_sample["walls_thermal_transmittance"].unique(): row = solid_wall_sample[ solid_wall_sample["walls_thermal_transmittance"] == uvalue ].sample(1) # Simulated IWI internal_wall_insulation_simulation = Property.create_recommendation_scoring_data( property_id=row["uprn"].values[0], recommendation_record=row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"internal_wall_insulation_uvalue_{uvalue}_{config_hash}", "type": "internal_wall_insulation", "new_u_value": best_internal_wall_uvalue, "parts": [] } ) # Simulated EWI external_wall_insulation_simulation = Property.create_recommendation_scoring_data( property_id=row["uprn"].values[0], recommendation_record=row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"external_wall_insulation_uvalue_{uvalue}_{config_hash}", "type": "external_wall_insulation", "new_u_value": best_external_wall_uvalue, "parts": [] } ) # The iww/ewi simulations will be next to each other, so we can see how they differ for the same property solid_wall_testing_data.append(internal_wall_insulation_simulation) solid_wall_testing_data.append(external_wall_insulation_simulation) # 3) Cavity wall insulation cavity_wall_sample = population[ population["is_cavity_wall"] & (~population["is_filled_cavity"]) & ( ~population["external_insulation"] ) & (~population["internal_insulation"]) ] # We take 1 sample for each value of walls_thermal_transmittance for uvalue in cavity_wall_sample["walls_thermal_transmittance"].unique(): row = cavity_wall_sample[ cavity_wall_sample["walls_thermal_transmittance"] == uvalue ].sample(1) # Simulated filled cavity filled_cavity_wall_insulation_simulation = Property.create_recommendation_scoring_data( property_id=row["uprn"].values[0], recommendation_record=row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"cavity_wall_insulation_uvalue_{uvalue}_{config_hash}", "type": "cavity_wall_insulation", "new_u_value": best_cavity_wall_uvalue, "parts": [] } ) cavity_wall_testing_data.append(filled_cavity_wall_insulation_simulation) # 4) Solid floor insulation solid_floor_sample = population[ population["is_solid"] & (population["floor_insulation_thickness"] == "none") ] solid_floor_uvalues = solid_floor_sample["floor_thermal_transmittance"].quantile([0.25, 0.5, 0.75]).values solid_floor_uvalues = {v for v in solid_floor_uvalues if not pd.isnull(v)} # We have many different values of u-value for solid floors, we we'll take a sample at the 25%, 50% and 75% # values # We must take a value that is in one of the unique values for floor_thermal_transmittance for uvalue in solid_floor_uvalues: nearest_value = solid_floor_sample['floor_thermal_transmittance'].sub(uvalue).abs().idxmin() nearest_row = solid_floor_sample.loc[[nearest_value]].sample(1) # Simulated solid floor insulation solid_floor_insulation_simulation = Property.create_recommendation_scoring_data( property_id=nearest_row["uprn"].values[0], recommendation_record=nearest_row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"solid_floor_insulation_uvalue_{uvalue}_{config_hash}", "type": "solid_floor_insulation", "new_u_value": None, # This doesn't matter at the moment "parts": [] } ) solid_floor_testing_data.append(solid_floor_insulation_simulation) # 5) Suspended floor insulation suspended_floor_sample = population[ population["is_suspended"] & (population["floor_insulation_thickness"] == "none") ] suspended_floor_uvalues = suspended_floor_sample["floor_thermal_transmittance"].quantile( [0.25, 0.5, 0.75] ).values suspended_floor_uvalues = {v for v in suspended_floor_uvalues if not pd.isnull(v)} # We take the same approach as for solid floors for uvalue in suspended_floor_uvalues: nearest_value = suspended_floor_sample['floor_thermal_transmittance'].sub(uvalue).abs().idxmin() nearest_row = suspended_floor_sample.loc[[nearest_value]].sample(1) # Simulated suspended floor insulation suspended_floor_insulation_simulation = Property.create_recommendation_scoring_data( property_id=nearest_row["uprn"].values[0], recommendation_record=nearest_row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"suspended_floor_insulation_uvalue_{uvalue}_{config_hash}", "type": "suspended_floor_insulation", "new_u_value": None, # This doesn't matter at the moment "parts": [] } ) suspended_floor_testing_data.append(suspended_floor_insulation_simulation) # 6) Windows - single glazing single_glazing_sample = population[ (population["glazing_type"] == "single") ] if not single_glazing_sample.empty: row = single_glazing_sample.sample(1) # For single glazed windows, we can recommend double glazing or secondary glazing # Simulated double glazing double_glazing_simulation = Property.create_recommendation_scoring_data( property_id=row["uprn"].values[0], recommendation_record=row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"windows_glazing_single_to_double_{config_hash}", "type": "windows_glazing", "new_u_value": None, # This doesn't matter at the moment "parts": [], "is_secondary_glazing": False } ) # Simulated secondary glazing secondary_glazing_simulation = Property.create_recommendation_scoring_data( property_id=row["uprn"].values[0], recommendation_record=row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"windows_glazing_single_to_secondary_{config_hash}", "type": "windows_glazing", "new_u_value": None, # This doesn't matter at the moment "parts": [], "is_secondary_glazing": True } ) # Add in simulation specific details # Add to the beginning of the dictionary double_glazing_simulation = { "simulation_ending_window_finish": "double", **double_glazing_simulation } secondary_glazing_simulation = { "simulation_ending_window_finish": "secondary", **secondary_glazing_simulation } single_glazed_testing_data.append(double_glazing_simulation) single_glazed_testing_data.append(secondary_glazing_simulation) # 7) Windows - partial double glazed partial_double_glazing_sample = population[ (population["glazing_type"] == "double") & (population["multi_glaze_proportion_starting"] > 0) & ( population["multi_glaze_proportion_starting"] < 100 ) ] partial_double_glazed_values = partial_double_glazing_sample["multi_glaze_proportion_starting"].quantile( [0.25, 0.5, 0.75] ).values # Take non-null values partial_double_glazed_values = [v for v in partial_double_glazed_values if not pd.isnull(v)] partial_double_glazed_values = set(partial_double_glazed_values) for value in partial_double_glazed_values: nearest_value = partial_double_glazing_sample['multi_glaze_proportion_starting'].sub(value).abs().idxmin() nearest_row = partial_double_glazing_sample.loc[[nearest_value]].sample(1) # If we start with partial double glazing, we recommend completing the job # Simulated double glazing double_glazing_simulation = Property.create_recommendation_scoring_data( property_id=nearest_row["uprn"].values[0], recommendation_record=nearest_row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"windows_glazing_partial_double_to_double_{value}_{config_hash}", "type": "windows_glazing", "new_u_value": None, # This doesn't matter at the moment "parts": [], "is_secondary_glazing": False } ) partial_double_glazed_testing_data.append(double_glazing_simulation) # 8) Windows - partial secondary glazed partial_secondary_glazing_sample = population[ (population["glazing_type"] == "secondary") & (population["multi_glaze_proportion_starting"] > 0) & ( population["multi_glaze_proportion_starting"] < 100 ) ] partial_secondary_glazed_values = partial_secondary_glazing_sample["multi_glaze_proportion_starting"].quantile( [0.25, 0.5, 0.75] ).values # Take non-null values partial_secondary_glazed_values = [v for v in partial_secondary_glazed_values if not pd.isnull(v)] partial_secondary_glazed_values = set(partial_secondary_glazed_values) for value in partial_secondary_glazed_values: nearest_value = partial_secondary_glazing_sample['multi_glaze_proportion_starting'].sub( value).abs().idxmin() nearest_row = partial_secondary_glazing_sample.loc[[nearest_value]].sample(1) # If we start with partial secondary glazing, we recommend completing the job # Simulated secondary glazing secondary_glazing_simulation = Property.create_recommendation_scoring_data( property_id=nearest_row["uprn"].values[0], recommendation_record=nearest_row.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"windows_glazing_partial_secondary_to_secondary_{value}_{config_hash}", "type": "windows_glazing", "new_u_value": None, # This doesn't matter at the moment "parts": [], "is_secondary_glazing": True } ) partial_secondary_glazed_testing_data.append(secondary_glazing_simulation) # 9) Solar PV # We only recommend solar for properties that have flat or pitched roofs, and no existing solar pitched_roof_no_solar = population[ (population["is_pitched"]) & (population["photo_supply_starting"] == 0) ] if not pitched_roof_no_solar.empty: pitched_roof_no_solar = pitched_roof_no_solar.sample(1) flat_roof_no_solar = population[ (population["is_flat"]) & (population["photo_supply_starting"] == 0) ] if not flat_roof_no_solar.empty: flat_roof_no_solar = flat_roof_no_solar.sample(1) # We simulate 30%, 40% and 50% coverage for coverage in [30, 40, 50]: if not pitched_roof_no_solar.empty: solar_simulation_pitched = Property.create_recommendation_scoring_data( property_id=pitched_roof_no_solar["uprn"].values[0], recommendation_record=pitched_roof_no_solar.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"pitched_solar_pv_coverage_{coverage}_percent_{config_hash}", "type": "solar_pv", "new_u_value": None, # This doesn't matter at the moment "parts": [], "photo_supply": coverage } ) pitched_roof_solar.append(solar_simulation_pitched) if not flat_roof_no_solar.empty: solar_simulation_flat = Property.create_recommendation_scoring_data( property_id=flat_roof_no_solar["uprn"].values[0], recommendation_record=flat_roof_no_solar.copy().to_dict("records")[0], recommendation={ "recommendation_id": f"flat_solar_pv_coverage_{coverage}_percent_{config_hash}", "type": "solar_pv", "new_u_value": None, # This doesn't matter at the moment "parts": [], "photo_supply": coverage } ) flat_roof_solar.append(solar_simulation_flat) # We store all of this data in s3, as it is save_data_to_s3( bucket_name="retrofit-datalake-dev", s3_file_name="sap_change_model/simulation-pipeline-data.json", data=json.dumps( { "loft_insulation_testing_data": loft_insulation_testing_data, "solid_wall_testing_data": solid_wall_testing_data, "cavity_wall_testing_data": cavity_wall_testing_data, "solid_floor_testing_data": solid_floor_testing_data, "suspended_floor_testing_data": suspended_floor_testing_data, "single_glazed_testing_data": single_glazed_testing_data, "partial_double_glazed_testing_data": partial_double_glazed_testing_data, "partial_secondary_glazed_testing_data": partial_secondary_glazed_testing_data, "pitched_roof_solar": pitched_roof_solar, "flat_roof_solar": flat_roof_solar } ) ) # For each simulation type, we score against the model from backend.ml_models.api import ModelApi from datetime import datetime created_at = datetime.now().isoformat() model_api = ModelApi(portfolio_id="simulation-testing-pipeline", timestamp=created_at) model_api.MODEL_PREFIXES = ["sap_change_predictions"] # 1) Loft insulation # We chunk up the data into 200 rows loft_insulation_testing_df = pd.DataFrame(loft_insulation_testing_data) loft_insulation_predictions = [] loft_to_loop_over = range(0, loft_insulation_testing_df.shape[0], 200) for chunk in tqdm(loft_to_loop_over, total=len(loft_to_loop_over)): loft_insulation_predictions_dict = model_api.predict_all( df=loft_insulation_testing_df.iloc[chunk:chunk + 200], bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) loft_insulation_predictions.append(loft_insulation_predictions_dict["sap_change_predictions"]) loft_insulation_predictions = pd.concat(loft_insulation_predictions) # Store final parquet in s3 save_dataframe_to_s3_parquet( df=loft_insulation_predictions, bucket_name="retrofit-datalake-dev", file_key=f"sap_change_model/simulation-pipeline-loft-insulation-predictions_{MODEL_VERSION}.parquet" ) # We now merge the loft insulation predictions onto the scoring data and calculate exactly how much the insulation # is worth loft_insulation_comparison_matrix = loft_insulation_testing_df[ ["simulation_starting_insulation_thickness", "simulation_ending_insulation_thickness", "uprn", "id", "sap_starting"] ].merge( loft_insulation_predictions.drop(columns=["recommendation_id"]), left_on="id", right_on="id", how="left" ) loft_insulation_comparison_matrix["measure_impact"] = loft_insulation_comparison_matrix["predictions"] - \ loft_insulation_comparison_matrix["sap_starting"] # We create a sap band grouping, for every 10 points of sap. So 1-10, 11-20, 21-30 etc loft_insulation_comparison_matrix["sap_band"] = pd.cut( loft_insulation_comparison_matrix["sap_starting"], bins=range(0, 101, 10), labels=range(1, 11) ) # Perform a group by describe loft_insulation_describe = loft_insulation_comparison_matrix.groupby( ["sap_band", "simulation_starting_insulation_thickness", "simulation_ending_insulation_thickness"] )[["measure_impact"]].describe().reset_index() for col in ["simulation_starting_insulation_thickness", "simulation_ending_insulation_thickness"]: loft_insulation_describe[col] = loft_insulation_describe[col].str.replace('none', "0") loft_insulation_describe[col] = loft_insulation_describe[col].astype(int) loft_insulation_describe = loft_insulation_describe.sort_values( ["simulation_ending_insulation_thickness", "simulation_starting_insulation_thickness"], ascending=True ) # In the training data, try and get just the rows that are loft insulation only # Things that change: # 1) roof_insulation_thickness # 3) roof_thermal_transmittance # 4) roof_energy_eff_ending loft_insulation_training_data = dataset.copy() loft_insulation_columns_we_need_the_same = [c for c in column_config.keys() if c not in [ "roof_insulation_thickness_ending", "roof_thermal_transmittance_ending", "roof_energy_eff_ending", "transaction_type_ending", "days_to_ending", "sap_ending", "heat_demand_ending", "carbon_ending", "total_floor_area_ending", "floor_height_ending", "estimated_perimeter_ending" ]] for ending_col in tqdm(loft_insulation_columns_we_need_the_same): starting_col = column_config[ending_col] loft_insulation_training_data = loft_insulation_training_data[ loft_insulation_training_data[ending_col] == loft_insulation_training_data[starting_col] ] # We get rows where the insulation starts at 200mm insulation_200mm_starting = loft_insulation_training_data[ (loft_insulation_training_data["roof_insulation_thickness"] == "200") & (loft_insulation_training_data["roof_insulation_thickness_ending"] == "300") ] # Let's use the API to find exactly the record from backend.SearchEpc import SearchEpc testing_model_api = ModelApi(portfolio_id="simulation-testing-loft-example", timestamp=created_at) testing_model_api.MODEL_PREFIXES = ["sap_change_predictions"] ############################################################################################################ # TODO:! # Findings: # 1) For uprn 10009320092, the number of rooms and number of heated rooms has changed and can change from # epc to epc. We should therefore include a starting and ending value for this # 2) Maybe we should include tenure??? Owner occupied homes are going to be a lot more unusual. Both investigation # 2 and 3 were owner occupied # 3) Maybe we should treat photo_supply missing differently than 0? ################################################################################################ # Investigation 1) searcher = SearchEpc( address1="2 Darkfield Way", postcode="TA7 8HY", auth_token="a2Nvbm5rb3dsZXNzYXJAZ21haWwuY29tOjY5MGJiMWM0NmIyOGI5ZDUxYzAxMzQzYzNiZGNlZGJjZDNmODQwMzA=", os_api_key="" ) searcher.uprn = "10009320092" searcher.find_property(skip_os=True) newest_epc = searcher.newest_epc older_epc = [epc for epc in searcher.older_epcs if epc["lmk-key"] == "5ae2f073004839510f9eeb1886160776a05697f8518b8b3b63d45f65686c4757"][0] # Iterate through the keys in the newest_epc and find the values in older epc that are different to the newest epc differences = {} for k, v in newest_epc.items(): if v != older_epc[k]: differences[k] = (v, older_epc[k]) testing_row = insulation_200mm_starting[insulation_200mm_starting["uprn"] == "10009320092"].copy() testing_row["id"] = "testing-200mm-loft-insulation-starting-baseline+recommendation_id_baseline" testing_row["recommendation_id"] = "recommendation_id_baseline" # The testing row has 4 rooms # Score in the model to see what we get baseline_prediction = testing_model_api.predict_all( df=testing_row, bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) baseline_pred_df = baseline_prediction["sap_change_predictions"] impact = baseline_pred_df["predictions"].values[0] - testing_row["sap_starting"].values[0] # Changing this from 4 rooms to 5 rooms has NO impact!! testing_row_5_rooms = testing_row.copy() testing_row_5_rooms["id"] = "testing-200mm-loft-insulation-starting-baseline+recommendation_id_5_rooms" testing_row_5_rooms["recommendation_id"] = "recommendation_id_5_rooms" testing_row_5_rooms["number_habitable_rooms"] = float(5) testing_row_5_rooms["number_heated_rooms"] = float(5) prediction_5_rooms = testing_model_api.predict_all( df=testing_row_5_rooms, bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) pred_df_5_rooms = prediction_5_rooms["sap_change_predictions"] impact_5_rooms = pred_df_5_rooms["predictions"].values[0] - testing_row_5_rooms["sap_starting"].values[0] ################################################################################################ # Investigation 2 searcher = SearchEpc( address1="19 Rossal Place", postcode="MK12 6JE", auth_token="a2Nvbm5rb3dsZXNzYXJAZ21haWwuY29tOjY5MGJiMWM0NmIyOGI5ZDUxYzAxMzQzYzNiZGNlZGJjZDNmODQwMzA=", os_api_key="" ) searcher.uprn = "25006966" searcher.find_property(skip_os=True) newest_epc = searcher.newest_epc older_epc = [epc for epc in searcher.older_epcs if epc["lmk-key"] == "fe23917ac59fbf3a608c76431941011ab4c7938546a432fc6212182caab31d73"][0] # Iterate through the keys in the newest_epc and find the values in older epc that are different to the newest epc differences = {} for k, v in newest_epc.items(): if v != older_epc[k]: differences[k] = (v, older_epc[k]) testing_row2 = insulation_200mm_starting[insulation_200mm_starting["uprn"] == "25006966"].copy() # THERE IS NOTHING CLEAR THAT IS CHANGING IN THIS RECORD THAT INDICATES SUGGESTS WE'RE MISSING INFORMATION ################################################################################################ # Investigation 3 insulation_200mm_starting[ insulation_200mm_starting["rdsap_change"] == insulation_200mm_starting["rdsap_change"].max() ]["uprn"].values[0] # This UPRN: 100060350521 searcher = SearchEpc( address1="138 Nicholas Crescent", postcode="PO15 5AN", auth_token="a2Nvbm5rb3dsZXNzYXJAZ21haWwuY29tOjY5MGJiMWM0NmIyOGI5ZDUxYzAxMzQzYzNiZGNlZGJjZDNmODQwMzA=", os_api_key="" ) searcher.uprn = "100060350521" searcher.find_property(skip_os=True) newest_epc = searcher.newest_epc older_epc = [epc for epc in searcher.older_epcs if epc["lmk-key"] == "9c4059762189b451191c98d2ef980a5364b8b7e0be3f064f681abcd4a0da681b"][0] # Iterate through the keys in the newest_epc and find the values in older epc that are different to the newest epc differences = {} for k, v in newest_epc.items(): if v != older_epc[k]: differences[k] = (v, older_epc[k]) # Nothing looks amiss about this record - let's score it in the model testing_row_3 = insulation_200mm_starting[insulation_200mm_starting["uprn"] == "100060350521"].copy() testing_row_3["id"] = "testing-200mm-loft-insulation-starting-baseline+recommendation_id_baseline" testing_row_3["recommendation_id"] = "recommendation_id_baseline" # The testing row has 4 rooms # Score in the model to see what we get baseline_prediction3 = testing_model_api.predict_all( df=testing_row_3, bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) baseline_pred_df3 = baseline_prediction3["sap_change_predictions"] impact3 = baseline_pred_df3["predictions"].values[0] - testing_row_3["sap_starting"].values[0] # TODO: Look at some of the example properties we have, and test using the model to score the impact of the # different measures when multiple measures are scored together e.g. cavity + loft together vs individually # Look at performance on loft only rows loft_insulation_training_data["id"] = (loft_insulation_training_data["uprn"] + "_loft_insulation + recommendation_id_placeholder") loft_only_predictions = [] loft_to_loop_over = range(0, loft_insulation_training_data.shape[0], 400) for chunk in tqdm(loft_to_loop_over, total=len(loft_to_loop_over)): loft_insulation_predictions_dict = model_api.predict_all( df=loft_insulation_training_data.iloc[chunk:chunk + 400], bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) loft_only_predictions.append(loft_insulation_predictions_dict["sap_change_predictions"]) loft_only_predictions = pd.concat(loft_only_predictions) def calculate_mape(actuals, predictions): """ Calculate the Mean Absolute Percentage Error (MAPE). Parameters: - actuals: A list or array of actual values. - predictions: A list or array of predicted values, corresponding to actuals. Returns: - mape: The MAPE score as a float. Note: This function assumes actuals and predictions are of the same length and does not contain zeros in the actuals (to avoid division by zero). """ # Convert inputs to numpy arrays for vectorized operations import numpy as np actuals = np.array(actuals) predictions = np.array(predictions) # Calculate the absolute percentage errors ape = np.abs((actuals - predictions) / actuals) * 100 # Calculate the mean of these percentage errors mape = np.mean(ape) return mape calculate_mape(actuals=loft_insulation_training_data["sap_ending"], predictions=loft_only_predictions["predictions"]) comparison_df = pd.DataFrame( { "sap_starting": loft_insulation_training_data["sap_starting"].values, "actual_sap_ending": loft_insulation_training_data["sap_ending"].values, "predicted_sap_ending": loft_only_predictions["predictions"].values } ) comparison_df["residual"] = abs(comparison_df["actual_sap_ending"] - comparison_df["predicted_sap_ending"]) comparison_df["residual"].describe() comparison_df[comparison_df["residual"] == comparison_df["residual"].max()] # TODO: Test scoring separately vs combined from etl.epc.Record import EPCRecord from etl.solar.SolarPhotoSupply import SolarPhotoSupply from utils.s3 import read_from_s3 import msgpack from recommendations.Recommendations import Recommendations import datetime from numpy import nan cleaning_data = read_dataframe_from_s3_parquet( bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet", ) uprn_filenames = read_dataframe_from_s3_parquet( bucket_name="retrofit-data-dev", file_key="spatial/filename_meta.parquet" ) photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev") cleaned = read_from_s3( s3_file_name="cleaned_epc_data/cleaned.bson", bucket_name="retrofit-data-dev".format(environment="retrofit-data-dev") ) cleaned = msgpack.unpackb(cleaned, raw=False) 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': nan, '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': nan, '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': nan, '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': 1226, 'type': 'flat_roof_insulation', 'description': 'Ravatherm XPS × 500 SL', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.03125, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.032, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': None, 'material_cost': 22.14, 'labour_cost': 10.66, 'labour_hours_per_unit': 0.48, 'plant_cost': 0.0, 'total_cost': 32.8, 'notes': None}, {'id': 1227, 'type': 'flat_roof_insulation', 'description': 'Ravatherm XPS × 500 SL', 'depth': 120.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.03125, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.032, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'https://www.panelsystems.co.uk/product/floormate-ravatherm-sb?attribute_pa_group=floormate-500a' '&attribute_pa_product-name=ravatherm-xps-x-500-sl&attribute_pa_length=1250&attribute_pa_width=600' '&attribute_pa_thickness=120&attribute_pa_unit-of-sale=pack-3-brds&attribute_pa_min-order-qty=10' '&gclid=CjwKCAiAjrarBhAWEiwA2qWdCKJK2iqlzUZ-mBFOfCLy2f5TldAbOj7G3LrvYw5JLaigplJAajzYpRoCtB8QAvD_BwE', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': None, 'material_cost': 26.187656, 'labour_cost': 10.66, 'labour_hours_per_unit': 0.48, 'plant_cost': 0.0, 'total_cost': 36.847656, 'notes': "SPONs didn't have this thickness, so the material price is based on the fact that on the link, " "the 120mm thickness is 18% more expensive per board than the 100mm thickness"}, {'id': 1228, 'type': 'flat_roof_insulation', 'description': 'Ravatherm XPS × 500 SL', 'depth': 140.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.03125, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.032, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'https://www.panelsystems.co.uk/product/floormate-ravatherm-sb?attribute_pa_group=floormate-500a' '&attribute_pa_product-name=ravatherm-xps-x-500-sl&attribute_pa_length=1250&attribute_pa_width=600' '&attribute_pa_thickness=120&attribute_pa_unit-of-sale=pack-3-brds&attribute_pa_min-order-qty=10' '&gclid=CjwKCAiAjrarBhAWEiwA2qWdCKJK2iqlzUZ-mBFOfCLy2f5TldAbOj7G3LrvYw5JLaigplJAajzYpRoCtB8QAvD_BwE', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': None, 'material_cost': 31.114737, 'labour_cost': 10.66, 'labour_hours_per_unit': 0.48, 'plant_cost': 0.0, 'total_cost': 41.77474, 'notes': "SPONs didn't have this thickness, so the material price is based on the fact that on the link, " "the 140mm thickness is 40% more expensive per board than the 100mm thickness"}, {'id': 1229, 'type': 'flat_roof_insulation', 'description': 'Foamglas T3+ Flat Roof Insulation', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.027777778, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.036, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': 95.83, 'material_cost': 109.09, 'labour_cost': 30.7, 'labour_hours_per_unit': 1.3, 'plant_cost': 0.0, 'total_cost': 139.79, 'notes': None}, {'id': 1230, 'type': 'flat_roof_insulation', 'description': 'Foamglas T4+ Flat Roof Insulation', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.024390243, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.041, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': 63.89, 'material_cost': 76.19, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 104.53, 'notes': None}, {'id': 1231, 'type': 'flat_roof_insulation', 'description': 'Ecotherm Eco-Versal General ' 'Purpose Insulation Board', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': 15.12, 'material_cost': 25.96, 'labour_cost': 30.7, 'labour_hours_per_unit': 1.3, 'plant_cost': 0.0, 'total_cost': 56.66, 'notes': None}, {'id': 1232, 'type': 'flat_roof_insulation', 'description': 'Ecotherm Eco-Versal General Purpose Insulation Board', 'depth': 120.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': 20.16, 'material_cost': 34.613335, 'labour_cost': 30.7, 'labour_hours_per_unit': 1.3, 'plant_cost': 0.0, 'total_cost': 65.31333, 'notes': "SPONs didn't have this thickness, so the material price is based on the fact that on the link, " "the 120mm thickness is 33% more expensive than the 100mm thickness"}, {'id': 1233, 'type': 'flat_roof_insulation', 'description': 'Ecotherm Eco-Versal General Purpose Insulation Board', 'depth': 150.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': 23.53, 'material_cost': 34.62, 'labour_cost': 33.06, 'labour_hours_per_unit': 1.4, 'plant_cost': 0.0, 'total_cost': 67.68, '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': nan, '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}, {'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': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), '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"}, {'id': 1235, 'type': 'windows_glazing', 'description': 'uPVC windows; Profile 22 or other equal and approved; reinforced where appropriate with ' 'aluminium alloy; in refurbishment work, including standard ironmongery; sills and factory ' 'glazed with low-e 24 mm double glazing; removing existing windows and fixing new in ' 'position; including lugs plugged and screwed to brickwork or blockwork; Casement/fixed ' 'light; including vents; e.p.d.m. glazing gaskets and weather seals; 1770 mm × 1200 mm; ref ' 'P312WW', 'depth': 0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_unit', '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, 20, 14, 37, 51, 728866), 'is_active': True, 'prime_material_cost': 176.55, 'material_cost': 182.25, 'labour_cost': 163.36, 'labour_hours_per_unit': 6.5, 'plant_cost': 0.0, 'total_cost': 345.61, 'notes': 'This is the cost of removal of existing windows and installation of new windows. This is a ' 'casement style window, which is the most common but also the cheapest style. In the cost ' 'estimation framework, we can inflate prices for different finishes, to be conservative on price. '}, {'id': 1109, 'type': 'cavity_wall_insulation', 'description': 'Expanded Polystyrene Beads cavity wall insulation', 'depth': 75.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.030303031, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.033, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'https://www.styrene.co.uk/downloads/Datasheets' '/Stylite_Cavity_Loose_Fill_Insulation_Datasheet_v20211.pdf', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 18.875, 'labour_cost': 1.125, 'labour_hours_per_unit': 0.065, 'plant_cost': 0.0, 'total_cost': 20.0, 'notes': "It is hard to find materials online. To price this, we've used this article: " "https://www.greenmatch.co.uk/blog/cavity-wall-insulation-cost It puts EPS beads at around £22 per " "meter squared, blowing wool insulation at £18 per meter squared and Polyurethane Foam at £26 per " "meter squared, when taking the most pessimistic prices. These rates have been used to adjust the " "price of the mineral wool insulation to give us the other forms of insulation"}, {'id': 1110, 'type': 'cavity_wall_insulation', 'description': 'Injected Polyurthane Foam cavity wall insulation', 'depth': 75.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.030303031, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.033, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'https://www.foaminstall.co.uk/wp-content/uploads/2017/04/Lapolla-Cavity-Fill-BBA-certificate-sheet1' '.pdf', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 22.875, 'labour_cost': 1.125, 'labour_hours_per_unit': 0.065, 'plant_cost': 0.0, 'total_cost': 24.0, 'notes': None}, {'id': 1111, 'type': 'loft_insulation', 'description': 'Crown Loft Roll 44 glass fibre roll', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.022727273, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 2.03, 'material_cost': 2.1, 'labour_cost': 1.56, 'labour_hours_per_unit': 0.09, 'plant_cost': 0.0, 'total_cost': 3.66, 'notes': None}, {'id': 1112, 'type': 'loft_insulation', 'description': 'Crown Loft Roll 44 glass fibre roll', 'depth': 150.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.022727273, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 3.06, 'material_cost': 3.16, 'labour_cost': 1.78, 'labour_hours_per_unit': 0.1, 'plant_cost': 0.0, 'total_cost': 4.94, 'notes': None}, {'id': 1113, 'type': 'loft_insulation', 'description': 'Crown Loft Roll 44 glass fibre roll', 'depth': 170.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.022727273, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'https://insulation4less.co.uk/products/knauf-170mm-combi-cut?variant=31671561257013&dfw_tracker' '=77750-31671561257013&utm_source=google&utm_medium=shopping&utm_campaign=shoptimised&gad_source=1' '&gclid=CjwKCAiAx_GqBhBQEiwAlDNAZi1LiTWKVn0W1vktOYAPPQU3hss5Tq2qNn6GNhodCQoRD_tvqCLdxhoCKnIQAvD_BwE', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 3.81938, 'labour_cost': 1.71304, 'labour_hours_per_unit': 0.11, 'plant_cost': 0.0, 'total_cost': 5.53242, 'notes': "We don't have a 170mm in SPONs so the material cost is based on the fact that the 170mm insulation " "is 87.4% of the cost of the 200mm insulation"}, {'id': 1114, 'type': 'loft_insulation', 'description': 'Crown Loft Roll 44 glass fibre roll', 'depth': 200.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.022727273, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 4.25, 'material_cost': 4.37, 'labour_cost': 1.96, 'labour_hours_per_unit': 0.11, 'plant_cost': 0.0, 'total_cost': 6.33, 'notes': None}, {'id': 1115, 'type': 'loft_insulation', 'description': 'Crown Loft Roll 44 glass fibre roll', 'depth': 270.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.022727273, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 5.91938, 'labour_cost': 1.96, 'labour_hours_per_unit': 0.11, 'plant_cost': 0.0, 'total_cost': 7.87938, 'notes': 'This is the 100mm product + the 170mm product'}, {'id': 1116, 'type': 'loft_insulation', 'description': 'Crown Loft Roll 44 glass fibre roll', 'depth': 300.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.022727273, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 6.47, 'labour_cost': 1.96, 'labour_hours_per_unit': 0.11, 'plant_cost': 0.0, 'total_cost': 8.43, 'notes': 'This is the 100mm product + the 200mm product'}, {'id': 1117, 'type': 'loft_insulation', 'description': 'Isover Mineral Wool Modular Roll', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 1.99, 'material_cost': 2.05, 'labour_cost': 1.6, 'labour_hours_per_unit': 0.09, 'plant_cost': 0.0, 'total_cost': 3.65, 'notes': None}, {'id': 1118, 'type': 'loft_insulation', 'description': 'Isover Mineral Wool Modular Roll', 'depth': 150.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 2.96, 'material_cost': 3.05, 'labour_cost': 1.78, 'labour_hours_per_unit': 0.1, 'plant_cost': 0.0, 'total_cost': 4.83, 'notes': None}, {'id': 1119, 'type': 'loft_insulation', 'description': 'Isover Mineral Wool Modular Roll', 'depth': 170.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'https://flooringwarehousedirect.co.uk/product/isover-spacesaver-roll-170mm-x-1160mm-x-7-03m-8-15m2/', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 3.8706238, 'labour_cost': 2.281361, 'labour_hours_per_unit': 0.12816635, 'plant_cost': 0.0, 'total_cost': 6.1519847, 'notes': "We don't have a 170mm in SPONs so the material cost is based on the fact that the 170mm insulation " "is 85.4% of the cost of the 200mm insulation"}, {'id': 1120, 'type': 'loft_insulation', 'description': 'Isover Mineral Wool Modular Roll', 'depth': 200.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 4.4, 'material_cost': 4.53, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15, 'plant_cost': 0.0, 'total_cost': 7.2, 'notes': None}, {'id': 1121, 'type': 'loft_insulation', 'description': 'Isover Mineral Wool Modular Roll', 'depth': 270.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 5.920624, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15, 'plant_cost': 0.0, 'total_cost': 8.590624, 'notes': 'This is the 100mm product + the 170mm product'}, {'id': 1122, 'type': 'loft_insulation', 'description': 'Isover Mineral Wool Modular Roll', 'depth': 300.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 6.58, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15, 'plant_cost': 0.0, 'total_cost': 9.25, 'notes': 'This is the 100mm product + the 200mm product'}, {'id': 1123, 'type': 'loft_insulation', 'description': 'Isover Acoustic Partition Roll', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 5.93, 'material_cost': 6.4, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15, 'plant_cost': 0.0, 'total_cost': 9.07, 'notes': 'This provides acoustic insulation as well'}, {'id': 1124, 'type': 'loft_insulation', 'description': 'Isover Acoustic Partition Roll', 'depth': 300.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 17.79, 'material_cost': 19.2, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15, 'plant_cost': 0.0, 'total_cost': 21.87, 'notes': 'This provides acoustic insulation as well'}, {'id': 1125, 'type': 'loft_insulation', 'description': 'Thermafleece EcoRoll Insulation', 'depth': 300.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.025641026, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.039, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 24.78, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15, 'plant_cost': 0.0, 'total_cost': 27.45, 'notes': 'This material is based on installing 3 layers of the 100mm product'}, {'id': 1126, 'type': 'loft_insulation', 'description': 'Thermafleece EcoRoll Insulation', 'depth': 280.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.025641026, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.039, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 23.36, 'labour_cost': 3.12, 'labour_hours_per_unit': 0.18, 'plant_cost': 0.0, 'total_cost': 26.48, 'notes': 'This material is based on installed 2 layers of the 140mm product'}, {'id': 1127, 'type': 'iwi_wall_demolition', 'description': 'Solid & Dry Lined walls: Hack of wall finishes with chipping hammer; plaster to walls.', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 10.27, 'labour_hours_per_unit': 0.33, 'plant_cost': 1.28, 'total_cost': 11.55, 'notes': None}, {'id': 1128, 'type': 'iwi_wall_demolition', 'description': 'Stud walls: Remove wall linings ' 'including battening behind; ' 'plasterboard and skim', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 6.23, 'labour_hours_per_unit': 0.2, 'plant_cost': 1.25, 'total_cost': 7.48, 'notes': None}, {'id': 1129, 'type': 'iwi_wall_demolition', 'description': 'Lathe and Plaster walls: Remove wall linings including battening behind; wood lath and ' 'plaster', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 6.85, 'labour_hours_per_unit': 0.22, 'plant_cost': 2.09, 'total_cost': 8.94, 'notes': None}, {'id': 1130, 'type': 'internal_wall_insulation', 'description': 'Foamglas Grade F Wall Insulation Slabs', 'depth': 60.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.02631579, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.038, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 41.69, 'material_cost': 53.33, 'labour_cost': 29.52, 'labour_hours_per_unit': 1.25, 'plant_cost': 0.0, 'total_cost': 82.85, 'notes': None}, {'id': 1131, 'type': 'internal_wall_insulation', 'description': 'Foamglas Grade F Wall Insulation Slabs', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.02631579, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.038, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 86.86, 'material_cost': 99.85, 'labour_cost': 29.52, 'labour_hours_per_unit': 1.25, 'plant_cost': 0.0, 'total_cost': 129.37, 'notes': None}, {'id': 1132, 'type': 'internal_wall_insulation', 'description': 'Foamglas Grade F Wall Insulation Slabs', 'depth': 150.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.02631579, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.038, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 130.29, 'material_cost': 144.58, 'labour_cost': 29.52, 'labour_hours_per_unit': 1.25, 'plant_cost': 0.0, 'total_cost': 174.1, 'notes': None}, {'id': 1133, 'type': 'internal_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board', 'depth': 30.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 6.16, 'material_cost': 16.73, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 45.07, 'notes': None}, {'id': 1134, 'type': 'internal_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 8.46, 'material_cost': 19.1, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 47.44, 'notes': None}, {'id': 1135, 'type': 'internal_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 15.12, 'material_cost': 25.96, 'labour_cost': 30.7, 'labour_hours_per_unit': 1.3, 'plant_cost': 0.0, 'total_cost': 56.66, 'notes': None}, {'id': 1136, 'type': 'internal_wall_insulation', 'description': 'Kingspan Kooltherm K18 insulated plasterboard', 'depth': 37.5, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04761905, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.021, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 26.86, 'labour_cost': 5.21, 'labour_hours_per_unit': 0.23, 'plant_cost': 0.0, 'total_cost': 32.07, 'notes': None}, {'id': 1137, 'type': 'internal_wall_insulation', 'description': 'Kingspan Kooltherm K18 insulated plasterboard', 'depth': 42.5, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04761905, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.021, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 17.37, 'labour_cost': 5.21, 'labour_hours_per_unit': 0.23, 'plant_cost': 0.0, 'total_cost': 22.58, 'notes': None}, {'id': 1138, 'type': 'internal_wall_insulation', 'description': 'Kingspan Kooltherm K18 insulated plasterboard', 'depth': 52.5, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04761905, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.021, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 21.74, 'labour_cost': 5.79, 'labour_hours_per_unit': 0.25, 'plant_cost': 0.0, 'total_cost': 27.53, 'notes': None}, {'id': 1139, 'type': 'internal_wall_insulation', 'description': 'Kingspan Kooltherm K18 insulated plasterboard', 'depth': 62.5, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04761905, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.021, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 19.3, 'labour_cost': 5.79, 'labour_hours_per_unit': 0.25, 'plant_cost': 0.0, 'total_cost': 25.09, 'notes': None}, {'id': 1140, 'type': 'internal_wall_insulation', 'description': 'Kingspan Kooltherm K18 insulated plasterboard', 'depth': 72.5, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04761905, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.021, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 23.15, 'labour_cost': 5.79, 'labour_hours_per_unit': 0.25, 'plant_cost': 0.0, 'total_cost': 28.94, 'notes': None}, {'id': 1141, 'type': 'iwi_vapour_barrier', 'description': 'Visqueen High Performance Vapour Barrier', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), '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': 1142, 'type': 'iwi_redecoration', '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', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.06, 'labour_cost': 6.58, 'labour_hours_per_unit': 0.25, 'plant_cost': 0.0, 'total_cost': 6.64, 'notes': None}, {'id': 1143, 'type': 'iwi_redecoration', 'description': 'Two coats emulsion paint on plaster, over 40mm girth; 3.5m - 5m high', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.41, 'labour_cost': 3.93, 'labour_hours_per_unit': 0.21, 'plant_cost': 0.0, 'total_cost': 4.34, 'notes': None}, {'id': 1144, 'type': 'iwi_redecoration', 'description': 'Fitting existing softwood skirting ' 'or architrave to new frames; 150mm ' 'high', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.01, 'labour_cost': 4.87, 'labour_hours_per_unit': 0.12, 'plant_cost': 0.0, 'total_cost': 4.88, 'notes': None}, {'id': 1145, 'type': 'suspended_floor_demolition', 'description': 'Removal of carpet and underfelt', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 3.32, 'labour_hours_per_unit': 0.11, 'plant_cost': 0.0, 'total_cost': 3.32, 'notes': 'We ignore the plant cost that is in SPONs because we assume the carpet is not scrapped and ' 'therefore there is no need for a skip'}, {'id': 1146, 'type': 'suspended_floor_demolition', 'description': 'Remove boarding; withdraw nails; set aside for reuse; ground level', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 9.34, 'labour_hours_per_unit': 0.3, 'plant_cost': 0.0, 'total_cost': 9.34, 'notes': None}, {'id': 1147, 'type': 'suspended_floor_vapour_barrier', 'description': 'Visqueen High Performance Vapour ' 'Barrier', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), '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': 1148, 'type': 'suspended_floor_insulation', 'description': 'Thermafleece CosyWool Roll', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.025641026, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.039, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 4.24, 'labour_cost': 1.56, 'labour_hours_per_unit': 0.09, 'plant_cost': 0.0, 'total_cost': 5.8, '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'}, {'id': 1149, 'type': 'suspended_floor_insulation', 'description': 'Thermafleece CosyWool Roll', 'depth': 75.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.025641026, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.039, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 6.31, 'labour_cost': 1.56, 'labour_hours_per_unit': 0.09, 'plant_cost': 0.0, 'total_cost': 7.87, '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'}, {'id': 1150, 'type': 'suspended_floor_insulation', 'description': 'Thermafleece CosyWool Roll', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.025641026, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.039, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 8.26, 'labour_cost': 1.56, 'labour_hours_per_unit': 0.1, 'plant_cost': 0.0, 'total_cost': 9.82, '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'}, {'id': 1151, 'type': 'suspended_floor_insulation', 'description': 'Thermafleece CosyWool Roll', 'depth': 140.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.025641026, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.039, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 11.68, 'labour_cost': 1.78, 'labour_hours_per_unit': 0.1, 'plant_cost': 0.0, 'total_cost': 13.46, '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'}, {'id': 1152, 'type': 'suspended_floor_insulation', 'description': 'Thermafleece TF35 high density wool insulating batts', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.028571429, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.035, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 6.63, 'labour_cost': 1.56, 'labour_hours_per_unit': 0.09, 'plant_cost': 0.0, 'total_cost': 8.19, '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'}, {'id': 1153, 'type': 'suspended_floor_insulation', 'description': 'Thermafleece TF35 high density wool insulating batts', 'depth': 75.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.028571429, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.035, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 10.31, 'labour_cost': 1.56, 'labour_hours_per_unit': 0.09, 'plant_cost': 0.0, 'total_cost': 11.87, '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'}, {'id': 1154, 'type': 'suspended_floor_insulation', 'description': 'Ecotherm Eco-Versal General Purpose Insulation Board', 'depth': 30.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 6.16, 'material_cost': 16.73, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 45.07, 'notes': None}, {'id': 1155, 'type': 'suspended_floor_insulation', 'description': 'Ecotherm Eco-Versal General Purpose ' 'Insulation Board', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 8.46, 'material_cost': 19.1, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 47.44, 'notes': None}, {'id': 1156, 'type': 'suspended_floor_insulation', 'description': 'Ecotherm Eco-Versal General Purpose Insulation Board', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 15.12, 'material_cost': 25.96, 'labour_cost': 30.7, 'labour_hours_per_unit': 1.3, 'plant_cost': 0.0, 'total_cost': 56.66, 'notes': None}, {'id': 1157, 'type': 'suspended_floor_insulation', 'description': 'Ecotherm Eco-Versal General Purpose ' 'Insulation Board', 'depth': 150.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 23.53, 'material_cost': 34.62, 'labour_cost': 33.06, 'labour_hours_per_unit': 1.4, 'plant_cost': 0.0, 'total_cost': 67.68, 'notes': None}, {'id': 1158, 'type': 'suspended_floor_insulation', 'description': 'Crown Loft Roll 44 glass fibre roll', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': 0.022727273, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 2.03, 'material_cost': 2.1, 'labour_cost': 1.56, 'labour_hours_per_unit': 0.09, 'plant_cost': 0.0, 'total_cost': 3.66, 'notes': None}, {'id': 1159, 'type': 'suspended_floor_insulation', 'description': 'Crown Loft Roll 44 glass fibre roll', 'depth': 150.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': 0.022727273, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 3.06, 'material_cost': 3.16, 'labour_cost': 1.78, 'labour_hours_per_unit': 0.1, 'plant_cost': 0.0, 'total_cost': 4.94, 'notes': None}, {'id': 1160, 'type': 'suspended_floor_insulation', 'description': 'Crown Loft Roll 44 glass fibre roll', 'depth': 200.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': 0.022727273, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 4.25, 'material_cost': 4.37, 'labour_cost': 1.96, 'labour_hours_per_unit': 0.11, 'plant_cost': 0.0, 'total_cost': 6.33, 'notes': None}, {'id': 1161, 'type': 'suspended_floor_insulation', 'description': 'Isover Mineral Wool Modular Roll', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 1.99, 'material_cost': 2.05, 'labour_cost': 1.6, 'labour_hours_per_unit': 0.09, 'plant_cost': 0.0, 'total_cost': 3.65, 'notes': None}, {'id': 1162, 'type': 'suspended_floor_insulation', 'description': 'Isover Mineral Wool Modular Roll', 'depth': 150.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 2.96, 'material_cost': 3.05, 'labour_cost': 1.78, 'labour_hours_per_unit': 0.1, 'plant_cost': 0.0, 'total_cost': 4.83, 'notes': None}, {'id': 1163, 'type': 'suspended_floor_insulation', 'description': 'Isover Mineral Wool Modular Roll', 'depth': 200.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 4.4, 'material_cost': 4.53, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15, 'plant_cost': 0.0, 'total_cost': 7.2, 'notes': None}, {'id': 1164, 'type': 'suspended_floor_insulation', 'description': 'Isover Acoustic Partition Roll', 'depth': 25.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': 0.025641026, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.039, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 1.67, 'material_cost': 2.01, 'labour_cost': 1.43, 'labour_hours_per_unit': 0.08, 'plant_cost': 0.0, 'total_cost': 3.44, 'notes': None}, {'id': 1165, 'type': 'suspended_floor_insulation', 'description': 'Isover Acoustic Partition Roll', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': 0.025641026, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.039, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 2.74, 'material_cost': 3.11, 'labour_cost': 1.6, 'labour_hours_per_unit': 0.09, 'plant_cost': 0.0, 'total_cost': 4.71, 'notes': None}, {'id': 1166, 'type': 'suspended_floor_insulation', 'description': 'Isover Acoustic Partition Roll', 'depth': 75.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 4.57, 'material_cost': 5.01, 'labour_cost': 1.78, 'labour_hours_per_unit': 0.1, 'plant_cost': 0.0, 'total_cost': 6.79, 'notes': None}, {'id': 1167, 'type': 'suspended_floor_insulation', 'description': 'Isover Acoustic Partition Roll', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': 0.023255814, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.043, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 5.93, 'material_cost': 6.4, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15, 'plant_cost': 0.0, 'total_cost': 9.07, 'notes': None}, {'id': 1168, 'type': 'suspended_floor_insulation', 'description': 'Kay-Cel Expanded Polystyrene Board', 'depth': 25.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.030303031, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.033, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 3.88, 'labour_cost': 3.24, 'labour_hours_per_unit': 0.14, 'plant_cost': 0.0, 'total_cost': 7.12, 'notes': None}, {'id': 1169, 'type': 'suspended_floor_insulation', 'description': 'Kay-Cel Expanded Polystyrene Board', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.030303031, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.033, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 6.62, 'labour_cost': 3.71, 'labour_hours_per_unit': 0.16, 'plant_cost': 0.0, 'total_cost': 10.33, 'notes': None}, {'id': 1170, 'type': 'suspended_floor_insulation', 'description': 'Kay-Cel Expanded Polystyrene Board', 'depth': 75.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.030303031, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.033, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 9.3, 'labour_cost': 4.17, 'labour_hours_per_unit': 0.18, 'plant_cost': 0.0, 'total_cost': 13.47, 'notes': None}, {'id': 1171, 'type': 'suspended_floor_insulation', 'description': 'Kay-Cel Expanded Polystyrene Board', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.030303031, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.033, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 12.02, 'labour_cost': 4.4, 'labour_hours_per_unit': 0.19, 'plant_cost': 0.0, 'total_cost': 16.42, 'notes': None}, {'id': 1172, 'type': 'suspended_floor_insulation', 'description': 'Kingspan Thermafloor TF70 High ' 'Performance Rigid Floor Insulation', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 10.36, 'labour_cost': 4.06, 'labour_hours_per_unit': 0.18, 'plant_cost': 0.0, 'total_cost': 14.42, 'notes': None}, {'id': 1173, 'type': 'suspended_floor_insulation', 'description': 'Kingspan Thermafloor TF70 High Performance Rigid Floor Insulation', 'depth': 75.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 15.35, 'labour_cost': 4.06, 'labour_hours_per_unit': 0.18, 'plant_cost': 0.0, 'total_cost': 19.41, 'notes': None}, {'id': 1174, 'type': 'suspended_floor_insulation', 'description': 'Kingspan Thermafloor TF70 High ' 'Performance Rigid Floor Insulation', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 19.17, 'labour_cost': 4.06, 'labour_hours_per_unit': 0.18, 'plant_cost': 0.0, 'total_cost': 23.23, 'notes': None}, {'id': 1175, 'type': 'suspended_floor_insulation', 'description': 'Kingspan Thermafloor TF70 High Performance Rigid Floor Insulation', 'depth': 125.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 26.59, 'labour_cost': 4.06, 'labour_hours_per_unit': 0.18, 'plant_cost': 0.0, 'total_cost': 30.65, 'notes': None}, {'id': 1176, 'type': 'suspended_floor_insulation', 'description': 'Kingspan Thermafloor TF70 High ' 'Performance Rigid Floor Insulation', 'depth': 150.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 31.13, 'labour_cost': 4.64, 'labour_hours_per_unit': 0.2, 'plant_cost': 0.0, 'total_cost': 35.77, 'notes': None}, {'id': 1177, 'type': 'suspended_floor_redecoration', 'description': 'refix floorboards previously set aside', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 1.54, 'labour_cost': 24.98, 'labour_hours_per_unit': 0.74, 'plant_cost': 0.0, 'total_cost': 26.52, 'notes': None}, {'id': 1178, 'type': 'suspended_floor_redecoration', 'description': 'Fitting carpet', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 6.59, 'labour_hours_per_unit': 0.37, 'plant_cost': 0.0, 'total_cost': 6.59, 'notes': 'SPONs does not have data on re-fitting the carpet so we use the data in Fitted carpeting; Gradus ' 'woven polypropylene tufted loop\n\n as a baseline. We assume re-use of carpets, therefore we need ' 'just labour rates'}, {'id': 1179, 'type': 'solid_floor_demolition', 'description': 'Removal of carpet and underfelt', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 3.32, 'labour_hours_per_unit': 0.11, 'plant_cost': 0.0, 'total_cost': 3.32, 'notes': 'We ignore the plant cost that is in SPONs because we assume the carpet is not scrapped and ' 'therefore there is no need for a skip'}, {'id': 1180, 'type': 'solid_floor_preparation', 'description': 'clean surface of concrete to receive new damp-proof membrane', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': None, 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), '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': None}, {'id': 1181, 'type': 'solid_floor_preparation', 'description': 'Clean out crack to form a 20mm×20mm ' 'groove and fill with cement: mortar ' 'mixed with bonding agent', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': None, 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 6.91, 'labour_cost': 18.99, 'labour_hours_per_unit': 0.61, 'plant_cost': 0.16, 'total_cost': 26.06, 'notes': 'This step is the assessment and repair of ' 'any damage to the concrete floor such as ' 'filling cracks or levelling uneven areas'}, {'id': 1182, 'type': 'solid_floor_vapour_barrier', 'description': 'Visqueen High Performance Vapour Barrier', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), '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': 1183, 'type': 'solid_floor_insulation', 'description': 'Kay-Cel Expanded Polystyrene Board', 'depth': 25.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.030303031, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.033, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 3.88, 'labour_cost': 3.24, 'labour_hours_per_unit': 0.14, 'plant_cost': 0.0, 'total_cost': 7.12, 'notes': None}, {'id': 1184, 'type': 'solid_floor_insulation', 'description': 'Kay-Cel Expanded Polystyrene Board', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.030303031, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.033, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 6.62, 'labour_cost': 3.71, 'labour_hours_per_unit': 0.16, 'plant_cost': 0.0, 'total_cost': 10.33, 'notes': None}, {'id': 1185, 'type': 'solid_floor_insulation', 'description': 'Kay-Cel Expanded Polystyrene Board', 'depth': 75.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.030303031, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.033, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 9.3, 'labour_cost': 4.17, 'labour_hours_per_unit': 0.18, 'plant_cost': 0.0, 'total_cost': 13.47, 'notes': None}, {'id': 1186, 'type': 'solid_floor_insulation', 'description': 'Kingspan Thermafloor TF70 High ' 'Performance Rigid Floor Insulation', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 10.36, 'labour_cost': 4.06, 'labour_hours_per_unit': 0.18, 'plant_cost': 0.0, 'total_cost': 14.42, 'notes': None}, {'id': 1187, 'type': 'solid_floor_insulation', 'description': 'Kingspan Thermafloor TF70 High Performance Rigid Floor Insulation', 'depth': 75.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 15.35, 'labour_cost': 4.06, 'labour_hours_per_unit': 0.18, 'plant_cost': 0.0, 'total_cost': 19.41, 'notes': None}, {'id': 1188, 'type': 'solid_floor_insulation', 'description': 'Ecotherm Eco-Versal General Purpose ' 'Insulation Board', 'depth': 30.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 6.16, 'material_cost': 16.73, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 45.07, 'notes': None}, {'id': 1189, 'type': 'solid_floor_insulation', 'description': 'Ecotherm Eco-Versal General Purpose Insulation Board', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 8.46, 'material_cost': 19.1, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 47.44, 'notes': None}, {'id': 1190, 'type': 'solid_floor_insulation', 'description': 'Ecotherm Eco-Versal General Purpose ' 'Insulation Board', 'depth': 60.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'https://londonbuildingsupplies.co.uk/products/60mm--ecotherm-eco-versal-general-purpose-pir-insulation-board---2.4m-x-1.2m-x-60mm.html', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 24.081198, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 52.421196, 'notes': "This material isn't in SPONs but checking" " online, is around 92% of the cost of the" " 100mm"}, {'id': 1191, 'type': 'solid_floor_insulation', 'description': 'Ecotherm Eco-Versal General Purpose Insulation Board', 'depth': 70.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'https://londonbuildingsupplies.co.uk/products/70mm--ecotherm-eco-versal-general-purpose-pir' '-insulation-board---2.4m-x-1.2m-x-70mm.html', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 27.089088, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 55.42909, 'notes': "This material isn't in SPONs but checking online, is around 104% of the cost of the 100mm (more " "expensive than 100mm)"}, {'id': 1192, 'type': 'solid_floor_insulation', 'description': 'Ecotherm Eco-Versal General Purpose Insulation Board', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 15.12, 'material_cost': 25.96, 'labour_cost': 30.7, 'labour_hours_per_unit': 1.3, 'plant_cost': 0.0, 'total_cost': 56.66, 'notes': None}, {'id': 1193, 'type': 'solid_floor_insulation', 'description': 'Ravatherm XPS X 500 SL Polystyrene Foam', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.032258064, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.031, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 11.07, 'labour_cost': 10.66, 'labour_hours_per_unit': 0.46, 'plant_cost': 0.0, 'total_cost': 21.73, 'notes': "In Spons, the thermal conductivity is 0.033 however the datasheet indicates it's 0.32: " "https://ravagobuildingsolutions.com/uk/wp-content/uploads/sites/30/2022/08/ravatherm-xps-x-500-sl" "-tds-version-1-20210901.pdf"}, {'id': 1194, 'type': 'solid_floor_insulation', 'description': 'Ravatherm XPS X 500 SL Polystyrene Foam', 'depth': 75.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.03125, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.032, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 16.28, 'labour_cost': 10.66, 'labour_hours_per_unit': 0.46, 'plant_cost': 0.0, 'total_cost': 26.94, 'notes': None}, {'id': 1195, 'type': 'solid_floor_redecoration', 'description': 'Screeded beds; protection to ' 'compressible formwork exceeding ' '600mm wide', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 9.6, 'material_cost': 9.89, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15, 'plant_cost': 0.0, 'total_cost': 12.56, 'notes': 'This is the screed layer, placed on top ' 'of the insulation'}, {'id': 1196, 'type': 'solid_floor_redecoration', 'description': 'Fitting carpet', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 6.59, 'labour_hours_per_unit': 0.37, 'plant_cost': 0.0, 'total_cost': 6.59, 'notes': 'SPONs does not have data on re-fitting the carpet so we use the data in Fitted carpeting; Gradus ' 'woven polypropylene tufted loop\n\n as a baseline. We assume re-use of carpets, therefore we need ' 'just labour rates'}, {'id': 1197, 'type': 'solid_floor_redecoration', 'description': 'Fitting existing softwood skirting or architrave to new frames; 150mm high', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.01, 'labour_cost': 4.87, 'labour_hours_per_unit': 0.12, 'plant_cost': 0.0, 'total_cost': 4.88, 'notes': None}, {'id': 1198, 'type': 'ewi_wall_demolition', 'description': 'Solid & Dry Lined walls: Hack of ' 'wall finishes with chipping hammer; ' 'plaster to walls.', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 10.27, 'labour_hours_per_unit': 0.33, 'plant_cost': 1.28, 'total_cost': 11.55, 'notes': None}, {'id': 1199, 'type': 'ewi_wall_demolition', 'description': 'Stud walls: Remove wall linings including battening behind; plasterboard and skim', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 6.23, 'labour_hours_per_unit': 0.2, 'plant_cost': 1.25, 'total_cost': 7.48, 'notes': None}, {'id': 1200, 'type': 'ewi_wall_demolition', 'description': 'Lathe and Plaster walls: Remove ' 'wall linings including battening ' 'behind; wood lath and plaster', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 6.85, 'labour_hours_per_unit': 0.22, 'plant_cost': 2.09, 'total_cost': 8.94, 'notes': None}, {'id': 1201, '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.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 7.3, 'labour_cost': 5.62, 'labour_hours_per_unit': 0.3, 'plant_cost': 0.0, 'total_cost': 12.92, 'notes': 'This work covers the preparation and priming of the wall before insulating'}, {'id': 1202, 'type': 'external_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board', 'depth': 30.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 6.16, 'material_cost': 16.73, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 45.07, 'notes': None}, {'id': 1203, 'type': 'external_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board', 'depth': 50.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 8.46, 'material_cost': 19.1, 'labour_cost': 28.34, 'labour_hours_per_unit': 1.2, 'plant_cost': 0.0, 'total_cost': 47.44, 'notes': None}, {'id': 1204, 'type': 'external_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 15.12, 'material_cost': 25.96, 'labour_cost': 30.7, 'labour_hours_per_unit': 1.3, 'plant_cost': 0.0, 'total_cost': 56.66, 'notes': None}, {'id': 1205, 'type': 'external_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board', 'depth': 150.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.045454547, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.022, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 23.53, 'material_cost': 34.62, 'labour_cost': 33.06, 'labour_hours_per_unit': 1.4, 'plant_cost': 0.0, 'total_cost': 67.68, 'notes': None}, {'id': 1206, 'type': 'external_wall_insulation', 'description': 'Foamglas Grade F Wall Insulation Slabs', 'depth': 60.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.02631579, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.038, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 41.69, 'material_cost': 53.33, 'labour_cost': 29.52, 'labour_hours_per_unit': 1.25, 'plant_cost': 0.0, 'total_cost': 82.85, 'notes': None}, {'id': 1207, 'type': 'external_wall_insulation', 'description': 'Foamglas Grade F Wall Insulation Slabs', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.02631579, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.038, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 86.86, 'material_cost': 99.85, 'labour_cost': 29.52, 'labour_hours_per_unit': 1.25, 'plant_cost': 0.0, 'total_cost': 129.37, 'notes': None}, {'id': 1208, 'type': 'external_wall_insulation', 'description': 'Foamglas Grade F Wall Insulation Slabs', 'depth': 150.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.02631579, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.038, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': 130.29, 'material_cost': 144.58, 'labour_cost': 29.52, 'labour_hours_per_unit': 1.25, 'plant_cost': 0.0, 'total_cost': 174.1, 'notes': None}, {'id': 1209, '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.0, 'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 0.0, 'labour_hours_per_unit': 0.0, 'plant_cost': 0.0, 'total_cost': 69.94, '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'}, {'id': 1210, 'type': 'low_energy_lighting_installation', 'description': 'Installation of fittings and cost of bub', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'https://www.checkatrade.com/blog/cost-guides/cost-install-downlights/ ' 'https://www.hamuch.com/cost/led-spot-light#:~:text=It%20costs%20an%20average%20of,' 'will%20drive%20up%20the%20cost.', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, 'material_cost': 20.0, 'labour_cost': 15.0, 'labour_hours_per_unit': 0.8, 'plant_cost': 0.0, 'total_cost': 66.0, 'notes': 'We estimate the unit economics from the checkatrade article. We assume that the average job ' 'consists of installing 6 lights based on the hamuch article. We use the median value of 400 for a ' 'job of 6 lights'}] testing_properties = [ { "address": "2 South Terrace", "postcode": "NN1 5JY" }, { "address": "8 Lindlings", "postcode": "HP1 2HA", }, { "address": "44 Lindlings", "postcode": "HP1 2HE", }, { "address": "46 Chaulden Terrace", "postcode": "HP1 2AN", }, { "address": "73 Long Chaulden", "postcode": "HP1 2HX", }, # { # "address": "77 Simmons Drive", # "postcode": "B32 1SL", # }, { "address": "139 School Road", "postcode": "B28 8JF", }, ] testing_properties_results = [] for testing_property_config in testing_properties: searcher = SearchEpc( address1=testing_property_config["address"], postcode=testing_property_config["postcode"], auth_token="a2Nvbm5rb3dsZXNzYXJAZ21haWwuY29tOjY5MGJiMWM0NmIyOGI5ZDUxYzAxMzQzYzNiZGNlZGJjZDNmODQwMzA=", os_api_key="" ) searcher.find_property(skip_os=True) epc_records = { 'original_epc': searcher.newest_epc.copy(), 'full_sap_epc': searcher.full_sap_epc.copy(), 'old_data': searcher.older_epcs.copy(), } prepared_epc = EPCRecord( epc_records=epc_records, run_mode="newdata", cleaning_data=cleaning_data ) p = Property( id=prepared_epc.uprn, address=searcher.address_clean, postcode=searcher.postcode_clean, epc_record=prepared_epc, ) p.get_spatial_data(uprn_filenames) p.get_components(cleaned, photo_supply_lookup, floor_area_decile_thresholds) recommender = Recommendations(property_instance=p, materials=materials) recommender.recommend() wall_recommendations = recommender.wall_recomender.recommendations loft_recommendations = recommender.roof_recommender.recommendations floor_recommendations = recommender.floor_recommender.recommendations solar_recommendations = recommender.solar_recommender.recommendation # We set wall recommendations to phase 0, loft to phase 1, floor to phase 2 and solar to phase 3 for rec in wall_recommendations: rec["phase"] = 0 for rec in loft_recommendations: rec["phase"] = 1 for rec in floor_recommendations: rec["phase"] = 2 for rec in solar_recommendations: rec["phase"] = 3 # TODO: TEMP! solar_recommendations[0]["photo_supply"] = 50 # Take just the cavity wall and loft recommendations p.create_base_difference_epc_record(cleaned_lookup=cleaned) wall_scoring_data = [] for wall_rec in wall_recommendations: recommendation_record = p.base_difference_record.df.to_dict("records")[0].copy() scoring_dict = p.create_recommendation_scoring_data( property_id=p.id, recommendation_record=recommendation_record, recommendations=[wall_rec], primary_recommendation_id=wall_rec["recommendation_id"] ) wall_scoring_data.append(scoring_dict) roof_scoring_data = [] for roof_rec in loft_recommendations: recommendation_record = p.base_difference_record.df.to_dict("records")[0].copy() scoring_dict = p.create_recommendation_scoring_data( property_id=p.id, recommendation_record=recommendation_record, recommendations=[roof_rec], primary_recommendation_id=roof_rec["recommendation_id"] ) roof_scoring_data.append(scoring_dict) floor_scoring_data = [] for floor_rec in floor_recommendations: recommendation_record = p.base_difference_record.df.to_dict("records")[0].copy() scoring_dict = p.create_recommendation_scoring_data( property_id=p.id, recommendation_record=recommendation_record, recommendations=[floor_rec], primary_recommendation_id=floor_rec["recommendation_id"] ) floor_scoring_data.append(scoring_dict) solar_scoring_data = [] for solar_rec in solar_recommendations: recommendation_record = p.base_difference_record.df.to_dict("records")[0].copy() scoring_dict = p.create_recommendation_scoring_data( property_id=p.id, recommendation_record=recommendation_record, recommendations=[solar_rec], primary_recommendation_id=solar_rec["recommendation_id"] ) solar_scoring_data.append(scoring_dict) # We now produce a combined, applying just the first roof recommendation to the first wall recommendation # Firstly apply the wall starting_record = p.base_difference_record.df.to_dict("records")[0].copy() scoring_dict_with_wall = p.create_recommendation_scoring_data( property_id=p.id, recommendation_record=starting_record.copy(), recommendations=[wall_recommendations[0]], primary_recommendation_id=wall_recommendations[0]["recommendation_id"] ) if wall_recommendations else starting_record.copy() scoring_dict_with_wall_and_roof = p.create_recommendation_scoring_data( property_id=p.id, recommendation_record=scoring_dict_with_wall.copy(), recommendations=[wall_recommendations[0], loft_recommendations[0], ], primary_recommendation_id=loft_recommendations[0]["recommendation_id"] ) if loft_recommendations else scoring_dict_with_wall.copy() scoring_dict_with_wall_roof_floor = p.create_recommendation_scoring_data( property_id=p.id, recommendation_record=scoring_dict_with_wall_and_roof.copy(), recommendations=[wall_recommendations[0], loft_recommendations[0], floor_recommendations[0]], primary_recommendation_id=floor_recommendations[0]["recommendation_id"] ) if floor_recommendations else scoring_dict_with_wall_and_roof.copy() scoring_dict_with_wall_roof_floor_solar = p.create_recommendation_scoring_data( property_id=p.id, recommendation_record=scoring_dict_with_wall_roof_floor.copy(), recommendations=[wall_recommendations[0], loft_recommendations[0], floor_recommendations[0], solar_recommendations[0]], primary_recommendation_id=solar_recommendations[0]["recommendation_id"] ) if solar_recommendations else scoring_dict_with_wall_roof_floor.copy() # We score each dataset with the model wall_only_predictions_dict = model_api.predict_all( df=pd.DataFrame(wall_scoring_data), bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) if wall_scoring_data else {"sap_change_predictions": pd.DataFrame()} roof_only_predictions_dict = model_api.predict_all( df=pd.DataFrame(roof_scoring_data), bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) if roof_scoring_data else {"sap_change_predictions": pd.DataFrame()} floor_only_predictions_dict = model_api.predict_all( df=pd.DataFrame(floor_scoring_data), bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) if floor_scoring_data else {"sap_change_predictions": pd.DataFrame()} solar_only_predictions_dict = model_api.predict_all( df=pd.DataFrame(solar_scoring_data), bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) if solar_scoring_data else {"sap_change_predictions": pd.DataFrame()} wall_roof_predictions_dict = model_api.predict_all( df=pd.DataFrame([scoring_dict_with_wall_and_roof]), bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) wall_roof_floor_predictions_dict = model_api.predict_all( df=pd.DataFrame([scoring_dict_with_wall_roof_floor]), bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) wall_roof_floor_solar_predictions_dict = model_api.predict_all( df=pd.DataFrame([scoring_dict_with_wall_roof_floor_solar]), bucket="retrofit-data-dev", prediction_buckets={ "sap_change_predictions": "retrofit-sap-predictions-dev", } ) wall_only_predictions = wall_only_predictions_dict["sap_change_predictions"].copy() wall_only_predictions["type"] = "wall_only" roof_only_predictions = roof_only_predictions_dict["sap_change_predictions"].copy() roof_only_predictions["type"] = "roof_only" floor_only_predictions = floor_only_predictions_dict["sap_change_predictions"].copy() floor_only_predictions["type"] = "floor_only" solar_only_predictions = solar_only_predictions_dict["sap_change_predictions"].copy() solar_only_predictions["type"] = "solar_only" wall_and_roof_predictions = wall_roof_predictions_dict["sap_change_predictions"].copy() wall_and_roof_predictions["type"] = "wall_and_roof" wall_roof_floor_predictions = wall_roof_floor_predictions_dict["sap_change_predictions"].copy() wall_roof_floor_predictions["type"] = "wall_roof_floor" wall_roof_floor_solar_predictions = wall_roof_floor_solar_predictions_dict["sap_change_predictions"].copy() wall_roof_floor_solar_predictions["type"] = "wall_roof_floor_solar" # We collate the results results = pd.concat( [ wall_only_predictions, roof_only_predictions, floor_only_predictions, solar_only_predictions, wall_and_roof_predictions, wall_roof_floor_predictions, wall_roof_floor_solar_predictions ] ) results["gain"] = results["predictions"] - int(p.data["current-energy-efficiency"]) results["address"] = p.address testing_properties_results.append(results) testing_properties_results = pd.concat(testing_properties_results)