From f8a4884deb9901654d1c265340f5ce3a391d6b91 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Wed, 13 Sep 2023 15:50:20 +0100 Subject: [PATCH] fixing unit tests for Property class --- .coveragerc | 3 +- .idea/Model.iml | 2 +- .idea/misc.xml | 2 +- backend/app/plan/router.py | 6 - backend/tests/test_property.py | 133 ++++++++++++------ .../epc_attributes/LightingAttributes.py | 22 ++- pytest.ini | 2 +- 7 files changed, 115 insertions(+), 55 deletions(-) diff --git a/.coveragerc b/.coveragerc index 0ed78884..a220497a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -9,4 +9,5 @@ omit = model_data/plotting/* recommendations/rdsap_tables.py model_data/simulation_system/* - model_data/cleaner_app.py \ No newline at end of file + model_data/cleaner_app.py + backend/app/* \ No newline at end of file diff --git a/.idea/Model.iml b/.idea/Model.iml index 05b9012b..b03b31b1 100644 --- a/.idea/Model.iml +++ b/.idea/Model.iml @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 3b05c6ac..ca0e1cd9 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index bc593031..d120a552 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -99,12 +99,6 @@ walls_decile_data = { 'Decile 9', 'Decile 10'], 'decile_boundaries': [6., 49., 51., 55., 64., 71., 76., 83., 96., 120., 2279.]} -lighting_averages = [ - {'lighting-description': 'good lighting efficiency', 'low-energy-lighting': 99.26666666666667}, - {'lighting-description': 'excellent lighting efficiency', 'low-energy-lighting': 100.0}, - {'lighting-description': 'below average lighting efficiency', 'low-energy-lighting': 0.0} -] - def filter_materials(materials): materials_by_type = defaultdict(list) diff --git a/backend/tests/test_property.py b/backend/tests/test_property.py index 1305a7db..679b25b0 100644 --- a/backend/tests/test_property.py +++ b/backend/tests/test_property.py @@ -2,7 +2,7 @@ import pytest import pandas as pd from unittest.mock import Mock from epc_api.client import EpcClient -from model_data.Property import Property +from backend.Property import Property from open_uprn.OpenUprnClient import OpenUprnClient from model_data.EpcClean import EpcClean @@ -19,6 +19,18 @@ mock_epc_response = { "hotwater-description": "Hot Water Description", "transaction-type": "rental", "lighting-description": "Good Lighting Efficiency", + "energy-consumption-current": "50", + "co2-emissions-current": "123", + "mechanical-ventilation": "natural", + 'photo-supply': 0, + "solar-water-heating-flag": "N", + "wind-turbine-count": 0, + "extension-count": 0, + "heat-loss-corridor": "no corridor", + "unheated-corridor-length": 0, + "mains-gas-flag": "Y", + "floor-height": 2.5, + "total-floor-area": 100 }, { "inspection-date": "2023-05-01", @@ -30,6 +42,18 @@ mock_epc_response = { "hotwater-description": "Hot Water Description", "transaction-type": "rental", "lighting-description": "Good Lighting Efficiency", + "energy-consumption-current": "50", + "co2-emissions-current": "123", + "mechanical-ventilation": "natural", + 'photo-supply': 0, + "solar-water-heating-flag": "N", + "wind-turbine-count": 0, + "extension-count": 0, + "heat-loss-corridor": "no corridor", + "unheated-corridor-length": 0, + "mains-gas-flag": "Y", + "floor-height": 2.5, + "total-floor-area": 100 } ] } @@ -42,6 +66,18 @@ mock_epc_response_dupe = { 'mainheat-description': 'Main Heating Description', 'hotwater-description': 'Hot Water Description', "transaction-type": "rental", "lighting-description": "Good Lighting Efficiency", + "energy-consumption-current": "50", + "co2-emissions-current": "123", + "mechanical-ventilation": "natural", + 'photo-supply': 0, + "solar-water-heating-flag": "N", + "wind-turbine-count": 0, + "extension-count": 0, + "heat-loss-corridor": "no corridor", + "unheated-corridor-length": 0, + "mains-gas-flag": "Y", + "floor-height": 2.5, + "total-floor-area": 100 }, { 'inspection-date': '2023-05-01', 'some-other-key': 'some-other-value', @@ -50,6 +86,18 @@ mock_epc_response_dupe = { 'hotwater-description': 'Hot Water Description', "transaction-type": "rental", "lighting-description": "Good Lighting Efficiency", + "energy-consumption-current": "50", + "co2-emissions-current": "123", + "mechanical-ventilation": "natural", + 'photo-supply': 0, + "solar-water-heating-flag": "N", + "wind-turbine-count": 0, + "extension-count": 0, + "heat-loss-corridor": "no corridor", + "unheated-corridor-length": 0, + "mains-gas-flag": "Y", + "floor-height": 2.5, + "total-floor-area": 100 }, { 'inspection-date': '2023-06-01', 'some-other-key': 'duplicate-date', @@ -58,6 +106,18 @@ mock_epc_response_dupe = { 'mainheat-description': 'Main Heating Description', 'hotwater-description': 'Hot Water Description', "transaction-type": "rental", "lighting-description": "Good Lighting Efficiency", + "energy-consumption-current": "50", + "co2-emissions-current": "123", + "mechanical-ventilation": "natural", + 'photo-supply': 0, + "solar-water-heating-flag": "N", + "wind-turbine-count": 0, + "extension-count": 0, + "heat-loss-corridor": "no corridor", + "unheated-corridor-length": 0, + "mains-gas-flag": "Y", + "floor-height": 2.5, + "total-floor-area": 100 } ] } @@ -66,11 +126,11 @@ mock_epc_response_dupe = { class TestProperty: @pytest.fixture(autouse=True) def property_instance(self, mock_epc_client, mock_open_uprn_client, mock_cleaner): - return Property("AB12CD", "Test Address", epc_client=mock_epc_client) + return Property(1, "AB12CD", "Test Address", epc_client=mock_epc_client) @pytest.fixture(autouse=True) def property_instance_dupe_data(self, mock_epc_client_dupe_data): - return Property("AB12CD", "Test Address", epc_client=mock_epc_client_dupe_data) + return Property(2, "AB12CD", "Test Address", epc_client=mock_epc_client_dupe_data) @pytest.fixture def mock_epc_client(self): @@ -99,15 +159,26 @@ class TestProperty: @pytest.fixture def mock_cleaner(self): - 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"}, - {"lighting-description": "Good Lighting Efficiency"}, - {"low-energy-lighting": 0} - ])) + lighting_averages = [ + {'lighting-description': 'good lighting efficiency', 'low-energy-lighting': 99.26666666666667}, + {'lighting-description': 'excellent lighting efficiency', 'low-energy-lighting': 100.0}, + {'lighting-description': 'below average lighting efficiency', 'low-energy-lighting': 0.0} + ] + + cleaner_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"}, + {"lighting-description": "Good Lighting Efficiency"}, + {"low-energy-lighting": 0} + ], + lighting_averages=lighting_averages + ) + + mock_cleaner = Mock(spec=cleaner_spec) mock_cleaner.cleaned = { "roof-description": [{"original_description": "Roof Description"}], "walls-description": [{"original_description": "Walls Description"}], @@ -119,14 +190,14 @@ class TestProperty: return mock_cleaner def test_init(self, mock_epc_client): - inst1 = Property("AB12CD", "Test Address", epc_client=mock_epc_client) + inst1 = Property(0, "AB12CD", "Test Address", epc_client=mock_epc_client) # Should be mocked auth token assert inst1.epc_client.auth_token == "mocked_auth_token" - inst2 = Property("AB12CD", "Test Address") + inst2 = Property(3, "AB12CD", "Test Address") assert inst2.epc_client.auth_token - inst3 = Property("AB12CD", "Test Address", data={"some": "data"}) + inst3 = Property(4, "AB12CD", "Test Address", data={"some": "data"}) assert inst3.data == {"some": "data"} data = inst3.search_address_epc() @@ -143,29 +214,9 @@ class TestProperty: with pytest.raises(Exception, match="More than one result found for this address - investigate me"): property_instance_dupe_data.search_address_epc() - def test_get_coordinates(self, property_instance, mock_open_uprn_client): - # 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, mock_open_uprn_client): - # 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, mock_cleaner, mock_epc_client): property_instance.search_address_epc() - property_instance.get_components(mock_cleaner) + property_instance.get_components(mock_cleaner.cleaned) # Verify that the components are set correctly assert property_instance.roof == {"original_description": "Roof Description"} @@ -180,7 +231,7 @@ class TestProperty: # 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) + property_instance.get_components(mock_cleaner.cleaned) def test_get_components_no_data(self, property_instance, mock_cleaner): # Modify the mock cleaner to have no attributes for a specific description @@ -190,7 +241,7 @@ class TestProperty: # Verify that ValueError is raised when no attributes are found with pytest.raises(ValueError, match="Property does not contain data"): - property_instance.get_components(mock_cleaner) + property_instance.get_components(mock_cleaner.cleaned) def test_get_components_no_attributes(self, property_instance, mock_cleaner): # Modify the mock cleaner to have no attributes for a specific description @@ -201,12 +252,12 @@ class TestProperty: # 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) + property_instance.get_components(mock_cleaner.cleaned) def test_get_components_multiple_attributes(self, property_instance, mock_cleaner): # This shouldn't happen - it would mean a cleaning error property_instance.search_address_epc() - mock_cleaner.cleaned = { + cleaned = { "roof-description": [ {"original_description": "Roof Description"}, {"original_description": "Roof Description"} @@ -215,4 +266,4 @@ class TestProperty: # 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) + property_instance.get_components(cleaned) diff --git a/model_data/epc_attributes/LightingAttributes.py b/model_data/epc_attributes/LightingAttributes.py index 6315db7f..94285170 100644 --- a/model_data/epc_attributes/LightingAttributes.py +++ b/model_data/epc_attributes/LightingAttributes.py @@ -13,14 +13,28 @@ class LightingAttributes: def __init__(self, description, averages): self.description: str = clean_description(description.lower()) - translation = self.WELSH_TEXT.get(self.description) - if translation: - self.nodata = False - self.description = translation + self.welsh_translation_search() self.description = correct_spelling(self.description) self.averages = averages + def welsh_translation_search(self): + """ + For welsh text describing the percentage of low energy lighting, we match the regular + expression and perform the translation + """ + lel_match = re.search(r"goleuadau ynni-isel mewn (\d+)%? ogçör mannau gosod", self.description) + + if lel_match: + # Perform the actual translation + percentage = lel_match.group(1) + self.description = f"low energy lighting in {percentage}% of fixed outlets" + else: + translation = self.WELSH_TEXT.get(self.description) + if translation: + self.nodata = False + self.description = translation + def process(self): description = self.description diff --git a/pytest.ini b/pytest.ini index b2fa207a..401ecc0f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] pythonpath = . addopts = --cov-report term-missing --cov=model_data --cov=recommendations -testpaths = model_data/tests recommendations/tests +testpaths = model_data/tests recommendations/tests backend/tests