Model/etl/testing_data/sap_model_simulation.py
2024-02-16 17:04:43 +00:00

2170 lines
152 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)