Coverage for toardb/stationmeta/schemas.py: 92%

547 statements  

« 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 

3 

4""" 

5Pydantic schemas for TOAR database 

6 

7""" 

8 

9from typing import List, Dict 

10from pydantic import BaseModel, Field, BaseConfig, Json, validator 

11from geoalchemy2 import WKTElement 

12from geoalchemy2.shape import to_shape 

13import datetime as dt 

14from toardb.contacts.schemas import Contact 

15import toardb 

16from toardb.utils.utils import provenance 

17 

18 

19# the following class was taken from: 

20# https://github.com/tiangolo/fastapi/issues/312 

21class Coordinates(BaseModel): 

22 lat: float = Field(..., ge=-90, le=90, description="latitude coordinate of station (decimal degrees_north). This is our best estimate of the station location which is not always identical to the official station coordinates (see potential changelog entry).") 

23 lng: float = Field(..., ge=-180, le=180, description="longitude coordinate of station (decimal degrees_east). This is our best estimate of the station location which is not always identical to the official station coordinates (see potential changelog entry).") 

24 alt: float = Field(..., description="altitude of station (in m above sea level). This is our best estimate of the station altitude, which is not always identical to the reported station altitude, but frequently uses the elevation from google earth instead (see potential changelog entry).") 

25 

26# ======== StationmetaCore ========= 

27 

28class StationmetaCoreBase(BaseModel): 

29 id: int = Field(None, description="for internal use only") 

30 codes: List[str] = Field(None, description="list of station's codes") 

31 name: str = Field(None, description="Station name for use in TOAR; normally equal to data provider’s name") 

32 coordinates: Coordinates = Field(None, description="Best estimate of station latitude, longitude and altitude") 

33 coordinate_validation_status: str = Field(None, description="Flag indicating whether the location of a station has been verified (see controlled vocabulary: Coordinate Validity)") 

34 country: str = Field(None, description="The country, where the station resides, or which operates the station (e.g. in Antarctica) (see controlled vocabulary: Country Code)") 

35 state: str = Field(None, description="The state or province, where the station resides") 

36 type: str = Field(None, description="Type of station (see controlled vocabulary: Station Type)") 

37 type_of_area: str = Field(None, description="Type of station area (see controlled vocabulary: Station Type Of Area)") 

38 timezone: str = Field(None, description="Station timezone (see controlled vocabulary: Timezone)") 

39 additional_metadata: Json = Field(None, description="Any additional metadata about the station as a JSON data structure") 

40 

41 class Config(BaseConfig): 

42 orm_mode = True 

43 

44 @validator('coordinate_validation_status') 

45 def check_coordinate_validation_status(cls, v): 

46 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.CV_vocabulary))[0].display_str 

47 

48 @validator('country') 

49 def check_country(cls, v): 

50 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.CN_vocabulary))[0].display_str 

51 

52 @validator('type') 

53 def check_type(cls, v): 

54 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.ST_vocabulary))[0].display_str 

55 

56 @validator('type_of_area') 

57 def check_type_of_area(cls, v): 

58 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TA_vocabulary))[0].display_str 

59 

60 @validator('timezone') 

61 def check_timezone(cls, v): 

62 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TZ_vocabulary))[0].display_str 

63 

64 

65class StationmetaCorePatch(BaseModel): 

66 codes: List[str] = None 

67 name: str = None 

68 coordinates: Coordinates = None 

69 coordinate_validation_status: str = None 

70 country: str = None 

71 state: str = None 

72 type: str = None 

73 type_of_area: str = None 

74 timezone: str = None 

75 additional_metadata: Json = None 

76 

77 class Config(BaseConfig): 

78 orm_mode = True 

79 

80 

81class StationmetaCoreCreate(StationmetaCoreBase): 

82 pass 

83 

84 @validator('coordinate_validation_status') 

85 def check_coordinate_validation_status(cls, v): 

86 if tuple(filter(lambda x: x.string == v, toardb.toardb.CV_vocabulary)): 

87 return v 

88 else: 

89 raise ValueError(f"coordinate validation status not known: {v}") 

90 

91 @validator('country') 

92 def check_country(cls, v): 

93 if tuple(filter(lambda x: x.string == v, toardb.toardb.CN_vocabulary)): 

94 return v 

95 else: 

96 raise ValueError(f"country not known: {v}") 

97 

98 @validator('type') 

99 def check_type(cls, v): 

100 if tuple(filter(lambda x: x.string == v, toardb.toardb.ST_vocabulary)): 

101 return v 

102 else: 

103 raise ValueError(f"type of environment not known: {v}") 

104 

105 @validator('type_of_area') 

106 def check_type_of_area(cls, v): 

107 if tuple(filter(lambda x: x.string == v, toardb.toardb.TA_vocabulary)): 

108 return v 

109 else: 

110 raise ValueError(f"type of area not known: {v}") 

111 

112 @validator('timezone') 

113 def check_timezone(cls, v): 

114 if tuple(filter(lambda x: x.string == v, toardb.toardb.TZ_vocabulary)): 

115 return v 

116 else: 

117 raise ValueError(f"timezone not known: {v}") 

118 

119 

120class StationmetaCore(StationmetaCoreBase): 

121 id: int = Field(..., description="for internal use only") 

122 

123 class Config: 

124 orm_mode = True 

125 

126 

127def get_geom_from_coordinates(coordinates: Coordinates) -> str: 

128 geom_wkte = f'SRID=4326;POINTZ({coordinates.lng} {coordinates.lat} {coordinates.alt})' 

129 return geom_wkte 

130 

131 

132def get_coordinates_from_geom(geom: WKTElement) -> Coordinates: 

133 shply_geom = to_shape(geom) 

134 coordinates = Coordinates(lng=shply_geom.x, lat=shply_geom.y, alt=shply_geom.z) 

135 return coordinates 

136 

137def get_coordinates_from_string(point: str) -> Coordinates: 

138 coordinates = Coordinates(**dict(zip(['lng','lat','alt'],point[9:-1].split()))) 

139 return coordinates 

140 

141# ======== StationmetaAnnotation ========= 

142 

143class StationmetaAnnotationBase(BaseModel): 

144 id: int = None 

145 kind: str = Field(..., description="kind of annotation (see controlled vocabulary: Kind Of Annotation)") 

146 text: str = Field(..., description="text of annotation") 

147 date_added: dt.datetime = Field(..., description="timestamp when annotation was added") 

148 approved: bool = Field(..., description="Flag indicating whether the annotation of a station has been verified") 

149 contributor_id: int = Field(..., description="ID of contributor who added the annotation") 

150 

151 @validator('kind') 

152 def check_kind(cls, v): 

153 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.AK_vocabulary))[0].display_str 

154 

155 

156class StationmetaAnnotationPatch(BaseModel): 

157 kind: int = None 

158 text: str = None 

159 date_added: dt.datetime = None 

160 approved: bool = None 

161 contributor_id: int = None 

162 

163 @validator('kind') 

164 def check_kind(cls, v): 

165 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.AK_vocabulary))[0].display_str 

166 

167 

168class StationmetaAnnotationCreate(StationmetaAnnotationBase): 

169 

170 @validator('kind') 

171 def check_kind(cls, v): 

172 if tuple(filter(lambda x: x.string == v, toardb.toardb.AK_vocabulary)): 

173 return v 

174 else: 

175 raise ValueError(f"kind of annotation code not known: {v}") 

176 

177 

178 

179class StationmetaAnnotation(StationmetaAnnotationBase): 

180 id: int = Field(..., description="for internal use only") 

181 

182 class Config: 

183 orm_mode = True 

184 

185# ======== StationmetaAux ========= 

186 

187# -------- StationmetaAuxDoc --------- 

188 

189class StationmetaAuxDocBase(BaseModel): 

190 id: int = None 

191 resource_description: str = Field(..., description="Description of the resource") 

192 date_added: dt.datetime = Field(..., description="Date of metadata record entry into TOAR database") 

193 resource: str = Field(..., description="auxillary document file") 

194 station_id: int = Field(..., description="internal station_id to which this resource belongs") 

195 

196 

197class StationmetaAuxDocPatch(BaseModel): 

198 resource_description: str = None 

199 date_added: dt.datetime = None 

200 resource: str = None 

201 station_id: int = None 

202 

203 

204class StationmetaAuxDocCreate(StationmetaAuxDocBase): 

205 pass 

206 

207 

208class StationmetaAuxDoc(StationmetaAuxDocBase): 

209 id: int = Field(..., description="for internal use only") 

210 

211 class Config: 

212 orm_mode = True 

213 

214# -------- StationmetaAuxImage --------- 

215 

216class StationmetaAuxImageBase(BaseModel): 

217 id: int = None 

218 resource_description: str = Field(..., description="Description of the resource") 

219 date_added: dt.datetime = Field(..., description="Date of metadata record entry into TOAR database") 

220 resource: str = Field(..., description="auxillary image file") 

221 image_height: int = Field(..., description="Number of image pixels in direction of height") 

222 image_width: int = Field(..., description="Number of image pixels in direction of width") 

223 station_id: int = Field(..., description="internal station_id to which this resource belongs") 

224 

225 

226class StationmetaAuxImagePatch(BaseModel): 

227 resource_description: str = None 

228 date_added: dt.datetime = None 

229 resource: str = None 

230 image_height: int = None 

231 image_width: int = None 

232 station_id: int = None 

233 

234 

235class StationmetaAuxImageCreate(StationmetaAuxImageBase): 

236 pass 

237 

238 

239class StationmetaAuxImage(StationmetaAuxImageBase): 

240 id: int = Field(..., description="for internal use only") 

241 

242 class Config: 

243 orm_mode = True 

244 

245# -------- StationmetaAuxUrl --------- 

246 

247class StationmetaAuxUrlBase(BaseModel): 

248 id: int = None 

249 resource_description: str = Field(..., description="Description of the resource") 

250 date_added: dt.datetime = Field(..., description="Date of metadata record entry into TOAR database") 

251 resource: str = Field(..., description="URL") 

252 station_id: int = Field(..., description="internal station_id to which this resource belongs") 

253 

254 

255class StationmetaAuxUrlPatch(BaseModel): 

256 resource_description: str = None 

257 date_added: dt.datetime = None 

258 resource: str = None 

259 station_id: int = None 

260 

261 

262class StationmetaAuxUrlCreate(StationmetaAuxUrlBase): 

263 pass 

264 

265 

266class StationmetaAuxUrl(StationmetaAuxUrlBase): 

267 id: int = Field(..., description="for internal use only") 

268 

269 class Config: 

270 orm_mode = True 

271 

272# ======== StationmetaGlobal ========= 

273 

274class StationmetaGlobalBase(BaseModel): 

275 mean_topography_srtm_alt_90m_year1994: float = Field(..., description="mean value within a radius of 90 m around station location of the following data of the year 1994: " + str(provenance['topography_srtm'])) 

276 mean_topography_srtm_alt_1km_year1994: float = Field(..., description="mean value within a radius of 1 km around station location of the following data of the year 1994: " + str(provenance['topography_srtm'])) 

277 max_topography_srtm_relative_alt_5km_year1994: float = Field(..., description="maximum value within a radius of 5 km around station location with relative altitude of the following data of the year 1994: " + str(provenance['topography_srtm'])) 

278 min_topography_srtm_relative_alt_5km_year1994: float = Field(..., description="minimum value within a radius of 5 km around station location with relative altitude of the following data of the year 1994: " + str(provenance['topography_srtm'])) 

279 stddev_topography_srtm_relative_alt_5km_year1994: float = Field(..., description="standard deviation within a radius of 5 km around station location with relative altitude of the following data of the year 1994: " + str(provenance['topography_srtm'])) 

280 climatic_zone_year2016: str = Field(..., description= "value for the year 2016 of the following data: " + str(provenance['climatic_zone']) + " (see controlled vocabulary: Climatic Zone 2019)") 

281 htap_region_tier1_year2010: str = Field(..., description= "value for the year 2010 of the following data: " + str(provenance['htap_region_tier1']) + " (see controlled vocabulary: Station HTAP Region)") 

282 dominant_landcover_year2012: str = Field(..., description="value for the year 2012 of the following data: " + str(provenance['landcover']) + " (see controlled vocabulary: Station Landcover Type)") 

283 landcover_description_25km_year2012: str = Field(..., description="description of the values for the year 2012 within a radius of 25 km around station location of the following data: " + str(provenance['landcover']) + " (see controlled vocabulary: Station Landcover Type)") 

284 dominant_ecoregion_year2017: str = Field(..., description="value for the year 2017 of the following data: " + str(provenance['ecoregion']) + " (see controlled vocabulary: Station ECO Region Type)") 

285 ecoregion_description_25km_year2017: str = Field(..., description="description of the values for the year 2017 within a radius of 25 km around station location of the following data: " + str(provenance['ecoregion']) + " (see controlled vocabulary: Station ECO Region Type)") 

286 distance_to_major_road_year2020: float = Field(..., description="value for the year 2020 of the following data: " + str(provenance['major_road'])) 

287 mean_stable_nightlights_1km_year2013: float = Field(..., description="mean value within a radius of 1 km around station location of the following data of the year 2013: " + str(provenance['stable_nightlights'])) 

288 mean_stable_nightlights_5km_year2013: float = Field(..., description="mean value within a radius of 5 km around station location of the following data of the year 2013: " + str(provenance['stable_nightlights'])) 

289 max_stable_nightlights_25km_year2013: float = Field(..., description="maximum value within a radius of 5 km around station location of the following data of the year 2013: " + str(provenance['stable_nightlights'])) 

290 max_stable_nightlights_25km_year1992: float = Field(..., description="maximum value within a radius of 25 km around station location of the following data of the year 2013: " + str(provenance['stable_nightlights'])) 

291 mean_population_density_250m_year2015: float = Field(..., description="mean value within a radius of 250 m around station location of the following data of the year 2015: " + str(provenance['population_density'])) 

292 mean_population_density_5km_year2015: float = Field(..., description="mean value within a radius of 5 km around station location of the following data of the year 2015: " + str(provenance['population_density'])) 

293 max_population_density_25km_year2015: float = Field(..., description="maximum value within a radius of 25 km around station location of the following data of the year 2015: " + str(provenance['population_density'])) 

294 mean_population_density_250m_year1990: float = Field(..., description="human population on a square of 250 m for the year 1990 (residents km-2)") 

295 mean_population_density_5km_year1990: float = Field(..., description="mean value within a radius of 250 m around station location of the following data of the year 1990: " + str(provenance['population_density'])) 

296 max_population_density_25km_year1990: float = Field(..., description="maximum value within a radius of 25 km around station location of the following data of the year 1990: " + str(provenance['population_density'])) 

297 mean_nox_emissions_10km_year2015: float = Field(..., description="mean value within a radius of 10 km around station location of the following data of the year 2015: " + str(provenance['nox_emissions'])) 

298 mean_nox_emissions_10km_year2000: float = Field(..., description="mean value within a radius of 10 km around station location of the following data of the year 2000: " + str(provenance['nox_emissions'])) 

299 toar1_category: str = Field(..., description="The station classification for the Tropsopheric Ozone Assessment Report based on the station proxy data that are stored in the TOAR database (see controlled vocabulary: Station TOAR Category)") 

300 toar2_category: str = Field(..., description="The station classification for the TOAR-II based on the station proxy data that are stored in the TOAR database and obtained from an ML approach (see controlled vocabulary: Station TOAR Category)") 

301# station_id: int = Field(..., description="internal station_id to which these global data belong") 

302 

303 

304 @validator('climatic_zone_year2016') 

305 def check_climatic_zone(cls, v): 

306 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.CZ_vocabulary))[0].display_str 

307 

308 @validator('toar1_category') 

309 def check_toar1_category(cls, v): 

310 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TC_vocabulary))[0].display_str 

311 

312 @validator('toar2_category') 

313 def check_toar2_category(cls, v): 

314 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TA_vocabulary))[0].display_str 

315 

316 @validator('htap_region_tier1_year2010') 

317 def check_htap_region_tier1(cls, v): 

318 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TR_vocabulary))[0].display_str 

319 

320 @validator('dominant_landcover_year2012') 

321 def check_dominant_landcover_year2012(cls, v): 

322 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.LC_vocabulary))[0].display_str 

323 

324 @validator('landcover_description_25km_year2012') 

325 def check_landcover_description_25km_year2012(cls, v): 

326 return get_full_description_from_abbreviation(toardb.toardb.LC_vocabulary, v) 

327 

328 @validator('dominant_ecoregion_year2017') 

329 def check_dominant_ecoregion_year2017(cls, v): 

330 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.ER_vocabulary))[0].display_str 

331 

332 @validator('ecoregion_description_25km_year2017') 

333 def check_ecoregion_description_25km_year2017(cls, v): 

334 return get_full_description_from_abbreviation(toardb.toardb.ER_vocabulary, v) 

335 

336 

337class StationmetaGlobalPatch(BaseModel): 

338 mean_topography_srtm_alt_90m_year1994: float = None 

339 mean_topography_srtm_alt_1km_year1994: float = None 

340 max_topography_srtm_relative_alt_5km_year1994: float = None 

341 min_topography_srtm_relative_alt_5km_year1994: float = None 

342 stddev_topography_srtm_relative_alt_5km_year1994: float = None 

343 climatic_zone_year2016: str = None 

344 htap_region_tier1_year2010: str = None 

345 dominant_landcover_year2012: str = None 

346 landcover_description_25km_year2012: str = None 

347 dominant_ecoregion_year2017: str = None 

348 ecoregion_description_25km_year2017: str = None 

349 distance_to_major_road_year2020: float = None 

350 mean_stable_nightlights_1km_year2013: float = None 

351 mean_stable_nightlights_5km_year2013: float = None 

352 max_stable_nightlights_25km_year2013: float = None 

353 max_stable_nightlights_25km_year1992: float = None 

354 mean_population_density_250m_year2015: float = None 

355 mean_population_density_5km_year2015: float = None 

356 max_population_density_25km_year2015: float = None 

357 mean_population_density_250m_year1990: float = None 

358 mean_population_density_5km_year1990: float = None 

359 max_population_density_25km_year1990: float = None 

360 mean_nox_emissions_10km_year2015: float = None 

361 mean_nox_emissions_10km_year2000: float = None 

362 toar1_category: str = None 

363 toar2_category: str = None 

364 station_id: int = None 

365 

366 @validator('climatic_zone_year2016') 

367 def check_climatic_zone(cls, v): 

368 if tuple(filter(lambda x: x.string == v, toardb.toardb.CZ_vocabulary)): 

369 return v 

370 else: 

371 raise ValueError(f"climatic zone not known: {v}") 

372 

373 @validator('toar1_category') 

374 def check_toar1_category(cls, v): 

375 if tuple(filter(lambda x: x.string == v, toardb.toardb.TC_vocabulary)): 

376 return v 

377 else: 

378 raise ValueError(f"TOAR1 category not known: {v}") 

379 

380 @validator('toar2_category') 

381 def check_toar2_category(cls, v): 

382 if tuple(filter(lambda x: x.string == v, toardb.toardb.TA_vocabulary)): 

383 return v 

384 else: 

385 raise ValueError(f"TOAR2 category not known: {v}") 

386 

387 @validator('htap_region_tier1_year2010') 

388 def check_htap_region_tier1(cls, v): 

389 if tuple(filter(lambda x: x.string == v, toardb.toardb.TR_vocabulary)): 

390 return v 

391 else: 

392 raise ValueError(f"HTAP region TIER1 not known: {v}") 

393 

394 @validator('dominant_landcover_year2012') 

395 def check_dominant_landcover_year2012(cls, v): 

396 if tuple(filter(lambda x: x.string == v, toardb.toardb.LC_vocabulary)): 

397 return v 

398 else: 

399 raise ValueError(f"dominant landcover (year2012) not known: {v}") 

400 

401 @validator('dominant_ecoregion_year2017') 

402 def check_dominant_ecoregion_year2017(cls, v): 

403 if tuple(filter(lambda x: x.string == v, toardb.toardb.ER_vocabulary)): 

404 return v 

405 else: 

406 raise ValueError(f"dominant ecoregion (year2017) not known: {v}") 

407 

408 @validator('landcover_description_25km_year2012') 

409 def check_landcover_description_25km_year2012(cls, v): 

410 try: 

411 desc = get_abbreviation_from_code_description(toardb.toardb.LC_vocabulary, v) 

412 return v 

413 except: 

414 raise ValueError(f"landcover_description_25km_year2012 not known: {v}") 

415 

416 @validator('ecoregion_description_25km_year2017') 

417 def check_ecoregion_description_25km_year2017(cls, v): 

418 try: 

419 desc = get_abbreviation_from_code_description(toardb.toardb.ER_vocabulary, v) 

420 return v 

421 except: 

422 raise ValueError(f"ecoregion_description_25km_year2017 not known: {v}") 

423 

424 

425def get_full_description_from_abbreviation (vocabulary, abbr_desc: str): 

426 full_description = '' 

427 if abbr_desc != '': 

428 for desc in abbr_desc.split(','): 

429 code, perc = desc.split(':') 

430 ftype = tuple(filter(lambda x: x.value == int(code), vocabulary))[0].display_str 

431 if full_description: 

432 full_description += ', ' + ftype + ': ' + perc 

433 else: 

434 full_description = ftype + ': ' + perc 

435 return full_description 

436 

437 

438def get_code_description_from_abbreviation (vocabulary, abbr_desc: str): 

439 code_description = '' 

440 if abbr_desc != '': 

441 for desc in abbr_desc.split(','): 

442 code, perc = desc.split(':') 

443 ftype = tuple(filter(lambda x: x.value == int(code.strip()), vocabulary))[0].string 

444 if code_description: 

445 code_description += ',' + ftype + ':' + perc.strip() 

446 else: 

447 code_description = ftype + ':' + perc.strip() 

448 return code_description 

449 

450 

451def get_abbreviation_from_code_description(vocabulary, full_desc: str): 

452 full_description = '' 

453 if full_desc != '': 

454 for desc in full_desc.split(','): 

455 code, perc = desc.split(':') 

456 abbr = tuple(filter(lambda x: x.string == code.strip(), vocabulary))[0].value 

457 if full_description: 

458 full_description += ',' + str(abbr) + ':' + perc.strip() 

459 else: 

460 full_description = str(abbr) + ':' + perc.strip() 

461 return full_description 

462 

463 

464class StationmetaGlobalFields(StationmetaGlobalPatch): 

465 pass 

466 

467 @validator('climatic_zone_year2016') 

468 def check_climatic_zone(cls, v): 

469 return v 

470 

471 @validator('toar1_category') 

472 def check_toar1_category(cls, v): 

473 return v 

474 

475 @validator('toar2_category') 

476 def check_toar2_category(cls, v): 

477 return v 

478 

479 @validator('htap_region_tier1_year2010') 

480 def check_htap_region_tier1(cls, v): 

481 return v 

482 

483 @validator('dominant_landcover_year2012') 

484 def check_dominant_landcover_year2012(cls, v): 

485 return v 

486 

487 @validator('dominant_ecoregion_year2017') 

488 def check_dominant_ecoregion_year2017(cls, v): 

489 return v 

490 

491 @validator('landcover_description_25km_year2012') 

492 def check_landcover_description_25km_year2012(cls, v): 

493 return v 

494 

495 @validator('ecoregion_description_25km_year2017') 

496 def check_ecoregion_description_25km_year2017(cls, v): 

497 return v 

498 

499 

500class StationmetaGlobalCreate(StationmetaGlobalBase): 

501 pass 

502 

503 @validator('climatic_zone_year2016') 

504 def check_climatic_zone(cls, v): 

505 if tuple(filter(lambda x: x.string == v, toardb.toardb.CZ_vocabulary)): 

506 return v 

507 else: 

508 raise ValueError(f"climatic zone not known: {v}") 

509 

510 @validator('toar1_category') 

511 def check_toar1_category(cls, v): 

512 if tuple(filter(lambda x: x.string == v, toardb.toardb.TC_vocabulary)): 

513 return v 

514 else: 

515 raise ValueError(f"TOAR1 category not known: {v}") 

516 

517 @validator('toar2_category') 

518 def check_toar2_category(cls, v): 

519 if tuple(filter(lambda x: x.string == v, toardb.toardb.TA_vocabulary)): 

520 return v 

521 else: 

522 raise ValueError(f"TOAR2 category not known: {v}") 

523 

524 @validator('htap_region_tier1_year2010') 

525 def check_htap_region_tier1(cls, v): 

526 if tuple(filter(lambda x: x.string == v, toardb.toardb.TR_vocabulary)): 

527 return v 

528 else: 

529 raise ValueError(f"HTAP region TIER1 not known: {v}") 

530 

531 @validator('dominant_landcover_year2012') 

532 def check_dominant_landcover_year2012(cls, v): 

533 if tuple(filter(lambda x: x.string == v, toardb.toardb.LC_vocabulary)): 

534 return v 

535 else: 

536 raise ValueError(f"dominant landcover (year2012) not known: {v}") 

537 

538 @validator('dominant_ecoregion_year2017') 

539 def check_dominant_ecoregion_year2017(cls, v): 

540 if tuple(filter(lambda x: x.string == v, toardb.toardb.ER_vocabulary)): 

541 return v 

542 else: 

543 raise ValueError(f"dominant ecoregion (year2017) not known: {v}") 

544 

545 

546class StationmetaGlobal(StationmetaGlobalBase): 

547 class Config: 

548 orm_mode = True 

549 

550 

551class StationmetaGlobalBaseNested(StationmetaGlobalBase): 

552 mean_topography_srtm_alt_90m_year1994: float = None 

553 mean_topography_srtm_alt_1km_year1994: float = None 

554 max_topography_srtm_relative_alt_5km_year1994: float = None 

555 min_topography_srtm_relative_alt_5km_year1994: float = None 

556 stddev_topography_srtm_relative_alt_5km_year1994: float = None 

557 climatic_zone_year2016: str = None 

558 htap_region_tier1_year2010: str = None 

559 dominant_landcover_year2012: str = None 

560 landcover_description_25km_year2012: str = None 

561 full_description_year2012: str = None 

562 dominant_ecoregion_year2017: str = None 

563 ecoregion_description_25km_year2017: str = None 

564 distance_to_major_road_year2020: float = None 

565 mean_stable_nightlights_1km_year2013: float = None 

566 mean_stable_nightlights_5km_year2013: float = None 

567 max_stable_nightlights_25km_year2013: float = None 

568 max_stable_nightlights_25km_year1992: float = None 

569 mean_population_density_250m_year2015: float = None 

570 mean_population_density_5km_year2015: float = None 

571 max_population_density_25km_year2015: float = None 

572 mean_population_density_250m_year1990: float = None 

573 mean_population_density_5km_year1990: float = None 

574 max_population_density_25km_year1990: float = None 

575 mean_nox_emissions_10km_year2015: float = None 

576 mean_nox_emissions_10km_year2000: float = None 

577 toar1_category: str = None 

578 toar2_category: str = None 

579 

580 

581class StationmetaGlobalNestedCreate(StationmetaGlobalBaseNested): 

582 pass 

583 

584 

585class StationmetaGlobalNested(StationmetaCoreBase, StationmetaGlobalBaseNested): 

586 class Config: 

587 orm_mode = True 

588 

589 

590# ======== StationmetaGlobalService ========= 

591 

592class StationmetaGlobalServiceBase(BaseModel): 

593 variable_name: str = Field(..., description="Variable name which will be returned by the service URL") 

594 service_url: str = Field(..., description="Service URL with %%LAT%% and %%LON%% placeholders to enter station or point location in floating point degrees") 

595 

596 

597class StationmetaGlobalService(StationmetaGlobalServiceBase): 

598 

599 class Config: 

600 orm_mode = True 

601 

602# ======== StationmetaRole ========= 

603 

604class StationmetaRoleBase(BaseModel): 

605 id: int = None 

606 role: str = Field(..., description="Role of contact (see controlled vocabulary: Role Code)") 

607 status: str = Field(..., description="Status of contact (see controlled vocabulary: Role Status)") 

608 contact: Contact 

609 

610 @validator('role') 

611 def check_role(cls, v): 

612 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.RC_vocabulary))[0].display_str 

613 

614 @validator('status') 

615 def check_status(cls, v): 

616 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.RS_vocabulary))[0].display_str 

617 

618 @validator('contact') 

619 def check_contact_is_private(cls, v): 

620 if v.person: 

621 if (v.person.id != 0): 

622 if (v.person.isprivate): 

623 return None 

624 else: 

625 return v.person 

626 else: 

627 return v.organisation 

628 

629 class Config: 

630 orm_mode = True 

631 

632 

633class StationmetaRolePatch(BaseModel): 

634 role: str 

635 status: str 

636# at the moment contact_id has to be given... 

637# ==> in the future: give unique contact_email 

638# patching stationmeta should not result in creating new contacts! 

639# ==> still to do: check, whether contact already exists (otherwise patching cannot be done) 

640 contact_id: int 

641 

642 class Config: 

643 orm_mode = True 

644 

645 

646class StationmetaRoleCreate(StationmetaRoleBase): 

647 contact_id: int 

648 contact: Contact = None 

649 

650 @validator('role') 

651 def check_role(cls, v): 

652 if tuple(filter(lambda x: x.string == v, toardb.toardb.RC_vocabulary)): 

653 return v 

654 else: 

655 raise ValueError(f"role code not known: {v}") 

656 

657 @validator('status') 

658 def check_status(cls, v): 

659 if tuple(filter(lambda x: x.string == v, toardb.toardb.RS_vocabulary)): 

660 return v 

661 else: 

662 raise ValueError(f"role status not known: {v}") 

663 

664 

665class StationmetaRole(StationmetaRoleBase): 

666 id: int = Field(..., description="for internal use only") 

667 contact: Contact = Field(..., description="Contact for this role") 

668 

669 class Config: 

670 orm_mode = True 

671 

672 

673# ======== StationmetaChangelog ========= 

674 

675class StationmetaChangelogBase(BaseModel): 

676 datetime: dt.datetime = Field(..., description="Date of change to the TOAR database") 

677 description: str = Field(..., description="Description of change") 

678 old_value: str = Field(..., description="value that has been changed in the TOAR database") 

679 new_value: str = Field(..., description="new, changed value") 

680 station_id: int = Field(..., description="internal ID of station to which this change belongs") 

681 author_id: int = Field(..., description="internal ID of person who submitted the change to the TOAR database") 

682 type_of_change: int = Field(..., description="Type of change (see controlled vocabulary: Type Of Change)") 

683 

684 

685 @validator('type_of_change') 

686 def check_role(cls, v): 

687 return tuple(filter(lambda x: x.value == int(v), toardb.toardb.CL_vocabulary))[0].display_str 

688 

689 class Config: 

690 orm_mode = True 

691 

692class StationmetaChangelog(StationmetaChangelogBase): 

693 

694 class Config: 

695 orm_mode = True 

696 

697 

698# ======== for nested view/upload ========= 

699 

700class StationmetaBase(StationmetaCoreBase): 

701 roles: List[StationmetaRole] = None 

702 annotations: List[StationmetaAnnotation] = None 

703 aux_images: List[StationmetaAuxImage] = None 

704 aux_docs: List[StationmetaAuxDoc] = None 

705 aux_urls: List[StationmetaAuxUrl] = None 

706 globalmeta: StationmetaGlobal = None 

707 changelog: List[StationmetaChangelog] = None 

708 

709 class Config: 

710 orm_mode = True 

711 

712 @validator('changelog') 

713 def order_changelog(cls, v): 

714 return sorted(v, key=lambda x: x.datetime) 

715 

716 @validator('roles') 

717 def check_roles(cls, v): 

718 if v == []: 

719 return None 

720 else: 

721 return v 

722 

723 @validator('annotations') 

724 def check_annotations(cls, v): 

725 if v == []: 

726 return None 

727 else: 

728 return v 

729 

730 

731class StationmetaPatch(StationmetaCoreCreate): 

732 #roles: List[StationmetaRolePatch] = None 

733 #annotations: List[StationmetaAnnotationPatch] = None 

734 # just to get things working 

735 roles: list = None 

736 annotations: list = None 

737 aux_images: List[StationmetaAuxImagePatch] = None 

738 aux_docs: List[StationmetaAuxDocPatch] = None 

739 aux_urls: List[StationmetaAuxUrlPatch] = None 

740 globalmeta: StationmetaGlobalPatch = None 

741 

742 class Config: 

743 orm_mode = True 

744 

745 

746class StationmetaCreate(StationmetaCoreCreate): 

747 roles: List[StationmetaRoleCreate] = None 

748 annotations: List[StationmetaAnnotationCreate] = None 

749 aux_images: List[StationmetaAuxImage] = None 

750 aux_docs: List[StationmetaAuxDoc] = None 

751 aux_urls: List[StationmetaAuxUrl] = None 

752 globalmeta: StationmetaGlobalNestedCreate = None 

753 

754 class Config: 

755 orm_mode = True 

756 

757 

758class StationmetaFields(StationmetaCorePatch): 

759 roles: List[StationmetaRolePatch] = None 

760 annotations: List[StationmetaAnnotationPatch] = None 

761 aux_images: List[StationmetaAuxImagePatch] = None 

762 aux_docs: List[StationmetaAuxDocPatch] = None 

763 aux_urls: List[StationmetaAuxUrlPatch] = None 

764 globalmeta: StationmetaGlobalFields = None 

765 

766 class Config: 

767 orm_mode = True 

768 

769 

770class Stationmeta(StationmetaBase): 

771 id: int = Field(None, description="for internal use only") 

772 

773 class Config: 

774 orm_mode = True 

775