from recommendations.Costs import Costs from unittest.mock import Mock import datetime class TestCosts: def test_cavity_wall_insulation(self): mock_property = Mock() mock_property.data = { "county": "Northamptonshire" } costs = Costs(mock_property) cwi_material = { "description": "cwi", "depth": 75, "thermal_conductivity": 0.037, "prime_cost": 5.17, "material_cost": 5.62, "labour_cost": 1.125, "labour_hours_per_unit": 0.065, } cwi_results = costs.cavity_wall_insulation( wall_area=95.9104281347967, material=cwi_material, ) 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() 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_per_unit": 0.11 } loft_results = costs.loft_insulation( floor_area=33.5, material=loft_material, ) 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() 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': 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() mock_property.data = { "county": "Northamptonshire" } 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_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'}] sus_floor_results = costs.suspended_floor_insulation( insulation_floor_area=33.5, material=sus_floor_material, non_insulation_materials=sus_floor_non_insulation_materials ) assert sus_floor_results == { 'total': 3337.07436, 'subtotal': 2780.8953, 'vat': 556.17906, 'contingency': 370.78604, 'preliminaries': 185.39302, 'material': 483.405, 'profit': 370.78604, 'labour_hours': 54.940000000000005, 'labour_days': 2.289166666666667, 'labour_cost': 1370.5252 } def test_solid_floor_insulation(self): mock_property = Mock() mock_property.data = { "county": "Northamptonshire" } costs = Costs(mock_property) sol_floor_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 } sol_floor_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} ] sol_floor_results = costs.solid_floor_insulation( insulation_floor_area=33.5, material=sol_floor_material, non_insulation_materials=sol_floor_non_insulation_materials ) assert sol_floor_results == { '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 } def test_external_wall_insulation(self): mock_property = Mock() mock_property.data = { "county": "Northamptonshire", "property-type": "House", "built-form": 'End-Terrace' } 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_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'}] ewi_results = costs.external_wall_insulation( wall_area=95.9104281347967, material=ewi_material, non_insulation_materials=ewi_non_insulation_materials ) assert ewi_results == { '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 } def test_flat_roof_insulation(self): mock_property = Mock() mock_property.data = { "county": "Northamptonshire" } costs = Costs(mock_property) flat_roof_material = {'id': 1225, 'type': 'flat_roof_insulation', 'description': 'Kingspan Thermaroof TR21 zero OPD ' 'urethene insulation board', 'depth': 100.0, 'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.025, 'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'SPONs', 'created_at': "now", 'is_active': True, 'prime_material_cost': None, 'material_cost': 50.95, 'labour_cost': 10.66, 'labour_hours_per_unit': 0.48, 'plant_cost': 0.0, 'total_cost': 61.61, 'notes': "SPONs didn't have a labour hours so we use " "0.48 which is similar to other materials"} flat_roof_non_insulation_materials = [ {'id': 17, 'type': 'mechanical_ventilation', 'description': 'Mechanical Extract Ventilation', 'depth': None, 'depth_unit': None, 'cost': 500, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': None, 'r_value_unit': None, 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': None, 'created_at': datetime.datetime(2023, 10, 18, 16, 39, 9, 827188), 'is_active': True, 'prime_material_cost': None, 'material_cost': None, 'labour_cost': None, 'labour_hours_per_unit': None, 'plant_cost': None, 'total_cost': None, 'notes': None}, {'id': 1221, 'type': 'flat_roof_preparation', 'description': 'clean surface to receive new damp-proof membrane', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 4.36, 'labour_hours_per_unit': 0.14, 'plant_cost': 0.0, 'total_cost': 4.36, 'notes': 'This data is based on concrete however forms a decent baseline for a Bituminous Felt flat roof'}, {'id': 1223, 'type': 'flat_roof_preparation', 'description': 'One coat primer; on wood surfaces before fixing; General surfaces; over 300 mm girth', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': None, 'material_cost': 2.49, 'labour_cost': 1.5, 'labour_hours_per_unit': 0.08, 'plant_cost': 0.0, 'total_cost': 3.99, 'notes': 'SPONs data gives us a baseline for a wood surface'}, {'id': 1224, 'type': 'flat_roof_vapour_barrier', 'description': 'Visqueen High Performance Vapour Barrier', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': 0.58, 'material_cost': 1.21, 'labour_cost': 0.48, 'labour_hours_per_unit': 0.02, 'plant_cost': 0.0, 'total_cost': 1.69, 'notes': None}, {'id': 1234, 'type': 'flat_roof_waterproofing', 'description': '20 mm thick two coat coverings; felt isolating membrane; to concrete (or ' 'timber) base; flat or to falls or slopes not exceeding 10° from horizontal', 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None, 'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 0.0, 'labour_hours_per_unit': 0.5, 'plant_cost': 0.0, 'total_cost': 31.13, 'notes': None} ] flat_roof_floor_results = costs.flat_roof_insulation( floor_area=33.5, material=flat_roof_material, non_insulation_materials=flat_roof_non_insulation_materials ) assert flat_roof_floor_results == {'total': 5325.327767999999, 'subtotal': 4437.773139999999, 'vat': 887.5546279999999, 'contingency': 459.07998, 'preliminaries': 306.05332, 'material': 1830.775, 'profit': 612.10664, 'labour_hours': 24.79, 'labour_days': 1.549375, 'labour_cost': 186.9032} assert costs.labour_adjustment_factor == 0.88