From 43a21965804b797e97d88e95e7bac4751d756a2d Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sat, 2 Dec 2023 16:58:34 +0000 Subject: [PATCH] Fixing costs unit tests --- .idea/Model.iml | 2 +- .idea/misc.xml | 2 +- recommendations/Costs.py | 6 +- recommendations/tests/test_costs.py | 131 +++++++++++++++------------- 4 files changed, 77 insertions(+), 64 deletions(-) diff --git a/.idea/Model.iml b/.idea/Model.iml index 4413bb06..b0f9c00d 100644 --- a/.idea/Model.iml +++ b/.idea/Model.iml @@ -7,7 +7,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index 6f308057..1122b380 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,7 +3,7 @@ - + diff --git a/recommendations/Costs.py b/recommendations/Costs.py index 23edd287..3fe0e560 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -40,6 +40,10 @@ class Costs: # We assume a conservative 10% contingency for all works which is a rate defined by SPONs CONTINGENCY = 0.1 + # We use a higher contingency rate for internal wall insulation because of the potential for issues with moving + # fittings and trimming doors, as well as scope for damage to the existing wall during preparation. + IWI_CONTINGENCY = 0.15 + # Where there is more uncertainty, a higher contingency rate is used HIGH_RISK_CONTINGENCY = 0.2 # When there is less uncertainty, a lower contingency rate is used @@ -232,7 +236,7 @@ class Costs: subtotal_before_profit = labour_costs + materials_costs + demolition_plant_costs - contingency_cost = subtotal_before_profit * self.CONTINGENCY + contingency_cost = subtotal_before_profit * self.IWI_CONTINGENCY preliminaries_cost = subtotal_before_profit * self.PRELIMINARIES profit_cost = subtotal_before_profit * self.PROFIT_MARGIN diff --git a/recommendations/tests/test_costs.py b/recommendations/tests/test_costs.py index 1ba601a8..2854b298 100644 --- a/recommendations/tests/test_costs.py +++ b/recommendations/tests/test_costs.py @@ -19,7 +19,7 @@ class TestCosts: "prime_cost": 5.17, "material_cost": 5.62, "labour_cost": 1.125, - "labour_hours": 0.065 + "labour_hours_per_unit": 0.065, } cwi_results = costs.cavity_wall_insulation( @@ -27,10 +27,12 @@ class TestCosts: material=cwi_material, ) - assert cwi_results == {'total': 1027.0280465530302, 'subtotal': 855.8567054608585, 'vat': 171.1713410921717, - 'contingency': 63.396792997100626, 'preliminaries': 63.396792997100626, - 'material': 539.0166061175574, 'profit': 95.09518949565093, - 'labour_hours': 6.234177828761786, 'labour_cost': 94.95132385344874} + assert cwi_results == { + 'total': 1065.0661223512907, 'subtotal': 887.5551019594088, 'vat': 177.51102039188177, + 'contingency': 63.396792997100626, 'preliminaries': 63.396792997100626, 'material': 539.0166061175574, + 'profit': 126.79358599420125, 'labour_hours': 6.234177828761786, 'labour_cost': 94.95132385344874, + 'labour_days': 0.38963611429761164 + } def test_loft_insulation(self): mock_property = Mock() @@ -46,7 +48,7 @@ class TestCosts: "prime_cost": None, "material_cost": 5.91938, "labour_cost": 1.96, - "labour_hours": 0.11 + "labour_hours_per_unit": 0.11 } loft_results = costs.loft_insulation( @@ -54,10 +56,11 @@ class TestCosts: 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, - 'labour_cost': 57.7808} + assert loft_results == { + 'total': 430.21445040000003, 'subtotal': 358.512042, 'vat': 71.70240840000001, + 'contingency': 25.608003000000004, 'preliminaries': 25.608003000000004, 'material': 198.29923000000002, + 'profit': 51.21600600000001, 'labour_hours': 3.685, 'labour_cost': 57.7808, 'labour_days': 0.460625 + } def test_internal_wall_insulation(self): mock_property = Mock() @@ -171,11 +174,14 @@ class TestCosts: 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, - 'labour_cost': 1927.1602026551818} + assert iwi_results == { + 'total': 6650.889456921851, 'subtotal': 5542.407880768209, 'vat': 1108.4815761536418, + 'contingency': 573.3525393898148, 'preliminaries': 382.2350262598765, + 'material': 1747.488000615996, + 'profit': 764.470052519753, 'labour_hours': 88.23759388401297, + 'labour_days': 2.757424808875405, + 'labour_cost': 1927.1602026551818 + } def test_suspended_floor_insulation(self): mock_property = Mock() @@ -185,16 +191,18 @@ class TestCosts: costs = Costs(mock_property) - sus_floor_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'} + sus_floor_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' + } sus_floor_non_insulation_materials = [ {'type': 'suspended_floor_demolition', 'description': 'Removal of carpet and underfelt', 'depth': 0, @@ -231,9 +239,8 @@ class TestCosts: ) assert sus_floor_results == { - 'total': 3003.366924, 'subtotal': 2502.80577, 'vat': 500.561154, - 'contingency': 185.39302, 'preliminaries': 185.39302, 'material': 483.405, - 'profit': 278.08952999999997, 'labour_hours': 54.940000000000005, + 'total': 3114.6027360000003, 'subtotal': 2595.50228, 'vat': 519.100456, 'contingency': 185.39302, + 'preliminaries': 185.39302, 'material': 483.405, 'profit': 370.78604, 'labour_hours': 54.940000000000005, 'labour_days': 2.289166666666667, 'labour_cost': 1370.5252 } @@ -263,28 +270,29 @@ class TestCosts: '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'}, + '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, @@ -316,8 +324,8 @@ class TestCosts: ) assert sol_floor_results == { - 'total': 3962.021952, 'subtotal': 3301.68496, 'vat': 660.336992, 'contingency': 353.75196, - 'preliminaries': 235.83464, 'material': 1006.3399999999999, 'profit': 353.75196, 'labour_hours': 57.285, + 'total': 4245.023520000001, 'subtotal': 3537.5196, 'vat': 707.5039200000001, 'contingency': 471.66928, + 'preliminaries': 235.83464, 'material': 1006.3399999999999, 'profit': 471.66928, 'labour_hours': 57.285, 'labour_days': 2.386875, 'labour_cost': 1346.6464 } @@ -331,11 +339,13 @@ class TestCosts: costs = Costs(mock_property) - ewi_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} + ewi_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 + } ewi_non_insulation_materials = [ {'type': 'ewi_wall_demolition', 'description': 'Solid & Dry Lined walls: Hack of wall finishes with chipping ' @@ -403,9 +413,8 @@ class TestCosts: ) assert ewi_results == { - 'total': 13590.909723215433, 'subtotal': 11325.758102679527, 'vat': 2265.1516205359053, - 'contingency': 808.9827216199662, 'preliminaries': 1213.4740824299492, - 'material': 4020.565147410677, 'profit': 1213.4740824299492, - 'labour_hours': 187.02533486285358, 'labour_days': 5.8445417144641745, + 'total': 14561.688989159393, 'subtotal': 12134.740824299493, 'vat': 2426.948164859899, + 'contingency': 808.9827216199662, 'preliminaries': 1617.9654432399325, 'material': 4020.565147410677, + 'profit': 1617.9654432399325, 'labour_hours': 187.02533486285358, 'labour_days': 5.8445417144641745, 'labour_cost': 3921.5600094613983 }