From a9a7d3f3a809fe40281cf732a26e2359fea9ae44 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 23 Nov 2023 16:05:04 +0000 Subject: [PATCH] Added test_external_wall_area --- backend/Property.py | 9 ++++--- recommendations/Costs.py | 24 +++++++------------ recommendations/recommendation_utils.py | 23 ++++++++++++++++-- recommendations/tests/test_costs.py | 0 .../tests/test_recommendation_utils.py | 15 ++++++++++++ 5 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 recommendations/tests/test_costs.py diff --git a/backend/Property.py b/backend/Property.py index 4106c60a..8bdfa7e9 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -12,7 +12,7 @@ from epc_api.client import EpcClient from BaseUtility import Definitions from recommendations.rdsap_tables import england_wales_age_band_lookup from recommendations.recommendation_utils import ( - estimate_perimeter, get_wall_type, estimate_wall_area, esimtate_pitched_roof_area + estimate_perimeter, get_wall_type, estimate_external_wall_area, esimtate_pitched_roof_area ) ENVIRONMENT = os.environ.get('ENVIRONMENT', 'dev') @@ -612,8 +612,11 @@ class Property(Definitions): self.floor_area / self.number_of_floors, self.number_of_rooms / self.number_of_floors ) - self.insulation_wall_area = estimate_wall_area( - num_floors=self.number_of_floors, floor_height=self.floor_height, perimeter=self.perimeter + self.insulation_wall_area = estimate_external_wall_area( + num_floors=self.number_of_floors, + floor_height=self.floor_height, + perimeter=self.perimeter, + built_form=self.data["built-form"], ) self.pitched_roof_area = esimtate_pitched_roof_area( diff --git a/recommendations/Costs.py b/recommendations/Costs.py index c6e4ceb7..d14b5f33 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -27,11 +27,6 @@ p2.search_address_epc() p1.set_basic_property_dimensions() p2.set_basic_property_dimensions() -import pandas as pd - -df = pd.read_csv("/Users/khalimconn-kowlessar/Downloads/Hestia Materials - external_wall_insulation.csv") -df = df.to_dict("records") - # This data comes from SPONs regional_labour_variations = [ {"Region": "Outer London (Spon’s 2023)", "Adjustment_Factor": 1.00}, @@ -120,28 +115,25 @@ class Costs: Calculates the total cost for cavity wall insulation based on material and labor costs, including contingency, preliminaries, profit, and VAT. + Because of some limitations in the SPONs data, there are no materials that can be blown through a wall, + therefore we have adapted similar materials, basing our estimates on 75mm cavity slabs, and have halved the + labour time required. That is why we still price based on wall area despite volume actually being the correct + metric. + :return: A dictionary containing detailed cost breakdown. """ # Cost per m2 # material = { - # "description": "Crown Dritherm Cavity Slab 37 (Thermal conductivity 0.037 W/mK) glass fibre batt or - # other " - # "equal; as full or partial cavity fill; including cutting and fitting around wall ties - # and " - # "retaining discs", + # "description": "cwi", # "depth": 75, # "thermal_conductivity": 0.037, # "prime_cost": 5.17, # "material_cost": 5.62, - # "labour_cost": 2.25, - # "labour_hours": 0.13 + # "labour_cost": 1.125, + # "labour_hours": 0.065 # } material_cost_per_m2 = material["material_cost"] - # wall_area = self.property.insulation_wall_area - - # This is the amount of material required in m3, assuming a standard 75mm depth - volume = 0.075 * wall_area base_material_cost = material_cost_per_m2 * wall_area labour_cost = material["labour_cost"] * wall_area * self.labour_adjustment_factor diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py index 7cba8257..217f313f 100644 --- a/recommendations/recommendation_utils.py +++ b/recommendations/recommendation_utils.py @@ -527,12 +527,31 @@ def get_wall_type( return None -def estimate_wall_area(num_floors, floor_height, perimeter): +def estimate_external_wall_area(num_floors, floor_height, perimeter, built_form): + """ + This method estimates the external wall area based on fundamental assumptions about the home + + + :param num_floors: Number of floors in the building. + :param floor_height: Height of one floor in meters. + :param perimeter: Total perimeter of the building on one floor in meters. + :param built_form: The built form of the property. This is used to determine the number of exposed walls. + :return: + """ wall_area_one_floor = perimeter * floor_height total_wall_area = wall_area_one_floor * num_floors - return total_wall_area + number_exposed_walls = { + 'End-Terrace': 3, + 'Mid-Terrace': 2, + 'Semi-Detached': 3, + 'Detached': 4, + } + + exposed_wall_area = total_wall_area * (number_exposed_walls[built_form] / 4) + + return exposed_wall_area def calculate_r_value_per_mm(thickness_mm, thermal_conductivity_w_mK): diff --git a/recommendations/tests/test_costs.py b/recommendations/tests/test_costs.py new file mode 100644 index 00000000..e69de29b diff --git a/recommendations/tests/test_recommendation_utils.py b/recommendations/tests/test_recommendation_utils.py index 22280ed5..73796979 100644 --- a/recommendations/tests/test_recommendation_utils.py +++ b/recommendations/tests/test_recommendation_utils.py @@ -405,3 +405,18 @@ def test_esimtate_pitched_roof_area(): ) assert zero_roof_area2 == 0 + + +def test_external_wall_area(): + # Arrange: Define the test cases + test_cases = [ + (2, 3, 40, 'End-Terrace', 180), # 3 exposed walls + (2, 3, 40, 'Mid-Terrace', 120), # 2 exposed walls + (2, 3, 40, 'Semi-Detached', 180), # 3 exposed walls + (2, 3, 40, 'Detached', 240), # 4 exposed walls + ] + + # Act and Assert: Run the test cases + for num_floors, floor_height, perimeter, built_form, expected in test_cases: + result = recommendation_utils.estimate_external_wall_area(num_floors, floor_height, perimeter, built_form) + assert result == expected, f"Test failed for {built_form}: Expected {expected}, got {result}"