Coverage for tests/test_data.py: 99%
417 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-03 20:32 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-03 20:32 +0000
1# SPDX-FileCopyrightText: 2021 Forschungszentrum Jülich GmbH
2# SPDX-License-Identifier: MIT
4import pytest
5import json
6import pandas as pd
7import csv
8from sqlalchemy import insert, MetaData
9from fastapi import Request
10from toardb.toardb import app
11from toardb.data.models import Data
12#from toardb.utils.timeseries_filtering import select_provider, merge_sequences, process_eea_data, format_eea_time_series, format_time_series
13from toardb.timeseries.models import Timeseries, timeseries_timeseries_roles_table
14from toardb.timeseries.models_programme import TimeseriesProgramme
15from toardb.timeseries.models_role import TimeseriesRole
16from toardb.stationmeta.models import StationmetaCore, StationmetaGlobal
17from toardb.stationmeta.schemas import get_geom_from_coordinates, Coordinates
18from toardb.variables.models import Variable
19from toardb.contacts.models import Person, Organisation, Contact
20from toardb.auth_user.models import AuthUser
21from toardb.test_base import (
22 client,
23 get_test_db,
24 override_dependency,
25 create_test_database,
26 url,
27 get_test_engine,
28 test_db_session as db,
29)
30# for mocking datetime.now(timezone.utc)
31import datetime
32from unittest.mock import patch
34# only datetime.now needs to be overridden because otherwise daterange-arguments would be provided as MagicMock-objects!
35class FixedDatetime(datetime.datetime):
36 @classmethod
37 def now(cls, tz=None):
38 return datetime.datetime(2023, 7, 28, 12, 0, 0)
41class TestApps:
42 def setup(self):
43 self.application_url = "/data/"
45 """Set up all the data before each test
46 If you want the setup only once (per test module),
47 the scope argument is not working in the expected way, as discussed here:
48 https://stackoverflow.com/questions/45817153/py-test-fixture-use-function-fixture-in-scope-fixture
49 """
50 @pytest.fixture(autouse=True)
51 def setup_db_data(self, db):
52 _db_conn = get_test_engine()
53 fake_conn = _db_conn.raw_connection()
54 fake_cur = fake_conn.cursor()
55 # id_seq will not be reset automatically between tests!
56 fake_cur.execute("ALTER SEQUENCE auth_user_id_seq RESTART WITH 1")
57 fake_conn.commit()
58 fake_cur.execute("ALTER SEQUENCE variables_id_seq RESTART WITH 1")
59 fake_conn.commit()
60 fake_cur.execute("ALTER SEQUENCE stationmeta_core_id_seq RESTART WITH 1")
61 fake_conn.commit()
62 fake_cur.execute("ALTER SEQUENCE stationmeta_global_id_seq RESTART WITH 1")
63 fake_conn.commit()
64 fake_cur.execute("ALTER SEQUENCE stationmeta_roles_id_seq RESTART WITH 1")
65 fake_conn.commit()
66 fake_cur.execute("ALTER SEQUENCE stationmeta_annotations_id_seq RESTART WITH 1")
67 fake_conn.commit()
68 fake_cur.execute("ALTER SEQUENCE stationmeta_aux_doc_id_seq RESTART WITH 1")
69 fake_conn.commit()
70 fake_cur.execute("ALTER SEQUENCE stationmeta_aux_image_id_seq RESTART WITH 1")
71 fake_conn.commit()
72 fake_cur.execute("ALTER SEQUENCE stationmeta_aux_url_id_seq RESTART WITH 1")
73 fake_conn.commit()
74 fake_cur.execute("ALTER SEQUENCE persons_id_seq RESTART WITH 1")
75 fake_conn.commit()
76 fake_cur.execute("ALTER SEQUENCE organisations_id_seq RESTART WITH 1")
77 fake_conn.commit()
78 fake_cur.execute("ALTER SEQUENCE contacts_id_seq RESTART WITH 1")
79 fake_conn.commit()
80 fake_cur.execute("ALTER SEQUENCE timeseries_annotations_id_seq RESTART WITH 1")
81 fake_conn.commit()
82 fake_cur.execute("ALTER SEQUENCE timeseries_roles_id_seq RESTART WITH 3")
83 fake_conn.commit()
84 fake_cur.execute("ALTER SEQUENCE timeseries_programmes_id_seq RESTART WITH 1")
85 fake_conn.commit()
86 fake_cur.execute("ALTER SEQUENCE timeseries_id_seq RESTART WITH 1")
87 fake_conn.commit()
88 infilename = "tests/fixtures/auth_user/auth.json"
89 with open(infilename) as f:
90 metajson=json.load(f)
91 for entry in metajson:
92 new_auth_user = AuthUser(**entry)
93 db.add(new_auth_user)
94 db.commit()
95 db.refresh(new_auth_user)
96 infilename = "tests/fixtures/contacts/persons.json"
97 with open(infilename) as f:
98 metajson=json.load(f)
99 for entry in metajson:
100 new_person = Person(**entry)
101 db.add(new_person)
102 db.commit()
103 db.refresh(new_person)
104 infilename = "tests/fixtures/contacts/organisations.json"
105 with open(infilename) as f:
106 metajson=json.load(f)
107 for entry in metajson:
108 new_organisation = Organisation(**entry)
109 db.add(new_organisation)
110 db.commit()
111 db.refresh(new_organisation)
112 infilename = "tests/fixtures/contacts/contacts.json"
113 with open(infilename) as f:
114 metajson=json.load(f)
115 for entry in metajson:
116 new_contact = Contact(**entry)
117 db.add(new_contact)
118 db.commit()
119 db.refresh(new_contact)
120 infilename = "tests/fixtures/variables/variables.json"
121 with open(infilename) as f:
122 metajson=json.load(f)
123 for entry in metajson:
124 new_variable = Variable(**entry)
125 db.add(new_variable)
126 db.commit()
127 db.refresh(new_variable)
128 infilename = "tests/fixtures/stationmeta/stationmeta_core.json"
129 with open(infilename) as f:
130 metajson=json.load(f)
131 for entry in metajson:
132 new_stationmeta_core = StationmetaCore(**entry)
133 # there's a mismatch with coordinates --> how to automatically switch back and forth?!
134 new_stationmeta_core.coordinates = get_geom_from_coordinates(Coordinates(**new_stationmeta_core.coordinates))
135 db.add(new_stationmeta_core)
136 db.commit()
137 db.refresh(new_stationmeta_core)
138 infilename = "tests/fixtures/stationmeta/stationmeta_global.json"
139 with open(infilename) as f:
140 metajson=json.load(f)
141 for entry in metajson:
142 new_stationmeta_global = StationmetaGlobal(**entry)
143 db.add(new_stationmeta_global)
144 db.commit()
145 db.refresh(new_stationmeta_global)
146 infilename = "tests/fixtures/timeseries/timeseries_programmes.json"
147 with open(infilename) as f:
148 metajson=json.load(f)
149 for entry in metajson:
150 new_timeseries_programme = TimeseriesProgramme(**entry)
151 db.add(new_timeseries_programme)
152 db.commit()
153 db.refresh(new_timeseries_programme)
154 infilename = "tests/fixtures/timeseries/timeseries.json"
155 with open(infilename) as f:
156 metajson=json.load(f)
157 for entry in metajson:
158 new_timeseries = Timeseries(**entry)
159 db.add(new_timeseries)
160 db.commit()
161 db.refresh(new_timeseries)
162 infilename = "tests/fixtures/timeseries/timeseries_roles.json"
163 with open(infilename) as f:
164 metajson=json.load(f)
165 for entry in metajson:
166 new_timeseries_role = TimeseriesRole(**entry)
167 db.add(new_timeseries_role)
168 db.commit()
169 db.refresh(new_timeseries_role)
170 infilename = "tests/fixtures/timeseries/timeseries_timeseries_roles.json"
171 with open(infilename) as f:
172 metajson=json.load(f)
173 for entry in metajson:
174 db.execute(insert(timeseries_timeseries_roles_table).values(timeseries_id=entry["timeseries_id"], role_id=entry["role_id"]))
175 db.execute("COMMIT")
176 infilename = "tests/fixtures/data/data.json"
177 with open(infilename) as f:
178 metajson=json.load(f)
179 for entry in metajson:
180 new_data = Data (**entry)
181 db.add(new_data)
182 db.commit()
183 db.refresh(new_data)
184 infilename = "tests/fixtures/data/staging_data.json"
185 with open(infilename) as f:
186 metajson=json.load(f)
187 for entry in metajson:
188 new_data = Data (**entry)
189 fake_cur.execute(("INSERT INTO staging.data(datetime, value, flags, timeseries_id, version) "
190 f" VALUES('{new_data.datetime}', {new_data.value}, {new_data.flags}, "
191 f"{new_data.timeseries_id}, '{new_data.version}');"))
192 fake_conn.commit()
193 with open("tests/fixtures/data/yearly_coverage.csv", newline="", encoding="utf-8") as f:
194 metadata = MetaData()
195 metadata.reflect(bind=_db_conn)
196 table = metadata.tables["yearly_coverage"]
197 reader = csv.DictReader(f)
198 rows = list(reader)
199 db.execute(insert(table), rows)
200 db.commit()
203 def test_get_special(self, client, db):
204 fixed_time = datetime.datetime(2023, 7, 28, 12, 0, 0)
205 with patch('toardb.timeseries.crud.dt.datetime') as mock_datetime:
206 mock_datetime.now.return_value = fixed_time
207 response = client.get("/data/timeseries/1")
208 expected_status_code = 200
209 assert response.status_code == expected_status_code
210 expected_resp = {'metadata': {'id': 1, 'label': 'CMA', 'order': 1, 'sampling_frequency': 'hourly',
211 'aggregation': 'mean',
212 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/',
213 'doi': '',
214 'data_start_date': '2003-09-07T15:30:00+00:00', 'data_end_date': '2016-12-31T14:30:00+00:00', 'coverage': -1.0,
215 'data_origin': 'instrument', 'data_origin_type': 'measurement', 'provider_version': 'N/A',
216 'sampling_height': 7.0, 'additional_metadata': {'original_units': 'ppb'},
217 'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9},
218 'coordinate_validation_status': 'not checked', 'country': 'China', 'state': 'Beijing Shi', 'type': 'unknown',
219 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai',
220 'additional_metadata': {'add_type': 'nature reservation'},
221 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'changelog': [],
222 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
223 'distance_to_major_road_year2020': -999.0,
224 'dominant_ecoregion_year2017': '-1 (undefined)',
225 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
226 'ecoregion_description_25km_year2017': '',
227 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
228 'landcover_description_25km_year2012': '',
229 'max_population_density_25km_year1990': -1.0,
230 'max_population_density_25km_year2015': -1.0,
231 'max_stable_nightlights_25km_year1992': -999.0,
232 'max_stable_nightlights_25km_year2013': -999.0,
233 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
234 'mean_nox_emissions_10km_year2000': -999.0,
235 'mean_nox_emissions_10km_year2015': -999.0,
236 'mean_population_density_250m_year1990': -1.0,
237 'mean_population_density_250m_year2015': -1.0,
238 'mean_population_density_5km_year1990': -1.0,
239 'mean_population_density_5km_year2015': -1.0,
240 'mean_stable_nightlights_1km_year2013': -999.0,
241 'mean_stable_nightlights_5km_year2013': -999.0,
242 'mean_topography_srtm_alt_1km_year1994': -999.0,
243 'mean_topography_srtm_alt_90m_year1994': -999.0,
244 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
245 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
246 'toar1_category': 'unclassified',
247 'toar2_category': 'suburban',
248 }},
249 'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene',
250 'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1', 'chemical_formula': 'C7H8', 'id': 7},
251 'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''},
252 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active',
253 'contact': {'id': 4, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt',
254 'kind': 'government', 'city': 'Dessau-Roßlau', 'postcode': '06844', 'street_address': 'Wörlitzer Platz 1',
255 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}},
256 {'id': 3, 'role': 'principal investigator', 'status': 'active',
257 'contact': {'id': 3, 'person': {'email': 's.schroeder@fz-juelich.de','id': 3, 'isprivate': False,
258 'name': 'Sabine Schröder', 'orcid': '0000-0002-0309-8010', 'phone': '+49-2461-61-6397'}}}
259 ],
260 'citation': 'Sabine Schröder: time series of toluene at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00'},
261 'data': [{'datetime': '2012-12-16T21:00:00+00:00', 'value': 21.581, 'flags': 'OK validated verified', 'version': '1.0', 'timeseries_id': 1},
262 {'datetime': '2012-12-16T22:00:00+00:00', 'value': 13.734, 'flags': 'OK validated verified', 'version': '1.0', 'timeseries_id': 1},
263 {'datetime': '2012-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'OK validated verified', 'version': '1.0', 'timeseries_id': 1},
264 {'datetime': '2012-12-17T00:00:00+00:00', 'value': 7.848, 'flags': 'OK validated QC passed', 'version': '1.0', 'timeseries_id': 1},
265 {'datetime': '2012-12-17T01:00:00+00:00', 'value': 15.696, 'flags': 'OK validated QC passed', 'version': '1.0', 'timeseries_id': 1},
266 {'datetime': '2012-12-17T02:00:00+00:00', 'value': 11.772, 'flags': 'OK validated verified', 'version': '1.0', 'timeseries_id': 1},
267 {'datetime': '2012-12-17T03:00:00+00:00', 'value': 13.734, 'flags': 'OK validated modified', 'version': '1.0', 'timeseries_id': 1},
268 {'datetime': '2012-12-17T04:00:00+00:00', 'value': 19.62, 'flags': 'OK validated QC passed', 'version': '1.0', 'timeseries_id': 1},
269 {'datetime': '2012-12-17T05:00:00+00:00', 'value': 15.696, 'flags': 'OK validated modified', 'version': '1.0', 'timeseries_id': 1},
270 {'datetime': '2012-12-17T06:00:00+00:00', 'value': 5.886, 'flags': 'OK validated verified', 'version': '1.0', 'timeseries_id': 1}]
271 }
272 assert response.json() == expected_resp
275 def test_get_timeseries_merged(self, client, db):
276 with patch('toardb.timeseries.crud.dt.datetime', FixedDatetime):
277 response = client.get("/data/timeseries_merged/?station_code=SDZ54421&variable_id=5")
278 expected_status_code = 200
279 assert response.status_code == expected_status_code
280 expected_resp = {'metadata': [{'id': 18763,
281 'data_end_date': '2025-02-25T14:00:00+00:00',
282 'data_origin_type': 'measurement',
283 'label': '', 'data_origin': 'instrument',
284 'sampling_height': 7.0, 'doi': '', 'order': 1, 'provider_version': 'N/A',
285 'coverage': -1.0, 'sampling_frequency': 'hourly',
286 'additional_metadata': {"original_units": "ppb"},
287 'aggregation': 'mean', 'data_start_date': '1991-01-01T00:00:00+00:00',
288 'station': {'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinate_validation_status': 'not checked',
289 'state': 'Beijing Shi', 'type_of_area': 'unknown', 'additional_metadata': {"add_type": "nature reservation"},
290 'id': 2, 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, 'country': 'China', 'type': 'unknown',
291 'timezone': 'Asia/Shanghai', 'aux_docs': [], 'aux_images': [], 'aux_urls': [], 'changelog': [],
292 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
293 'distance_to_major_road_year2020': -999.0,
294 'dominant_ecoregion_year2017': '-1 (undefined)',
295 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
296 'ecoregion_description_25km_year2017': '',
297 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
298 'landcover_description_25km_year2012': '',
299 'max_population_density_25km_year1990': -1.0,
300 'max_population_density_25km_year2015': -1.0,
301 'max_stable_nightlights_25km_year1992': -999.0,
302 'max_stable_nightlights_25km_year2013': -999.0,
303 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
304 'mean_nox_emissions_10km_year2000': -999.0,
305 'mean_nox_emissions_10km_year2015': -999.0,
306 'mean_population_density_250m_year1990': -1.0,
307 'mean_population_density_250m_year2015': -1.0,
308 'mean_population_density_5km_year1990': -1.0,
309 'mean_population_density_5km_year2015': -1.0,
310 'mean_stable_nightlights_1km_year2013': -999.0,
311 'mean_stable_nightlights_5km_year2013': -999.0,
312 'mean_topography_srtm_alt_1km_year1994': -999.0,
313 'mean_topography_srtm_alt_90m_year1994': -999.0,
314 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
315 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
316 'toar1_category': 'unclassified',
317 'toar2_category': 'suburban',
318 }},
319 'variable': {'id': 5, 'displayname': 'Ozone', 'units': 'nmol mol-1', 'longname': 'ozone',
320 'name': 'o3', 'cf_standardname': 'mole_fraction_of_ozone_in_air', 'chemical_formula': 'O3'},
321 'programme': {'longname': '', 'homepage': '', 'name': '', 'id': 0, 'description': ''},
322 'roles': [{'role': 'resource provider',
323 'status': 'active',
324 'id': 1,
325 'contact': {'id': 5,
326 'organisation': {'city': 'Jülich',
327 'contact_url': 'mailto:toar-data@fz-juelich.de',
328 'country': 'Germany',
329 'homepage': 'https://www.fz-juelich.de',
330 'id': 2,
331 'kind': 'research',
332 'longname': 'Forschungszentrum Jülich',
333 'name': 'FZJ',
334 'postcode': '52425',
335 'street_address': 'Wilhelm-Johnen-Straße',
336 },
337 },}],
338 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/',
339 'citation': 'Forschungszentrum Jülich: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00',
340 'attribution': 'Test-Attributions to be announced'},
341 {'id': 434870,
342 'data_end_date': '2025-02-25T14:00:00+00:00',
343 'data_origin_type': 'measurement',
344 'label': '', 'data_origin': 'instrument',
345 'sampling_height': 7.0, 'doi': '', 'order': 2, 'provider_version': 'N/A',
346 'coverage': -1.0, 'sampling_frequency': 'hourly',
347 'additional_metadata': {"original_units": "ppb"},
348 'aggregation': 'mean', 'data_start_date': '1991-01-01T00:00:00+00:00',
349 'station': {'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinate_validation_status': 'not checked',
350 'state': 'Beijing Shi', 'type_of_area': 'unknown', 'additional_metadata': {"add_type": "nature reservation"},
351 'id': 2, 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, 'country': 'China', 'type': 'unknown',
352 'timezone': 'Asia/Shanghai', 'aux_docs': [], 'aux_images': [], 'aux_urls': [], 'changelog': [],
353 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
354 'distance_to_major_road_year2020': -999.0,
355 'dominant_ecoregion_year2017': '-1 (undefined)',
356 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
357 'ecoregion_description_25km_year2017': '',
358 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
359 'landcover_description_25km_year2012': '',
360 'max_population_density_25km_year1990': -1.0,
361 'max_population_density_25km_year2015': -1.0,
362 'max_stable_nightlights_25km_year1992': -999.0,
363 'max_stable_nightlights_25km_year2013': -999.0,
364 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
365 'mean_nox_emissions_10km_year2000': -999.0,
366 'mean_nox_emissions_10km_year2015': -999.0,
367 'mean_population_density_250m_year1990': -1.0,
368 'mean_population_density_250m_year2015': -1.0,
369 'mean_population_density_5km_year1990': -1.0,
370 'mean_population_density_5km_year2015': -1.0,
371 'mean_stable_nightlights_1km_year2013': -999.0,
372 'mean_stable_nightlights_5km_year2013': -999.0,
373 'mean_topography_srtm_alt_1km_year1994': -999.0,
374 'mean_topography_srtm_alt_90m_year1994': -999.0,
375 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
376 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
377 'toar1_category': 'unclassified',
378 'toar2_category': 'suburban',
379 }},
380 'variable': {'id': 5, 'displayname': 'Ozone', 'units': 'nmol mol-1', 'longname': 'ozone',
381 'name': 'o3', 'cf_standardname': 'mole_fraction_of_ozone_in_air', 'chemical_formula': 'O3'},
382 'programme': {'longname': '', 'homepage': '', 'name': '', 'id': 0, 'description': ''},
383 'roles': [{'role': 'resource provider',
384 'status': 'active',
385 'id': 2,
386 'contact': {'id': 4,
387 'organisation': {'city': 'Dessau-Roßlau',
388 'contact_url': 'mailto:immission@uba.de',
389 'country': 'Germany',
390 'homepage': 'https://www.umweltbundesamt.de',
391 'id': 1,
392 'kind': 'government',
393 'longname': 'Umweltbundesamt',
394 'name': 'UBA',
395 'postcode': '06844',
396 'street_address': 'Wörlitzer Platz 1',
397 },
398 },
399 }],
400 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/',
401 'citation': 'Umweltbundesamt: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00'},
402 {'id': 30890,
403 'data_end_date': '2025-02-25T14:00:00+00:00',
404 'data_origin_type': 'measurement',
405 'label': '', 'data_origin': 'instrument',
406 'sampling_height': 7.0, 'doi': '', 'order': 2, 'provider_version': 'N/A',
407 'coverage': -1.0, 'sampling_frequency': 'hourly',
408 'additional_metadata': {"original_units": "ppb"},
409 'aggregation': 'mean', 'data_start_date': '1991-01-01T00:00:00+00:00',
410 'station': {'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinate_validation_status': 'not checked',
411 'state': 'Beijing Shi', 'type_of_area': 'unknown', 'additional_metadata': {"add_type": "nature reservation"},
412 'id': 2, 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, 'country': 'China', 'type': 'unknown',
413 'timezone': 'Asia/Shanghai', 'aux_docs': [], 'aux_images': [], 'aux_urls': [], 'changelog': [],
414 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
415 'distance_to_major_road_year2020': -999.0,
416 'dominant_ecoregion_year2017': '-1 (undefined)',
417 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
418 'ecoregion_description_25km_year2017': '',
419 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
420 'landcover_description_25km_year2012': '',
421 'max_population_density_25km_year1990': -1.0,
422 'max_population_density_25km_year2015': -1.0,
423 'max_stable_nightlights_25km_year1992': -999.0,
424 'max_stable_nightlights_25km_year2013': -999.0,
425 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
426 'mean_nox_emissions_10km_year2000': -999.0,
427 'mean_nox_emissions_10km_year2015': -999.0,
428 'mean_population_density_250m_year1990': -1.0,
429 'mean_population_density_250m_year2015': -1.0,
430 'mean_population_density_5km_year1990': -1.0,
431 'mean_population_density_5km_year2015': -1.0,
432 'mean_stable_nightlights_1km_year2013': -999.0,
433 'mean_stable_nightlights_5km_year2013': -999.0,
434 'mean_topography_srtm_alt_1km_year1994': -999.0,
435 'mean_topography_srtm_alt_90m_year1994': -999.0,
436 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
437 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
438 'toar1_category': 'unclassified',
439 'toar2_category': 'suburban',
440 }},
441 'variable': {'id': 5, 'displayname': 'Ozone', 'units': 'nmol mol-1', 'longname': 'ozone',
442 'name': 'o3', 'cf_standardname': 'mole_fraction_of_ozone_in_air', 'chemical_formula': 'O3'},
443 'programme': {'longname': '', 'homepage': '', 'name': '', 'id': 0, 'description': ''},
444 'roles': [{'role': 'resource provider',
445 'status': 'active',
446 'id': 1,
447 'contact': {'id': 5,
448 'organisation': {'city': 'Jülich',
449 'contact_url': 'mailto:toar-data@fz-juelich.de',
450 'country': 'Germany',
451 'homepage': 'https://www.fz-juelich.de',
452 'id': 2,
453 'kind': 'research',
454 'longname': 'Forschungszentrum Jülich',
455 'name': 'FZJ',
456 'postcode': '52425',
457 'street_address': 'Wilhelm-Johnen-Straße',
458 },
459 },}],
460 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/',
461 'citation': 'Forschungszentrum Jülich: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00',
462 'attribution': 'Test-Attributions to be announced'}],
463 'data': [{'datetime': '1991-03-17T04:00:00+00:00', 'value': 19.62, 'flags': 'OK validated verified', 'timeseries_id': 18763, 'version': '1.0'},
464 {'datetime': '1992-05-21T17:00:00+00:00', 'value': 31.42, 'flags': 'OK validated verified', 'timeseries_id': 18763, 'version': '1.0'},
465 {'datetime': '1999-09-01T12:00:00+00:00', 'value': 11.42, 'flags': 'OK validated verified', 'timeseries_id': 18763, 'version': '1.0'},
466 {'datetime': '2001-09-13T08:00:00+00:00', 'value': 11.42, 'flags': 'OK validated verified', 'timeseries_id': 434870, 'version': '1.0'},
467 {'datetime': '2005-11-03T08:00:00+00:00', 'value': 27.87, 'flags': 'OK validated verified', 'timeseries_id': 434870, 'version': '1.0'},
468 {'datetime': '2009-04-01T02:00:00+00:00', 'value': 7.87, 'flags': 'OK validated verified', 'timeseries_id': 434870, 'version': '1.0'},
469 {'datetime': '2012-12-01T23:00:00+00:00', 'value': 14.73, 'flags': 'OK validated verified', 'timeseries_id': 434870, 'version': '1.0'},
470 {'datetime': '2012-12-02T00:00:00+00:00', 'value': 14.74, 'flags': 'OK validated verified', 'timeseries_id': 434870, 'version': '1.0'},
471 {'datetime': '2013-12-31T23:00:00+00:00', 'value': 12.65, 'flags': 'OK validated verified', 'timeseries_id': 30890, 'version': '1.0'},
472 {'datetime': '2015-08-16T15:00:00+00:00', 'value': 87.19, 'flags': 'OK validated verified', 'timeseries_id': 30890, 'version': '1.0'},
473 {'datetime': '2019-07-24T16:00:00+00:00', 'value': 67.43, 'flags': 'OK validated verified', 'timeseries_id': 30890, 'version': '1.0'},
474 {'datetime': '2023-01-18T22:00:00+00:00', 'value': 5.18, 'flags': 'OK validated verified', 'timeseries_id': 18763, 'version': '1.0'},
475 {'datetime': '2025-01-18T22:00:00+00:00', 'value': 5.18, 'flags': 'OK validated verified', 'timeseries_id': 18763, 'version': '1.0'}]}
476 assert response.json() == expected_resp
479 def test_get_timeseries_merged_with_fields(self, client, db):
480 with patch('toardb.timeseries.crud.dt.datetime', FixedDatetime):
481 response = client.get("/data/timeseries_merged/?station_code=SDZ54421&variable_id=5&fields=datetime,value,timeseries_id")
482 expected_status_code = 200
483 assert response.status_code == expected_status_code
484 expected_resp = {'metadata': [{'id': 18763,
485 'data_end_date': '2025-02-25T14:00:00+00:00',
486 'data_origin_type': 'measurement',
487 'label': '', 'data_origin': 'instrument',
488 'sampling_height': 7.0, 'doi': '', 'order': 1, 'provider_version': 'N/A',
489 'coverage': -1.0, 'sampling_frequency': 'hourly',
490 'additional_metadata': {"original_units": "ppb"},
491 'aggregation': 'mean', 'data_start_date': '1991-01-01T00:00:00+00:00',
492 'station': {'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinate_validation_status': 'not checked',
493 'state': 'Beijing Shi', 'type_of_area': 'unknown', 'additional_metadata': {"add_type": "nature reservation"},
494 'id': 2, 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, 'country': 'China', 'type': 'unknown',
495 'timezone': 'Asia/Shanghai', 'aux_docs': [], 'aux_images': [], 'aux_urls': [], 'changelog': [],
496 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
497 'distance_to_major_road_year2020': -999.0,
498 'dominant_ecoregion_year2017': '-1 (undefined)',
499 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
500 'ecoregion_description_25km_year2017': '',
501 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
502 'landcover_description_25km_year2012': '',
503 'max_population_density_25km_year1990': -1.0,
504 'max_population_density_25km_year2015': -1.0,
505 'max_stable_nightlights_25km_year1992': -999.0,
506 'max_stable_nightlights_25km_year2013': -999.0,
507 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
508 'mean_nox_emissions_10km_year2000': -999.0,
509 'mean_nox_emissions_10km_year2015': -999.0,
510 'mean_population_density_250m_year1990': -1.0,
511 'mean_population_density_250m_year2015': -1.0,
512 'mean_population_density_5km_year1990': -1.0,
513 'mean_population_density_5km_year2015': -1.0,
514 'mean_stable_nightlights_1km_year2013': -999.0,
515 'mean_stable_nightlights_5km_year2013': -999.0,
516 'mean_topography_srtm_alt_1km_year1994': -999.0,
517 'mean_topography_srtm_alt_90m_year1994': -999.0,
518 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
519 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
520 'toar1_category': 'unclassified',
521 'toar2_category': 'suburban',
522 }},
523 'variable': {'id': 5, 'displayname': 'Ozone', 'units': 'nmol mol-1', 'longname': 'ozone',
524 'name': 'o3', 'cf_standardname': 'mole_fraction_of_ozone_in_air', 'chemical_formula': 'O3'},
525 'programme': {'longname': '', 'homepage': '', 'name': '', 'id': 0, 'description': ''},
526 'roles': [{'role': 'resource provider',
527 'status': 'active',
528 'id': 1,
529 'contact': {'id': 5,
530 'organisation': {'city': 'Jülich',
531 'contact_url': 'mailto:toar-data@fz-juelich.de',
532 'country': 'Germany',
533 'homepage': 'https://www.fz-juelich.de',
534 'id': 2,
535 'kind': 'research',
536 'longname': 'Forschungszentrum Jülich',
537 'name': 'FZJ',
538 'postcode': '52425',
539 'street_address': 'Wilhelm-Johnen-Straße',
540 },
541 },}],
542 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/',
543 'citation': 'Forschungszentrum Jülich: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00',
544 'attribution': 'Test-Attributions to be announced'},
545 {'id': 434870,
546 'data_end_date': '2025-02-25T14:00:00+00:00',
547 'data_origin_type': 'measurement',
548 'label': '', 'data_origin': 'instrument',
549 'sampling_height': 7.0, 'doi': '', 'order': 2, 'provider_version': 'N/A',
550 'coverage': -1.0, 'sampling_frequency': 'hourly',
551 'additional_metadata': {"original_units": "ppb"},
552 'aggregation': 'mean', 'data_start_date': '1991-01-01T00:00:00+00:00',
553 'station': {'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinate_validation_status': 'not checked',
554 'state': 'Beijing Shi', 'type_of_area': 'unknown', 'additional_metadata': {"add_type": "nature reservation"},
555 'id': 2, 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, 'country': 'China', 'type': 'unknown',
556 'timezone': 'Asia/Shanghai', 'aux_docs': [], 'aux_images': [], 'aux_urls': [], 'changelog': [],
557 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
558 'distance_to_major_road_year2020': -999.0,
559 'dominant_ecoregion_year2017': '-1 (undefined)',
560 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
561 'ecoregion_description_25km_year2017': '',
562 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
563 'landcover_description_25km_year2012': '',
564 'max_population_density_25km_year1990': -1.0,
565 'max_population_density_25km_year2015': -1.0,
566 'max_stable_nightlights_25km_year1992': -999.0,
567 'max_stable_nightlights_25km_year2013': -999.0,
568 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
569 'mean_nox_emissions_10km_year2000': -999.0,
570 'mean_nox_emissions_10km_year2015': -999.0,
571 'mean_population_density_250m_year1990': -1.0,
572 'mean_population_density_250m_year2015': -1.0,
573 'mean_population_density_5km_year1990': -1.0,
574 'mean_population_density_5km_year2015': -1.0,
575 'mean_stable_nightlights_1km_year2013': -999.0,
576 'mean_stable_nightlights_5km_year2013': -999.0,
577 'mean_topography_srtm_alt_1km_year1994': -999.0,
578 'mean_topography_srtm_alt_90m_year1994': -999.0,
579 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
580 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
581 'toar1_category': 'unclassified',
582 'toar2_category': 'suburban',
583 }},
584 'variable': {'id': 5, 'displayname': 'Ozone', 'units': 'nmol mol-1', 'longname': 'ozone',
585 'name': 'o3', 'cf_standardname': 'mole_fraction_of_ozone_in_air', 'chemical_formula': 'O3'},
586 'programme': {'longname': '', 'homepage': '', 'name': '', 'id': 0, 'description': ''},
587 'roles': [{'role': 'resource provider',
588 'status': 'active',
589 'id': 2,
590 'contact': {'id': 4,
591 'organisation': {'city': 'Dessau-Roßlau',
592 'contact_url': 'mailto:immission@uba.de',
593 'country': 'Germany',
594 'homepage': 'https://www.umweltbundesamt.de',
595 'id': 1,
596 'kind': 'government',
597 'longname': 'Umweltbundesamt',
598 'name': 'UBA',
599 'postcode': '06844',
600 'street_address': 'Wörlitzer Platz 1',
601 },
602 },
603 }],
604 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/',
605 'citation': 'Umweltbundesamt: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00'},
606 {'id': 30890,
607 'data_end_date': '2025-02-25T14:00:00+00:00',
608 'data_origin_type': 'measurement',
609 'label': '', 'data_origin': 'instrument',
610 'sampling_height': 7.0, 'doi': '', 'order': 2, 'provider_version': 'N/A',
611 'coverage': -1.0, 'sampling_frequency': 'hourly',
612 'additional_metadata': {"original_units": "ppb"},
613 'aggregation': 'mean', 'data_start_date': '1991-01-01T00:00:00+00:00',
614 'station': {'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinate_validation_status': 'not checked',
615 'state': 'Beijing Shi', 'type_of_area': 'unknown', 'additional_metadata': {"add_type": "nature reservation"},
616 'id': 2, 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, 'country': 'China', 'type': 'unknown',
617 'timezone': 'Asia/Shanghai', 'aux_docs': [], 'aux_images': [], 'aux_urls': [], 'changelog': [],
618 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
619 'distance_to_major_road_year2020': -999.0,
620 'dominant_ecoregion_year2017': '-1 (undefined)',
621 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
622 'ecoregion_description_25km_year2017': '',
623 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
624 'landcover_description_25km_year2012': '',
625 'max_population_density_25km_year1990': -1.0,
626 'max_population_density_25km_year2015': -1.0,
627 'max_stable_nightlights_25km_year1992': -999.0,
628 'max_stable_nightlights_25km_year2013': -999.0,
629 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
630 'mean_nox_emissions_10km_year2000': -999.0,
631 'mean_nox_emissions_10km_year2015': -999.0,
632 'mean_population_density_250m_year1990': -1.0,
633 'mean_population_density_250m_year2015': -1.0,
634 'mean_population_density_5km_year1990': -1.0,
635 'mean_population_density_5km_year2015': -1.0,
636 'mean_stable_nightlights_1km_year2013': -999.0,
637 'mean_stable_nightlights_5km_year2013': -999.0,
638 'mean_topography_srtm_alt_1km_year1994': -999.0,
639 'mean_topography_srtm_alt_90m_year1994': -999.0,
640 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
641 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
642 'toar1_category': 'unclassified',
643 'toar2_category': 'suburban',
644 }},
645 'variable': {'id': 5, 'displayname': 'Ozone', 'units': 'nmol mol-1', 'longname': 'ozone',
646 'name': 'o3', 'cf_standardname': 'mole_fraction_of_ozone_in_air', 'chemical_formula': 'O3'},
647 'programme': {'longname': '', 'homepage': '', 'name': '', 'id': 0, 'description': ''},
648 'roles': [{'role': 'resource provider',
649 'status': 'active',
650 'id': 1,
651 'contact': {'id': 5,
652 'organisation': {'city': 'Jülich',
653 'contact_url': 'mailto:toar-data@fz-juelich.de',
654 'country': 'Germany',
655 'homepage': 'https://www.fz-juelich.de',
656 'id': 2,
657 'kind': 'research',
658 'longname': 'Forschungszentrum Jülich',
659 'name': 'FZJ',
660 'postcode': '52425',
661 'street_address': 'Wilhelm-Johnen-Straße',
662 },
663 },}],
664 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/',
665 'citation': 'Forschungszentrum Jülich: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00',
666 'attribution': 'Test-Attributions to be announced'}],
667 'data': [{'datetime': '1991-03-17T04:00:00+00:00', 'value': 19.62, 'timeseries_id': 18763},
668 {'datetime': '1992-05-21T17:00:00+00:00', 'value': 31.42, 'timeseries_id': 18763},
669 {'datetime': '1999-09-01T12:00:00+00:00', 'value': 11.42, 'timeseries_id': 18763},
670 {'datetime': '2001-09-13T08:00:00+00:00', 'value': 11.42, 'timeseries_id': 434870},
671 {'datetime': '2005-11-03T08:00:00+00:00', 'value': 27.87, 'timeseries_id': 434870},
672 {'datetime': '2009-04-01T02:00:00+00:00', 'value': 7.87, 'timeseries_id': 434870},
673 {'datetime': '2012-12-01T23:00:00+00:00', 'value': 14.73, 'timeseries_id': 434870},
674 {'datetime': '2012-12-02T00:00:00+00:00', 'value': 14.74, 'timeseries_id': 434870},
675 {'datetime': '2013-12-31T23:00:00+00:00', 'value': 12.65, 'timeseries_id': 30890},
676 {'datetime': '2015-08-16T15:00:00+00:00', 'value': 87.19, 'timeseries_id': 30890},
677 {'datetime': '2019-07-24T16:00:00+00:00', 'value': 67.43, 'timeseries_id': 30890},
678 {'datetime': '2023-01-18T22:00:00+00:00', 'value': 5.18, 'timeseries_id': 18763},
679 {'datetime': '2025-01-18T22:00:00+00:00', 'value': 5.18, 'timeseries_id': 18763}]}
680 assert response.json() == expected_resp
683 def test_get_timeseries_merged_with_fields_csv(self, client, db):
684 with patch('toardb.timeseries.crud.dt.datetime', FixedDatetime):
685 response = client.get("/data/timeseries_merged/?station_code=SDZ54421&variable_id=5&fields=datetime,value,timeseries_id&format=csv")
686 expected_status_code = 200
687 assert response.status_code == expected_status_code
688 expected_resp = ''.join(['#{\n',
689 '# "id": 18763,\n',
690 '# "label": "",\n',
691 '# "order": 1,\n',
692 '# "sampling_frequency": "hourly",\n',
693 '# "aggregation": "mean",\n',
694 '# "data_start_date": "1991-01-01T00:00:00+00:00",\n',
695 '# "data_end_date": "2025-02-25T14:00:00+00:00",\n',
696 '# "data_origin": "instrument",\n',
697 '# "data_origin_type": "measurement",\n',
698 '# "provider_version": "N/A",\n',
699 '# "sampling_height": 7.0,\n',
700 '# "additional_metadata": {\n',
701 '# "original_units": "ppb"\n',
702 '# },\n',
703 '# "data_license_accepted": null,\n',
704 '# "dataset_approved_by_provider": null,\n',
705 '# "doi": "",\n',
706 '# "coverage": -1.0,\n',
707 '# "station": {\n',
708 '# "id": 2,\n',
709 '# "codes": [\n',
710 '# "SDZ54421"\n',
711 '# ],\n',
712 '# "name": "Shangdianzi",\n',
713 '# "coordinates": {\n',
714 '# "lat": 40.65,\n',
715 '# "lng": 117.106,\n',
716 '# "alt": 293.9\n',
717 '# },\n',
718 '# "coordinate_validation_status": "not checked",\n',
719 '# "country": "China",\n',
720 '# "state": "Beijing Shi",\n',
721 '# "type": "unknown",\n',
722 '# "type_of_area": "unknown",\n',
723 '# "timezone": "Asia/Shanghai",\n',
724 '# "additional_metadata": {\n',
725 '# "add_type": "nature reservation"\n',
726 '# },\n',
727 '# "roles": null,\n',
728 '# "annotations": null,\n',
729 '# "aux_images": [],\n',
730 '# "aux_docs": [],\n',
731 '# "aux_urls": [],\n',
732 '# "globalmeta": {\n',
733 '# "mean_topography_srtm_alt_90m_year1994": -999.0,\n',
734 '# "mean_topography_srtm_alt_1km_year1994": -999.0,\n',
735 '# "max_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
736 '# "min_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
737 '# "stddev_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
738 '# "climatic_zone_year2016": "6 (warm temperate dry)",\n',
739 '# "htap_region_tier1_year2010": "11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)",\n',
740 '# "dominant_landcover_year2012": "11 (Cropland, rainfed, herbaceous cover)",\n',
741 '# "landcover_description_25km_year2012": "",\n',
742 '# "dominant_ecoregion_year2017": "-1 (undefined)",\n',
743 '# "ecoregion_description_25km_year2017": "",\n',
744 '# "distance_to_major_road_year2020": -999.0,\n',
745 '# "mean_stable_nightlights_1km_year2013": -999.0,\n',
746 '# "mean_stable_nightlights_5km_year2013": -999.0,\n',
747 '# "max_stable_nightlights_25km_year2013": -999.0,\n',
748 '# "max_stable_nightlights_25km_year1992": -999.0,\n',
749 '# "mean_population_density_250m_year2015": -1.0,\n',
750 '# "mean_population_density_5km_year2015": -1.0,\n',
751 '# "max_population_density_25km_year2015": -1.0,\n',
752 '# "mean_population_density_250m_year1990": -1.0,\n',
753 '# "mean_population_density_5km_year1990": -1.0,\n',
754 '# "max_population_density_25km_year1990": -1.0,\n',
755 '# "mean_nox_emissions_10km_year2015": -999.0,\n',
756 '# "mean_nox_emissions_10km_year2000": -999.0,\n',
757 '# "toar1_category": "unclassified",\n',
758 '# "toar2_category": "suburban"\n',
759 '# },\n',
760 '# "changelog": []\n',
761 '# },\n',
762 '# "variable": {\n',
763 '# "name": "o3",\n',
764 '# "longname": "ozone",\n',
765 '# "displayname": "Ozone",\n',
766 '# "cf_standardname": "mole_fraction_of_ozone_in_air",\n',
767 '# "units": "nmol mol-1",\n',
768 '# "chemical_formula": "O3",\n',
769 '# "id": 5\n',
770 '# },\n',
771 '# "programme": {\n',
772 '# "id": 0,\n',
773 '# "name": "",\n',
774 '# "longname": "",\n',
775 '# "homepage": "",\n',
776 '# "description": ""\n',
777 '# },\n',
778 '# "roles": [\n',
779 '# {\n',
780 '# "id": 1,\n',
781 '# "role": "resource provider",\n',
782 '# "status": "active",\n',
783 '# "contact": {\n',
784 '# "id": 5,\n',
785 '# "person": null,\n',
786 '# "organisation": {\n',
787 '# "id": 2,\n',
788 '# "name": "FZJ",\n',
789 '# "longname": "Forschungszentrum Jülich",\n',
790 '# "kind": "research",\n',
791 '# "city": "Jülich",\n',
792 '# "postcode": "52425",\n',
793 '# "street_address": "Wilhelm-Johnen-Straße",\n',
794 '# "country": "Germany",\n',
795 '# "homepage": "https://www.fz-juelich.de",\n',
796 '# "contact_url": "mailto:toar-data@fz-juelich.de"\n',
797 '# }\n',
798 '# }\n',
799 '# }\n',
800 '# ],\n',
801 '# "annotations": null,\n',
802 '# "changelog": null,\n',
803 '# "citation": "Forschungszentrum Jülich: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00",\n',
804 '# "attribution": "Test-Attributions to be announced",\n',
805 '# "license": "This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/"\n',
806 '#}\n',
807 '#{\n',
808 '# "id": 434870,\n',
809 '# "label": "",\n',
810 '# "order": 2,\n',
811 '# "sampling_frequency": "hourly",\n',
812 '# "aggregation": "mean",\n',
813 '# "data_start_date": "1991-01-01T00:00:00+00:00",\n',
814 '# "data_end_date": "2025-02-25T14:00:00+00:00",\n',
815 '# "data_origin": "instrument",\n',
816 '# "data_origin_type": "measurement",\n',
817 '# "provider_version": "N/A",\n',
818 '# "sampling_height": 7.0,\n',
819 '# "additional_metadata": {\n',
820 '# "original_units": "ppb"\n',
821 '# },\n',
822 '# "data_license_accepted": null,\n',
823 '# "dataset_approved_by_provider": null,\n',
824 '# "doi": "",\n',
825 '# "coverage": -1.0,\n',
826 '# "station": {\n',
827 '# "id": 2,\n',
828 '# "codes": [\n',
829 '# "SDZ54421"\n',
830 '# ],\n',
831 '# "name": "Shangdianzi",\n',
832 '# "coordinates": {\n',
833 '# "lat": 40.65,\n',
834 '# "lng": 117.106,\n',
835 '# "alt": 293.9\n',
836 '# },\n',
837 '# "coordinate_validation_status": "not checked",\n',
838 '# "country": "China",\n',
839 '# "state": "Beijing Shi",\n',
840 '# "type": "unknown",\n',
841 '# "type_of_area": "unknown",\n',
842 '# "timezone": "Asia/Shanghai",\n',
843 '# "additional_metadata": {\n',
844 '# "add_type": "nature reservation"\n',
845 '# },\n',
846 '# "roles": null,\n',
847 '# "annotations": null,\n',
848 '# "aux_images": [],\n',
849 '# "aux_docs": [],\n',
850 '# "aux_urls": [],\n',
851 '# "globalmeta": {\n',
852 '# "mean_topography_srtm_alt_90m_year1994": -999.0,\n',
853 '# "mean_topography_srtm_alt_1km_year1994": -999.0,\n',
854 '# "max_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
855 '# "min_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
856 '# "stddev_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
857 '# "climatic_zone_year2016": "6 (warm temperate dry)",\n',
858 '# "htap_region_tier1_year2010": "11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)",\n',
859 '# "dominant_landcover_year2012": "11 (Cropland, rainfed, herbaceous cover)",\n',
860 '# "landcover_description_25km_year2012": "",\n',
861 '# "dominant_ecoregion_year2017": "-1 (undefined)",\n',
862 '# "ecoregion_description_25km_year2017": "",\n',
863 '# "distance_to_major_road_year2020": -999.0,\n',
864 '# "mean_stable_nightlights_1km_year2013": -999.0,\n',
865 '# "mean_stable_nightlights_5km_year2013": -999.0,\n',
866 '# "max_stable_nightlights_25km_year2013": -999.0,\n',
867 '# "max_stable_nightlights_25km_year1992": -999.0,\n',
868 '# "mean_population_density_250m_year2015": -1.0,\n',
869 '# "mean_population_density_5km_year2015": -1.0,\n',
870 '# "max_population_density_25km_year2015": -1.0,\n',
871 '# "mean_population_density_250m_year1990": -1.0,\n',
872 '# "mean_population_density_5km_year1990": -1.0,\n',
873 '# "max_population_density_25km_year1990": -1.0,\n',
874 '# "mean_nox_emissions_10km_year2015": -999.0,\n',
875 '# "mean_nox_emissions_10km_year2000": -999.0,\n',
876 '# "toar1_category": "unclassified",\n',
877 '# "toar2_category": "suburban"\n',
878 '# },\n',
879 '# "changelog": []\n',
880 '# },\n',
881 '# "variable": {\n',
882 '# "name": "o3",\n',
883 '# "longname": "ozone",\n',
884 '# "displayname": "Ozone",\n',
885 '# "cf_standardname": "mole_fraction_of_ozone_in_air",\n',
886 '# "units": "nmol mol-1",\n',
887 '# "chemical_formula": "O3",\n',
888 '# "id": 5\n',
889 '# },\n',
890 '# "programme": {\n',
891 '# "id": 0,\n',
892 '# "name": "",\n',
893 '# "longname": "",\n',
894 '# "homepage": "",\n',
895 '# "description": ""\n',
896 '# },\n',
897 '# "roles": [\n',
898 '# {\n',
899 '# "id": 2,\n',
900 '# "role": "resource provider",\n',
901 '# "status": "active",\n',
902 '# "contact": {\n',
903 '# "id": 4,\n',
904 '# "person": null,\n',
905 '# "organisation": {\n',
906 '# "id": 1,\n',
907 '# "name": "UBA",\n',
908 '# "longname": "Umweltbundesamt",\n',
909 '# "kind": "government",\n',
910 '# "city": "Dessau-Roßlau",\n',
911 '# "postcode": "06844",\n',
912 '# "street_address": "Wörlitzer Platz 1",\n',
913 '# "country": "Germany",\n',
914 '# "homepage": "https://www.umweltbundesamt.de",\n',
915 '# "contact_url": "mailto:immission@uba.de"\n',
916 '# }\n',
917 '# }\n',
918 '# }\n',
919 '# ],\n',
920 '# "annotations": null,\n',
921 '# "changelog": null,\n',
922 '# "citation": "Umweltbundesamt: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00",\n',
923 '# "attribution": null,\n',
924 '# "license": "This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/"\n',
925 '#}\n',
926 '#{\n',
927 '# "id": 30890,\n',
928 '# "label": "",\n',
929 '# "order": 2,\n',
930 '# "sampling_frequency": "hourly",\n',
931 '# "aggregation": "mean",\n',
932 '# "data_start_date": "1991-01-01T00:00:00+00:00",\n',
933 '# "data_end_date": "2025-02-25T14:00:00+00:00",\n',
934 '# "data_origin": "instrument",\n',
935 '# "data_origin_type": "measurement",\n',
936 '# "provider_version": "N/A",\n',
937 '# "sampling_height": 7.0,\n',
938 '# "additional_metadata": {\n',
939 '# "original_units": "ppb"\n',
940 '# },\n',
941 '# "data_license_accepted": null,\n',
942 '# "dataset_approved_by_provider": null,\n',
943 '# "doi": "",\n',
944 '# "coverage": -1.0,\n',
945 '# "station": {\n',
946 '# "id": 2,\n',
947 '# "codes": [\n',
948 '# "SDZ54421"\n',
949 '# ],\n',
950 '# "name": "Shangdianzi",\n',
951 '# "coordinates": {\n',
952 '# "lat": 40.65,\n',
953 '# "lng": 117.106,\n',
954 '# "alt": 293.9\n',
955 '# },\n',
956 '# "coordinate_validation_status": "not checked",\n',
957 '# "country": "China",\n',
958 '# "state": "Beijing Shi",\n',
959 '# "type": "unknown",\n',
960 '# "type_of_area": "unknown",\n',
961 '# "timezone": "Asia/Shanghai",\n',
962 '# "additional_metadata": {\n',
963 '# "add_type": "nature reservation"\n',
964 '# },\n',
965 '# "roles": null,\n',
966 '# "annotations": null,\n',
967 '# "aux_images": [],\n',
968 '# "aux_docs": [],\n',
969 '# "aux_urls": [],\n',
970 '# "globalmeta": {\n',
971 '# "mean_topography_srtm_alt_90m_year1994": -999.0,\n',
972 '# "mean_topography_srtm_alt_1km_year1994": -999.0,\n',
973 '# "max_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
974 '# "min_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
975 '# "stddev_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
976 '# "climatic_zone_year2016": "6 (warm temperate dry)",\n',
977 '# "htap_region_tier1_year2010": "11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)",\n',
978 '# "dominant_landcover_year2012": "11 (Cropland, rainfed, herbaceous cover)",\n',
979 '# "landcover_description_25km_year2012": "",\n',
980 '# "dominant_ecoregion_year2017": "-1 (undefined)",\n',
981 '# "ecoregion_description_25km_year2017": "",\n',
982 '# "distance_to_major_road_year2020": -999.0,\n',
983 '# "mean_stable_nightlights_1km_year2013": -999.0,\n',
984 '# "mean_stable_nightlights_5km_year2013": -999.0,\n',
985 '# "max_stable_nightlights_25km_year2013": -999.0,\n',
986 '# "max_stable_nightlights_25km_year1992": -999.0,\n',
987 '# "mean_population_density_250m_year2015": -1.0,\n',
988 '# "mean_population_density_5km_year2015": -1.0,\n',
989 '# "max_population_density_25km_year2015": -1.0,\n',
990 '# "mean_population_density_250m_year1990": -1.0,\n',
991 '# "mean_population_density_5km_year1990": -1.0,\n',
992 '# "max_population_density_25km_year1990": -1.0,\n',
993 '# "mean_nox_emissions_10km_year2015": -999.0,\n',
994 '# "mean_nox_emissions_10km_year2000": -999.0,\n',
995 '# "toar1_category": "unclassified",\n',
996 '# "toar2_category": "suburban"\n',
997 '# },\n',
998 '# "changelog": []\n',
999 '# },\n',
1000 '# "variable": {\n',
1001 '# "name": "o3",\n',
1002 '# "longname": "ozone",\n',
1003 '# "displayname": "Ozone",\n',
1004 '# "cf_standardname": "mole_fraction_of_ozone_in_air",\n',
1005 '# "units": "nmol mol-1",\n',
1006 '# "chemical_formula": "O3",\n',
1007 '# "id": 5\n',
1008 '# },\n',
1009 '# "programme": {\n',
1010 '# "id": 0,\n',
1011 '# "name": "",\n',
1012 '# "longname": "",\n',
1013 '# "homepage": "",\n',
1014 '# "description": ""\n',
1015 '# },\n',
1016 '# "roles": [\n',
1017 '# {\n',
1018 '# "id": 1,\n',
1019 '# "role": "resource provider",\n',
1020 '# "status": "active",\n',
1021 '# "contact": {\n',
1022 '# "id": 5,\n',
1023 '# "person": null,\n',
1024 '# "organisation": {\n',
1025 '# "id": 2,\n',
1026 '# "name": "FZJ",\n',
1027 '# "longname": "Forschungszentrum Jülich",\n',
1028 '# "kind": "research",\n',
1029 '# "city": "Jülich",\n',
1030 '# "postcode": "52425",\n',
1031 '# "street_address": "Wilhelm-Johnen-Straße",\n',
1032 '# "country": "Germany",\n',
1033 '# "homepage": "https://www.fz-juelich.de",\n',
1034 '# "contact_url": "mailto:toar-data@fz-juelich.de"\n',
1035 '# }\n',
1036 '# }\n',
1037 '# }\n',
1038 '# ],\n',
1039 '# "annotations": null,\n',
1040 '# "changelog": null,\n',
1041 '# "citation": "Forschungszentrum Jülich: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00",\n',
1042 '# "attribution": "Test-Attributions to be announced",\n',
1043 '# "license": "This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/"\n',
1044 '#}\n',
1045 'datetime,value,timeseries_id\n',
1046 '1991-03-17 04:00:00+00:00,19.62,18763\n',
1047 '1992-05-21 17:00:00+00:00,31.42,18763\n',
1048 '1999-09-01 12:00:00+00:00,11.42,18763\n',
1049 '2001-09-13 08:00:00+00:00,11.42,434870\n',
1050 '2005-11-03 08:00:00+00:00,27.87,434870\n',
1051 '2009-04-01 02:00:00+00:00,7.87,434870\n',
1052 '2012-12-01 23:00:00+00:00,14.73,434870\n',
1053 '2012-12-02 00:00:00+00:00,14.74,434870\n',
1054 '2013-12-31 23:00:00+00:00,12.65,30890\n',
1055 '2015-08-16 15:00:00+00:00,87.19,30890\n',
1056 '2019-07-24 16:00:00+00:00,67.43,30890\n',
1057 '2023-01-18 22:00:00+00:00,5.18,18763\n',
1058 '2025-01-18 22:00:00+00:00,5.18,18763'])
1059 assert response.text == expected_resp
1062 def test_get_timeseries_merged_with_fields_and_daterange_csv(self, client, db):
1063 with patch('toardb.timeseries.crud.dt.datetime', FixedDatetime):
1064 response = client.get("/data/timeseries_merged/?station_code=SDZ54421&variable_id=5&fields=datetime,value,timeseries_id&daterange=2005-01-01,2014-12-31%2023:00&format=csv")
1065 expected_status_code = 200
1066 assert response.status_code == expected_status_code
1067 expected_resp = ''.join(['#{\n',
1068 '# "id": 434870,\n',
1069 '# "label": "",\n',
1070 '# "order": 2,\n',
1071 '# "sampling_frequency": "hourly",\n',
1072 '# "aggregation": "mean",\n',
1073 '# "data_start_date": "1991-01-01T00:00:00+00:00",\n',
1074 '# "data_end_date": "2025-02-25T14:00:00+00:00",\n',
1075 '# "data_origin": "instrument",\n',
1076 '# "data_origin_type": "measurement",\n',
1077 '# "provider_version": "N/A",\n',
1078 '# "sampling_height": 7.0,\n',
1079 '# "additional_metadata": {\n',
1080 '# "original_units": "ppb"\n',
1081 '# },\n',
1082 '# "data_license_accepted": null,\n',
1083 '# "dataset_approved_by_provider": null,\n',
1084 '# "doi": "",\n',
1085 '# "coverage": -1.0,\n',
1086 '# "station": {\n',
1087 '# "id": 2,\n',
1088 '# "codes": [\n',
1089 '# "SDZ54421"\n',
1090 '# ],\n',
1091 '# "name": "Shangdianzi",\n',
1092 '# "coordinates": {\n',
1093 '# "lat": 40.65,\n',
1094 '# "lng": 117.106,\n',
1095 '# "alt": 293.9\n',
1096 '# },\n',
1097 '# "coordinate_validation_status": "not checked",\n',
1098 '# "country": "China",\n',
1099 '# "state": "Beijing Shi",\n',
1100 '# "type": "unknown",\n',
1101 '# "type_of_area": "unknown",\n',
1102 '# "timezone": "Asia/Shanghai",\n',
1103 '# "additional_metadata": {\n',
1104 '# "add_type": "nature reservation"\n',
1105 '# },\n',
1106 '# "roles": null,\n',
1107 '# "annotations": null,\n',
1108 '# "aux_images": [],\n',
1109 '# "aux_docs": [],\n',
1110 '# "aux_urls": [],\n',
1111 '# "globalmeta": {\n',
1112 '# "mean_topography_srtm_alt_90m_year1994": -999.0,\n',
1113 '# "mean_topography_srtm_alt_1km_year1994": -999.0,\n',
1114 '# "max_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
1115 '# "min_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
1116 '# "stddev_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
1117 '# "climatic_zone_year2016": "6 (warm temperate dry)",\n',
1118 '# "htap_region_tier1_year2010": "11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)",\n',
1119 '# "dominant_landcover_year2012": "11 (Cropland, rainfed, herbaceous cover)",\n',
1120 '# "landcover_description_25km_year2012": "",\n',
1121 '# "dominant_ecoregion_year2017": "-1 (undefined)",\n',
1122 '# "ecoregion_description_25km_year2017": "",\n',
1123 '# "distance_to_major_road_year2020": -999.0,\n',
1124 '# "mean_stable_nightlights_1km_year2013": -999.0,\n',
1125 '# "mean_stable_nightlights_5km_year2013": -999.0,\n',
1126 '# "max_stable_nightlights_25km_year2013": -999.0,\n',
1127 '# "max_stable_nightlights_25km_year1992": -999.0,\n',
1128 '# "mean_population_density_250m_year2015": -1.0,\n',
1129 '# "mean_population_density_5km_year2015": -1.0,\n',
1130 '# "max_population_density_25km_year2015": -1.0,\n',
1131 '# "mean_population_density_250m_year1990": -1.0,\n',
1132 '# "mean_population_density_5km_year1990": -1.0,\n',
1133 '# "max_population_density_25km_year1990": -1.0,\n',
1134 '# "mean_nox_emissions_10km_year2015": -999.0,\n',
1135 '# "mean_nox_emissions_10km_year2000": -999.0,\n',
1136 '# "toar1_category": "unclassified",\n',
1137 '# "toar2_category": "suburban"\n',
1138 '# },\n',
1139 '# "changelog": []\n',
1140 '# },\n',
1141 '# "variable": {\n',
1142 '# "name": "o3",\n',
1143 '# "longname": "ozone",\n',
1144 '# "displayname": "Ozone",\n',
1145 '# "cf_standardname": "mole_fraction_of_ozone_in_air",\n',
1146 '# "units": "nmol mol-1",\n',
1147 '# "chemical_formula": "O3",\n',
1148 '# "id": 5\n',
1149 '# },\n',
1150 '# "programme": {\n',
1151 '# "id": 0,\n',
1152 '# "name": "",\n',
1153 '# "longname": "",\n',
1154 '# "homepage": "",\n',
1155 '# "description": ""\n',
1156 '# },\n',
1157 '# "roles": [\n',
1158 '# {\n',
1159 '# "id": 2,\n',
1160 '# "role": "resource provider",\n',
1161 '# "status": "active",\n',
1162 '# "contact": {\n',
1163 '# "id": 4,\n',
1164 '# "person": null,\n',
1165 '# "organisation": {\n',
1166 '# "id": 1,\n',
1167 '# "name": "UBA",\n',
1168 '# "longname": "Umweltbundesamt",\n',
1169 '# "kind": "government",\n',
1170 '# "city": "Dessau-Roßlau",\n',
1171 '# "postcode": "06844",\n',
1172 '# "street_address": "Wörlitzer Platz 1",\n',
1173 '# "country": "Germany",\n',
1174 '# "homepage": "https://www.umweltbundesamt.de",\n',
1175 '# "contact_url": "mailto:immission@uba.de"\n',
1176 '# }\n',
1177 '# }\n',
1178 '# }\n',
1179 '# ],\n',
1180 '# "annotations": null,\n',
1181 '# "changelog": null,\n',
1182 '# "citation": "Umweltbundesamt: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00",\n',
1183 '# "attribution": null,\n',
1184 '# "license": "This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/"\n',
1185 '#}\n',
1186 '#{\n',
1187 '# "id": 30890,\n',
1188 '# "label": "",\n',
1189 '# "order": 2,\n',
1190 '# "sampling_frequency": "hourly",\n',
1191 '# "aggregation": "mean",\n',
1192 '# "data_start_date": "1991-01-01T00:00:00+00:00",\n',
1193 '# "data_end_date": "2025-02-25T14:00:00+00:00",\n',
1194 '# "data_origin": "instrument",\n',
1195 '# "data_origin_type": "measurement",\n',
1196 '# "provider_version": "N/A",\n',
1197 '# "sampling_height": 7.0,\n',
1198 '# "additional_metadata": {\n',
1199 '# "original_units": "ppb"\n',
1200 '# },\n',
1201 '# "data_license_accepted": null,\n',
1202 '# "dataset_approved_by_provider": null,\n',
1203 '# "doi": "",\n',
1204 '# "coverage": -1.0,\n',
1205 '# "station": {\n',
1206 '# "id": 2,\n',
1207 '# "codes": [\n',
1208 '# "SDZ54421"\n',
1209 '# ],\n',
1210 '# "name": "Shangdianzi",\n',
1211 '# "coordinates": {\n',
1212 '# "lat": 40.65,\n',
1213 '# "lng": 117.106,\n',
1214 '# "alt": 293.9\n',
1215 '# },\n',
1216 '# "coordinate_validation_status": "not checked",\n',
1217 '# "country": "China",\n',
1218 '# "state": "Beijing Shi",\n',
1219 '# "type": "unknown",\n',
1220 '# "type_of_area": "unknown",\n',
1221 '# "timezone": "Asia/Shanghai",\n',
1222 '# "additional_metadata": {\n',
1223 '# "add_type": "nature reservation"\n',
1224 '# },\n',
1225 '# "roles": null,\n',
1226 '# "annotations": null,\n',
1227 '# "aux_images": [],\n',
1228 '# "aux_docs": [],\n',
1229 '# "aux_urls": [],\n',
1230 '# "globalmeta": {\n',
1231 '# "mean_topography_srtm_alt_90m_year1994": -999.0,\n',
1232 '# "mean_topography_srtm_alt_1km_year1994": -999.0,\n',
1233 '# "max_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
1234 '# "min_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
1235 '# "stddev_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
1236 '# "climatic_zone_year2016": "6 (warm temperate dry)",\n',
1237 '# "htap_region_tier1_year2010": "11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)",\n',
1238 '# "dominant_landcover_year2012": "11 (Cropland, rainfed, herbaceous cover)",\n',
1239 '# "landcover_description_25km_year2012": "",\n',
1240 '# "dominant_ecoregion_year2017": "-1 (undefined)",\n',
1241 '# "ecoregion_description_25km_year2017": "",\n',
1242 '# "distance_to_major_road_year2020": -999.0,\n',
1243 '# "mean_stable_nightlights_1km_year2013": -999.0,\n',
1244 '# "mean_stable_nightlights_5km_year2013": -999.0,\n',
1245 '# "max_stable_nightlights_25km_year2013": -999.0,\n',
1246 '# "max_stable_nightlights_25km_year1992": -999.0,\n',
1247 '# "mean_population_density_250m_year2015": -1.0,\n',
1248 '# "mean_population_density_5km_year2015": -1.0,\n',
1249 '# "max_population_density_25km_year2015": -1.0,\n',
1250 '# "mean_population_density_250m_year1990": -1.0,\n',
1251 '# "mean_population_density_5km_year1990": -1.0,\n',
1252 '# "max_population_density_25km_year1990": -1.0,\n',
1253 '# "mean_nox_emissions_10km_year2015": -999.0,\n',
1254 '# "mean_nox_emissions_10km_year2000": -999.0,\n',
1255 '# "toar1_category": "unclassified",\n',
1256 '# "toar2_category": "suburban"\n',
1257 '# },\n',
1258 '# "changelog": []\n',
1259 '# },\n',
1260 '# "variable": {\n',
1261 '# "name": "o3",\n',
1262 '# "longname": "ozone",\n',
1263 '# "displayname": "Ozone",\n',
1264 '# "cf_standardname": "mole_fraction_of_ozone_in_air",\n',
1265 '# "units": "nmol mol-1",\n',
1266 '# "chemical_formula": "O3",\n',
1267 '# "id": 5\n',
1268 '# },\n',
1269 '# "programme": {\n',
1270 '# "id": 0,\n',
1271 '# "name": "",\n',
1272 '# "longname": "",\n',
1273 '# "homepage": "",\n',
1274 '# "description": ""\n',
1275 '# },\n',
1276 '# "roles": [\n',
1277 '# {\n',
1278 '# "id": 1,\n',
1279 '# "role": "resource provider",\n',
1280 '# "status": "active",\n',
1281 '# "contact": {\n',
1282 '# "id": 5,\n',
1283 '# "person": null,\n',
1284 '# "organisation": {\n',
1285 '# "id": 2,\n',
1286 '# "name": "FZJ",\n',
1287 '# "longname": "Forschungszentrum Jülich",\n',
1288 '# "kind": "research",\n',
1289 '# "city": "Jülich",\n',
1290 '# "postcode": "52425",\n',
1291 '# "street_address": "Wilhelm-Johnen-Straße",\n',
1292 '# "country": "Germany",\n',
1293 '# "homepage": "https://www.fz-juelich.de",\n',
1294 '# "contact_url": "mailto:toar-data@fz-juelich.de"\n',
1295 '# }\n',
1296 '# }\n',
1297 '# }\n',
1298 '# ],\n',
1299 '# "annotations": null,\n',
1300 '# "changelog": null,\n',
1301 '# "citation": "Forschungszentrum Jülich: time series of o3 at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00",\n',
1302 '# "attribution": "Test-Attributions to be announced",\n',
1303 '# "license": "This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/"\n',
1304 '#}\n',
1305 'datetime,value,timeseries_id\n',
1306 '2005-11-03 08:00:00+00:00,27.87,434870\n',
1307 '2009-04-01 02:00:00+00:00,7.87,434870\n',
1308 '2012-12-01 23:00:00+00:00,14.73,434870\n',
1309 '2012-12-02 00:00:00+00:00,14.74,434870\n',
1310 '2013-12-31 23:00:00+00:00,12.65,30890'])
1311 assert response.text == expected_resp
1314 def test_get_timeseries_merged_unknown_stationcode(self, client, db):
1315 with patch('toardb.timeseries.crud.dt.datetime', FixedDatetime):
1316 response = client.get("/data/timeseries_merged/?station_code=DEJUEL01&variable_id=5&fields=datetime,value,timeseries_id")
1317 expected_status_code = 404
1318 assert response.status_code == expected_status_code
1319 expected_resp = {'detail': "Metadata for station 'DEJUEL01' not found."}
1320 assert response.json() == expected_resp
1323 def test_get_timeseries_merged_no_table_entry(self, client, db):
1324 #still to be done in the code: introduce 406 (in this special case!)
1325 with patch('toardb.timeseries.crud.dt.datetime', FixedDatetime):
1326 response = client.get("/data/timeseries_merged/?station_code=SDZ54421&variable_id=7&fields=datetime,value,timeseries_id",
1327 headers={"email": "s.schroeder@fz-juelich.de"})
1328 expected_status_code = 406
1329 assert response.status_code == expected_status_code
1330 expected_resp = "time series not in yearly coverage table"
1331 assert response.json() == expected_resp
1334 # the data/map-endpoint is a special need of the analysis service
1335 def test_get_map_data(self, client, db):
1336 response = client.get("/data/map/?variable_id=7&daterange=2012-12-16T21:00,2012-12-17T06:00",
1337 headers={"email": "s.schroeder@fz-juelich.de"})
1338 expected_status_code = 200
1339 assert response.status_code == expected_status_code
1340 expected_resp = [{'timeseries_id': 1, 'value': 21.581},
1341 {'timeseries_id': 1, 'value': 13.734},
1342 {'timeseries_id': 1, 'value': 13.734},
1343 {'timeseries_id': 1, 'value': 7.848},
1344 {'timeseries_id': 1, 'value': 15.696},
1345 {'timeseries_id': 1, 'value': 11.772},
1346 {'timeseries_id': 1, 'value': 13.734},
1347 {'timeseries_id': 1, 'value': 19.62},
1348 {'timeseries_id': 1, 'value': 15.696},
1349 {'timeseries_id': 1, 'value': 5.886}]
1350 #There is no ordering of the resulting list elements:
1351 #Therefore, do the check "manually"
1352 got_resp = response.json()
1353 assert len(expected_resp) == len(got_resp)
1354 for elem in got_resp:
1355 assert elem in expected_resp
1358 def test_get_data_with_fields(self, client, db):
1359 fixed_time = datetime.datetime(2023, 7, 28, 12, 0, 0)
1360 with patch('toardb.timeseries.crud.dt.datetime') as mock_datetime:
1361 mock_datetime.now.return_value = fixed_time
1362 response = client.get("/data/timeseries/1?fields=datetime,value", headers={"email": "j.doe@fz-juelich.de"})
1363 expected_status_code = 200
1364 assert response.status_code == expected_status_code
1365 expected_resp = {'metadata': {'id': 1,
1366 'label': 'CMA',
1367 'order': 1,
1368 'sampling_frequency': 'hourly',
1369 'aggregation': 'mean',
1370 'data_start_date': '2003-09-07T15:30:00+00:00',
1371 'data_end_date': '2016-12-31T14:30:00+00:00',
1372 'data_origin': 'instrument',
1373 'data_origin_type': 'measurement',
1374 'provider_version': 'N/A',
1375 'sampling_height': 7.0,
1376 'additional_metadata': {'original_units': 'ppb'},
1377 'doi': '',
1378 'coverage': -1.0,
1379 'station': {'id': 2,
1380 'codes': ['SDZ54421'],
1381 'name': 'Shangdianzi',
1382 'coordinates': {'lat': 40.65,
1383 'lng': 117.106,
1384 'alt': 293.9},
1385 'coordinate_validation_status': 'not checked',
1386 'country': 'China',
1387 'state': 'Beijing Shi',
1388 'type': 'unknown',
1389 'type_of_area': 'unknown',
1390 'timezone': 'Asia/Shanghai',
1391 'additional_metadata': {'add_type': 'nature reservation'},
1392 'aux_images': [],
1393 'aux_docs': [],
1394 'aux_urls': [],
1395 'changelog': [],
1396 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
1397 'distance_to_major_road_year2020': -999.0,
1398 'dominant_ecoregion_year2017': '-1 (undefined)',
1399 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
1400 'ecoregion_description_25km_year2017': '',
1401 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
1402 'landcover_description_25km_year2012': '',
1403 'max_population_density_25km_year1990': -1.0,
1404 'max_population_density_25km_year2015': -1.0,
1405 'max_stable_nightlights_25km_year1992': -999.0,
1406 'max_stable_nightlights_25km_year2013': -999.0,
1407 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
1408 'mean_nox_emissions_10km_year2000': -999.0,
1409 'mean_nox_emissions_10km_year2015': -999.0,
1410 'mean_population_density_250m_year1990': -1.0,
1411 'mean_population_density_250m_year2015': -1.0,
1412 'mean_population_density_5km_year1990': -1.0,
1413 'mean_population_density_5km_year2015': -1.0,
1414 'mean_stable_nightlights_1km_year2013': -999.0,
1415 'mean_stable_nightlights_5km_year2013': -999.0,
1416 'mean_topography_srtm_alt_1km_year1994': -999.0,
1417 'mean_topography_srtm_alt_90m_year1994': -999.0,
1418 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
1419 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
1420 'toar1_category': 'unclassified',
1421 'toar2_category': 'suburban',
1422 }},
1423 'variable': {'name': 'toluene',
1424 'longname': 'toluene',
1425 'displayname': 'Toluene',
1426 'cf_standardname': 'mole_fraction_of_toluene_in_air',
1427 'units': 'nmol mol-1',
1428 'chemical_formula': 'C7H8',
1429 'id': 7},
1430 'programme': {'id': 0,
1431 'name': '',
1432 'longname': '',
1433 'homepage': '',
1434 'description': ''},
1435 'roles': [{'id': 2,
1436 'role': 'resource provider',
1437 'status': 'active',
1438 'contact': {'id': 4,
1439 'organisation': {'id': 1,
1440 'name': 'UBA',
1441 'longname': 'Umweltbundesamt',
1442 'kind': 'government',
1443 'city': 'Dessau-Roßlau',
1444 'postcode': '06844',
1445 'street_address': 'Wörlitzer Platz 1',
1446 'country': 'Germany',
1447 'homepage': 'https://www.umweltbundesamt.de',
1448 'contact_url': 'mailto:immission@uba.de'}}},
1449 {'id': 3,
1450 'role': 'principal investigator',
1451 'status': 'active',
1452 'contact': {'id': 3,
1453 'person': {'email': 's.schroeder@fz-juelich.de',
1454 'id': 3,
1455 'isprivate': False,
1456 'name': 'Sabine Schröder',
1457 'orcid': '0000-0002-0309-8010',
1458 'phone': '+49-2461-61-6397'}}}],
1459 'citation': 'Sabine Schröder: time series of toluene at '
1460 'Shangdianzi, accessed from the TOAR database on '
1461 '2023-07-28 12:00:00',
1462 'license': 'This data is published under a Creative Commons '
1463 'Attribution 4.0 International (CC BY 4.0). '
1464 'https://creativecommons.org/licenses/by/4.0/'},
1465 'data': [{'datetime': '2012-12-16T21:00:00+00:00', 'value': 21.581},
1466 {'datetime': '2012-12-16T22:00:00+00:00', 'value': 13.734},
1467 {'datetime': '2012-12-16T23:00:00+00:00', 'value': 13.734},
1468 {'datetime': '2012-12-17T00:00:00+00:00', 'value': 7.848},
1469 {'datetime': '2012-12-17T01:00:00+00:00', 'value': 15.696},
1470 {'datetime': '2012-12-17T02:00:00+00:00', 'value': 11.772},
1471 {'datetime': '2012-12-17T03:00:00+00:00', 'value': 13.734},
1472 {'datetime': '2012-12-17T04:00:00+00:00', 'value': 19.62},
1473 {'datetime': '2012-12-17T05:00:00+00:00', 'value': 15.696},
1474 {'datetime': '2012-12-17T06:00:00+00:00', 'value': 5.886}]
1475 }
1476 assert response.json() == expected_resp
1479 def test_get_data_as_csv_with_fields(self, client, db):
1480 fixed_time = datetime.datetime(2023, 7, 28, 12, 0, 0)
1481 with patch('toardb.timeseries.crud.dt.datetime') as mock_datetime:
1482 mock_datetime.now.return_value = fixed_time
1483 response = client.get("/data/timeseries/1?format=csv&fields=datetime,value")
1484 expected_status_code = 200
1485 assert response.status_code == expected_status_code
1486 expected_resp = ''.join(['#{\n',
1487 '# "id": 1,\n',
1488 '# "label": "CMA",\n',
1489 '# "order": 1,\n',
1490 '# "sampling_frequency": "hourly",\n',
1491 '# "aggregation": "mean",\n',
1492 '# "data_start_date": "2003-09-07T15:30:00+00:00",\n',
1493 '# "data_end_date": "2016-12-31T14:30:00+00:00",\n',
1494 '# "data_origin": "instrument",\n',
1495 '# "data_origin_type": "measurement",\n',
1496 '# "provider_version": "N/A",\n',
1497 '# "sampling_height": 7.0,\n',
1498 '# "additional_metadata": {\n',
1499 '# "original_units": "ppb"\n',
1500 '# },\n',
1501 '# "data_license_accepted": null,\n',
1502 '# "dataset_approved_by_provider": null,\n',
1503 '# "doi": "",\n',
1504 '# "coverage": -1.0,\n',
1505 '# "station": {\n',
1506 '# "id": 2,\n',
1507 '# "codes": [\n',
1508 '# "SDZ54421"\n',
1509 '# ],\n',
1510 '# "name": "Shangdianzi",\n',
1511 '# "coordinates": {\n',
1512 '# "lat": 40.65,\n',
1513 '# "lng": 117.106,\n',
1514 '# "alt": 293.9\n',
1515 '# },\n',
1516 '# "coordinate_validation_status": "not checked",\n',
1517 '# "country": "China",\n',
1518 '# "state": "Beijing Shi",\n',
1519 '# "type": "unknown",\n',
1520 '# "type_of_area": "unknown",\n',
1521 '# "timezone": "Asia/Shanghai",\n',
1522 '# "additional_metadata": {\n',
1523 '# "add_type": "nature reservation"\n',
1524 '# },\n',
1525 '# "roles": null,\n',
1526 '# "annotations": null,\n',
1527 '# "aux_images": [],\n',
1528 '# "aux_docs": [],\n',
1529 '# "aux_urls": [],\n',
1530 '# "globalmeta": {\n',
1531 '# "mean_topography_srtm_alt_90m_year1994": -999.0,\n',
1532 '# "mean_topography_srtm_alt_1km_year1994": -999.0,\n',
1533 '# "max_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
1534 '# "min_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
1535 '# "stddev_topography_srtm_relative_alt_5km_year1994": -999.0,\n',
1536 '# "climatic_zone_year2016": "6 (warm temperate dry)",\n',
1537 '# "htap_region_tier1_year2010": "11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)",\n',
1538 '# "dominant_landcover_year2012": "11 (Cropland, rainfed, herbaceous cover)",\n',
1539 '# "landcover_description_25km_year2012": "",\n',
1540 '# "dominant_ecoregion_year2017": "-1 (undefined)",\n',
1541 '# "ecoregion_description_25km_year2017": "",\n',
1542 '# "distance_to_major_road_year2020": -999.0,\n',
1543 '# "mean_stable_nightlights_1km_year2013": -999.0,\n',
1544 '# "mean_stable_nightlights_5km_year2013": -999.0,\n',
1545 '# "max_stable_nightlights_25km_year2013": -999.0,\n',
1546 '# "max_stable_nightlights_25km_year1992": -999.0,\n',
1547 '# "mean_population_density_250m_year2015": -1.0,\n',
1548 '# "mean_population_density_5km_year2015": -1.0,\n',
1549 '# "max_population_density_25km_year2015": -1.0,\n',
1550 '# "mean_population_density_250m_year1990": -1.0,\n',
1551 '# "mean_population_density_5km_year1990": -1.0,\n',
1552 '# "max_population_density_25km_year1990": -1.0,\n',
1553 '# "mean_nox_emissions_10km_year2015": -999.0,\n',
1554 '# "mean_nox_emissions_10km_year2000": -999.0,\n',
1555 '# "toar1_category": "unclassified",\n',
1556 '# "toar2_category": "suburban"\n',
1557 '# },\n',
1558 '# "changelog": []\n',
1559 '# },\n',
1560 '# "variable": {\n',
1561 '# "name": "toluene",\n',
1562 '# "longname": "toluene",\n',
1563 '# "displayname": "Toluene",\n',
1564 '# "cf_standardname": "mole_fraction_of_toluene_in_air",\n',
1565 '# "units": "nmol mol-1",\n',
1566 '# "chemical_formula": "C7H8",\n',
1567 '# "id": 7\n',
1568 '# },\n',
1569 '# "programme": {\n',
1570 '# "id": 0,\n',
1571 '# "name": "",\n',
1572 '# "longname": "",\n',
1573 '# "homepage": "",\n',
1574 '# "description": ""\n',
1575 '# },\n',
1576 '# "roles": [\n',
1577 '# {\n',
1578 '# "id": 2,\n',
1579 '# "role": "resource provider",\n',
1580 '# "status": "active",\n',
1581 '# "contact": {\n',
1582 '# "id": 4,\n',
1583 '# "person": null,\n',
1584 '# "organisation": {\n',
1585 '# "id": 1,\n',
1586 '# "name": "UBA",\n',
1587 '# "longname": "Umweltbundesamt",\n',
1588 '# "kind": "government",\n',
1589 '# "city": "Dessau-Roßlau",\n',
1590 '# "postcode": "06844",\n',
1591 '# "street_address": "Wörlitzer Platz 1",\n',
1592 '# "country": "Germany",\n',
1593 '# "homepage": "https://www.umweltbundesamt.de",\n',
1594 '# "contact_url": "mailto:immission@uba.de"\n',
1595 '# }\n',
1596 '# }\n',
1597 '# },\n',
1598 '# {\n',
1599 '# "id": 3,\n',
1600 '# "role": "principal investigator",\n',
1601 '# "status": "active",\n',
1602 '# "contact": {\n',
1603 '# "id": 3,\n',
1604 '# "person": {\n',
1605 '# "id": 3,\n',
1606 '# "name": "Sabine Schröder",\n',
1607 '# "email": "s.schroeder@fz-juelich.de",\n',
1608 '# "phone": "+49-2461-61-6397",\n',
1609 '# "orcid": "0000-0002-0309-8010",\n',
1610 '# "isprivate": false\n',
1611 '# },\n',
1612 '# "organisation": null\n',
1613 '# }\n',
1614 '# }\n',
1615 '# ],\n',
1616 '# "annotations": null,\n',
1617 '# "changelog": null,\n',
1618 '# "citation": "Sabine Schröder: time series of toluene at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00",\n',
1619 '# "attribution": null,\n',
1620 '# "license": "This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/"\n',
1621 '#}\n',
1622 'datetime,value\n',
1623 '2012-12-16 21:00:00+00:00,21.581\n',
1624 '2012-12-16 22:00:00+00:00,13.734\n',
1625 '2012-12-16 23:00:00+00:00,13.734\n',
1626 '2012-12-17 00:00:00+00:00,7.848\n',
1627 '2012-12-17 01:00:00+00:00,15.696\n',
1628 '2012-12-17 02:00:00+00:00,11.772\n',
1629 '2012-12-17 03:00:00+00:00,13.734\n',
1630 '2012-12-17 04:00:00+00:00,19.62\n',
1631 '2012-12-17 05:00:00+00:00,15.696\n',
1632 '2012-12-17 06:00:00+00:00,5.886'])
1633 assert response.text == expected_resp
1636 def test_get_no_data_with_variable_and_timerange(self, client, db):
1637 # see: https://gitlab.jsc.fz-juelich.de/esde/toar-data/toardb_fastapi/-/issues/171
1638 response = client.get("/data/map/?variable_id=25&daterange=2012-12-16T21:00,2012-12-17T06:00",
1639 headers={"email": "s.schroeder@fz-juelich.de"})
1640 expected_status_code = 404
1641 assert response.status_code == expected_status_code
1642 expected_response = {'detail': 'Data not found.'}
1643 assert response.json() == expected_response
1646 def test_insert_new_wrong_credentials(self, client, db):
1647 response = client.post("/data/timeseries/?toarqc_config_type=standard",
1648 files={"file": open("tests/fixtures/data/toluene_SDZ54421_2013_2013_v1-0.dat", "rb")},
1649 headers={"email": "j.doe@fz-juelich.de"})
1650 expected_status_code=401
1651 assert response.status_code == expected_status_code
1652 expected_resp = {'detail': 'Unauthorized.'}
1653 assert response.json() == expected_resp
1656 def test_insert_new_without_credentials(self, client, db):
1657 response = client.post("/data/timeseries/?toarqc_config_type=standard",
1658 files={"file": open("tests/fixtures/data/toluene_SDZ54421_2013_2013_v1-0.dat", "rb")})
1659 expected_status_code=401
1660 assert response.status_code == expected_status_code
1661 expected_resp = {'detail': 'Unauthorized.'}
1662 assert response.json() == expected_resp
1665 def test_insert_new(self, client, db):
1666 response = client.post("/data/timeseries/?toarqc_config_type=standard",
1667 files={"file": open("tests/fixtures/data/toluene_SDZ54421_2013_2013_v1-0.dat", "rb")},
1668 headers={"email": "s.schroeder@fz-juelich.de"})
1669 expected_status_code = 200
1670 assert response.status_code == expected_status_code
1671 expected_resp = {'detail': {'message': 'Data successfully inserted.'}}
1672 assert response.json() == expected_resp
1675 def test_insert_duplicate(self, client, db):
1676 response = client.post("/data/timeseries/?toarqc_config_type=standard",
1677 files={"file": open("tests/fixtures/data/toluene_SDZ54421_2012_2012_v1-0.dat", "rb")},
1678 headers={"email": "s.schroeder@fz-juelich.de"})
1679 expected_status_code = 400
1680 assert response.status_code == expected_status_code
1681 expected_resp = {'detail': 'Data for timeseries already registered.'}
1682 assert response.json() == expected_resp
1685 def test_insert_new_as_bulk(self, client, db):
1686 df = pd.read_csv("tests/fixtures/data/toluene_SDZ54421_2013_2013_v1-0.dat",
1687 delimiter=';', comment='#', header=None, names=["datetime", "value", "flags"], parse_dates=["datetime"])
1688 df['datetime'] = df['datetime'].dt.tz_localize('UTC')
1689 df['version'] = '000001.000000.00000000000000'
1690 df['timeseries_id'] = 2
1691 df_json=df.to_json(orient='records', date_format='iso')
1692 response = client.post("/data/timeseries/bulk/", data=df_json, headers={"email": "s.schroeder@fz-juelich.de"})
1693 expected_status_code = 200
1694 assert response.status_code == expected_status_code
1695 expected_resp = {'detail': {'message': 'Data successfully inserted.'}}
1696 assert response.json() == expected_resp
1699 """def test_insert_missing_id(self, client, db):
1700 response = client.post("/data/timeseries/", files={"file": open("tests/fixtures/data/o3_SDZ54421_2012_2012_v1-0.dat", "rb")})
1701 expected_status_code = 400
1702 assert response.status_code == expected_status_code
1703 expected_resp = {'detail': '"Timeseries not found for station SDZ54421, variable o3, label CMA"'}
1704 assert response.json() == expected_resp"""
1707 def test_get_data_not_found(self, client, db):
1708 response = client.get("/data/timeseries/-1")
1709 expected_repsonse_code = 404
1710 assert response.status_code == expected_repsonse_code
1711 expected_response = {"detail": "Data not found."}
1712 assert response.json() == expected_response
1715 def test_get_data_invalid_type(self, client, db):
1716 response = client.get("/data/timeseries/A")
1717 expected_repsonse_code = 422
1718 assert response.status_code == expected_repsonse_code
1719 """expected_response = {"detail": "Data not found."}
1720 assert response.json() == expected_response"""
1723 def test_get_data2(self, client, db):
1724 fixed_time = datetime.datetime(2023, 7, 28, 12, 0, 0)
1725 with patch('toardb.timeseries.crud.dt.datetime') as mock_datetime:
1726 mock_datetime.now.return_value = fixed_time
1727 response = client.get("/data/timeseries/id/1", headers={"email": "s.schroeder@fz-juelich.de"})
1728 expected_status_code = 200
1729 assert response.status_code == expected_status_code
1730 expected_response = {'metadata': {'id': 1, 'label': 'CMA', 'order': 1, 'sampling_frequency': 'hourly', 'aggregation': 'mean',
1731 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/',
1732 'doi': '',
1733 'data_start_date': '2003-09-07T15:30:00+00:00', 'data_end_date': '2016-12-31T14:30:00+00:00', 'coverage': -1.0, 'data_origin': 'instrument',
1734 'data_origin_type': 'measurement', 'provider_version': 'N/A', 'sampling_height': 7.0, 'additional_metadata': {'original_units': 'ppb'},
1735 'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9},
1736 'coordinate_validation_status': 'not checked', 'country': 'China', 'state': 'Beijing Shi', 'type': 'unknown',
1737 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai',
1738 'additional_metadata': {'add_type': 'nature reservation'},
1739 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'changelog': [],
1740 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
1741 'distance_to_major_road_year2020': -999.0,
1742 'dominant_ecoregion_year2017': '-1 (undefined)',
1743 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
1744 'ecoregion_description_25km_year2017': '',
1745 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
1746 'landcover_description_25km_year2012': '',
1747 'max_population_density_25km_year1990': -1.0,
1748 'max_population_density_25km_year2015': -1.0,
1749 'max_stable_nightlights_25km_year1992': -999.0,
1750 'max_stable_nightlights_25km_year2013': -999.0,
1751 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
1752 'mean_nox_emissions_10km_year2000': -999.0,
1753 'mean_nox_emissions_10km_year2015': -999.0,
1754 'mean_population_density_250m_year1990': -1.0,
1755 'mean_population_density_250m_year2015': -1.0,
1756 'mean_population_density_5km_year1990': -1.0,
1757 'mean_population_density_5km_year2015': -1.0,
1758 'mean_stable_nightlights_1km_year2013': -999.0,
1759 'mean_stable_nightlights_5km_year2013': -999.0,
1760 'mean_topography_srtm_alt_1km_year1994': -999.0,
1761 'mean_topography_srtm_alt_90m_year1994': -999.0,
1762 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
1763 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
1764 'toar1_category': 'unclassified',
1765 'toar2_category': 'suburban',
1766 }},
1767 'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene',
1768 'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1', 'chemical_formula': 'C7H8', 'id': 7},
1769 'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''},
1770 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active',
1771 'contact': {'id': 4, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt',
1772 'kind': 'government', 'city': 'Dessau-Roßlau', 'postcode': '06844', 'street_address': 'Wörlitzer Platz 1',
1773 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}},
1774 {'id': 3, 'role': 'principal investigator', 'status': 'active',
1775 'contact': {'id': 3, 'person': {'email': 's.schroeder@fz-juelich.de','id': 3, 'isprivate': False,
1776 'name': 'Sabine Schröder', 'orcid': '0000-0002-0309-8010', 'phone': '+49-2461-61-6397'}}}],
1777 'citation': 'Sabine Schröder: time series of toluene at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00'},
1778 'data': [{'datetime': '2012-12-16T21:00:00+00:00', 'value': 21.581, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'},
1779 {'datetime': '2012-12-16T22:00:00+00:00', 'value': 13.734, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'},
1780 {'datetime': '2012-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'},
1781 {'datetime': '2012-12-17T00:00:00+00:00', 'value': 7.848, 'flags': 'OK validated QC passed', 'timeseries_id': 1, 'version': '1.0'},
1782 {'datetime': '2012-12-17T01:00:00+00:00', 'value': 15.696, 'flags': 'OK validated QC passed', 'timeseries_id': 1, 'version': '1.0'},
1783 {'datetime': '2012-12-17T02:00:00+00:00', 'value': 11.772, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'},
1784 {'datetime': '2012-12-17T03:00:00+00:00', 'value': 13.734, 'flags': 'OK validated modified', 'timeseries_id': 1, 'version': '1.0'},
1785 {'datetime': '2012-12-17T04:00:00+00:00', 'value': 19.62, 'flags': 'OK validated QC passed', 'timeseries_id': 1, 'version': '1.0'},
1786 {'datetime': '2012-12-17T05:00:00+00:00', 'value': 15.696, 'flags': 'OK validated modified', 'timeseries_id': 1, 'version': '1.0'},
1787 {'datetime': '2012-12-17T06:00:00+00:00', 'value': 5.886, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'}]}
1788 assert response.json() == expected_response
1791 def test_get_data2_not_found(self, client, db):
1792 response = client.get("/data/timeseries/id/-1", headers={"email": "s.schroeder@fz-juelich.de"})
1793 expected_repsonse_code = 404
1794 assert response.status_code == expected_repsonse_code
1795 expected_response = {"detail": "Data not found."}
1796 assert response.json() == expected_response
1798 def test_get_data2_invalid_type(self, client, db):
1799 response = client.get("/data/timeseries/id/A")
1800 expected_repsonse_code = 422
1801 assert response.status_code == expected_repsonse_code
1802 """expected_response = {"detail": "Data not found."}
1803 assert response.json() == expected_response"""
1806 def test_get_data_with_daterange(self, client, db):
1807 with patch('toardb.timeseries.crud.dt.datetime', FixedDatetime):
1808 response = client.get("/data/timeseries/id/2?flags=AllOK&daterange=2012-12-16%2023:00,%202012-12-17%2006:00",
1809 headers={"email": "s.schroeder@fz-juelich.de"})
1810 expected_status_code = 200
1811 assert response.status_code == expected_status_code
1812 expected_response = {'metadata': {'id': 2,
1813 'label': 'CMA',
1814 'order': 1,
1815 'sampling_frequency': 'hourly',
1816 'aggregation': 'mean',
1817 'data_start_date': '2003-09-07T15:30:00+00:00',
1818 'data_end_date': '2016-12-31T14:30:00+00:00',
1819 'data_origin': 'instrument',
1820 'data_origin_type': 'measurement',
1821 'provider_version': 'N/A',
1822 'sampling_height': 7.0,
1823 'additional_metadata': {'original_units': {'since_19740101000000': 'nmol/mol'},
1824 'measurement_method': 'uv_abs',
1825 'absorption_cross_section': 0,
1826 'ebas_metadata_19740101000000_29y': {'Submitter': 'Unknown, Lady, lady.unknown@unknown.com, some long division name, SHORT, , 111 Streetname, , zipcode, Boulder, CO, USA', 'Data level': '2', 'Frameworks': 'GAW-WDCRG NOAA-ESRL', 'Station code': 'XXX', 'Station name': 'Secret'}},
1827 'doi': '',
1828 'coverage': -1.0,
1829 'station': {'id': 3,
1830 'codes': ['China_test8'],
1831 'name': 'Test_China',
1832 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0},
1833 'coordinate_validation_status': 'not checked',
1834 'country': 'China',
1835 'state': 'Shandong Sheng',
1836 'type': 'unknown',
1837 'type_of_area': 'unknown',
1838 'timezone': 'Asia/Shanghai',
1839 'additional_metadata': {},
1840 'aux_images': [],
1841 'aux_docs': [],
1842 'aux_urls': [],
1843 'changelog': [],
1844 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
1845 'distance_to_major_road_year2020': -999.0,
1846 'dominant_ecoregion_year2017': '-1 (undefined)',
1847 'dominant_landcover_year2012': '10 (Cropland, rainfed)',
1848 'ecoregion_description_25km_year2017': '',
1849 'landcover_description_25km_year2012': '',
1850 'htap_region_tier1_year2010': '10 (SAF Sub Saharan/sub Sahel Africa)',
1851 'max_population_density_25km_year1990': -1.0,
1852 'max_population_density_25km_year2015': -1.0,
1853 'max_stable_nightlights_25km_year1992': -999.0,
1854 'max_stable_nightlights_25km_year2013': -999.0,
1855 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
1856 'mean_nox_emissions_10km_year2000': -999.0,
1857 'mean_nox_emissions_10km_year2015': -999.0,
1858 'mean_population_density_250m_year1990': -1.0,
1859 'mean_population_density_250m_year2015': -1.0,
1860 'mean_population_density_5km_year1990': -1.0,
1861 'mean_population_density_5km_year2015': -1.0,
1862 'mean_stable_nightlights_1km_year2013': -999.0,
1863 'mean_stable_nightlights_5km_year2013': -999.0,
1864 'mean_topography_srtm_alt_1km_year1994': -999.0,
1865 'mean_topography_srtm_alt_90m_year1994': -999.0,
1866 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
1867 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
1868 'toar1_category': 'unclassified',
1869 'toar2_category': 'suburban',
1870 }},
1871 'variable': {'name': 'o3',
1872 'longname': 'ozone',
1873 'displayname': 'Ozone',
1874 'cf_standardname': 'mole_fraction_of_ozone_in_air',
1875 'units': 'nmol mol-1',
1876 'chemical_formula': 'O3',
1877 'id': 5},
1878 'programme': {'id': 0,
1879 'name': '',
1880 'longname': '',
1881 'homepage': '',
1882 'description': ''},
1883 'roles': [{'id': 1,
1884 'role': 'resource provider',
1885 'status': 'active',
1886 'contact': {'id': 5,
1887 'organisation': {'id': 2,
1888 'name': 'FZJ',
1889 'longname': 'Forschungszentrum Jülich',
1890 'kind': 'research',
1891 'city': 'Jülich',
1892 'postcode': '52425',
1893 'street_address': 'Wilhelm-Johnen-Straße',
1894 'country': 'Germany',
1895 'homepage': 'https://www.fz-juelich.de',
1896 'contact_url': 'mailto:toar-data@fz-juelich.de'}}}],
1897 'citation': 'Forschungszentrum Jülich: time series of o3 at Test_China, accessed from the TOAR database on 2023-07-28 12:00:00',
1898 'attribution': 'Test-Attributions to be announced',
1899 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/'},
1900 'data': [{'datetime': '2012-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '1.0'},
1901 {'datetime': '2012-12-17T00:00:00+00:00', 'value': 7.848, 'flags': 'OK validated modified', 'timeseries_id': 2, 'version': '1.0'},
1902 {'datetime': '2012-12-17T01:00:00+00:00', 'value': 15.696, 'flags': 'OK validated modified', 'timeseries_id': 2, 'version': '1.0'},
1903 {'datetime': '2012-12-17T02:00:00+00:00', 'value': 11.772, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '1.0'},
1904 {'datetime': '2012-12-17T03:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '1.0'},
1905 {'datetime': '2012-12-17T04:00:00+00:00', 'value': 19.62, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '1.0'}]}
1906 assert response.json() == expected_response
1909 def test_get_data_with_specific_flags(self, client, db):
1910 with patch('toardb.timeseries.crud.dt.datetime', FixedDatetime):
1911 response = client.get("/data/timeseries/id/2?flags=OKValidatedVerified,OKValidatedQCPassed&daterange=2012-12-16%2023:00,%202012-12-17%2006:00",
1912 headers={"email": "s.schroeder@fz-juelich.de"})
1913 expected_status_code = 200
1914 assert response.status_code == expected_status_code
1915 expected_response = {'metadata': {'id': 2,
1916 'label': 'CMA',
1917 'order': 1,
1918 'sampling_frequency': 'hourly',
1919 'aggregation': 'mean',
1920 'data_start_date': '2003-09-07T15:30:00+00:00',
1921 'data_end_date': '2016-12-31T14:30:00+00:00',
1922 'data_origin': 'instrument',
1923 'data_origin_type': 'measurement',
1924 'provider_version': 'N/A',
1925 'sampling_height': 7.0,
1926 'additional_metadata': {'original_units': {'since_19740101000000': 'nmol/mol'},
1927 'measurement_method': 'uv_abs',
1928 'absorption_cross_section': 0,
1929 'ebas_metadata_19740101000000_29y': {'Submitter': 'Unknown, Lady, lady.unknown@unknown.com, some long division name, SHORT, , 111 Streetname, , zipcode, Boulder, CO, USA', 'Data level': '2', 'Frameworks': 'GAW-WDCRG NOAA-ESRL', 'Station code': 'XXX', 'Station name': 'Secret'}},
1930 'doi': '',
1931 'coverage': -1.0,
1932 'station': {'id': 3,
1933 'codes': ['China_test8'],
1934 'name': 'Test_China',
1935 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0},
1936 'coordinate_validation_status': 'not checked',
1937 'country': 'China',
1938 'state': 'Shandong Sheng',
1939 'type': 'unknown',
1940 'type_of_area': 'unknown',
1941 'timezone': 'Asia/Shanghai',
1942 'additional_metadata': {},
1943 'aux_images': [],
1944 'aux_docs': [],
1945 'aux_urls': [],
1946 'changelog': [],
1947 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
1948 'distance_to_major_road_year2020': -999.0,
1949 'dominant_ecoregion_year2017': '-1 (undefined)',
1950 'dominant_landcover_year2012': '10 (Cropland, rainfed)',
1951 'ecoregion_description_25km_year2017': '',
1952 'htap_region_tier1_year2010': '10 (SAF Sub Saharan/sub Sahel Africa)',
1953 'landcover_description_25km_year2012': '',
1954 'max_population_density_25km_year1990': -1.0,
1955 'max_population_density_25km_year2015': -1.0,
1956 'max_stable_nightlights_25km_year1992': -999.0,
1957 'max_stable_nightlights_25km_year2013': -999.0,
1958 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
1959 'mean_nox_emissions_10km_year2000': -999.0,
1960 'mean_nox_emissions_10km_year2015': -999.0,
1961 'mean_population_density_250m_year1990': -1.0,
1962 'mean_population_density_250m_year2015': -1.0,
1963 'mean_population_density_5km_year1990': -1.0,
1964 'mean_population_density_5km_year2015': -1.0,
1965 'mean_stable_nightlights_1km_year2013': -999.0,
1966 'mean_stable_nightlights_5km_year2013': -999.0,
1967 'mean_topography_srtm_alt_1km_year1994': -999.0,
1968 'mean_topography_srtm_alt_90m_year1994': -999.0,
1969 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
1970 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
1971 'toar1_category': 'unclassified',
1972 'toar2_category': 'suburban'
1973 }
1974 },
1975 'variable': {'name': 'o3',
1976 'longname': 'ozone',
1977 'displayname': 'Ozone',
1978 'cf_standardname': 'mole_fraction_of_ozone_in_air',
1979 'units': 'nmol mol-1',
1980 'chemical_formula': 'O3',
1981 'id': 5},
1982 'programme': {'id': 0,
1983 'name': '',
1984 'longname': '',
1985 'homepage': '',
1986 'description': ''},
1987 'roles': [{'id': 1,
1988 'role': 'resource provider',
1989 'status': 'active',
1990 'contact': {'id': 5,
1991 'organisation': {'id': 2,
1992 'name': 'FZJ',
1993 'longname': 'Forschungszentrum Jülich',
1994 'kind': 'research',
1995 'city': 'Jülich',
1996 'postcode': '52425',
1997 'street_address': 'Wilhelm-Johnen-Straße',
1998 'country': 'Germany',
1999 'homepage': 'https://www.fz-juelich.de',
2000 'contact_url': 'mailto:toar-data@fz-juelich.de'}}}],
2001 'citation': 'Forschungszentrum Jülich: time series of o3 at Test_China, accessed from the TOAR database on 2023-07-28 12:00:00',
2002 'attribution': 'Test-Attributions to be announced',
2003 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/'},
2004 'data': [{'datetime': '2012-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '1.0'},
2005 {'datetime': '2012-12-17T02:00:00+00:00', 'value': 11.772, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '1.0'},
2006 {'datetime': '2012-12-17T03:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '1.0'},
2007 {'datetime': '2012-12-17T04:00:00+00:00', 'value': 19.62, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '1.0'}]}
2008 assert response.json() == expected_response
2011 def test_get_data_with_staging(self, client, db):
2012 with patch('toardb.timeseries.crud.dt.datetime', FixedDatetime):
2013 response = client.get("/data/timeseries_with_staging/id/2", headers={"email": "s.schroeder@fz-juelich.de"})
2014 expected_status_code = 200
2015 assert response.status_code == expected_status_code
2016 expected_response = {'metadata': {'id': 2,
2017 'label': 'CMA',
2018 'order': 1,
2019 'sampling_frequency': 'hourly',
2020 'aggregation': 'mean',
2021 'data_start_date': '2003-09-07T15:30:00+00:00',
2022 'data_end_date': '2016-12-31T14:30:00+00:00',
2023 'data_origin': 'instrument',
2024 'data_origin_type': 'measurement',
2025 'provider_version': 'N/A',
2026 'sampling_height': 7.0,
2027 'additional_metadata':
2028 {'original_units': {'since_19740101000000': 'nmol/mol'},
2029 'measurement_method': 'uv_abs',
2030 'absorption_cross_section': 0,
2031 'ebas_metadata_19740101000000_29y':
2032 {'Submitter': 'Unknown, Lady, lady.unknown@unknown.com, '
2033 'some long division name, SHORT, , '
2034 '111 Streetname, , zipcode, Boulder, CO, USA',
2035 'Data level': '2',
2036 'Frameworks': 'GAW-WDCRG NOAA-ESRL',
2037 'Station code': 'XXX',
2038 'Station name': 'Secret'}},
2039 'doi': '',
2040 'coverage': -1.0,
2041 'station': {'id': 3,
2042 'codes': ['China_test8'],
2043 'name': 'Test_China',
2044 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0},
2045 'coordinate_validation_status': 'not checked',
2046 'country': 'China',
2047 'state': 'Shandong Sheng',
2048 'type': 'unknown',
2049 'type_of_area': 'unknown',
2050 'timezone': 'Asia/Shanghai',
2051 'additional_metadata': {},
2052 'aux_images': [],
2053 'aux_docs': [],
2054 'aux_urls': [],
2055 'changelog': [],
2056 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
2057 'distance_to_major_road_year2020': -999.0,
2058 'dominant_ecoregion_year2017': '-1 (undefined)',
2059 'dominant_landcover_year2012': '10 (Cropland, rainfed)',
2060 'ecoregion_description_25km_year2017': '',
2061 'htap_region_tier1_year2010': '10 (SAF Sub Saharan/sub Sahel Africa)',
2062 'landcover_description_25km_year2012': '',
2063 'max_population_density_25km_year1990': -1.0,
2064 'max_population_density_25km_year2015': -1.0,
2065 'max_stable_nightlights_25km_year1992': -999.0,
2066 'max_stable_nightlights_25km_year2013': -999.0,
2067 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
2068 'mean_nox_emissions_10km_year2000': -999.0,
2069 'mean_nox_emissions_10km_year2015': -999.0,
2070 'mean_population_density_250m_year1990': -1.0,
2071 'mean_population_density_250m_year2015': -1.0,
2072 'mean_population_density_5km_year1990': -1.0,
2073 'mean_population_density_5km_year2015': -1.0,
2074 'mean_stable_nightlights_1km_year2013': -999.0,
2075 'mean_stable_nightlights_5km_year2013': -999.0,
2076 'mean_topography_srtm_alt_1km_year1994': -999.0,
2077 'mean_topography_srtm_alt_90m_year1994': -999.0,
2078 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
2079 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
2080 'toar1_category': 'unclassified',
2081 'toar2_category': 'suburban'
2082 }
2083 },
2084 'variable': {'name': 'o3',
2085 'longname': 'ozone',
2086 'displayname': 'Ozone',
2087 'cf_standardname': 'mole_fraction_of_ozone_in_air',
2088 'units': 'nmol mol-1',
2089 'chemical_formula': 'O3',
2090 'id': 5},
2091 'programme': {'id': 0,
2092 'name': '',
2093 'longname': '',
2094 'homepage': '',
2095 'description': ''},
2096 'roles': [{'id': 1,
2097 'role': 'resource provider',
2098 'status': 'active',
2099 'contact': {'id': 5,
2100 'organisation':
2101 {'id': 2,
2102 'name': 'FZJ',
2103 'longname': 'Forschungszentrum Jülich',
2104 'kind': 'research',
2105 'city': 'Jülich',
2106 'postcode': '52425',
2107 'street_address': 'Wilhelm-Johnen-Straße',
2108 'country': 'Germany',
2109 'homepage': 'https://www.fz-juelich.de',
2110 'contact_url': 'mailto:toar-data@fz-juelich.de'}}}],
2111 'citation': 'Forschungszentrum Jülich: time series of o3 at '
2112 'Test_China, accessed from the TOAR database on '
2113 '2023-07-28 12:00:00',
2114 'attribution': 'Test-Attributions to be announced',
2115 'license': 'This data is published under a Creative Commons '
2116 'Attribution 4.0 International (CC BY 4.0). '
2117 'https://creativecommons.org/licenses/by/4.0/'
2118 },
2119 'data': [{'datetime': '2012-12-16T21:00:00+00:00', 'value': 21.581, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '1.0'},
2120 {'datetime': '2012-12-16T22:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '1.0'},
2121 {'datetime': '2012-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '1.0'},
2122 {'datetime': '2012-12-17T00:00:00+00:00', 'value': 7.848, 'flags': 'OK validated modified', 'timeseries_id': 2, 'version': '1.0'},
2123 {'datetime': '2012-12-17T01:00:00+00:00', 'value': 15.696, 'flags': 'OK validated modified', 'timeseries_id': 2, 'version': '1.0'},
2124 {'datetime': '2012-12-17T02:00:00+00:00', 'value': 11.772, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '1.0'},
2125 {'datetime': '2012-12-17T03:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '1.0'},
2126 {'datetime': '2012-12-17T04:00:00+00:00', 'value': 19.62, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '1.0'},
2127 {'datetime': '2013-12-16T21:00:00+00:00', 'value': 21.581, 'flags': 'questionable validated confirmed', 'timeseries_id': 2, 'version': '1.0'},
2128 {'datetime': '2013-12-16T22:00:00+00:00', 'value': 13.734, 'flags': 'questionable validated confirmed', 'timeseries_id': 2, 'version': '1.0'},
2129 {'datetime': '2013-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'questionable validated confirmed', 'timeseries_id': 2, 'version': '1.0'},
2130 {'datetime': '2013-12-17T00:00:00+00:00', 'value': 7.848, 'flags': 'questionable validated unconfirmed', 'timeseries_id': 2, 'version': '1.0'},
2131 {'datetime': '2013-12-17T01:00:00+00:00', 'value': 15.696, 'flags': 'questionable validated unconfirmed', 'timeseries_id': 2, 'version': '1.0'},
2132 {'datetime': '2013-12-17T02:00:00+00:00', 'value': 11.772, 'flags': 'questionable validated confirmed', 'timeseries_id': 2, 'version': '1.0'},
2133 {'datetime': '2013-12-17T03:00:00+00:00', 'value': 13.734, 'flags': 'questionable validated flagged', 'timeseries_id': 2, 'version': '1.0'},
2134 {'datetime': '2013-12-17T04:00:00+00:00', 'value': 19.62, 'flags': 'questionable validated unconfirmed', 'timeseries_id': 2, 'version': '1.0'},
2135 {'datetime': '2013-12-17T05:00:00+00:00', 'value': 15.696, 'flags': 'questionable validated flagged', 'timeseries_id': 2, 'version': '1.0'},
2136 {'datetime': '2013-12-17T06:00:00+00:00', 'value': 5.886, 'flags': 'questionable validated confirmed', 'timeseries_id': 2, 'version': '1.0'},
2137 {'datetime': '2014-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '0.0.20141217235502'},
2138 {'datetime': '2014-12-17T00:00:00+00:00', 'value': 7.848, 'flags': 'OK validated modified', 'timeseries_id': 2, 'version': '0.0.20141217235502'},
2139 {'datetime': '2014-12-17T01:00:00+00:00', 'value': 15.696, 'flags': 'OK validated modified', 'timeseries_id': 2, 'version': '0.0.20141217235502'},
2140 {'datetime': '2014-12-17T02:00:00+00:00', 'value': 11.772, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '0.0.20141217235502'},
2141 {'datetime': '2014-12-17T03:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '0.0.20141217235502'},
2142 {'datetime': '2014-12-17T04:00:00+00:00', 'value': 19.62, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '0.0.20141217235502'}]
2143 }
2144 assert response.json() == expected_response
2147 def test_get_no_data_with_staging(self, client, db):
2148 response = client.get("/data/timeseries_with_staging/id/5", headers={"email": "s.schroeder@fz-juelich.de"})
2149 expected_status_code = 404
2150 assert response.status_code == expected_status_code
2151 expected_response = {'detail': 'Data not found.'}
2152 assert response.json() == expected_response
2154 def test_create_data_record(self, client, db):
2155 response = client.post("/data/timeseries/record/?series_id=2&datetime=2021-08-23%2015:00:00&value=67.3&flag=OK&version=000001.000001.00000000000000",
2156 headers={"email": "s.schroeder@fz-juelich.de"})
2157 expected_status_code = 200
2158 assert response.status_code == expected_status_code
2159 expected_response = 'Data successfully inserted.'
2160 assert response.json() == expected_response
2161 patched_response = client.get("/timeseries/2")
2162 assert patched_response.status_code == expected_status_code
2163 patched_response_changelog = patched_response.json()["changelog"][-1]
2164 patched_response_changelog["datetime"] = ""
2165 expected_changelog = {'datetime': '', 'description': 'data record created', 'old_value': '', 'new_value': '',
2166 'timeseries_id': 2, 'author_id': 1, 'type_of_change': 'created', 'period_start': '2021-08-23T15:00:00+00:00',
2167 'period_end': '2021-08-23T15:00:00+00:00', 'version': '000001.000001.00000000000000'}
2168 assert patched_response_changelog == expected_changelog
2171 def test_patch_data(self, client, db):
2172 response = client.patch("/data/timeseries/?description=test patch&version=000002.000000.00000000000000",
2173 files={"file": open("tests/fixtures/data/toluene_SDZ54421_2012_2012_v2-0.dat", "rb")},
2174 headers={"email": "s.schroeder@fz-juelich.de"})
2175 expected_status_code = 200
2176 assert response.status_code == expected_status_code
2177 expected_response = {'detail': {'message': 'Data successfully inserted.'}}
2178 assert response.json() == expected_response
2179 patched_response = client.get("/timeseries/1")
2180 assert patched_response.status_code == expected_status_code
2181 patched_response_changelog = patched_response.json()["changelog"][-1]
2182 patched_response_changelog["datetime"] = ""
2183 expected_changelog = {'datetime': '', 'description': 'test patch', 'old_value': '', 'new_value': '',
2184 'timeseries_id': 1, 'author_id': 1, 'type_of_change': 'unspecified data value corrections', 'period_start': '2012-12-15T14:00:00+00:00',
2185 'period_end': '2012-12-17T17:00:00+00:00', 'version': '000002.000000.00000000000000'}
2186 assert patched_response_changelog == expected_changelog
2189 def test_patch_bulk_data(self, client, db):
2190 # show version of the original time series
2191 version_response = client.get("/data/timeseries/next_version/1")
2192 expected_status_code = 200
2193 assert version_response.status_code == expected_status_code
2194 expected_response = '000001.000001.00000000000000'
2195 assert version_response.json() == expected_response
2196 version_response = client.get("/data/timeseries/next_version/1?major=True")
2197 expected_status_code = 200
2198 assert version_response.status_code == expected_status_code
2199 expected_response = '000002.000000.00000000000000'
2200 assert version_response.json() == expected_response
2201 response = client.patch("/data/timeseries/bulk/?description=test patch bulk data&version=000002.000000.00000000000000",
2202 data='''[{"datetime": "2012-12-17T00:00:00+00:00", "value": 7.848, "flags": 0, "timeseries_id": 1, "version": "000002.000000.00000000000000"},
2203 {"datetime": "2012-12-17T01:00:00+00:00", "value": 15.696, "flags": 0, "timeseries_id": 1, "version": "000002.000000.00000000000000"},
2204 {"datetime": "2012-12-17T02:00:00+00:00", "value": 11.772, "flags": 0, "timeseries_id": 1, "version": "000002.000000.00000000000000"},
2205 {"datetime": "2012-12-17T03:00:00+00:00", "value": 13.734, "flags": 0, "timeseries_id": 1, "version": "000002.000000.00000000000000"},
2206 {"datetime": "2012-12-17T04:00:00+00:00", "value": 19.62, "flags": 0, "timeseries_id": 1, "version": "000002.000000.00000000000000"}]''',
2207 headers={"email": "s.schroeder@fz-juelich.de"} )
2208 expected_status_code = 200
2209 assert response.status_code == expected_status_code
2210 expected_response = {'detail': {'message': 'Data successfully inserted.'}}
2211 assert response.json() == expected_response
2212 # check, what has been inserted!
2213 response = client.get("/data/timeseries/id/1", headers={"email": "s.schroeder@fz-juelich.de"})
2214 expected_status_code = 200
2215 expected_response = {'metadata': {'id': 1,
2216 'label': 'CMA',
2217 'order': 1,
2218 'sampling_frequency': 'hourly',
2219 'aggregation': 'mean',
2220 'data_start_date': '2003-09-07T15:30:00+00:00',
2221 'data_end_date': '2016-12-31T14:30:00+00:00',
2222 'coverage': -1.0,
2223 'data_origin': 'instrument',
2224 'data_origin_type': 'measurement',
2225 'provider_version': 'N/A',
2226 'sampling_height': 7.0,
2227 'additional_metadata': {'original_units': 'ppb'},
2228 'station': {'id': 2,
2229 'codes': ['SDZ54421'],
2230 'name': 'Shangdianzi',
2231 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9},
2232 'coordinate_validation_status': 'not checked',
2233 'country': 'China',
2234 'state': 'Beijing Shi',
2235 'type': 'unknown',
2236 'type_of_area': 'unknown',
2237 'timezone': 'Asia/Shanghai',
2238 'additional_metadata': {'add_type': 'nature reservation'},
2239 'aux_images': [],
2240 'aux_docs': [],
2241 'aux_urls': [],
2242 'changelog': [],
2243 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
2244 'distance_to_major_road_year2020': -999.0,
2245 'dominant_ecoregion_year2017': '-1 (undefined)',
2246 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
2247 'ecoregion_description_25km_year2017': '',
2248 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
2249 'landcover_description_25km_year2012': '',
2250 'max_population_density_25km_year1990': -1.0,
2251 'max_population_density_25km_year2015': -1.0,
2252 'max_stable_nightlights_25km_year1992': -999.0,
2253 'max_stable_nightlights_25km_year2013': -999.0,
2254 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
2255 'mean_nox_emissions_10km_year2000': -999.0,
2256 'mean_nox_emissions_10km_year2015': -999.0,
2257 'mean_population_density_250m_year1990': -1.0,
2258 'mean_population_density_250m_year2015': -1.0,
2259 'mean_population_density_5km_year1990': -1.0,
2260 'mean_population_density_5km_year2015': -1.0,
2261 'mean_stable_nightlights_1km_year2013': -999.0,
2262 'mean_stable_nightlights_5km_year2013': -999.0,
2263 'mean_topography_srtm_alt_1km_year1994': -999.0,
2264 'mean_topography_srtm_alt_90m_year1994': -999.0,
2265 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
2266 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
2267 'toar1_category': 'unclassified',
2268 'toar2_category': 'suburban'
2269 }
2270 },
2271 'variable': {'name': 'toluene',
2272 'longname': 'toluene',
2273 'displayname': 'Toluene',
2274 'cf_standardname': 'mole_fraction_of_toluene_in_air',
2275 'units': 'nmol mol-1',
2276 'chemical_formula': 'C7H8',
2277 'id': 7
2278 },
2279 'programme': {'id': 0,
2280 'name': '',
2281 'longname': '',
2282 'homepage': '',
2283 'description': ''
2284 },
2285 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active',
2286 'contact': {'id': 4,
2287 'organisation': {'id': 1,
2288 'name': 'UBA',
2289 'longname': 'Umweltbundesamt',
2290 'kind': 'government',
2291 'city': 'Dessau-Roßlau',
2292 'postcode': '06844',
2293 'street_address': 'Wörlitzer Platz 1',
2294 'country': 'Germany',
2295 'homepage': 'https://www.umweltbundesamt.de',
2296 'contact_url': 'mailto:immission@uba.de',
2297 }
2298 }
2299 },
2300 {'id': 3, 'role': 'principal investigator', 'status': 'active',
2301 'contact': {'id': 3,
2302 'person': {'email': 's.schroeder@fz-juelich.de',
2303 'id': 3,
2304 'isprivate': False,
2305 'name': 'Sabine Schröder',
2306 'orcid': '0000-0002-0309-8010',
2307 'phone': '+49-2461-61-6397'
2308 }
2309 }
2310 }],
2311 'changelog': [{'datetime': '',
2312 'description': 'test patch bulk data',
2313 'old_value': '',
2314 'new_value': '',
2315 'timeseries_id': 1,
2316 'author_id': 1,
2317 'type_of_change': 'replaced data with a new version',
2318 'period_start': '2012-12-17T00:00:00+00:00',
2319 'period_end': '2012-12-17T04:00:00+00:00',
2320 'version': '000002.000000.00000000000000'
2321 }],
2322 'citation': 'Sabine Schröder: time series of toluene at Shangdianzi, accessed from the TOAR database',
2323 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/',
2324 'doi': ''},
2325 'data': [{'datetime': '2012-12-16T21:00:00+00:00', 'value': 21.581, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'},
2326 {'datetime': '2012-12-16T22:00:00+00:00', 'value': 13.734, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'},
2327 {'datetime': '2012-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'},
2328 {'datetime': '2012-12-17T00:00:00+00:00', 'value': 7.848, 'flags': 'OK validated QC passed', 'timeseries_id': 1, 'version': '2.0'},
2329 {'datetime': '2012-12-17T01:00:00+00:00', 'value': 15.696, 'flags': 'OK validated QC passed', 'timeseries_id': 1, 'version': '2.0'},
2330 {'datetime': '2012-12-17T02:00:00+00:00', 'value': 11.772, 'flags': 'OK validated QC passed', 'timeseries_id': 1, 'version': '2.0'},
2331 {'datetime': '2012-12-17T03:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 1, 'version': '2.0'},
2332 {'datetime': '2012-12-17T04:00:00+00:00', 'value': 19.62, 'flags': 'OK validated QC passed', 'timeseries_id': 1, 'version': '2.0'},
2333 {'datetime': '2012-12-17T05:00:00+00:00', 'value': 15.696, 'flags': 'OK validated modified', 'timeseries_id': 1, 'version': '1.0'},
2334 {'datetime': '2012-12-17T06:00:00+00:00', 'value': 5.886, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'}]
2335 }
2336 patched_response = response.json()
2337 patched_response['metadata']['changelog'][0]['datetime'] = ""
2338 dindex = patched_response['metadata']['citation'].index(" on 2")
2339 patched_response['metadata']['citation'] = patched_response['metadata']['citation'][:dindex]
2340 assert patched_response == expected_response
2341 # also show that next_version has changed by this patch
2342 version_response = client.get("/data/timeseries/next_version/1")
2343 expected_status_code = 200
2344 assert version_response.status_code == expected_status_code
2345 expected_response = '000002.000001.00000000000000'
2346 assert version_response.json() == expected_response
2347 version_response = client.get("/data/timeseries/next_version/1?major=True")
2348 expected_status_code = 200
2349 assert version_response.status_code == expected_status_code
2350 expected_response = '000003.000000.00000000000000'
2351 assert version_response.json() == expected_response
2354 def test_patch_bulk_data2(self, client, db):
2355 response = client.patch("/data/timeseries/bulk/?toarqc_config_type=realtime&no_archive=True&description=NOLOG&version=000000.000001.20230208144921&force=True",
2356 data='''[{"datetime": "2012-12-17T00:00:00+00:00", "value": 7.848, "flags": 0, "timeseries_id": 1, "version": "000002.000000.00000000000000"},
2357 {"datetime": "2012-12-17T01:00:00+00:00", "value": 15.696, "flags": 0, "timeseries_id": 1, "version": "000002.000000.00000000000000"},
2358 {"datetime": "2012-12-17T02:00:00+00:00", "value": 11.772, "flags": 0, "timeseries_id": 1, "version": "000002.000000.00000000000000"},
2359 {"datetime": "2012-12-17T03:00:00+00:00", "value": 13.734, "flags": 0, "timeseries_id": 2, "version": "000002.000000.00000000000000"},
2360 {"datetime": "2012-12-17T04:00:00+00:00", "value": 19.62, "flags": 0, "timeseries_id": 2, "version": "000002.000000.00000000000000"}]''',
2361 headers={"email": "s.schroeder@fz-juelich.de"})
2362 expected_status_code = 200
2363 assert response.status_code == expected_status_code
2364 expected_response = {'detail': {'message': 'Data successfully inserted.'}}
2365 assert response.json() == expected_response
2367"""
2368 def test_select_provider(self, client, db):
2369 data = [(46, 5, 18763, 2010, 0.14, 1),
2370 (46, 5, 18763, 2010, 0.17, 2),
2371 (46, 5, 18764, 2015, 0.9442, 1),
2372 (46, 5, 18764, 2015, 0.5442, 1),
2373 ]
2374 expected_result = [
2375 (2010, 18763, 0.14),
2376 (2015, 18764, 0.9442)
2377 ]
2378 assert select_provider(data) == expected_result
2379 def test_merge_sequences(self, client, db):
2380 data = [(2009, 18762, 0.3), (2010, 18762, 0.3), (2011, 18762, 0.3)]
2381 expected_result = [(2009, 2011, 18762)]
2382 assert merge_sequences(data) == expected_result
2383 def test_format_time_series(self, client, db):
2384 data = [(2009, 2011, 18762), (2009, 2011, 18763)]
2385 expected_result = [[f"2009-01-01", f"2011-12-31 23:00", 18762], [f"2009-01-01", f"2011-12-31 23:00", 18763]]
2386 assert format_time_series(data) == expected_result
2387 def test_process_eea_data(self, client, db):
2388 data = [(46, 5, 18763, 2012, 0.14, 1, 'v8', datetime.datetime(2012, 6, 1, 2, 0, tzinfo=datetime.timezone.utc), datetime.datetime(2012, 12, 31, 23, 0, tzinfo=datetime.timezone.utc)), (46, 5, 18763, 2013, 0.14, 1, 'v9', datetime.datetime(2013, 1, 1, 0, 0, tzinfo=datetime.timezone.utc), datetime.datetime(2013, 12, 5, 23, 0, tzinfo=datetime.timezone.utc))]
2389 expected_result = (datetime.datetime(2012, 6, 1, 2, 0, tzinfo=datetime.timezone.utc), datetime.datetime(2013, 12, 5, 23, 0, tzinfo=datetime.timezone.utc), 18763)
2390 assert process_eea_data(data) == expected_result
2391 def test_format_eea_time_series(self, client, db):
2392 data = (datetime.datetime(2012, 6, 1, 2, 0, tzinfo=datetime.timezone.utc), datetime.datetime(2013, 12, 5, 23, 0, tzinfo=datetime.timezone.utc), 18763)
2393 expected_result = [["2012-06-01 2:00", "2013-12-05 23:00", 18763]]
2394 assert format_time_series(data) == expected_result
2395 def test_patch_eea_data1(self, client, db):
2396 response = client.post("timeseries_merged/?timeseries_id=434870") # to check: are these timeseries registered as EEA data?
2397 expected_status_code = 200
2398 assert response.status_code == expected_status_code
2399 expected_response = expected_resp = {
2400 "id": 434870,
2401 "data_start_date": "2001-09-13 08:00:00+00",
2402 "data_end_date": "2012-12-02 00:00:00+00",
2403 "provider_version": "v8"
2404 }
2405 assert response == expected_response
2406 def test_patch_eea_data2(self, client, db):
2407 response = client.post("timeseries_merged/?timeseries_id=30890")
2408 expected_status_code = 200
2409 assert response.status_code == expected_status_code
2410 expected_response = expected_resp = {
2411 "id": 30890,
2412 "data_start_date": "2012-12-02 01:00:00+00",
2413 "data_end_date": "2019-07-24 16:00:00+00",
2414 "provider_version": "v9"
2415 }
2416 assert response == expected_response
2417"""