diff --git a/src/open_irceline/api.py b/src/open_irceline/api.py index 262b199..69bdb67 100644 --- a/src/open_irceline/api.py +++ b/src/open_irceline/api.py @@ -1,6 +1,6 @@ import asyncio import socket -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date from typing import Tuple, List, Dict import aiohttp @@ -40,29 +40,35 @@ class IrcelineClient: return round(result[0]), round(result[1]) async def get_rio_value(self, - timestamp: datetime, + timestamp: datetime | date, features: List[RioFeature], position: Tuple[float, float] ) -> Dict[RioFeature, FeatureValue]: """ - Call the WFS API to get the interpolated level of RioFeature + Call the WFS API to get the interpolated level of RioFeature. Raises exception upon API error :param timestamp: datetime for which to get the data for :param features: list of RioFeature to fetch from the API :param position: decimal degrees pair of coordinates :return: dict with the response (key is RioFeature, value is FeatureValue with actual value and timestamp) """ - # Remove one hour from timestamp to handle case where the hour just passed but the data is not yet there + # Remove one hour/day from timestamp to handle case where the hour just passed but the data is not yet there # (e.g. 5.01 PM, but the most recent data is for 4.00 PM) - timestamp = timestamp.replace(microsecond=0, second=0, minute=0) - timedelta(hours=1) + if isinstance(timestamp, datetime): + timestamp = timestamp.replace(microsecond=0, second=0, minute=0) - timedelta(hours=1) + elif isinstance(timestamp, date): + timestamp = timestamp - timedelta(days=1) + coord = self.epsg_transform(position) querystring = {"service": "WFS", "version": "1.3.0", "request": "GetFeature", "outputFormat": "application/json", "typeName": ",".join(features), - "cql_filter": f"timestamp>='{timestamp.isoformat()}' AND " - f"INTERSECTS(the_geom, POINT ({coord[0]} {coord[1]}))"} + "cql_filter": + f"{'timestamp' if isinstance(timestamp, datetime) else 'date'}>='{timestamp.isoformat()}'" + f" AND " + f"INTERSECTS(the_geom, POINT ({coord[0]} {coord[1]}))"} r: ClientResponse = await self._api_wrapper(rio_wfs_base_url, querystring) return self.format_result('rio', await r.json(), features) @@ -82,14 +88,21 @@ class IrcelineClient: features_api = data.get('features', []) result = dict() for f in features_api: + props = f.get('properties', {}) if (f.get('id', None) is None or - f.get('properties', {}).get('value', None) is None or - f.get('properties', {}).get('timestamp', None) is None): + props.get('value', None) is None): + continue + if (props.get('timestamp', None) is None and + props.get('date', None) is None): continue try: - timestamp = datetime.fromisoformat(f.get('properties', {}).get('timestamp')) - value = float(f.get('properties', {}).get('value')) + if 'timestamp' in props.keys(): + timestamp = datetime.fromisoformat(props.get('timestamp')) + else: + # Cut last character as the date is written '2024-06-15Z' which is not ISO compliant + timestamp = date.fromisoformat(props.get('date')[:-1]) + value = float(props.get('value')) except (TypeError, ValueError): continue diff --git a/src/open_irceline/data.py b/src/open_irceline/data.py index 93533a2..4dd36d5 100644 --- a/src/open_irceline/data.py +++ b/src/open_irceline/data.py @@ -1,6 +1,6 @@ from enum import StrEnum from typing import TypedDict -from datetime import datetime +from datetime import datetime, date class RioFeature(StrEnum): @@ -23,5 +23,5 @@ class RioFeature(StrEnum): class FeatureValue(TypedDict): - timestamp: datetime | None + timestamp: datetime | date value: int | float | None diff --git a/tests/fixtures/rio_wfs_dmean.json b/tests/fixtures/rio_wfs_dmean.json new file mode 100644 index 0000000..ab938c7 --- /dev/null +++ b/tests/fixtures/rio_wfs_dmean.json @@ -0,0 +1,243 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": "bc_dmean.fid-280be381_1901d555556_494e", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 182000, + 128000 + ], + [ + 182000, + 132000 + ], + [ + 186000, + 132000 + ], + [ + 186000, + 128000 + ], + [ + 182000, + 128000 + ] + ] + ] + }, + "geometry_name": "the_geom", + "properties": { + "id": 1102, + "date": "2024-06-14Z", + "value": 0.2, + "network": "Wallonia" + } + }, + { + "type": "Feature", + "id": "bc_dmean.fid-280be381_1901d555556_494f", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 182000, + 128000 + ], + [ + 182000, + 132000 + ], + [ + 186000, + 132000 + ], + [ + 186000, + 128000 + ], + [ + 182000, + 128000 + ] + ] + ] + }, + "geometry_name": "the_geom", + "properties": { + "id": 1102, + "date": "2024-06-15Z", + "value": 0.1, + "network": "Wallonia" + } + }, + { + "type": "Feature", + "id": "pm10_dmean.fid-280be381_1901d555556_4950", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 182000, + 128000 + ], + [ + 182000, + 132000 + ], + [ + 186000, + 132000 + ], + [ + 186000, + 128000 + ], + [ + 182000, + 128000 + ] + ] + ] + }, + "geometry_name": "the_geom", + "properties": { + "id": 1102, + "date": "2024-06-14Z", + "value": 4.8, + "network": "Wallonia" + } + }, + { + "type": "Feature", + "id": "pm10_dmean.fid-280be381_1901d555556_4951", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 182000, + 128000 + ], + [ + 182000, + 132000 + ], + [ + 186000, + 132000 + ], + [ + 186000, + 128000 + ], + [ + 182000, + 128000 + ] + ] + ] + }, + "geometry_name": "the_geom", + "properties": { + "id": 1102, + "date": "2024-06-15Z", + "value": 5.9, + "network": "Wallonia" + } + }, + { + "type": "Feature", + "id": "pm25_dmean.fid-280be381_1901d555556_4952", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 182000, + 128000 + ], + [ + 182000, + 132000 + ], + [ + 186000, + 132000 + ], + [ + 186000, + 128000 + ], + [ + 182000, + 128000 + ] + ] + ] + }, + "geometry_name": "the_geom", + "properties": { + "id": 1102, + "date": "2024-06-14Z", + "value": 1.3, + "network": "Wallonia" + } + }, + { + "type": "Feature", + "id": "pm25_dmean.fid-280be381_1901d555556_4953", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 182000, + 128000 + ], + [ + 182000, + 132000 + ], + [ + 186000, + 132000 + ], + [ + 186000, + 128000 + ], + [ + 182000, + 128000 + ] + ] + ] + }, + "geometry_name": "the_geom", + "properties": { + "id": 1102, + "date": "2024-06-15Z", + "value": 1.1, + "network": "Wallonia" + } + } + ], + "totalFeatures": 6, + "numberMatched": 6, + "numberReturned": 6, + "timeStamp": "2024-06-15T19:30:09.581Z", + "crs": { + "type": "name", + "properties": { + "name": "urn:ogc:def:crs:EPSG::31370" + } + } +} \ No newline at end of file diff --git a/tests/test_api.py b/tests/test_api.py index f5bb086..1e10c02 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,12 +1,15 @@ +from datetime import datetime, date + +from aiohttp import ClientSession +from freezegun import freeze_time + from src.open_irceline.api import IrcelineClient from src.open_irceline.data import RioFeature, FeatureValue from tests.conftest import get_api_data -from datetime import datetime -from freezegun import freeze_time @freeze_time(datetime.fromisoformat("2024-06-15T16:55:03.419Z")) -async def test_format_result(): +async def test_format_result_hmean(): data = get_api_data('rio_wfs.json') result = IrcelineClient.format_result('rio', data, [RioFeature.NO2_HMEAN, RioFeature.O3_HMEAN]) @@ -22,3 +25,28 @@ async def test_format_result(): } assert result == expected + + +@freeze_time(datetime.fromisoformat("2024-06-15T19:30:09.581Z")) + +async def test_format_result_dmean(): + data = get_api_data('rio_wfs_dmean.json') + result = IrcelineClient.format_result('rio', data, + [RioFeature.BC_DMEAN, RioFeature.PM10_DMEAN, RioFeature.PM25_DMEAN]) + + expected = { + str(RioFeature.BC_DMEAN): FeatureValue(timestamp=date(2024, 6, 15), value=0.1), + str(RioFeature.PM10_DMEAN): FeatureValue(timestamp=date(2024, 6, 15), value=5.9), + str(RioFeature.PM25_DMEAN): FeatureValue(timestamp=date(2024, 6, 15), value=1.1), + } + + assert result == expected + +async def test_run(): + async with ClientSession() as session: + api = IrcelineClient(session) + r = await api.get_rio_value( + date.today(), + [RioFeature.BC_DMEAN, RioFeature.PM10_DMEAN, RioFeature.PM25_DMEAN], + (4.8637, 50.4656)) + print(r)