Model/recommendations/Costs.py
2023-11-23 14:51:30 +00:00

896 lines
51 KiB
Python
Raw 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 numpy as np
# Example - delete me
from backend.Property import Property
print("DELETE ME IN COSTS CLASS")
from epc_api.client import EpcClient
epc_client = EpcClient(auth_token=AUTH_TOKEN)
p1 = Property(
postcode="NN1 5JY",
address1="2 South Terrace",
epc_client=epc_client,
id=0
)
p2 = Property(
postcode="PO12 4TY",
address1="25 Albert Street",
epc_client=epc_client,
id=0
)
p1.search_address_epc()
p2.search_address_epc()
p1.set_basic_property_dimensions()
p2.set_basic_property_dimensions()
import pandas as pd
df = pd.read_csv("/Users/khalimconn-kowlessar/Downloads/Hestia Materials - external_wall_insulation.csv")
df = df.to_dict("records")
# This data comes from SPONs
regional_labour_variations = [
{"Region": "Outer London (Spons 2023)", "Adjustment_Factor": 1.00},
{"Region": "Inner London", "Adjustment_Factor": 1.05},
{"Region": "South East", "Adjustment_Factor": 0.96},
{"Region": "South West", "Adjustment_Factor": 0.90},
{"Region": "East of England", "Adjustment_Factor": 0.93},
{"Region": "East Midlands", "Adjustment_Factor": 0.88},
{"Region": "West Midlands", "Adjustment_Factor": 0.87},
{"Region": "North East", "Adjustment_Factor": 0.83},
{"Region": "North West", "Adjustment_Factor": 0.88},
{"Region": "Yorkshire and Humberside", "Adjustment_Factor": 0.86},
{"Region": "Wales", "Adjustment_Factor": 0.88},
{"Region": "Scotland", "Adjustment_Factor": 0.88},
{"Region": "Northern Ireland", "Adjustment_Factor": 0.76}
]
county_map = {
"Northamptonshire": "East Midlands",
"Hampshire": "South East",
}
class Costs:
"""
A class to calculate the costs associated with construction works,
specifically focusing on cavity wall insulation.
It includes contingency, preliminaries, profit margin, and VAT calculations.
As a sense check, there is a useful article from checkatrade on retrofitting and expected costs:
https://www.checkatrade.com/blog/cost-guides/retrofit-insulation-cost/
Another useful article for benchmarking the cost of floor insulation:
https://www.checkatrade.com/blog/cost-guides/floor-insulation-cost/
"""
# Contingency is a percentage of the total cost of the work and covers unforseen expenses
# We assume a conservative 10% contingency for all works which is a rate defined by SPONs
CONTINGENCY = 0.1
# Where there is more uncertainty, a higher contingency rate is used
HIGH_RISK_CONTINGENCY = 0.15
# When there is less uncertainty, a lower contingency rate is used
LOW_RISK_CONTINGENCY = 0.05
# Preliminaries are a percentage of the total cost of the work and covers the cost of site-specific costs
# such as site preparation, safety measures and project management. This rate can vary but we'll assume a 10%
# rate, on the total cost before VAT, as recommended by SPONs
PRELIMINARIES = 0.1
# For higher risk projects, a higher preliminaries rate is used. SPONs indicates that a higher risk project might
# have a preliminaries of 12-14% so we use 12% as the median for the preliminaries rate.
# For External wall insulation (EWI), we use 15% as the preliminaries rate if we think the property might
# need scaffolding, otherwise we use 12%. This is to account for any site preparation that might be required
EWI_NO_SCAFFOLDING_PRELIMINARIES = 0.12
EWI_SCAFFOLDING_PRELIMINARIES = 0.15
VAT_RATE = 0.2
PROFIT_MARGIN = 0.15
def __init__(self, property_instance):
"""
Initializes the Costs class with a property instance.
:param property_instance: Instance of a Property class containing relevant details like wall area.
"""
if not hasattr(property_instance, 'insulation_wall_area'):
raise ValueError("Property instance must have an 'insulation_wall_area' attribute")
self.property = property_instance
self.regional_labour_variations = regional_labour_variations
self.county = county_map.get(self.property.data["county"], None)
if self.county is None:
raise ValueError("County not found in county map")
self.labour_adjustment_factor = [
x["Adjustment_Factor"] for x in self.regional_labour_variations if
x["Region"] == self.county
][0]
if not self.labour_adjustment_factor:
raise ValueError("Labour adjustment factor not found")
def cavity_wall_insulation(self, wall_area, material):
"""
Calculates the total cost for cavity wall insulation based on material and labor costs,
including contingency, preliminaries, profit, and VAT.
:return: A dictionary containing detailed cost breakdown.
"""
# Cost per m2
# material = {
# "description": "Crown Dritherm Cavity Slab 37 (Thermal conductivity 0.037 W/mK) glass fibre batt or
# other "
# "equal; as full or partial cavity fill; including cutting and fitting around wall ties
# and "
# "retaining discs",
# "depth": 75,
# "thermal_conductivity": 0.037,
# "prime_cost": 5.17,
# "material_cost": 5.62,
# "labour_cost": 2.25,
# "labour_hours": 0.13
# }
material_cost_per_m2 = material["material_cost"]
# wall_area = self.property.insulation_wall_area
# This is the amount of material required in m3, assuming a standard 75mm depth
volume = 0.075 * wall_area
base_material_cost = material_cost_per_m2 * wall_area
labour_cost = material["labour_cost"] * wall_area * self.labour_adjustment_factor
subtotal_before_profit = base_material_cost + labour_cost
contingency_cost = subtotal_before_profit * self.CONTINGENCY
preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES
profit_cost = subtotal_before_profit * self.PROFIT_MARGIN
subtotal_before_vat = subtotal_before_profit + contingency_cost + preliminaries_cost + profit_cost
vat_cost = subtotal_before_vat * self.VAT_RATE
total_cost = subtotal_before_vat + vat_cost
labour_hours = material["labour_hours"] * wall_area
return {
"total": total_cost,
"subtotal": subtotal_before_vat,
"vat": vat_cost,
"contingency": contingency_cost,
"preliminaries": preliminaries_cost,
"material": base_material_cost,
"profit": profit_cost,
"labour_hours": labour_hours
}
def loft_insulation(self, floor_area, material):
"""
Calculates the total cost for cavity wall insulation based on material and labor costs,
including contingency, preliminaries, profit, and VAT.
:return: A dictionary containing detailed cost breakdown.
"""
# Cost per m2
# material = {
# "description": "Crown Loft Roll 44 glass fibre roll",
# "depth": 270,
# "thermal_conductivity": 0.044,
# "prime_cost": None,
# "material_cost": 5.91938,
# "labour_cost": 1.96,
# "labour_hours": 0.11
# }
material_cost_per_m2 = material["material_cost"]
# floor_area = self.property.floor_area
base_material_cost = material_cost_per_m2 * floor_area
labour_cost = material["labour_cost"] * floor_area * self.labour_adjustment_factor
subtotal_before_profit = base_material_cost + labour_cost
contingency_cost = subtotal_before_profit * self.CONTINGENCY
preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES
profit_cost = subtotal_before_profit * self.PROFIT_MARGIN
subtotal_before_vat = subtotal_before_profit + contingency_cost + preliminaries_cost + profit_cost
vat_cost = subtotal_before_vat * self.VAT_RATE
total_cost = subtotal_before_vat + vat_cost
labour_hours = material["labour_hours"] * floor_area
return {
"total": total_cost,
"subtotal": subtotal_before_vat,
"vat": vat_cost,
"contingency": contingency_cost,
"preliminaries": preliminaries_cost,
"material": base_material_cost,
"profit": profit_cost,
"labour_hours": labour_hours
}
def internal_wall_insulation(self, wall_area, material, non_insulation_materials):
"""
Broadly speaking, the high level steps to an internal wall insulation job are the following:
1) Demolition: This involves removing existing wall linings, fittings, and any other obstacles.
It's important to factor in the disposal of debris and the potential need for additional protective
measures to ensure the safety of the work area.
2) Insulation Installation: This is the core part of the process where the chosen insulation material is
applied. The choice of insulation material will depend on several factors including thermal performance,
wall construction, and space constraints.
3) Vapour Barrier Installation: This is crucial for preventing moisture from penetrating the insulation,
which can compromise its effectiveness and lead to mold growth.
4) Re-decoration: This involves applying plaster to the wall and then painting.
The quality of finish here is important for both aesthetic and functional reasons.
5) Trim and Finishing Work: Post-insulation, tasks such as re-installing skirting boards, door frames,
or window sills might be necessary.
:return:
"""
# Parsing the provided table into a list of dictionaries
# non_insulation_materials = [
# {'type': 'iwi_wall_demolition',
# 'description': 'Solid & Dry Lined walls: Hack of wall finishes with chipping hammer; plaster to walls.',
# 'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0,
# 'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 10.27,
# 'labour_hours_per_unit': 0.33, 'plant_cost': 1.28, 'total_cost': 11.55, 'link': 'SPONs', 'Notes': 0.0},
# {'type': 'iwi_wall_demolition',
# 'description': 'Stud walls: Remove wall linings including battening behind; plasterboard and skim',
# 'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0,
# 'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 6.23,
# 'labour_hours_per_unit': 0.2, 'plant_cost': 1.25, 'total_cost': 7.48, 'link': 'SPONs', 'Notes': 0.0},
# {'type': 'iwi_wall_demolition',
# 'description': 'Lathe and Plaster walls: Remove wall linings including battening behind; wood lath and '
# 'plaster',
# 'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0,
# 'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 6.85,
# 'labour_hours_per_unit': 0.22, 'plant_cost': 2.09, 'total_cost': 8.94, 'link': 'SPONs', 'Notes': 0.0},
# {'Notes': "",
# 'cost_unit': "",
# 'depth': "",
# 'depth_unit': "",
# 'description': 'Visqueen High Performance Vapour Barrier',
# 'labour_cost': 0.48,
# 'labour_hours_per_unit': 0.02,
# 'link': 'SPONs',
# 'material_cost': 1.21,
# 'plant_cost': 0,
# 'prime_material_cost': 0.58,
# 'thermal_conductivity': "",
# 'thermal_conductivity_unit': "",
# 'total_cost': 1.69,
# 'type': 'iwi_vapour_barrier'},
# {'Notes': "",
# 'cost_unit': "",
# 'depth': "",
# 'depth_unit': "",
# 'description': 'Plaster; one coat Thistle board finish or other equal; steel trowelled; 3 mm thick work '
# 'to walls or ceilings; one coat; to plasterboard base; over 600mm wide',
# 'labour_cost': 6.58,
# 'labour_hours_per_unit': 0.25,
# 'link': "",
# 'material_cost': 0.06,
# 'plant_cost': 0,
# 'prime_material_cost': 0.0,
# 'thermal_conductivity': "",
# 'thermal_conductivity_unit': "",
# 'total_cost': 6.64,
# 'type': 'iwi_redecoration'},
# {'Notes': "",
# 'cost_unit': "",
# 'depth': "",
# 'depth_unit': "",
# 'description': 'Two coats emulsion paint on plaster, over 40mm girth; 3.5m - '
# '5m high',
# 'labour_cost': 0.0,
# 'labour_hours_per_unit': 0.21,
# 'link': "",
# 'material_cost': 0.41,
# 'plant_cost': 0,
# 'prime_material_cost': "",
# 'thermal_conductivity': "",
# 'thermal_conductivity_unit': "",
# 'total_cost': 4.34,
# 'type': 'iwi_redecoration'},
# {'Notes': "",
# 'cost_unit': "",
# 'depth': "",
# 'depth_unit': "",
# 'description': 'Fitting existing softwood skirting or architrave to new '
# 'frames; 150mm high',
# 'labour_cost': 4.87,
# 'labour_hours_per_unit': 0.01,
# 'link': "",
# 'material_cost': 4.86,
# 'plant_cost': 0,
# 'prime_material_cost': "",
# 'thermal_conductivity': "",
# 'thermal_conductivity_unit': "",
# 'total_cost': 4.88,
# 'type': 'iwi_redecoration'}
# ]
#
# material = {
# "type": "internal_wall_insulation",
# "description": "Ecotherm Eco-Versal PIR Insulation Board",
# "depth": 150,
# "depth_unit": "mm",
# "cost_unit": "gbp_per_m2",
# "thermal_conductivity": 0.022,
# "thermal_conductivity_unit": "watt_per_meter_kelvin",
# "prime_material_cost": "",
# "material_cost": 11.68,
# "labour_cost": 3.12,
# "labour_hours_per_unit": 0.18,
# "plant_cost": "",
# "total_cost": 14.8,
# "link": "SPONs"
# }
# Cost per m2
# wall_area = self.property.insulation_wall_area
# Extract and check the different types of data we'll need
demolition_data = [x for x in non_insulation_materials if x["type"] == "iwi_wall_demolition"]
vapour_barrier_data = [x for x in non_insulation_materials if x["type"] == "iwi_vapour_barrier"]
redecoration_data = [x for x in non_insulation_materials if x["type"] == "iwi_redecoration"]
if not demolition_data:
raise ValueError("No data found for iwi_wall_demolition")
if (len(vapour_barrier_data) != 1) or (len(redecoration_data) != 3):
raise ValueError("Incorrect number of data entries for non-insulation materials")
# Break out the individual material costs
# Since we don't know the exact wall construction, we take an average for demolition costs, since
# the cost will depend on the type of wall construction
demolition_material_costs = np.mean([x["material_cost"] * wall_area for x in demolition_data])
insulation_material_costs = material["material_cost"] * wall_area
vapour_barrier_material_costs = vapour_barrier_data[0]["material_cost"] * wall_area
redecoration_material_costs = sum([x["material_cost"] * wall_area for x in redecoration_data])
demolition_plant_costs = np.mean([x["plant_cost"] * wall_area for x in demolition_data])
# Again for demolition, we average since we aren't sure which demolition process will be used
demolition_labour_costs = np.mean([x["labour_cost"] * wall_area for x in demolition_data])
insulation_labour_costs = material["labour_cost"] * wall_area
vapour_barrier_labour_costs = vapour_barrier_data[0]["labour_cost"] * wall_area
redecoration_labour_costs = sum([x["labour_cost"] * wall_area for x in redecoration_data])
labour_costs = (demolition_labour_costs + insulation_labour_costs + vapour_barrier_labour_costs +
redecoration_labour_costs)
labour_costs = labour_costs * self.labour_adjustment_factor
materials_costs = (demolition_material_costs + insulation_material_costs + vapour_barrier_material_costs +
redecoration_material_costs)
subtotal_before_profit = labour_costs + materials_costs + demolition_plant_costs
contingency_cost = subtotal_before_profit * self.CONTINGENCY
preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES
profit_cost = subtotal_before_profit * self.PROFIT_MARGIN
subtotal_before_vat = subtotal_before_profit + contingency_cost + preliminaries_cost + profit_cost
vat_cost = subtotal_before_vat * self.VAT_RATE
total_cost = subtotal_before_vat + vat_cost
demolition_labour_hours = np.mean([x["labour_hours_per_unit"] * wall_area for x in demolition_data])
insulation_labour_hours = material["labour_hours_per_unit"] * wall_area
vapour_barrier_labour_hours = vapour_barrier_data[0]["labour_hours_per_unit"] * wall_area
redecoration_labour_hours = sum([x["labour_hours_per_unit"] * wall_area for x in redecoration_data])
labour_hours = (demolition_labour_hours + insulation_labour_hours + vapour_barrier_labour_hours +
redecoration_labour_hours)
# To install internal wall insulation, a small to medium size project might be conducted by a team of 3-5 people
labour_days = (labour_hours / 8) / 5
return {
"total": total_cost,
"subtotal": subtotal_before_vat,
"vat": vat_cost,
"contingency": contingency_cost,
"preliminaries": preliminaries_cost,
"material": materials_costs,
"profit": profit_cost,
"labour_hours": labour_hours,
"labour_days": labour_days
}
def suspended_floor_insulation(self, insulation_floor_area, material, non_insulation_materials):
"""
We characterise the steps for suspended floor insulation as the following tasks:
1) Removal of Carpet and Underfelt: Where necessary, remove existing floor coverings to access the floorboards.
2) Removal of Floor Boarding: Carefully remove floorboards to access the space beneath for insulation.
3) Installation of Vapour Barrier: Install a vapour barrier to prevent moisture from affecting
the insulation and floor structure.
4) Installation of Insulation: Fit the chosen insulation material between the joists in the floor void.
5) Refixing Floorboards: Replace and secure the floorboards after insulation installation.
6) Re-carpeting: Lay down the carpet or other floor coverings once the insulation and floorboards are in place.
:return:
"""
# material = {'type': 'suspended_floor_insulation', 'description': 'Thermafleece CosyWool Roll', 'depth': 140.0,
# 'depth_unit': 'mm', 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.039,
# 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'prime_material_cost': 0,
# 'material_cost': 11.68, 'labour_cost': 1.78, 'labour_hours_per_unit': 0.1, 'plant_cost': 0,
# 'total_cost': 13.46, 'link': 'SPONs',
# 'Notes': 'Spons did not contain labour costs so we use values for similar insulations. We use
# the '
# 'same values as in Crown loft roll 44, since it is also an insulation roll'}
#
# non_insulation_materials = [
# {'type': 'suspended_floor_demolition', 'description': 'Removal of carpet and underfelt', 'depth': 0,
# 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 3.32, 'labour_hours_per_unit': 0.11,
# 'plant_cost': 0, 'total_cost': 3.32, 'link': 'SPONs',
# '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'},
# {'type': 'suspended_floor_demolition',
# 'description': 'Remove boarding; withdraw nails; set aside for reuse; ground level', 'depth': 0,
# 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 9.34, 'labour_hours_per_unit': 0.3,
# 'plant_cost': 0, 'total_cost': 9.34, 'link': 'SPONs', 'Notes': 0},
# {'type': 'suspended_floor_vapour_barrier', 'description': 'Visqueen High Performance Vapour Barrier',
# 'depth': 0, 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0,
# 'thermal_conductivity_unit': 0, 'prime_material_cost': 0.58, 'material_cost': 1.21, 'labour_cost': 0.48,
# 'labour_hours_per_unit': 0.02, 'plant_cost': 0, 'total_cost': 1.69, 'link': 'SPONs', 'Notes': 0},
# {'type': 'suspended_floor_redecoration', 'description': 'refix floorboards previously set aside',
# 'depth': 0, 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0,
# 'thermal_conductivity_unit': 0, 'prime_material_cost': 0, 'material_cost': 1.54, 'labour_cost': 24.98,
# 'labour_hours_per_unit': 0.74, 'plant_cost': 0, 'total_cost': 26.52, 'link': 'SPONs', 'Notes': 0},
# {'type': 'suspended_floor_redecoration', 'description': 'Fitting carpet', 'depth': 0, 'depth_unit': 0,
# 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 6.59, 'labour_hours_per_unit': 0.37,
# 'plant_cost': 0, 'total_cost': 6.59, 'link': 'SPONs',
# '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'}]
# insulation_floor_area = self.property.floor_area / self.property.number_of_floors
demolition_data = [x for x in non_insulation_materials if x["type"] == "suspended_floor_demolition"]
vapour_barrier_data = [x for x in non_insulation_materials if x["type"] == "suspended_floor_vapour_barrier"]
redecoration_data = [x for x in non_insulation_materials if x["type"] == "suspended_floor_redecoration"]
if (len(demolition_data) != 2) or (len(vapour_barrier_data) != 1) or (len(redecoration_data) != 2):
raise ValueError("Incorrect number of data entries for non-insulation materials")
# Break out the individual material costs
demolition_material_costs = sum([x["material_cost"] * insulation_floor_area for x in demolition_data])
insulation_material_costs = material["material_cost"] * insulation_floor_area
vapour_barrier_material_costs = vapour_barrier_data[0]["material_cost"] * insulation_floor_area
redecoration_material_costs = sum([x["material_cost"] * insulation_floor_area for x in redecoration_data])
demolition_labour_costs = sum([x["labour_cost"] * insulation_floor_area for x in demolition_data])
insulation_labour_costs = material["labour_cost"] * insulation_floor_area
vapour_barrier_labour_costs = vapour_barrier_data[0]["labour_cost"] * insulation_floor_area
redecoration_labour_costs = sum([x["labour_cost"] * insulation_floor_area for x in redecoration_data])
labour_costs = (demolition_labour_costs + insulation_labour_costs + vapour_barrier_labour_costs +
redecoration_labour_costs)
labour_costs = labour_costs * self.labour_adjustment_factor
materials_costs = (demolition_material_costs + insulation_material_costs + vapour_barrier_material_costs +
redecoration_material_costs)
subtotal_before_profit = labour_costs + materials_costs
contingency_cost = subtotal_before_profit * self.CONTINGENCY
preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES
profit_cost = subtotal_before_profit * self.PROFIT_MARGIN
subtotal_before_vat = subtotal_before_profit + contingency_cost + preliminaries_cost + profit_cost
vat_cost = subtotal_before_vat * self.VAT_RATE
total_cost = subtotal_before_vat + vat_cost
demolition_labour_hours = sum([x["labour_hours_per_unit"] * insulation_floor_area for x in demolition_data])
insulation_labour_hours = material["labour_hours_per_unit"] * insulation_floor_area
vapour_barrier_labour_hours = vapour_barrier_data[0]["labour_hours_per_unit"] * insulation_floor_area
redecoration_labour_hours = sum([x["labour_hours_per_unit"] * insulation_floor_area for x in redecoration_data])
labour_hours = (demolition_labour_hours + insulation_labour_hours + vapour_barrier_labour_hours +
redecoration_labour_hours)
# Assume a team of 3 people for a small to medium size project
labour_days = (labour_hours / 8) / 3
return {
"total": total_cost,
"subtotal": subtotal_before_vat,
"vat": vat_cost,
"contingency": contingency_cost,
"preliminaries": preliminaries_cost,
"material": materials_costs,
"profit": profit_cost,
"labour_hours": labour_hours,
"labour_days": labour_days
}
def solid_floor_insulation(self, insulation_floor_area, material, non_insulation_materials):
"""
We characterise the steps for solid floor insulation as the following tasks:
1) Removal of Carpet and Underfelt: This is the initial stage where any existing floor coverings, like carpets,
tiles, or linoleum, are carefully removed. This exposes the solid floor beneath, which is typically concrete.
2) Preparation of Flooring: This step is critical. It involves:
- Cleaning the existing floor surface thoroughly to remove debris and ensure a flat surface.
- Assessing and repairing any damage to the concrete floor. This might include filling cracks or leveling
uneven areas.
3) Installation of a Damp Proof Membrane (DPM): Before installing insulation, a DPM is often laid down to
prevent moisture from rising into the insulation and the interior space. This step is crucial in areas prone to
dampness.
4) Install Insulation: The insulation, often in the form of rigid foam boards, is laid over the DPM.
The choice of insulation material will depend on the desired thermal properties and the available floor height.
Care is taken to minimize thermal bridges and ensure a snug fit between insulation boards.
5) Laying a New Subfloor: Over the insulation, a new subfloor is often installed. This could be a layer of
screed (a type of concrete) or wooden boarding, depending on the specific requirements and preferences.
6) Re-decoration and Finishing Touches: Once the subfloor is in place and has set or dried (if necessary),
the final floor finish can be applied. This might involve:
- Laying new tiles, wooden flooring, or other chosen materials.
- If you're planning to re-carpet, this would be the stage to do it.
- Skirting boards may need to be refitted or replaced.
7) Considerations for Doors and Fixtures: It's important to note that raising the floor level can affect door
thresholds and other fixtures. Doors may need to be trimmed, and fixtures might need adjustments.
:param material:
:param non_insulation_materials:
:return:
"""
# material = {
# 'type': 'solid_floor_insulation', 'description': 'Kay-Cel Expanded Polystyrene Board',
# 'depth': 100.0, 'depth_unit': 'mm', 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.033,
# 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'prime_material_cost': 0,
# 'material_cost': 12.02, 'labour_cost': 4.4, 'labour_hours_per_unit': 0.19, 'plant_cost': 0,
# 'total_cost': 16.42, 'link': 'SPONs', 'Notes': 0
# }
#
# non_insulation_materials = [
# {'type': 'solid_floor_demolition', 'description': 'Removal of carpet and underfelt', 'depth': 0,
# 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 3.32, 'labour_hours_per_unit': 0.11,
# 'plant_cost': 0, 'total_cost': 3.32, 'link': 'SPONs',
# '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'},
# {'type': 'solid_floor_preparation',
# 'description': 'clean surface of concrete to receive new damp-proof membrane', 'depth': 0,
# 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 4.36, 'labour_hours_per_unit': 0.14,
# 'plant_cost': 0, 'total_cost': 4.36, 'link': 0, 'Notes': 0}, {'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, 'depth_unit': 0,
# 'cost_unit': 0,
# 'thermal_conductivity': 0,
# 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0,
# 'material_cost': 6.91,
# 'labour_cost': 18.99,
# 'labour_hours_per_unit': 0.61,
# 'plant_cost': 0.16,
# 'total_cost': 26.06, 'link': 0,
# 'Notes': 'This step is the '
# 'assessment and repair of '
# 'any damage to the concrete '
# 'floor such as filling '
# 'cracks or levelling uneven '
# 'areas'},
# {'type': 'solid_floor_vapour_barrier', 'description': 'Visqueen High Performance Vapour Barrier',
# 'depth': 0, 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0,
# 'thermal_conductivity_unit': 0, 'prime_material_cost': 0.58, 'material_cost': 1.21, 'labour_cost': 0.48,
# 'labour_hours_per_unit': 0.02, 'plant_cost': 0, 'total_cost': 1.69, 'link': 'SPONs', 'Notes': 0},
# {'type': 'solid_floor_redecoration',
# 'description': 'Screeded beds; protection to compressible formwork exceeding 600mm wide', 'depth': 0,
# 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 9.6, 'material_cost': 9.89, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15,
# 'plant_cost': 0, 'total_cost': 12.56, 'link': 'SPONs',
# 'Notes': 'This is the screed layer, placed on top of the insulation'},
# {'type': 'solid_floor_redecoration', 'description': 'Fitting carpet', 'depth': 0, 'depth_unit': 0,
# 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 6.59, 'labour_hours_per_unit': 0.37,
# 'plant_cost': 0, 'total_cost': 6.59, 'link': 'SPONs',
# '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'},
# {'type': 'solid_floor_redecoration',
# 'description': 'Fitting existing softwood skirting or architrave to new frames; 150mm high', 'depth': 0,
# 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 0.01, 'labour_cost': 4.87, 'labour_hours_per_unit': 0.12,
# 'plant_cost': 0, 'total_cost': 4.88, 'link': 'SPONs', 'Notes': 0}
# ]
# insulation_floor_area = self.property.floor_area / self.property.number_of_floors
demolition_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_demolition"]
preparation_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_preparation"]
vapour_barrier_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_vapour_barrier"]
redecoration_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_redecoration"]
if ((len(demolition_data) != 1) or (len(preparation_data) != 2) or (len(vapour_barrier_data) != 1) or
(len(redecoration_data) != 3)):
raise ValueError("Incorrect number of data entries for non-insulation materials")
# Break out the individual material costs
preparation_material_costs = sum([x["material_cost"] * insulation_floor_area for x in preparation_data])
insulation_material_costs = material["material_cost"] * insulation_floor_area
vapour_barrier_material_costs = vapour_barrier_data[0]["material_cost"] * insulation_floor_area
redecoration_material_costs = sum([x["material_cost"] * insulation_floor_area for x in redecoration_data])
demolition_labour_costs = sum([x["labour_cost"] * insulation_floor_area for x in demolition_data])
preparation_labour_costs = sum([x["labour_cost"] * insulation_floor_area for x in preparation_data])
insulation_labour_costs = material["labour_cost"] * insulation_floor_area
vapour_barrier_labour_costs = vapour_barrier_data[0]["labour_cost"] * insulation_floor_area
redecoration_labour_costs = sum([x["labour_cost"] * insulation_floor_area for x in redecoration_data])
preparation_plant_costs = sum([x["plant_cost"] * insulation_floor_area for x in preparation_data])
labour_costs = (demolition_labour_costs + insulation_labour_costs + vapour_barrier_labour_costs +
redecoration_labour_costs + preparation_labour_costs)
labour_costs = labour_costs * self.labour_adjustment_factor
materials_cost = (preparation_material_costs + insulation_material_costs + vapour_barrier_material_costs +
redecoration_material_costs)
subtotal_before_profit = labour_costs + materials_cost + preparation_plant_costs
# We use HIGH_RISH_CONTINGENCY because of the potential for issues with moving fittings and trimming doors,
# as well as scope for damage to the existing floor during preparation.
contingency_cost = subtotal_before_profit * self.HIGH_RISK_CONTINGENCY
preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES
profit_cost = subtotal_before_profit * self.PROFIT_MARGIN
subtotal_before_vat = subtotal_before_profit + contingency_cost + preliminaries_cost + profit_cost
vat_cost = subtotal_before_vat * self.VAT_RATE
total_cost = subtotal_before_vat + vat_cost
demolition_labour_hours = sum([x["labour_hours_per_unit"] * insulation_floor_area for x in demolition_data])
preparation_labour_hours = sum([x["labour_hours_per_unit"] * insulation_floor_area for x in preparation_data])
insulation_labour_hours = material["labour_hours_per_unit"] * insulation_floor_area
vapour_barrier_labour_hours = vapour_barrier_data[0]["labour_hours_per_unit"] * insulation_floor_area
redecoration_labour_hours = sum([x["labour_hours_per_unit"] * insulation_floor_area for x in redecoration_data])
labour_hours = (demolition_labour_hours + insulation_labour_hours + vapour_barrier_labour_hours +
redecoration_labour_hours + preparation_labour_hours)
# Assume a team of 3 people for a small to medium size project
labour_days = (labour_hours / 8) / 3
return {
"total": total_cost,
"subtotal": subtotal_before_vat,
"vat": vat_cost,
"contingency": contingency_cost,
"preliminaries": preliminaries_cost,
"material": materials_cost,
"profit": profit_cost,
"labour_hours": labour_hours,
"labour_days": labour_days
}
def external_wall_insulation(self, wall_area, material, non_insulation_materials):
"""
We characterise external wall insulation as the following steps:
1) Preparation of the Area: Tidying up the surroundings, trimming back foliage, and laying down protective
sheets to protect the flooring and landscaping around the work area.
2) Scaffolding Setup (if needed): Erecting scaffolding for safe access to the walls of semi-detached or
detached houses. For terraced houses or lower-level work, scaffolding might not be necessary.
3) Wall Surface Preparation: Cleaning the wall surface, removing any loose or flaking material,
and possibly applying a primer. If the existing wall is weak or damaged, partial or full replacement
of the top surface may be necessary.
4) Applying Primer: If the existing wall is suitable, applying a primer to improve adhesion of the insulation
boards and stabilize the wall surface, especially if it's old or weathered.
5) Insulation Application: Attaching insulation boards to the primed wall using adhesive, mechanical fixings,
or a combination of both.
6) Basecoat and Mesh Application: Applying a basecoat embedded with a reinforcing mesh over the insulation.
This layer provides strength and helps prevent cracking.
7) Decorative Finish: Applying a decorative finish, such as render or cladding, which protects the insulation
and provides an aesthetic look.
8) Reinstalling Fixtures: Reattaching any fixtures like downpipes, satellite dishes, or lighting fixtures that
were removed during preparation. Extensions or adjustments may be required due to the increased wall thickness.
9) Inspection and Cleanup: Conducting a thorough inspection to ensure quality and integrity of the EWI system,
followed by cleaning up the site to remove all debris and materials.
In the actual materials data, at this point, we have costing for:
- wall preparation, hacking off existing wall finishes, linings, etc (ewi_wall_demolition)
- wall surface cleaning and priming (ewi_wall_preparation)
- insulation (external_wall_insulation)
- basecoat and mesh with decorative render topcoat finish (ewi_basecoat_and_mesh)
All of this data comes from SPONS, however there are some clear features missing. Because we could not find
suitable cost records in SPONS for steps like cleaning the area, setting up small scale scaffolding,
re-attaching any fitings and cleaning up the area afterwards, instead we have accounted for these steps by
increasing the preliminaries rate. It is acknowldeged though, that this is not ideal and that the cost of these
steps should be included in the materials data. We will look to improve this in the future, with data from
installers
:param wall_area:
:param material:
:param non_insulation_materials:
:return:
"""
# For semi detatched and detatched houses, as well as maisonettes, we price for scaffolding
if self.property.data["property-type"] == "House":
if self.property.data["built-form"] in ['Semi-Detached', 'Detached', "End-Terrace"]:
preliminaries_rate = self.EWI_SCAFFOLDING_PRELIMINARIES
else:
preliminaries_rate = self.EWI_NO_SCAFFOLDING_PRELIMINARIES
elif self.property.data["property-type"] == "Maisonette":
preliminaries_rate = self.EWI_SCAFFOLDING_PRELIMINARIES
elif self.property.data["property-type"] == "Bungalow":
preliminaries_rate = self.EWI_NO_SCAFFOLDING_PRELIMINARIES
else:
raise ValueError("Unsupported property type - haven't handled flats")
# non_insulation_materials = [x for x in df if x["type"] != "external_wall_insulation"]
# insulation_materials = [x for x in df if x["type"] == "external_wall_insulation"]
# material = {'type': 'external_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board',
# 'depth': 150.0, 'depth_unit': 'mm', 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.022,
# 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'prime_material_cost': 23.53,
# 'material_cost': 34.62, 'labour_cost': 33.06, 'labour_hours_per_unit': 1.4, 'plant_cost': 0,
# 'total_cost': 67.68, 'link': 'SPONs', 'Notes': 0}
# non_insulation_materials = [
# {'type': 'ewi_wall_demolition',
# 'description': 'Solid & Dry Lined walls: Hack of wall finishes with chipping '
# 'hammer; plaster to walls.',
# 'depth': 0, 'depth_unit': 0, 'cost_unit': 'gbp_per_m2',
# 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 10.27,
# 'labour_hours_per_unit': 0.33, 'plant_cost': 1.28, 'total_cost': 11.55,
# 'link': 'SPONs', 'Notes': 0}, {'type': 'ewi_wall_demolition',
# 'description': 'Stud walls: Remove wall linings '
# 'including battening behind; '
# 'plasterboard and skim',
# 'depth': 0, 'depth_unit': 0,
# 'cost_unit': 'gbp_per_m2',
# 'thermal_conductivity': 0,
# 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 0,
# 'labour_cost': 6.23, 'labour_hours_per_unit': 0.2,
# 'plant_cost': 1.25, 'total_cost': 7.48,
# 'link': 'SPONs', 'Notes': 0},
# {'type': 'ewi_wall_demolition',
# 'description': 'Lathe and Plaster walls: Remove wall linings including battening '
# 'behind; wood lath and plaster',
# 'depth': 0, 'depth_unit': 0, 'cost_unit': 'gbp_per_m2',
# 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 6.85,
# 'labour_hours_per_unit': 0.22, 'plant_cost': 2.09, 'total_cost': 8.94,
# 'link': 'SPONs', 'Notes': 0}, {'type': 'ewi_wall_preparation',
# 'description': 'Clean and prepare surfaces, '
# 'one coat Keim dilution, '
# 'one coat primer and two coats '
# 'of Keim Ecosil paint; Brick or '
# 'block walls; over 300 mm girth',
# 'depth': 0, 'depth_unit': 0, 'cost_unit': 0,
# 'thermal_conductivity': 0,
# 'thermal_conductivity_unit': 0,
# 'prime_material_cost': 0, 'material_cost': 7.3,
# 'labour_cost': 5.62, 'labour_hours_per_unit': 0.3,
# 'plant_cost': 0, 'total_cost': 12.92,
# 'link': 'SPONs',
# 'Notes': 'This work covers the preparation and '
# 'priming of the wall before insulating'},
# {'type': 'ewi_wall_redecoration',
# 'description': 'EPS insulation fixed with adhesive to SFS structure (measured '
# 'separately) with horizontal PVC intermediate track and vertical '
# 'T-spines; with glassfibre mesh reinforcement embedded in Sto '
# 'Armat Classic Basecoat Render and Stolit K 1.5 Decorative '
# 'Topcoat Render (white)',
# 'depth': 0, 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0,
# 'thermal_conductivity_unit': 0, 'prime_material_cost': 0, 'material_cost': 0,
# 'labour_cost': 0, 'labour_hours_per_unit': 0, 'plant_cost': 0,
# 'total_cost': 69.94, 'link': 'SPONs',
# 'Notes': 'This material in SPONs is for 70mm EPS insulation, which comes in at a '
# 'cost of 99.17 per meter square. This includes the cost of insulation. '
# 'To get the costing for just the works and not the insulation, '
# 'we subtract the cost of EPS insulation, using Ravathem 75mm insulation '
# 'as an example, which costs £29.23 per meter square, giving us the cost '
# 'of the remaining works without insulation. This material gives us a '
# 'cost for basecoat, mesh application and a render finish'}]
demolition_data = [x for x in non_insulation_materials if x["type"] == "ewi_wall_demolition"]
preparation_data = [x for x in non_insulation_materials if x["type"] == "ewi_wall_preparation"]
redecoration_data = [x for x in non_insulation_materials if x["type"] == "ewi_wall_redecoration"]
if (len(demolition_data) != 3) or (len(preparation_data) != 1) or (len(redecoration_data) != 1):
raise ValueError("Incorrect number of data entries for non-insulation materials")
# Break out the individual material costs
# Since we don't know the exact wall construction, we take an average for demolition costs, since
# the cost will depend on the type of wall construction
demolition_material_costs = np.mean([x["material_cost"] * wall_area for x in demolition_data])
insulation_material_costs = material["material_cost"] * wall_area
preparation_material_costs = preparation_data[0]["material_cost"] * wall_area
redecoration_material_costs = redecoration_data[0]["material_cost"] * wall_area
demolition_plant_costs = np.mean([x["plant_cost"] * wall_area for x in demolition_data])
demolition_labour_costs = np.mean([x["labour_cost"] * wall_area for x in demolition_data])
insulation_labour_costs = material["labour_cost"] * wall_area
preparation_labour_costs = preparation_data[0]["labour_cost"] * wall_area
redecoration_labour_costs = redecoration_data[0]["labour_cost"] * wall_area
labour_costs = (demolition_labour_costs + insulation_labour_costs + redecoration_labour_costs +
preparation_labour_costs)
labour_costs = labour_costs * self.labour_adjustment_factor
materials_costs = (demolition_material_costs + insulation_material_costs + preparation_material_costs +
redecoration_material_costs)
subtotal_before_profit = labour_costs + materials_costs + demolition_plant_costs
contingency_cost = subtotal_before_profit * self.CONTINGENCY
preliminaries_cost = subtotal_before_profit * preliminaries_rate
profit_cost = subtotal_before_profit * self.PROFIT_MARGIN
subtotal_before_vat = subtotal_before_profit + contingency_cost + preliminaries_cost + profit_cost
vat_cost = subtotal_before_vat * self.VAT_RATE
total_cost = subtotal_before_vat + vat_cost
demolition_labour_hours = np.mean([x["labour_hours_per_unit"] * wall_area for x in demolition_data])
insulation_labour_hours = material["labour_hours_per_unit"] * wall_area
preparation_labour_hours = preparation_data[0]["labour_hours_per_unit"] * wall_area
redecoration_labour_hours = redecoration_data[0]["labour_hours_per_unit"] * wall_area
labour_hours = (demolition_labour_hours + insulation_labour_hours + redecoration_labour_hours +
preparation_labour_hours)
# Assume a team of 3-5 people for a small to medium size project
labour_days = (labour_hours / 8) / 4
return {
"total": total_cost,
"subtotal": subtotal_before_vat,
"vat": vat_cost,
"contingency": contingency_cost,
"preliminaries": preliminaries_cost,
"material": materials_costs,
"profit": profit_cost,
"labour_hours": labour_hours,
"labour_days": labour_days
}