diff --git a/.idea/Model.iml b/.idea/Model.iml
index ac61a988..10f9791d 100644
--- a/.idea/Model.iml
+++ b/.idea/Model.iml
@@ -5,6 +5,7 @@
+
diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py
index 4a68e7a7..73993e78 100644
--- a/backend/app/plan/router.py
+++ b/backend/app/plan/router.py
@@ -130,18 +130,21 @@ async def trigger_plan(body: PlanTriggerRequest):
new_values=[float(p.data["total-floor-area"])],
)[0]
- # This
+ # This is placeholder, until the full dataset is loaded into the database and we just make a read to the
+ # database
walls_u_value_estimate = [
x for x in uvalue_estimates_walls
if (x['local-authority'] == p.data["local-authority"]) &
(x['property-type'] == p.data["property-type"]) &
(x['built-form'] == p.data["built-form"]) &
(x['walls-energy-eff'] == p.data["walls-energy-eff"]) &
- (x['walls-env-eff'] == p.data["walls-env-eff"]) &
- (x['total-floor-area_group'] == total_floor_area_group_decile)
+ (x['walls-env-eff'] == p.data["walls-env-eff"])
]
- wall_recomendations = WallRecommendations(property_instance=p, uvalue_estimates=walls_u_value_estimate)
+ wall_recomendations = WallRecommendations(
+ property_instance=p, uvalue_estimates=walls_u_value_estimate,
+ total_floor_area_group_decile=total_floor_area_group_decile
+ )
wall_recomendations.recommend()
wall_recomendations = wall_recomendations.recommendations
# insert property id
diff --git a/model_data/pytest.ini b/model_data/pytest.ini
deleted file mode 100644
index 6ab74d5e..00000000
--- a/model_data/pytest.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[pytest]
-addopts = --cov-report term-missing --cov=model_data
-testpaths = model_data/tests
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 00000000..1019b4a6
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+addopts = --cov-report term-missing --cov=model_data --cov=recommendations
+testpaths = model_data/tests recommendations/tests
diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py
index 572bdf92..2e14aef5 100644
--- a/recommendations/WallRecommendations.py
+++ b/recommendations/WallRecommendations.py
@@ -1,6 +1,5 @@
import itertools
import math
-from statistics import mean
from model_data.Property import Property
from model_data.BaseUtility import BaseUtility
@@ -217,9 +216,10 @@ class WallRecommendations(BaseUtility):
"solid_brick": 2,
}
- def __init__(self, property_instance: Property, uvalue_estimates):
+ def __init__(self, property_instance: Property, uvalue_estimates, total_floor_area_group_decile):
self.property = property_instance
self.uvalue_estimates = uvalue_estimates
+ self.total_floor_area_group_decile = total_floor_area_group_decile
# For audit purposes, when estimating u values we'll store it
self.estimated_u_value = None
@@ -400,47 +400,6 @@ class WallRecommendations(BaseUtility):
rec for rec in self.recommendations if rec["new_u_value"] >= self.DIMINISHING_RETURNS_U_VALUE
]
- def _get_walls_uvalue_estimate(self):
-
- """
- Wrapper function which contains the methodology to extract a property's walls u-value estimate
- when we don't have a true value and if we can't base our assumption off of the material
- :return:
- """
-
- if not self.uvalue_estimates:
- raise ValueError("No U-value estimate found for the given property")
-
- # Because of how spuriously populated the data is for number-habitable-rooms and number-heated-rooms,
- # we will try and filter on these to see if we get a result
-
- habitable_rooms_filer = [
- x for x in self.uvalue_estimates if
- x["number-habitable-rooms"] == self.property.data["number-habitable-rooms"]
- ]
-
- if not habitable_rooms_filer:
- # Take a mean of all the u-value estimates
- return mean(
- [x["median_thermal_transmittance"] for x in self.uvalue_estimates if x["median_thermal_transmittance"]]
- )
-
- # Try perform a filter on heated rooms
- heated_rooms_filter = [
- x for x in habitable_rooms_filer if
- x["number-heated-rooms"] == self.property.data["number-heated-rooms"]
- ]
-
- if not heated_rooms_filter:
- # Take a mean of all the u-value estimates
- return mean(
- [x["median_thermal_transmittance"] for x in habitable_rooms_filer if x["median_thermal_transmittance"]]
- )
-
- return mean(
- [x["median_thermal_transmittance"] for x in heated_rooms_filter if x["median_thermal_transmittance"]]
- )
-
@staticmethod
def rvalue_per_mm(total_r_value: float, thickness_mm: float) -> float:
"""Return R-value per mm.
diff --git a/recommendations/__init__.py b/recommendations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py
index 1e6f307a..bb6ed9b9 100644
--- a/recommendations/recommendation_utils.py
+++ b/recommendations/recommendation_utils.py
@@ -1,4 +1,6 @@
from copy import deepcopy
+from model_data.Property import Property
+from statistics import mean
def r_value_per_mm_to_u_value(depth_mm: int, r_value_per_mm: float):
@@ -110,3 +112,56 @@ def get_recommended_part(part, selected_depth):
recommended_part["depths"] = [selected_depth]
return recommended_part
+
+
+def get_uvalue_estimate(uvalue_estimates, property: Property):
+ """
+ Wrapper function which contains the methodology to extract a property's walls u-value estimate
+ when we don't have a true value and if we can't base our assumption off of the material
+ :return:
+ """
+
+ if not uvalue_estimates:
+ raise ValueError("No U-value estimate found for the given property - investigate")
+
+ # We try and filter on total_floor_area_group_decile
+ floor_area_filter = [
+ x for x in uvalue_estimates if
+ x["total-floor-area_group"] == property.data["total_floor_area_group_decile"]
+ ]
+
+ if not floor_area_filter:
+ # Take a mean of all the u-value estimates
+ return mean(
+ [x["median_thermal_transmittance"] for x in uvalue_estimates if x["median_thermal_transmittance"]]
+ )
+
+ # Because of how spuriously populated the data is for number-habitable-rooms and number-heated-rooms,
+ # we will try and filter on these to see if we get a result
+
+ habitable_rooms_filer = [
+ x for x in floor_area_filter if
+ x["number-habitable-rooms"] == property.data["number-habitable-rooms"]
+ ]
+
+ if not habitable_rooms_filer:
+ # Take a mean of all the u-value estimates
+ return mean(
+ [x["median_thermal_transmittance"] for x in floor_area_filter if x["median_thermal_transmittance"]]
+ )
+
+ # Try perform a filter on heated rooms
+ heated_rooms_filter = [
+ x for x in habitable_rooms_filer if
+ x["number-heated-rooms"] == property.data["number-heated-rooms"]
+ ]
+
+ if not heated_rooms_filter:
+ # Take a mean of all the u-value estimates
+ return mean(
+ [x["median_thermal_transmittance"] for x in habitable_rooms_filer if x["median_thermal_transmittance"]]
+ )
+
+ return mean(
+ [x["median_thermal_transmittance"] for x in heated_rooms_filter if x["median_thermal_transmittance"]]
+ )
diff --git a/recommendations/tests/test_recommendation_utils.py b/recommendations/tests/test_recommendation_utils.py
new file mode 100644
index 00000000..6e07f1db
--- /dev/null
+++ b/recommendations/tests/test_recommendation_utils.py
@@ -0,0 +1,74 @@
+import pytest
+from unittest.mock import MagicMock
+from recommendations import recommendation_utils
+
+
+class TestRecommendationUtils:
+ @pytest.fixture
+ def property_mock(self):
+ PropertyMock = MagicMock()
+ PropertyMock.data = {
+ 'total_floor_area_group_decile': 'Decile 1',
+ 'number-habitable-rooms': 3,
+ 'number-heated-rooms': 2
+ }
+ return PropertyMock
+
+ def test_r_value_per_mm_to_u_value(self):
+ assert recommendation_utils.r_value_per_mm_to_u_value(1, 2) == 0.5
+ with pytest.raises(ZeroDivisionError):
+ recommendation_utils.r_value_per_mm_to_u_value(0, 2)
+
+ def test_calculate_u_value_uplift(self):
+ assert recommendation_utils.calculate_u_value_uplift(1, 2) == (0.33333333333333337, 0.6666666666666666)
+ with pytest.raises(ZeroDivisionError):
+ recommendation_utils.calculate_u_value_uplift(0, 2)
+ with pytest.raises(ZeroDivisionError):
+ recommendation_utils.calculate_u_value_uplift(1, 0)
+
+ def test_is_diminishing_returns(self):
+ assert not recommendation_utils.is_diminishing_returns([1, 2, 3], 1, 1, 1)
+ assert recommendation_utils.is_diminishing_returns([1, 2, 3], 0.5, 1, 1)
+ assert not recommendation_utils.is_diminishing_returns([], 1, None, 1)
+
+ def test_update_lowest_selected_u_value(self):
+ assert recommendation_utils.update_lowest_selected_u_value(1, 2) == 1
+ assert recommendation_utils.update_lowest_selected_u_value(None, 2) == 2
+ assert recommendation_utils.update_lowest_selected_u_value(1, 0.5) == 0.5
+
+ def test_get_recommended_part(self):
+ part = {'depths': [1, 2, 3]}
+ assert recommendation_utils.get_recommended_part(part, 1) == {'depths': [1]}
+
+ def test_get_uvalue_estimate(self, property_mock):
+ uvalue_estimates = [
+ {
+ 'total-floor-area_group': 'Decile 1',
+ 'number-habitable-rooms': 3,
+ 'number-heated-rooms': 2,
+ 'median_thermal_transmittance': 1
+ },
+ {
+ 'total-floor-area_group': 'Decile 1',
+ 'number-habitable-rooms': 3,
+ 'number-heated-rooms': 2,
+ 'median_thermal_transmittance': 2
+ }
+ ]
+
+ assert recommendation_utils.get_uvalue_estimate(uvalue_estimates, property_mock) == 1.5
+
+ with pytest.raises(ValueError):
+ recommendation_utils.get_uvalue_estimate([], property_mock)
+
+ # Test with missing 'median_thermal_transmittance' key
+ uvalue_estimates_missing_key = [
+ {
+ 'total-floor-area_group': 'Decile 1',
+ 'number-habitable-rooms': 3,
+ 'number-heated-rooms': 2
+ }
+ ]
+
+ with pytest.raises(KeyError):
+ recommendation_utils.get_uvalue_estimate(uvalue_estimates_missing_key, property_mock)