Added property tests

This commit is contained in:
Khalim Conn-Kowlessar 2023-06-19 19:42:08 +01:00
parent 3f2992bcb0
commit 35a9679220
5 changed files with 172 additions and 22 deletions

View file

@ -54,3 +54,21 @@ class BoreholeClient:
distance_m = math.sqrt((x2_bng - x1_bng) ** 2 + (y2_bng - y1_bng) ** 2)
distance_km = distance_m / 1000 # convert meters to kilometers
return distance_m, distance_km
# EXAMPLE
# There are ~1.4 million entries in this dataset and so we firstly want to reduce the number of
# entries in here if possible before we produce any form of comparison between our properties, to infer
# the distance from the property to the nearest borehole
# Let's take a sample
# borehold_compare_to = borehole_client.data[0]
# property = input_properties[0]
#
# # for each property, find the nearest borehole
# # This is just an example, looking at the distance from a property to a borehole
# dist_m, dist_km = borehole_client.distance_between_bng_coords(
# x1_bng=property.coordinates["x_coordinate"],
# y1_bng=property.coordinates["y_coordinate"],
# x2_bng=borehold_compare_to["X"],
# y2_bng=borehold_compare_to["Y"],
# )

View file

@ -21,7 +21,7 @@ class EpcClean:
"hotwater-description",
"main-fuel",
"mainheat-description",
"main-heating-controls",
"mainheatcont-description",
"roof-description",
"walls-description",
"windows-description",
@ -50,7 +50,7 @@ class EpcClean:
self.clean_wrapper(field="hotwater-description", cleaning_cls=HotWaterAttributes)
self.clean_wrapper(field="main-fuel", cleaning_cls=MainFuelAttributes)
self.clean_wrapper(field="mainheat-description", cleaning_cls=MainHeatAttributes)
self.clean_wrapper(field="main-heating-controls", cleaning_cls=MainheatControlAttributes)
self.clean_wrapper(field="mainheatcont-description", cleaning_cls=MainheatControlAttributes)
self.clean_wrapper(field="roof-description", cleaning_cls=RoofAttributes)
self.clean_wrapper(field="walls-description", cleaning_cls=WallAttributes)
self.clean_wrapper(field="windows-description", cleaning_cls=WindowAttributes)

View file

@ -1,9 +1,30 @@
from epc_api.client import EpcClient
from model_data.config import EPC_AUTH_TOKEN
from model_data.OpenUprnClient import OpenUprnClient
from model_data.EpcClean import EpcClean
class Property:
ATTRIBUTE_MAP = {
"floor-description": "floor",
"hotwater-description": "hotwater",
"main-fuel": "main_fuel",
"mainheat-description": "main_heating",
"mainheatcont-description": "main_heating_controls",
"roof-description": "roof",
"walls-description": "walls",
"windows-description": "windows",
}
floor = None
hotwater = None
main_fuel = None
main_heating = None
main_heating_controls = None
roof = None
walls = None
windows = None
coordinates = None
def __init__(self, postcode, address1, epc_client=None, data=None):
@ -56,3 +77,23 @@ class Property:
)
self.coordinates = {key.lower(): value for key, value in self.coordinates.items()}
def get_components(self, cleaner: EpcClean):
"""
Given the cleaning that has been performed, we'll use this to identify the property
components, from roof to walls to windows, heating and hot water
:param cleaner:
:return:
"""
if not cleaner.cleaned:
raise ValueError("Cleaner does not contain cleaned data")
for description, attribute in cleaner.cleaned.items():
attributes = [
x for x in cleaner.cleaned[description] if x["original_description"] == self.data[description]
]
if len(attributes) != 1:
raise ValueError("Either No attributes or multiple found for %s" % description)
setattr(self, self.ATTRIBUTE_MAP[description], attributes[0])

View file

@ -106,19 +106,7 @@ def handler():
)
borehole_client.read()
# There are ~1.4 million entries in this dataset and so we firstly want to reduce the number of
# entries in here if possible before we produce any form of comparison between our properties, to infer
# the distance from the property to the nearest borehole
# Let's take a sample
borehold_compare_to = borehole_client.data[0]
property = input_properties[0]
# for each property, find the nearest borehole
# This is just an example, looking at the distance from a property to a borehole
dist_m, dist_km = borehole_client.distance_between_bng_coords(
x1_bng=property.coordinates["x_coordinate"],
y1_bng=property.coordinates["y_coordinate"],
x2_bng=borehold_compare_to["X"],
y2_bng=borehold_compare_to["Y"],
)
# Now, for our input properties, we need to identify the components of the building, based
# on the cleaning we've done
for p in input_properties:
p.get_components(cleaner)

View file

@ -1,7 +1,10 @@
import pytest
import pandas as pd
from unittest.mock import Mock
from epc_api.client import EpcClient
from model_data.Property import Property
from model_data.OpenUprnClient import OpenUprnClient
from model_data.EpcClean import EpcClean
# Define some test data
mock_epc_response = {
@ -9,21 +12,54 @@ mock_epc_response = {
{
"inspection-date": "2023-06-01",
"some-other-key": "some-value",
# add other keys as necessary
"roof-description": "Roof Description",
"walls-description": "Walls Description",
"windows-description": "Windows Description",
"mainheat-description": "Main Heating Description",
"hotwater-description": "Hot Water Description"
},
{
"inspection-date": "2023-05-01",
"some-other-key": "some-other-value",
# add other keys as necessary
"roof-description": "Roof Description",
"walls-description": "Walls Description",
"windows-description": "Windows Description",
"mainheat-description": "Main Heating Description",
"hotwater-description": "Hot Water Description"
}
]
}
# Create a mock EPC client
mock_client = Mock(spec=EpcClient())
mock_client.domestic.search.return_value = mock_epc_response
mock_client.domestic.search.return_value = mock_epc_response.copy()
mock_client.auth_token = "mocked_auth_token"
# Create a mock OpenUprnClient instance
mock_open_uprn_client = Mock(spec=OpenUprnClient(path=None, uprns=[12345]))
mock_open_uprn_client.data = pd.DataFrame(
[
{"UPRN": 12345, "longitude": 1.2345, "latitude": 2.3456},
{"UPRN": 12346, "longitude": 3.4567, "latitude": 4.5678}
]
)
# Create a mock EpcClean instance
mock_cleaner = Mock(spec=EpcClean(data=[
{"roof-description": "Roof Description"},
{"walls-description": "Walls Description"},
{"windows-description": "Windows Description"},
{"mainheat-description": "Main Heating Description"},
{"hotwater-description": "Hot Water Description"}
]))
mock_cleaner.cleaned = {
"roof-description": [{"original_description": "Roof Description"}],
"walls-description": [{"original_description": "Walls Description"}],
"windows-description": [{"original_description": "Windows Description"}],
"mainheat-description": [{"original_description": "Main Heating Description"}],
"hotwater-description": [{"original_description": "Hot Water Description"}]
}
class TestProperty:
@pytest.fixture
@ -53,10 +89,77 @@ class TestProperty:
def test_search_address_epc_multiple_results(self, property_instance):
# Modify the mock response to return two results with the same date
mock_epc_response["rows"].append({
mock_client.domestic.search.return_value["rows"].append({
"inspection-date": "2023-06-01",
"some-other-key": "duplicate-date"
})
with pytest.raises(Exception, match="More than one result found for this address - investigate me"):
property_instance.search_address_epc()
# Reset the change
mock_client.domestic.search.return_value["rows"].pop(-1)
assert len(mock_client.domestic.search.return_value["rows"]) == 1
def test_get_coordinates(self, property_instance):
# Set up the mock OpenUprnClient
property_instance.data = {"uprn": 12345}
property_instance.get_coordinates(mock_open_uprn_client)
# Verify that the coordinates are set correctly
assert property_instance.coordinates == {
"uprn": 12345,
"longitude": 1.2345,
"latitude": 2.3456
}
def test_get_coordinates_without_open_uprn_data(self, property_instance):
# Modify the mock OpenUprnClient to not have read any data
mock_open_uprn_client.data = None
# Verify that ValueError is raised when OpenUprnClient data is None
with pytest.raises(ValueError, match="OpenUprnClient has not read data"):
property_instance.get_coordinates(mock_open_uprn_client)
def test_get_components(self, property_instance):
property_instance.search_address_epc()
property_instance.get_components(mock_cleaner)
# Verify that the components are set correctly
assert property_instance.roof == {"original_description": "Roof Description"}
assert property_instance.walls == {"original_description": "Walls Description"}
assert property_instance.windows == {"original_description": "Windows Description"}
assert property_instance.main_heating == {"original_description": "Main Heating Description"}
assert property_instance.hotwater == {"original_description": "Hot Water Description"}
def test_get_components_without_cleaned_data(self, property_instance):
# Modify the mock EpcClean to not have cleaned data
mock_cleaner.cleaned = {}
# Verify that ValueError is raised when EpcClean doesn't contain cleaned data
with pytest.raises(ValueError, match="Cleaner does not contain cleaned data"):
property_instance.get_components(mock_cleaner)
def test_get_components_no_attributes(self, property_instance):
# Modify the mock cleaner to have no attributes for a specific description
mock_cleaner.cleaned = {
"roof-description": []
}
# Verify that ValueError is raised when no attributes are found
with pytest.raises(ValueError, match="Either No attributes or multiple found for roof-description"):
property_instance.get_components(mock_cleaner)
def test_get_components_multiple_attributes(self, property_instance):
# This shouldn't happen - it would mean a cleaning error
property_instance.search_address_epc()
mock_cleaner.cleaned = {
"roof-description": [
{"original_description": "Roof Description"},
{"original_description": "Roof Description"}
]
}
# Verify that ValueError is raised when multiple attributes are found
with pytest.raises(ValueError, match="Either No attributes or multiple found for roof-description"):
property_instance.get_components(mock_cleaner)