mirror of
https://github.com/jdejaegh/python-irceline.git
synced 2025-06-26 19:35:40 +02:00
Add tests for BelAQI and fix bugs
This commit is contained in:
parent
95fa6dde65
commit
ee5304ec8b
7 changed files with 407 additions and 21 deletions
|
@ -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)
|
||||
|
|
|
@ -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/m³)
|
||||
: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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
319
tests/fixtures/rio_wfs_for_belaqi.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue