Add tests for BelAQI and fix bugs

This commit is contained in:
Jules 2024-06-23 14:42:10 +02:00
parent 95fa6dde65
commit ee5304ec8b
Signed by: jdejaegh
GPG key ID: 99D6D184CA66933A
7 changed files with 407 additions and 21 deletions

View file

@ -40,7 +40,6 @@ class IrcelineBaseClient(ABC):
:param querystring: dict to build the query string
:return: response from the client
"""
if headers is None:
headers = dict()
if 'User-Agent' not in headers:
@ -228,7 +227,6 @@ class IrcelineForecastClient(IrcelineBaseClient):
except IrcelineApiError:
# retry for the day before
yesterday = timestamp - timedelta(days=1)
print('here')
url = f"{forecast_base_url}/BE_{feature}_{yesterday.strftime('%Y%m%d')}_d{d}.csv"
try:
r: ClientResponse = await self._api_cached_wrapper(url)

View file

@ -23,6 +23,8 @@ def belaqi_index(pm10: float, pm25: float, o3: float, no2: float) -> BelAqiIndex
:param no2: NO2 max 1-hourly mean per day (or latest hourly mean for real-time) (µg/)
:return: BelAQI index from 1 to 10 (Value of BelAqiIndex enum)
"""
if pm10 is None or pm25 is None or o3 is None or no2 is None:
raise ValueError("All the components should be valued (at lest one is None here)")
if pm10 < 0 or pm25 < 0 or o3 < 0 or no2 < 0:
raise ValueError("All the components should have a positive value")
@ -62,6 +64,7 @@ async def belaqi_index_actual(rio_client: IrcelineRioClient, position: Tuple[flo
timestamp: datetime | None = None) -> BelAqiIndex:
"""
Get current BelAQI index value for the given position using the rio_client
Raise ValueError if one or more components are not available
:param rio_client: client for the RIO WFS service
:param position: position for which to get the data
:param timestamp: desired time for the data (now if None)
@ -71,21 +74,27 @@ async def belaqi_index_actual(rio_client: IrcelineRioClient, position: Tuple[flo
timestamp = datetime.utcnow()
components = await rio_client.get_data(
timestamp=timestamp,
features=[RioFeature.PM10_24HMEAN, RioFeature.PM25_24HMEAN, RioFeature.O3_HMEAN, RioFeature.NO2_HMEAN],
features=[RioFeature.PM10_24HMEAN,
RioFeature.PM25_24HMEAN,
RioFeature.O3_HMEAN,
RioFeature.NO2_HMEAN],
position=position
)
return belaqi_index(components[RioFeature.PM10_24HMEAN].get('value', -1),
components[RioFeature.PM25_24HMEAN].get('value', -1),
components[RioFeature.O3_HMEAN].get('value', -1),
components[RioFeature.NO2_HMEAN].get('value', -1))
return belaqi_index(
components.get(RioFeature.PM10_24HMEAN, {}).get('value', -1),
components.get(RioFeature.PM25_24HMEAN, {}).get('value', -1),
components.get(RioFeature.O3_HMEAN, {}).get('value', -1),
components.get(RioFeature.NO2_HMEAN, {}).get('value', -1)
)
async def belaqi_index_forecast(forecast_client: IrcelineForecastClient, position: Tuple[float, float],
timestamp: date | None = None) -> Dict[date, BelAqiIndex]:
timestamp: date | None = None) -> Dict[date, BelAqiIndex | None]:
"""
Get forecasted BelAQI index value for the given position using the forecast_client.
Data is downloaded for the given day and the four next days
Value is None for the date if one or more components cannot be downloaded
:param forecast_client: client for the forecast data
:param position: position for which to get the data
:param timestamp: day at which the forecast are issued
@ -95,7 +104,9 @@ async def belaqi_index_forecast(forecast_client: IrcelineForecastClient, positio
timestamp = date.today()
components = await forecast_client.get_data(
timestamp=timestamp,
features=[ForecastFeature.PM10_DMEAN, ForecastFeature.PM25_DMEAN, ForecastFeature.O3_MAXHMEAN,
features=[ForecastFeature.PM10_DMEAN,
ForecastFeature.PM25_DMEAN,
ForecastFeature.O3_MAXHMEAN,
ForecastFeature.NO2_MAXHMEAN],
position=position
)
@ -103,9 +114,14 @@ async def belaqi_index_forecast(forecast_client: IrcelineForecastClient, positio
result = dict()
for _, day in components.keys():
result[day] = belaqi_index(components[(ForecastFeature.PM10_DMEAN, day)].get('value', -1),
components[(ForecastFeature.PM25_DMEAN, day)].get('value', -1),
components[(ForecastFeature.O3_MAXHMEAN, day)].get('value', -1),
components[(ForecastFeature.NO2_MAXHMEAN, day)].get('value', -1))
try:
result[day] = belaqi_index(
components.get((ForecastFeature.PM10_DMEAN, day), {}).get('value', -1),
components.get((ForecastFeature.PM25_DMEAN, day), {}).get('value', -1),
components.get((ForecastFeature.O3_MAXHMEAN, day), {}).get('value', -1),
components.get((ForecastFeature.NO2_MAXHMEAN, day), {}).get('value', -1)
)
except ValueError:
result[day] = None
return result

View file

@ -15,7 +15,7 @@ class SizedDict(OrderedDict):
super().__setitem__(key, value)
self.move_to_end(key)
if len(self) > self._size:
print('drop', self.popitem(False)[0])
self.popitem(False)
def __getitem__(self, key):
self.move_to_end(key)

View file

@ -4,7 +4,6 @@ from unittest.mock import Mock, AsyncMock
import aiohttp
def get_api_data(fixture: str, plain=False) -> str | dict:
with open(f'tests/fixtures/{fixture}', 'r') as file:
if plain:
@ -12,7 +11,7 @@ def get_api_data(fixture: str, plain=False) -> str | dict:
return json.load(file)
def get_mock_session_json(json_file=None, text_file=None):
def get_mock_session(json_file=None, text_file=None):
# Create the mock response
mock_response = Mock()
if json_file is not None:

319
tests/fixtures/rio_wfs_for_belaqi.json vendored Normal file
View file

@ -0,0 +1,319 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "pm10_24hmean.fid--d1ce43_19045107e20_1893",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
182000,
128000
],
[
182000,
132000
],
[
186000,
132000
],
[
186000,
128000
],
[
182000,
128000
]
]
]
},
"geometry_name": "the_geom",
"properties": {
"id": 1102,
"timestamp": "2024-06-23T11:00:00Z",
"value": 7.3,
"network": "Wallonia"
}
},
{
"type": "Feature",
"id": "pm10_24hmean.fid--d1ce43_19045107e20_1894",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
182000,
128000
],
[
182000,
132000
],
[
186000,
132000
],
[
186000,
128000
],
[
182000,
128000
]
]
]
},
"geometry_name": "the_geom",
"properties": {
"id": 1102,
"timestamp": "2024-06-23T12:00:00Z",
"value": 7.3,
"network": "Wallonia"
}
},
{
"type": "Feature",
"id": "pm25_24hmean.fid--d1ce43_19045107e20_1895",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
182000,
128000
],
[
182000,
132000
],
[
186000,
132000
],
[
186000,
128000
],
[
182000,
128000
]
]
]
},
"geometry_name": "the_geom",
"properties": {
"id": 1102,
"timestamp": "2024-06-23T11:00:00Z",
"value": 3.3,
"network": "Wallonia"
}
},
{
"type": "Feature",
"id": "pm25_24hmean.fid--d1ce43_19045107e20_1896",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
182000,
128000
],
[
182000,
132000
],
[
186000,
132000
],
[
186000,
128000
],
[
182000,
128000
]
]
]
},
"geometry_name": "the_geom",
"properties": {
"id": 1102,
"timestamp": "2024-06-23T12:00:00Z",
"value": 3.2,
"network": "Wallonia"
}
},
{
"type": "Feature",
"id": "o3_hmean.fid--d1ce43_19045107e20_1897",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
182000,
128000
],
[
182000,
132000
],
[
186000,
132000
],
[
186000,
128000
],
[
182000,
128000
]
]
]
},
"geometry_name": "the_geom",
"properties": {
"id": 1102,
"timestamp": "2024-06-23T11:00:00Z",
"value": 69,
"network": "Wallonia"
}
},
{
"type": "Feature",
"id": "o3_hmean.fid--d1ce43_19045107e20_1898",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
182000,
128000
],
[
182000,
132000
],
[
186000,
132000
],
[
186000,
128000
],
[
182000,
128000
]
]
]
},
"geometry_name": "the_geom",
"properties": {
"id": 1102,
"timestamp": "2024-06-23T12:00:00Z",
"value": 73,
"network": "Wallonia"
}
},
{
"type": "Feature",
"id": "no2_hmean.fid--d1ce43_19045107e20_1899",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
182000,
128000
],
[
182000,
132000
],
[
186000,
132000
],
[
186000,
128000
],
[
182000,
128000
]
]
]
},
"geometry_name": "the_geom",
"properties": {
"id": 1102,
"timestamp": "2024-06-23T11:00:00Z",
"value": 4,
"network": "Wallonia"
}
},
{
"type": "Feature",
"id": "no2_hmean.fid--d1ce43_19045107e20_189a",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
182000,
128000
],
[
182000,
132000
],
[
186000,
132000
],
[
186000,
128000
],
[
182000,
128000
]
]
]
},
"geometry_name": "the_geom",
"properties": {
"id": 1102,
"timestamp": "2024-06-23T12:00:00Z",
"value": 3,
"network": "Wallonia"
}
}
],
"totalFeatures": 8,
"numberMatched": 8,
"numberReturned": 8,
"timeStamp": "2024-06-23T12:31:50.858Z",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:EPSG::31370"
}
}
}

View file

@ -6,7 +6,7 @@ from src.open_irceline import rio_wfs_base_url, user_agent
from src.open_irceline.api import IrcelineRioClient
from src.open_irceline.data import RioFeature, FeatureValue
from src.open_irceline.utils import epsg_transform
from tests.conftest import get_api_data, get_mock_session_json
from tests.conftest import get_api_data, get_mock_session
@freeze_time(datetime.fromisoformat("2024-06-15T16:55:03.419Z"))
@ -93,7 +93,7 @@ def test_parse_capabilities_with_error():
async def test_api_rio():
pos = (50.4657, 4.8647)
x, y = epsg_transform(pos)
session = get_mock_session_json('rio_wfs.json')
session = get_mock_session('rio_wfs.json')
client = IrcelineRioClient(session)
@ -117,7 +117,7 @@ async def test_api_rio():
async def test_api_rio_get_capabilities():
session = get_mock_session_json(text_file='capabilities.xml')
session = get_mock_session(text_file='capabilities.xml')
client = IrcelineRioClient(session)
_ = await client.get_rio_capabilities()

View file

@ -1,7 +1,12 @@
from datetime import date, timedelta, datetime
from random import randint, seed
from freezegun import freeze_time
import pytest
from src.open_irceline.belaqi import belaqi_index
from src.open_irceline.api import IrcelineForecastClient, IrcelineRioClient
from src.open_irceline.belaqi import belaqi_index, belaqi_index_forecast, belaqi_index_actual
from src.open_irceline.data import BelAqiIndex
from tests.conftest import get_mock_session_many_csv, get_mock_session
def test_belaqi_index():
@ -140,4 +145,53 @@ def test_belaqi_value_error():
with pytest.raises(ValueError):
belaqi_index(1, 0, 12, -8888)
# TODO add more test for the other BelAQI functions
@freeze_time(datetime.fromisoformat("2024-06-19T19:30:09.581Z"))
async def test_belaqi_index_forecast():
session = get_mock_session_many_csv()
client = IrcelineForecastClient(session)
pos = (50.55, 4.85)
result = await belaqi_index_forecast(client, pos)
expected_days = {date(2024, 6, 19) + timedelta(days=i) for i in range(5)}
assert set(result.keys()) == expected_days
for v in result.values():
assert v == BelAqiIndex.GOOD
async def test_belaqi_index_forecast_missing_day():
session = get_mock_session_many_csv()
client = IrcelineForecastClient(session)
pos = (50.55, 4.85)
result = await belaqi_index_forecast(client, pos, date(2024, 6, 21))
expected_days = {date(2024, 6, 21) + timedelta(days=i) for i in range(5)}
print(result)
assert set(result.keys()) == expected_days
for v in result.values():
assert v is None
@freeze_time(datetime.fromisoformat("2024-06-23T12:30:09.581Z"))
async def test_belaqi_index_actual():
session = get_mock_session(json_file='rio_wfs_for_belaqi.json')
client = IrcelineRioClient(session)
pos = (50.55, 4.85)
result = await belaqi_index_actual(client, pos)
print(result)
assert result == BelAqiIndex.FAIRLY_GOOD
@freeze_time(datetime.fromisoformat("2024-06-23T12:30:09.581Z"))
async def test_belaqi_index_actual_missing_value():
session = get_mock_session(json_file='rio_wfs.json')
client = IrcelineRioClient(session)
pos = (50.55, 4.85)
with pytest.raises(ValueError):
_ = await belaqi_index_actual(client, pos)