From 11ba4c64d08efe5fbae89c3910fb79721ed59192 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 23 Nov 2023 16:19:22 +0000 Subject: [PATCH] tested iwi costs --- backend/Property.py | 5 +- recommendations/Costs.py | 121 +------------------- recommendations/FloorRecommendations.py | 4 +- recommendations/RoofRecommendations.py | 1 + recommendations/tests/test_costs.py | 143 ++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 121 deletions(-) diff --git a/backend/Property.py b/backend/Property.py index 8bdfa7e9..a3328156 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -82,6 +82,7 @@ class Property(Definitions): self.insulation_wall_area = None self.floor_area = None self.pitched_roof_area = None + self.insulation_floor_area = None if epc_client: self.epc_client = epc_client @@ -619,8 +620,10 @@ class Property(Definitions): built_form=self.data["built-form"], ) + self.insulation_floor_area = self.floor_area / self.number_of_floors + self.pitched_roof_area = esimtate_pitched_roof_area( - floor_area=self.floor_area / self.number_of_floors, floor_height=self.floor_height + floor_area=self.insulation_floor_area, floor_height=self.floor_height ) def set_wall_type(self): diff --git a/recommendations/Costs.py b/recommendations/Costs.py index 1fb1114d..e8f9d122 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -160,19 +160,7 @@ class Costs: :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 @@ -225,110 +213,6 @@ class Costs: :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"] @@ -365,7 +249,8 @@ class Costs: subtotal_before_profit = labour_costs + materials_costs + demolition_plant_costs - contingency_cost = subtotal_before_profit * self.CONTINGENCY + # We use high risk contingency for iwi + contingency_cost = subtotal_before_profit * self.HIGH_RISK_CONTINGENCY preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES profit_cost = subtotal_before_profit * self.PROFIT_MARGIN @@ -384,7 +269,7 @@ class Costs: 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 + labour_days = (labour_hours / 8) / 4 return { "total": total_cost, diff --git a/recommendations/FloorRecommendations.py b/recommendations/FloorRecommendations.py index bc24b6c3..5b194e0d 100644 --- a/recommendations/FloorRecommendations.py +++ b/recommendations/FloorRecommendations.py @@ -58,7 +58,7 @@ class FloorRecommendations(Definitions): ) property_type = self.property.data["property-type"] - floor_area = self.property.floor_area / self.property.number_of_floors + floor_area = self.property.insulation_floor_area year_built = self.property.year_built if self.property.floor["another_property_below"] | (self.property.floor["insulation_thickness"] in [ @@ -137,7 +137,7 @@ class FloorRecommendations(Definitions): if new_u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value) - quantity = self.property.floor_area / self.property.number_of_floors + quantity = self.property.insulation_floor_area estimated_cost = cost_per_unit * quantity diff --git a/recommendations/RoofRecommendations.py b/recommendations/RoofRecommendations.py index bfa63908..4f96f629 100644 --- a/recommendations/RoofRecommendations.py +++ b/recommendations/RoofRecommendations.py @@ -161,6 +161,7 @@ class RoofRecommendations: if new_u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value) + # TODO: We should use the floor area divided by the number of floors to get the area of the roof estimated_cost = cost_per_unit * self.property.floor_area if roof["is_pitched"]: diff --git a/recommendations/tests/test_costs.py b/recommendations/tests/test_costs.py index 8ca12e0b..8e2d76ce 100644 --- a/recommendations/tests/test_costs.py +++ b/recommendations/tests/test_costs.py @@ -31,3 +31,146 @@ class TestCosts: 'contingency': 63.396792997100626, 'preliminaries': 63.396792997100626, 'material': 539.0166061175574, 'profit': 95.09518949565093, 'labour_hours': 6.234177828761786} + + def test_loft_insulation(self): + mock_property = Mock() + mock_property.data = { + "county": "Northamptonshire" + } + + costs = Costs(mock_property) + loft_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 + } + + loft_results = costs.loft_insulation( + floor_area=33.5, + material=loft_material, + ) + + assert loft_results == {'total': 414.8496486, 'subtotal': 345.70804050000004, 'vat': 69.14160810000001, + 'contingency': 25.608003000000004, 'preliminaries': 25.608003000000004, + 'material': 198.29923000000002, 'profit': 38.4120045, 'labour_hours': 3.685} + + def test_internal_wall_insulation(self): + mock_property = Mock() + mock_property.data = { + "county": "Northamptonshire" + } + + costs = Costs(mock_property) + iwi_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'} + ] + + iwi_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" + } + + iwi_results = costs.internal_wall_insulation( + wall_area=95.9104281347967, + material=iwi_material, + non_insulation_materials=iwi_non_insulation_materials + ) + + assert iwi_results == {'total': 6421.5484411659245, 'subtotal': 5351.29036763827, 'vat': 1070.258073527654, + 'contingency': 573.3525393898148, 'preliminaries': 382.2350262598765, + 'material': 1747.488000615996, 'profit': 573.3525393898148, + 'labour_hours': 88.23759388401297, 'labour_days': 2.757424808875405}