From 9adfa4c07538bd03228cf6cdc991c1c9b5f93b92 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 22 Nov 2023 22:29:55 +0000 Subject: [PATCH] implemented suspended floor insulation --- backend/Property.py | 5 +- recommendations/Costs.py | 120 ++++++++++++++++++++++++ recommendations/recommendation_utils.py | 19 ---- 3 files changed, 122 insertions(+), 22 deletions(-) diff --git a/backend/Property.py b/backend/Property.py index 370eca06..4106c60a 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -1,5 +1,4 @@ from datetime import datetime -from collections import Counter import re import os import pandas as pd @@ -13,7 +12,7 @@ from epc_api.client import EpcClient from BaseUtility import Definitions from recommendations.rdsap_tables import england_wales_age_band_lookup from recommendations.recommendation_utils import ( - estimate_floors, estimate_perimeter, get_wall_type, estimate_wall_area, esimtate_pitched_roof_area + estimate_perimeter, get_wall_type, estimate_wall_area, esimtate_pitched_roof_area ) ENVIRONMENT = os.environ.get('ENVIRONMENT', 'dev') @@ -596,7 +595,7 @@ class Property(Definitions): self.number_of_rooms = float(self.data["number-habitable-rooms"]) if self.data["property-type"] == "House": - self.number_of_floors = estimate_floors(self.floor_area, self.number_of_rooms) + self.number_of_floors = 2 elif self.data["property-type"] in ["Flat", "Bungalow"]: self.number_of_floors = 1 elif self.data["property-type"] == "Maisonette": diff --git a/recommendations/Costs.py b/recommendations/Costs.py index c855e690..49298d5f 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -27,6 +27,12 @@ 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 - suspended_floor_insulation.csv") +df = df.to_dict("records") + +# This data comes from SPONs regional_labour_variations = [ {"Region": "Outer London (Spon’s 2023)", "Adjustment_Factor": 1.00}, {"Region": "Inner London", "Adjustment_Factor": 1.05}, @@ -397,3 +403,117 @@ class Costs: "labour_hours": labour_hours, "labour_days": labour_days } + + def suspended_floor_insulation(self, 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 + } diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py index 063a274c..7cba8257 100644 --- a/recommendations/recommendation_utils.py +++ b/recommendations/recommendation_utils.py @@ -527,25 +527,6 @@ def get_wall_type( return None -def estimate_floors(floor_area, num_rooms): - """ - Simple utility funciton, which assuming a 15m squared room, estimates the number of floors in a property - :param floor_area: Gross floor area of a property - :param num_rooms: Number of rooms in a property - :return: Number of floors in a property - """ - # Estimate total room area - total_room_area = num_rooms * 15 - - # Estimate the number of floors - floors = floor_area / total_room_area - - # Round up to the nearest whole number - floors = round(floors) - - return floors - - def estimate_wall_area(num_floors, floor_height, perimeter): wall_area_one_floor = perimeter * floor_height