diff --git a/recommendations/Costs.py b/recommendations/Costs.py index 84a7468c..239dadd1 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -771,19 +771,38 @@ class Costs: :return: """ - number_of_windows = 7 - windows_cost = 345 + material_cost = material["material_cost"] * number_of_windows * self.SASH_WINDOW_INFLATION_FACTOR + labour_cost = ( + material["labour_cost"] * number_of_windows * self.SASH_WINDOW_INFLATION_FACTOR * + self.labour_adjustment_factor + ) - material_cost = windows_cost * number_of_windows * self.SASH_WINDOW_INFLATION_FACTOR + subtotal = material_cost + labour_cost - subtotal = material_cost - - contingency_cost = subtotal * 0.2 - preliminaries_cost = subtotal * 0.2 - profit_cost = subtotal * 0.2 + contingency_cost = subtotal * self.CONTINGENCY + preliminaries_cost = subtotal * self.PRELIMINARIES + profit_cost = subtotal * self.PROFIT_MARGIN subtotal_before_vat = subtotal + contingency_cost + preliminaries_cost + profit_cost - vat_cost = subtotal_before_vat * 0.2 + vat_cost = subtotal_before_vat * self.VAT_RATE total_cost = subtotal_before_vat + vat_cost + + labour_hours = material["labour_hours_per_unit"] * number_of_windows + + # Assume a team of 2 + labour_days = (labour_hours / 8) / 2 + + return { + "total": total_cost, + "subtotal": subtotal_before_vat, + "vat": vat_cost, + "contingency": contingency_cost, + "preliminaries": preliminaries_cost, + "material": material_cost, + "profit": profit_cost, + "labour_hours": labour_hours, + "labour_cost": labour_cost, + "labour_days": labour_days + } diff --git a/recommendations/WindowsRecommendations.py b/recommendations/WindowsRecommendations.py index d3424d1b..474071d9 100644 --- a/recommendations/WindowsRecommendations.py +++ b/recommendations/WindowsRecommendations.py @@ -11,14 +11,13 @@ class WindowsRecommendations: self.recommendation = [] - self.glazing_materials = [ - material for material in materials if material["type"] == "window_glazing" + self.glazing_material = [ + material for material in materials if material["type"] == "windows_glazing" ] - # TODO: This will be populated with the works associated to upgrading glazing. This will involve removal of - # previous glazing and installation of new glazing. This will not include the cost of the windows material - # themselfs - self.glazing_works_materials = [] + if len(self.glazing_material) != 1: + raise ValueError("There should only be one window glazing material") + self.glazing_material = self.glazing_material[0] def recommend(self): """ @@ -34,12 +33,14 @@ class WindowsRecommendations: if not number_of_windows: raise ValueError("Number of windows not specified") - if self.property.windows["has_glazing"] & self.property.windows["glazing_coverage"] == "full": + if self.property.windows["has_glazing"] & (self.property.windows["glazing_coverage"] == "full"): return # We then price the job based on the number of windows that there are - - cost_result = {} + cost_result = self.costs.window_glazing( + number_of_windows=number_of_windows, + material=self.glazing_material, + ) description = None diff --git a/recommendations/tests/test_data/materials.py b/recommendations/tests/test_data/materials.py index d7241be5..187d1401 100644 --- a/recommendations/tests/test_data/materials.py +++ b/recommendations/tests/test_data/materials.py @@ -942,8 +942,24 @@ materials = [ 'https://www.hamuch.com/cost/led-spot-light#:~:text=It%20costs%20an%20average%20of,' 'will%20drive%20up%20the%20cost.', 'created_at': datetime.datetime(2023, 11, 28, 22, 49, 12, 244907), 'is_active': True, 'prime_material_cost': None, - 'material_cost': 20.0, 'labour_cost': 46.0, 'labour_hours_per_unit': 0.8, 'plant_cost': 0.0, 'total_cost': 66.0, + 'material_cost': 20.0, 'labour_cost': 15.0, 'labour_hours_per_unit': 0.8, 'plant_cost': 0.0, 'total_cost': 66.0, 'notes': 'We estimate the unit economics from the checkatrade article. We assume that the average job consists ' 'of installing 6 lights based on the hamuch article. We use the median value of 400 for a job of 6 ' - 'lights'} + 'lights'}, + {'id': 1235, 'type': 'windows_glazing', + 'description': 'uPVC windows; Profile 22 or other equal and approved; reinforced where appropriate with ' + 'aluminium alloy; in refurbishment work, including standard ironmongery; sills and factory glazed ' + 'with low-e 24 mm double glazing; removing existing windows and fixing new in position; including ' + 'lugs plugged and screwed to brickwork or blockwork; Casement/fixed light; including vents; ' + 'e.p.d.m. glazing gaskets and weather seals; 1770 mm × 1200 mm; ref P312WW', + 'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_unit', '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, 11, 28, 22, 49, 12, 244907), + 'is_active': True, 'prime_material_cost': 176.55, + 'material_cost': 182.25, 'labour_cost': 163.36, 'labour_hours_per_unit': 6.5, 'plant_cost': 0.0, + 'total_cost': 345.61, + 'notes': 'This is the cost of removal of existing windows and installation of new windows. This is a casement ' + 'style window, which is the most common but also the cheapest style. In the cost estimation framework, ' + 'we can inflate prices for different finishes, to be conservative on price.'} ] diff --git a/recommendations/tests/test_window_recommendations.py b/recommendations/tests/test_window_recommendations.py index da50d25c..1a146b41 100644 --- a/recommendations/tests/test_window_recommendations.py +++ b/recommendations/tests/test_window_recommendations.py @@ -1,6 +1,7 @@ from recommendations.WindowsRecommendations import WindowsRecommendations from backend.Property import Property from unittest.mock import Mock +from recommendations.tests.test_data.materials import materials class TestWindowRecommendations: @@ -25,6 +26,10 @@ class TestWindowRecommendations: 'glazing_type': 'single', 'no_data': False } - property_1.number_of_windows = 5 + property_1.number_of_windows = 7 - recommender = WindowsRecommendations(property_instance=property_1, materials=[]) + recommender = WindowsRecommendations(property_instance=property_1, materials=materials) + + assert not recommender.recommendation + + recommender.recommend()