From c96fafa70138ca69be4718537e8cb0a8997a6c4e Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 13 Nov 2023 15:57:51 +0900 Subject: [PATCH] basic implementation of pitched roof area estimate --- backend/Property.py | 9 +++++- recommendations/RoofRecommendations.py | 23 +++++++++++++- recommendations/recommendation_utils.py | 31 +++++++++++++++++++ .../tests/test_roof_recommendations.py | 22 +++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/backend/Property.py b/backend/Property.py index b0c6b083..1094e7b2 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -11,7 +11,9 @@ from utils.s3 import read_dataframe_from_s3_parquet 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_floors, estimate_perimeter, get_wall_type, estimate_wall_area +from recommendations.recommendation_utils import ( + estimate_floors, estimate_perimeter, get_wall_type, estimate_wall_area, esimtate_pitched_roof_area +) ENVIRONMENT = os.environ.get('ENVIRONMENT', 'dev') EPC_AUTH_TOKEN = os.environ.get('EPC_AUTH_TOKEN') @@ -79,6 +81,7 @@ class Property(Definitions): self.floor_height = None self.insulation_wall_area = None self.floor_area = None + self.pitched_roof_area = None if epc_client: self.epc_client = epc_client @@ -587,6 +590,10 @@ class Property(Definitions): num_floors=self.number_of_floors, floor_height=self.floor_height, perimeter=self.perimeter ) + self.pitched_roof_area = esimtate_pitched_roof_area( + floor_area=self.floor_area / self.number_of_floors, floor_height=self.floor_height + ) + def set_wall_type(self): """ This method sets the wall type of the property, using a simple approach based on the wall description diff --git a/recommendations/RoofRecommendations.py b/recommendations/RoofRecommendations.py index d364dea9..cf7c2a0f 100644 --- a/recommendations/RoofRecommendations.py +++ b/recommendations/RoofRecommendations.py @@ -54,10 +54,13 @@ class RoofRecommendations: u_value = get_roof_u_value(**{**self.property.roof, "age_band": self.property.age_band}) if self.property.roof["is_pitched"]: - # We recommend loft insulation self.recommend_loft_insulation(u_value, insulation_thickness) return + if self.property.roof["is_roof_room"]: + self.recommend_room_roof_insulation(u_value, insulation_thickness) + return + raise NotImplementedError("Implement me") @staticmethod @@ -132,3 +135,21 @@ class RoofRecommendations: ) self.recommendations = recommendations + + def recommend_room_roof_insulation(self, u_value, insulation_thickness): + """ + This method recommends room in roof insulation for properties that have been identified + to possess a room in roof. + + Because we currently have limited data about the construction of the roof, we make the following + assumptions: + 1) The room in roof has a sloped roof. + We will make some basic estimations about the area of the roof given the floor area and the height of the + floors + 2) Insulation of external walls is covered by the wall recommendation class + 3) We assume a "Gable" roof type + + :param u_value: + :param insulation_thickness: + :return: + """ diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py index 9205cec7..954998f4 100644 --- a/recommendations/recommendation_utils.py +++ b/recommendations/recommendation_utils.py @@ -1,6 +1,7 @@ import math from copy import deepcopy +import numpy as np import pandas as pd from recommendations.rdsap_tables import ( @@ -594,3 +595,33 @@ def convert_thickness_to_numeric(string_thickness): return int(string_thickness.replace("+", "")) return int(string_thickness) + + +def esimtate_pitched_roof_area(floor_area: float, floor_height: float) -> float: + """ + This function will estimate the area of a pitched roof, given the floor area below the roof and the floor + height of the property. + + Given limited information about the home, this is a very rough method to estimate the roof area and we + assume the the room is a gable roof. + + We assume a roughly average pitch of 45 degrees + + Note that both floor area and height should be in the same units. E.g. if floor area is meters squared, + floor height should be in meters + + :param floor_area: area of the home's floor + :param floor_height: height of the home's floors + :return: Numerical estimate of the surface area of the top of the pitched roof + """ + + # We estimate the length of the wall by just modelling the house as a square + wall_width = np.sqrt(floor_area) + + # We're modelling the roof as two triangles where we know two of the three sides. + # The floor height makes up one side and half of the wall width makes up the other side + slope = np.sqrt(np.square(wall_width / 2) + np.square(floor_height)) + + area = 2 * (slope * wall_width) + + return area diff --git a/recommendations/tests/test_roof_recommendations.py b/recommendations/tests/test_roof_recommendations.py index 8243a35b..b822f829 100644 --- a/recommendations/tests/test_roof_recommendations.py +++ b/recommendations/tests/test_roof_recommendations.py @@ -192,3 +192,25 @@ class TestRoofRecommendations: roof_recommender6.recommend() assert len(roof_recommender6.recommendations) == 0 + + def test_uninsulated_room_in_roof(self): + property_instance7 = Property(id=0, address1="fake", postcode="fake", epc_client=Mock()) + property_instance7.age_band = "F" + property_instance7.floor_area = 100 + property_instance7.roof = { + 'original_description': 'Roof room(s), no insulation (assumed)', + 'clean_description': 'Roof room(s), no insulation', + 'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False, + 'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False, 'is_at_rafters': False, + 'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True, 'insulation_thickness': 'none' + } + + property_instance7 + + roof_recommender7 = RoofRecommendations( + property_instance=property_instance7, materials=loft_insulation_materials + ) + + assert not roof_recommender7.recommendations + + roof_recommender7.recommend()