mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Added unit tests for recommendation utils and still restructuing repo
This commit is contained in:
parent
39a76daecc
commit
caedd79523
8 changed files with 142 additions and 50 deletions
1
.idea/Model.iml
generated
1
.idea/Model.iml
generated
|
|
@ -5,6 +5,7 @@
|
||||||
<sourceFolder url="file://$MODULE_DIR$/backend" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/backend" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/model_data" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/model_data" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.10 (hestia-data)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.10 (hestia-data)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|
|
||||||
|
|
@ -130,18 +130,21 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
new_values=[float(p.data["total-floor-area"])],
|
new_values=[float(p.data["total-floor-area"])],
|
||||||
)[0]
|
)[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 = [
|
walls_u_value_estimate = [
|
||||||
x for x in uvalue_estimates_walls
|
x for x in uvalue_estimates_walls
|
||||||
if (x['local-authority'] == p.data["local-authority"]) &
|
if (x['local-authority'] == p.data["local-authority"]) &
|
||||||
(x['property-type'] == p.data["property-type"]) &
|
(x['property-type'] == p.data["property-type"]) &
|
||||||
(x['built-form'] == p.data["built-form"]) &
|
(x['built-form'] == p.data["built-form"]) &
|
||||||
(x['walls-energy-eff'] == p.data["walls-energy-eff"]) &
|
(x['walls-energy-eff'] == p.data["walls-energy-eff"]) &
|
||||||
(x['walls-env-eff'] == p.data["walls-env-eff"]) &
|
(x['walls-env-eff'] == p.data["walls-env-eff"])
|
||||||
(x['total-floor-area_group'] == total_floor_area_group_decile)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
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.recommend()
|
||||||
wall_recomendations = wall_recomendations.recommendations
|
wall_recomendations = wall_recomendations.recommendations
|
||||||
# insert property id
|
# insert property id
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[pytest]
|
|
||||||
addopts = --cov-report term-missing --cov=model_data
|
|
||||||
testpaths = model_data/tests
|
|
||||||
3
pytest.ini
Normal file
3
pytest.ini
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[pytest]
|
||||||
|
addopts = --cov-report term-missing --cov=model_data --cov=recommendations
|
||||||
|
testpaths = model_data/tests recommendations/tests
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
from statistics import mean
|
|
||||||
|
|
||||||
from model_data.Property import Property
|
from model_data.Property import Property
|
||||||
from model_data.BaseUtility import BaseUtility
|
from model_data.BaseUtility import BaseUtility
|
||||||
|
|
@ -217,9 +216,10 @@ class WallRecommendations(BaseUtility):
|
||||||
"solid_brick": 2,
|
"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.property = property_instance
|
||||||
self.uvalue_estimates = uvalue_estimates
|
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
|
# For audit purposes, when estimating u values we'll store it
|
||||||
self.estimated_u_value = None
|
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
|
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
|
@staticmethod
|
||||||
def rvalue_per_mm(total_r_value: float, thickness_mm: float) -> float:
|
def rvalue_per_mm(total_r_value: float, thickness_mm: float) -> float:
|
||||||
"""Return R-value per mm.
|
"""Return R-value per mm.
|
||||||
|
|
|
||||||
0
recommendations/__init__.py
Normal file
0
recommendations/__init__.py
Normal file
|
|
@ -1,4 +1,6 @@
|
||||||
from copy import deepcopy
|
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):
|
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]
|
recommended_part["depths"] = [selected_depth]
|
||||||
|
|
||||||
return recommended_part
|
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"]]
|
||||||
|
)
|
||||||
|
|
|
||||||
74
recommendations/tests/test_recommendation_utils.py
Normal file
74
recommendations/tests/test_recommendation_utils.py
Normal file
|
|
@ -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)
|
||||||
Loading…
Add table
Reference in a new issue