mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
added prune diminishing recommendations
This commit is contained in:
parent
5bfded838f
commit
0717245947
2 changed files with 178 additions and 11 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import pint
|
||||
import re
|
||||
import itertools
|
||||
import math
|
||||
|
||||
from model_data.Property import Property
|
||||
from model_data.ConservationAreaClient import ConservationAreaClient
|
||||
|
|
@ -217,6 +217,8 @@ class WallRecommendations(BaseUtility):
|
|||
self.property = property_instance
|
||||
self.year_built = self._year_property_was_built()
|
||||
self.uvalue_estimates = uvalue_estimates
|
||||
# For audit purposes, when estimating u values we'll store it
|
||||
self.estimated_u_value = None
|
||||
|
||||
# Will contains a list of recommended measures
|
||||
self.recommendations = []
|
||||
|
|
@ -299,6 +301,7 @@ class WallRecommendations(BaseUtility):
|
|||
u_value = self.DEFAULT_U_VALUES["solid_brick"]
|
||||
else:
|
||||
u_value = self._get_walls_uvalue_estimate()
|
||||
self.estimated_u_value = u_value
|
||||
|
||||
if u_value >= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
||||
self.find_insulation(u_value)
|
||||
|
|
@ -306,6 +309,38 @@ class WallRecommendations(BaseUtility):
|
|||
|
||||
raise NotImplementedError("Not implemented yet")
|
||||
|
||||
def _is_diminishing_returns(self, new_u_value, lowest_selected_u_value):
|
||||
"""
|
||||
What are defines diminishing returns?
|
||||
1) The new u value is lower than the lowest selected u value
|
||||
2) The new u value is below the diminishing returns threshold
|
||||
3) We already have some recommendations so there is no need to
|
||||
insert another recommendation in
|
||||
"""
|
||||
|
||||
# if we don't have anything selected, lowest_selected_u_value will be missing
|
||||
if lowest_selected_u_value is None:
|
||||
if self.recommendations:
|
||||
raise ValueError("Recommendations should be empty - investigate")
|
||||
# This means that nothing has been selected yet
|
||||
# the new u value is less than the threshold, however this MIGHT be the only
|
||||
# solution and so we consider it
|
||||
return False
|
||||
|
||||
# We should already have recommendations
|
||||
if not self.recommendations:
|
||||
raise ValueError("Recommendations should not be empty - investigate")
|
||||
|
||||
# We already have a solution that is suitable so we want to make sure that
|
||||
# any new solutin actually has a higher u-value as it will either be
|
||||
# 1) cheaper
|
||||
# 2) thinner with a more efficient material
|
||||
is_diminishing = (new_u_value < self.DIMINISHING_RETURNS_U_VALUE) and (
|
||||
new_u_value < lowest_selected_u_value
|
||||
)
|
||||
|
||||
return is_diminishing
|
||||
|
||||
def find_insulation(self, u_value):
|
||||
"""
|
||||
This function contains the logic for finding potential insulation measures for a property, depending
|
||||
|
|
@ -320,21 +355,34 @@ class WallRecommendations(BaseUtility):
|
|||
iwi_parts = [part for part in wall_parts if part["type"] == "internal_wall_insulation"]
|
||||
|
||||
# Recommend external and internal wall insulation separately
|
||||
|
||||
lowest_selected_u_value = None
|
||||
for part in ewi_parts + iwi_parts:
|
||||
|
||||
for depth in part["depths"]:
|
||||
part_u_value = self.r_value_per_mm_to_u_value(depth, part["r_value_per_mm"])
|
||||
|
||||
_, new_u_value = self.calculate_u_value_uplift(u_value, part_u_value)
|
||||
new_u_value = round(new_u_value, 2)
|
||||
if new_u_value < self.DIMINISHING_RETURNS_U_VALUE and self.recommendations:
|
||||
# If we already have a solution that is suitable and does not cross the diminishing returns
|
||||
# threshold, but meets the part L requirements, we don't need to recommend anything
|
||||
new_u_value = math.ceil(new_u_value * 100.0) / 100.0
|
||||
|
||||
# If I have a lowest U value and my new u value is higher than that but lower than the
|
||||
# diminishing returns threshold, it can be considered
|
||||
|
||||
# If I have a lowest U value and my new u value is lower than the lowest value, it's
|
||||
# further into the diminishing returns threshold and can shouldn't be
|
||||
|
||||
if self._is_diminishing_returns(new_u_value, lowest_selected_u_value):
|
||||
continue
|
||||
|
||||
# We allow a small tolerance for error so we don't discount the recommendation entirely
|
||||
# if it's close, since this is an estimated new u-value
|
||||
if new_u_value - self.U_VALUE_ERROR <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
||||
if new_u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
||||
|
||||
if lowest_selected_u_value is None:
|
||||
lowest_selected_u_value = new_u_value
|
||||
|
||||
if new_u_value <= lowest_selected_u_value:
|
||||
lowest_selected_u_value = new_u_value
|
||||
|
||||
self.recommendations.append(
|
||||
self._get_recommended_part(part, depth, new_u_value)
|
||||
)
|
||||
|
|
@ -368,6 +416,21 @@ class WallRecommendations(BaseUtility):
|
|||
]
|
||||
self.recommendations.append(recommendation)
|
||||
|
||||
self.prune_diminishing_recommendations()
|
||||
|
||||
def prune_diminishing_recommendations(self):
|
||||
# For any recommendations, if we have at least 1 reommendation that does not exhibit diminishing returns
|
||||
# we trim all others that are beyond the diminishing returns threshold
|
||||
|
||||
# We first check if we have any recommendations that are not diminishing returns
|
||||
not_diminishing_return = [
|
||||
rec for rec in self.recommendations if rec["new_u_value"] >= self.DIMINISHING_RETURNS_U_VALUE
|
||||
]
|
||||
if not not_diminishing_return:
|
||||
self.recommendations = [
|
||||
rec for rec in self.recommendations if rec["new_u_value"] >= self.DIMINISHING_RETURNS_U_VALUE
|
||||
]
|
||||
|
||||
def _get_walls_uvalue_estimate(self):
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
import pytest
|
||||
import pickle
|
||||
from unittest.mock import Mock
|
||||
from model_data.recommendations.WallRecommendations import WallRecommendations
|
||||
|
||||
|
||||
|
|
@ -20,6 +21,18 @@ class TestWallRecommendations:
|
|||
) as f:
|
||||
return pickle.load(f)
|
||||
|
||||
@pytest.fixture
|
||||
def mock_wall_rec_instance(self):
|
||||
# Creating a mock instance of WallRecommendations with the necessary attributes
|
||||
property_mock = Mock()
|
||||
property_mock.full_sap_epc = {"lodgement-date": "2000-01-01"} # or any date you want
|
||||
property_mock.data = {"construction-age-band": "1950"} # or any other data that fits your tests
|
||||
|
||||
uvalue_estimates_mock = Mock()
|
||||
|
||||
mock_wall_rec_instance = WallRecommendations(property_mock, uvalue_estimates_mock)
|
||||
return mock_wall_rec_instance
|
||||
|
||||
def test_init(self, input_properties, uvalue_estimates):
|
||||
obj = WallRecommendations(property_instance=input_properties[0], uvalue_estimates=uvalue_estimates)
|
||||
assert obj
|
||||
|
|
@ -61,7 +74,7 @@ class TestWallRecommendations:
|
|||
assert recommender.property.data["property-type"] == "Flat"
|
||||
|
||||
recommender.recommend()
|
||||
# This should result in some recommendations, all of which should be insulation
|
||||
# This should result in some recommendations, all of which should be internal insulation
|
||||
assert recommender.recommendations
|
||||
|
||||
rec_types = {rec["type"] for rec in recommender.recommendations}
|
||||
|
|
@ -73,6 +86,97 @@ class TestWallRecommendations:
|
|||
recommender.recommendations
|
||||
)
|
||||
|
||||
def test_nothing(self, input_properties, uvalue_estimates):
|
||||
assert input_properties
|
||||
assert uvalue_estimates
|
||||
def test_solid_brick_insulation(self, input_properties, uvalue_estimates):
|
||||
"""
|
||||
This tests a property with a wall description of Solid brick, as built, insulation (assumed)
|
||||
The property was built in 1991, after cavity walls were introduced
|
||||
However, we're told this property is solid brik so we assume no cavity.
|
||||
We're also told that it has some insulation already
|
||||
|
||||
We'll need to estimate the u-value for this property since we don't know what the insulation is
|
||||
and how thick it is. We'll estimate the u-value based on similar properties, by matching properties
|
||||
with similar attributes such as the walls energy efficiency rating, the property type, the number of rooms,
|
||||
etc
|
||||
|
||||
This property is not in a conservation area, however it's a flat so we don't recommend external wall insulation
|
||||
"""
|
||||
recommender = WallRecommendations(property_instance=input_properties[6], uvalue_estimates=uvalue_estimates)
|
||||
|
||||
assert recommender.property.walls["original_description"] == "Solid brick, as built, insulated (assumed)"
|
||||
assert recommender.year_built == 1991
|
||||
assert not recommender.ewi_valid
|
||||
assert recommender.property.in_conservation_area == "not_in_conservation_area"
|
||||
assert recommender.property.data["property-type"] == "Flat"
|
||||
assert recommender.estimated_u_value is None
|
||||
|
||||
recommender.recommend()
|
||||
|
||||
# We should result in some recommendations, all of which should be internal wall insulation
|
||||
assert recommender.recommendations
|
||||
assert recommender.estimated_u_value == 0.45
|
||||
|
||||
rec_types = {rec["type"] for rec in recommender.recommendations}
|
||||
assert rec_types == {"internal_wall_insulation"}
|
||||
|
||||
# Check the recommendations provide a u value below the minimum
|
||||
assert all(
|
||||
rec["new_u_value"] < WallRecommendations.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE for rec in
|
||||
recommender.recommendations
|
||||
)
|
||||
|
||||
for rec in recommender.recommendations:
|
||||
assert rec["new_u_value"] < 0.3
|
||||
|
||||
def test_is_diminishing_returns_no_recommendations(self, mock_wall_rec_instance):
|
||||
# We have no recommendations but the new u value is below the diminishing returns threshold
|
||||
# however since there are no recommendaions yet, we should include it and say
|
||||
# it is NOT diminishing
|
||||
mock_wall_rec_instance.recommendations = []
|
||||
|
||||
new_u_value = 0.24
|
||||
assert new_u_value < WallRecommendations.DIMINISHING_RETURNS_U_VALUE
|
||||
|
||||
assert not mock_wall_rec_instance._is_diminishing_returns(new_u_value, None)
|
||||
|
||||
def test_is_diminishing_returns_diminishing(self, mock_wall_rec_instance):
|
||||
# We have a recommendation already and the new u value falls below the diminishing returns
|
||||
# threshold and is also lower than the lowest selected u value, so we should say this IS
|
||||
# diminishing returns
|
||||
mock_wall_rec_instance.recommendations = [Mock()]
|
||||
|
||||
new_u_value = 0.2
|
||||
lowest_selected_u_value = 0.23
|
||||
assert new_u_value < WallRecommendations.DIMINISHING_RETURNS_U_VALUE
|
||||
|
||||
assert mock_wall_rec_instance._is_diminishing_returns(new_u_value, lowest_selected_u_value)
|
||||
|
||||
def test_is_diminishing_returns_is_diminishing(self, mock_wall_rec_instance):
|
||||
# We have a recommendation already and the new u value falls below the diminishing returns
|
||||
# threshold and it's lower than the lowest selected u value, so we should say this IS DIMINISHIN
|
||||
mock_wall_rec_instance.recommendations = [Mock()]
|
||||
|
||||
new_u_value = 0.24
|
||||
lowest_selected_u_value = 0.26
|
||||
|
||||
assert new_u_value < WallRecommendations.DIMINISHING_RETURNS_U_VALUE
|
||||
|
||||
assert mock_wall_rec_instance._is_diminishing_returns(new_u_value, lowest_selected_u_value)
|
||||
|
||||
def test_is_diminishing_returns_not_diminishing(self, mock_wall_rec_instance):
|
||||
# We have a recommendation already and the new u value falls below the diminishing returns
|
||||
# threshold however it's higher than the lowest selected u value, so we should say this is NOT
|
||||
# diminishing returns
|
||||
mock_wall_rec_instance.recommendations = [Mock()]
|
||||
|
||||
new_u_value = 0.24
|
||||
lowest_selected_u_value = 0.22
|
||||
|
||||
assert new_u_value < WallRecommendations.DIMINISHING_RETURNS_U_VALUE
|
||||
|
||||
assert not mock_wall_rec_instance._is_diminishing_returns(new_u_value, lowest_selected_u_value)
|
||||
|
||||
def test_is_diminishing_returns_error_in_recommendations(self, mock_wall_rec_instance):
|
||||
# Testing case where there's an error in recommendations
|
||||
mock_wall_rec_instance.recommendations = []
|
||||
with pytest.raises(ValueError):
|
||||
mock_wall_rec_instance._is_diminishing_returns(0.2, 0.24)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue