From 63de7c19df65dfdc58f51dd3a62230ca95cf8129 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 23 Nov 2023 14:51:30 +0000 Subject: [PATCH] completed pricing for ewi --- recommendations/Costs.py | 348 +++++++++++++++++++++++++++++++-------- 1 file changed, 278 insertions(+), 70 deletions(-) diff --git a/recommendations/Costs.py b/recommendations/Costs.py index 1c8cf6d8..c6e4ceb7 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -29,7 +29,7 @@ p2.set_basic_property_dimensions() import pandas as pd -df = pd.read_csv("/Users/khalimconn-kowlessar/Downloads/Hestia Materials - solid_floor_insulation.csv") +df = pd.read_csv("/Users/khalimconn-kowlessar/Downloads/Hestia Materials - external_wall_insulation.csv") df = df.to_dict("records") # This data comes from SPONs @@ -61,8 +61,11 @@ class Costs: 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 walls and expected costs: + 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 @@ -70,13 +73,22 @@ class Costs: CONTINGENCY = 0.1 # Where there is more uncertainty, a higher contingency rate is used - HIGH_RISH_CONTINGENCY = 0.15 + 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 @@ -355,7 +367,8 @@ class Costs: demolition_plant_costs = np.mean([x["plant_cost"] * wall_area for x in demolition_data]) - demolition_labour_costs = sum([x["labour_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]) @@ -380,7 +393,7 @@ class Costs: total_cost = subtotal_before_vat + vat_cost - demolition_labour_hours = sum([x["labour_hours_per_unit"] * wall_area for x in demolition_data]) + 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]) @@ -553,70 +566,70 @@ class Costs: :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} - ] + # 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 @@ -655,7 +668,7 @@ class 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_RISH_CONTINGENCY + contingency_cost = subtotal_before_profit * self.HIGH_RISK_CONTINGENCY preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES profit_cost = subtotal_before_profit * self.PROFIT_MARGIN @@ -686,3 +699,198 @@ class Costs: "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 + }