diff --git a/.idea/Model.iml b/.idea/Model.iml index b0f9c00d..4413bb06 100644 --- a/.idea/Model.iml +++ b/.idea/Model.iml @@ -7,7 +7,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index ca0e1cd9..3b05c6ac 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py index 818e7e98..8464c02b 100644 --- a/recommendations/WallRecommendations.py +++ b/recommendations/WallRecommendations.py @@ -9,6 +9,7 @@ from recommendations.recommendation_utils import ( r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns, update_lowest_selected_u_value, get_recommended_part, get_wall_u_value ) +from recommendations.config import PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION class WallRecommendations(Definitions): @@ -35,14 +36,6 @@ class WallRecommendations(Definitions): # we still consider it as an option U_VALUE_ERROR = 0.01 - # TODO: Review this value against RdSAP - # Page 19 of rdsap here: - # https://files.bregroup.com/bre-co-uk-file-library-copy/filelibrary/SAP/2012/RdSAP-9.93/RdSAP_2012_9.93.pdf - # provides default U-values for solid brick walls depending on age band - DEFAULT_U_VALUES = { - "solid_brick": 2, - } - def __init__( self, property_instance: Property, @@ -113,6 +106,7 @@ class WallRecommendations(Definitions): is_granite_or_whinstone=self.property.walls["is_granite_or_whinstone"], is_sandstone_or_limestone=self.property.walls["is_sandstone_or_limestone"], ) + self.estimated_u_value = u_value if self.property.walls["is_solid_brick"]: @@ -127,16 +121,13 @@ class WallRecommendations(Definitions): if is_cavity_wall: if u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE: # Test filling cavity - self.find_cavity_insulation(u_value) - - if insulation_thickness not in ["none", None]: - raise ValueError("Implement me") + self.find_cavity_insulation(u_value, insulation_thickness) return raise NotImplementedError("Not implemented yet") - def find_cavity_insulation(self, u_value): + def find_cavity_insulation(self, u_value, insulation_thickness): """ This method tests different materials to fill the cavity wall, determining which material will give us the best U-value. @@ -150,11 +141,15 @@ class WallRecommendations(Definitions): therefore we'll use 50mm as the base assumption :param u_value: u_value of the starting wall + :param insulation_thickness: describes the insulation level of the wall. If "below average", we have a partially + filled cavity wall """ cavity_wall_fills = [m for m in self.materials if m["type"] == "cavity_wall_insulation"] - cavity_width = 50 + if insulation_thickness == "below average": + cavity_width = 50 * (1 - PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION) + # Test the different fill options lowest_selected_u_value = None recommendations = [] diff --git a/recommendations/config.py b/recommendations/config.py index 750453b0..20169ffd 100644 --- a/recommendations/config.py +++ b/recommendations/config.py @@ -6,3 +6,11 @@ UPGRADES_MAP = { 'Suspended, no insulation (assumed)': 'Suspended, insulated (assumed)', 'Solid, no insulation (assumed)': 'Solid, insulated (assumed)', } + +PARTIAL_CAVITY_DESCRIPTIONS = [ + "Cavity wall, as built, partial insulation", + "Cavity wall, partial insulation", +] + +# We assume that the cavity is partially filled with insulation, and is filled 25% full +PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION = 0.25 diff --git a/recommendations/rdsap_tables.py b/recommendations/rdsap_tables.py index a622a4cc..0ce139ab 100644 --- a/recommendations/rdsap_tables.py +++ b/recommendations/rdsap_tables.py @@ -226,10 +226,10 @@ epc_wall_description_map = { "Cavity wall, as built, partial insulation": "Filled cavity", "Cavity wall, filled cavity": "Filled cavity", "Cavity wall, as built, no insulation": "Cavity as built", - "Cavity wall, as built, insulated": "Unfilled cavity with 100 mm external or internal insulation", + "Cavity wall, as built, insulated": "Filled cavity", "Cavity wall, with external insulation": "Unfilled cavity with 100 mm external or internal insulation", - "Cavity wall, insulated": "Unfilled cavity with 100 mm external or internal insulation", - 'Cavity wall, partial insulation': "Unfilled cavity with 50 mm external or internal insulation", + "Cavity wall, insulated": "Filled cavity", + 'Cavity wall, partial insulation': "Filled cavity", "Cavity wall,": "Cavity as built", # General case of cavity wall without further details "Cavity wall, filled cavity and external insulation": diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py index 8289624d..f405d962 100644 --- a/recommendations/recommendation_utils.py +++ b/recommendations/recommendation_utils.py @@ -7,6 +7,7 @@ from recommendations.rdsap_tables import ( epc_wall_description_map, wall_uvalues_df, default_wall_thickness, table_s9 as s9, table_s10 as s10, table_s11 as s11 ) +from recommendations.config import PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION, PARTIAL_CAVITY_DESCRIPTIONS def r_value_per_mm_to_u_value(depth_mm: int, r_value_per_mm: float): @@ -146,7 +147,9 @@ def apply_formula_s_5_1_1(is_granite_or_whinstone, is_sandstone_or_limestone, ag raise ValueError("This should only be called when is_granite_or_whinstone or is_sandstone_or_limestone is True") -def get_wall_u_value(clean_description, age_band, is_granite_or_whinstone, is_sandstone_or_limestone): +def get_wall_u_value( + clean_description, age_band, is_granite_or_whinstone, is_sandstone_or_limestone +): """ Given some features about a wall, this function will query the wall u-value table and return the u-value :param clean_description: Cleaned up description of the wall from the EPC data @@ -156,13 +159,23 @@ def get_wall_u_value(clean_description, age_band, is_granite_or_whinstone, is_sa :return: """ - mapped_description = epc_wall_description_map[clean_description] + if clean_description in PARTIAL_CAVITY_DESCRIPTIONS: + # If we have a partial cavity fill, we linearly interpolate the u-value. This isn't necessarily the perfect + # method and how we do this should be explored, however we want to distinguish between the old + filled_uvalue = float(wall_uvalues_df[wall_uvalues_df["Wall_type"] == "Filled cavity"][age_band].values[0]) + unfilled_uvalue = float(wall_uvalues_df[wall_uvalues_df["Wall_type"] == "Cavity as built"][age_band].values[0]) - mapped_value = wall_uvalues_df[wall_uvalues_df["Wall_type"] == mapped_description][age_band].values[0] + mapped_value = str( + unfilled_uvalue - (PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION * (unfilled_uvalue - filled_uvalue)) + ) + else: + mapped_description = epc_wall_description_map[clean_description] - if pd.isnull(mapped_value) and "Park home" in mapped_description: - # We don't know enough in this case so we default to 0 - return 0 + mapped_value = wall_uvalues_df[wall_uvalues_df["Wall_type"] == mapped_description][age_band].values[0] + + if pd.isnull(mapped_value) and "Park home" in mapped_description: + # We don't know enough in this case so we default to 0 + return 0 if mapped_value == "a": # The rdSap documentation indicateswe should use a formula to calculate the u-value diff --git a/recommendations/tests/test_wall_recommendations.py b/recommendations/tests/test_wall_recommendations.py index eeed8daf..e910a8f5 100644 --- a/recommendations/tests/test_wall_recommendations.py +++ b/recommendations/tests/test_wall_recommendations.py @@ -470,3 +470,36 @@ class TestCavityWallRecommensations: assert np.isclose(recommender.recommendations[1]["new_u_value"], 0.26) assert np.isclose(recommender.recommendations[1]["cost"], 1250) + + def test_fill_partial_filled_cavity(self): + input_property = Property(id=1, postcode="F4k3", address1="123 fake street", epc_client=Mock()) + input_property.walls = { + 'original_description': 'Cavity wall, as built, partial insulation (assumed)', + 'clean_description': 'Cavity wall, as built, partial insulation', + 'thermal_transmittance': None, 'thermal_transmittance_unit': None, + 'is_cavity_wall': True, 'is_filled_cavity': False, 'is_solid_brick': False, + 'is_system_built': False, 'is_timber_frame': False, 'is_granite_or_whinstone': False, + 'is_as_built': True, 'is_cob': False, 'is_assumed': True, + 'is_sandstone_or_limestone': False, 'is_park_home': False, + 'insulation_thickness': 'below average', 'external_insulation': False, + 'internal_insulation': False + } + input_property.age_band = "C" + input_property.insulation_wall_area = 50 + + recommender = WallRecommendations( + property_instance=input_property, + materials=cavity_wall_insulation_parts + ) + + assert not recommender.recommendations + + recommender.recommend() + + assert recommender.recommendations + assert recommender.estimated_u_value == 1.3 + assert np.isclose(recommender.recommendations[0]["new_u_value"], 0.56) + assert np.isclose(recommender.recommendations[0]["cost"], 1000) + + assert np.isclose(recommender.recommendations[1]["new_u_value"], 0.57) + assert np.isclose(recommender.recommendations[1]["cost"], 1250)