avwx.structs
Contains dataclasses to hold report data.
1"""Contains dataclasses to hold report data.""" 2 3# stdlib 4from __future__ import annotations 5 6from dataclasses import dataclass, field 7from typing import TYPE_CHECKING, TypeAlias 8 9if TYPE_CHECKING: 10 from datetime import datetime 11 12# module 13from avwx.exceptions import MissingExtraModule 14from avwx.load_utils import LazyLoad 15from avwx.static.core import IN_UNITS, NA_UNITS 16 17try: 18 from typing import Self 19except ImportError: 20 from typing_extensions import Self 21try: 22 from shapely.geometry import Point, Polygon 23except ModuleNotFoundError: 24 Point, Polygon = TypeAlias, TypeAlias # type: ignore 25 26AIRCRAFT = LazyLoad("aircraft") 27 28 29@dataclass 30class Aircraft: 31 code: str 32 type: str 33 34 @classmethod 35 def from_icao(cls, code: str) -> Self: 36 """Load an Aircraft from an ICAO aircraft code.""" 37 try: 38 return cls(code=code, type=AIRCRAFT[code]) 39 except KeyError as key_error: 40 msg = f"{code} is not a known aircraft code" 41 raise ValueError(msg) from key_error 42 43 44@dataclass 45class Units: 46 accumulation: str 47 altimeter: str 48 altitude: str 49 temperature: str 50 visibility: str 51 wind_speed: str 52 53 @classmethod 54 def international(cls) -> Self: 55 """Create default internation units.""" 56 return cls(**IN_UNITS) 57 58 @classmethod 59 def north_american(cls) -> Self: 60 """Create default North American units.""" 61 return cls(**NA_UNITS) 62 63 64@dataclass 65class Number: 66 repr: str 67 value: int | float | None 68 spoken: str 69 70 71@dataclass 72class Fraction(Number): 73 numerator: int 74 denominator: int 75 normalized: str 76 77 78@dataclass 79class Altitude(Number): 80 """A vertical limit that knows whether it is a flight level. 81 82 ``value`` is in hundreds of feet when ``flight_level`` is set, and in 83 ``Units.altitude`` otherwise. 84 """ 85 86 flight_level: bool = False 87 88 89@dataclass 90class Timestamp: 91 repr: str 92 dt: datetime | None 93 94 95@dataclass 96class Code: 97 repr: str 98 value: str 99 100 @classmethod 101 def from_dict( 102 cls, 103 key: str | None, 104 codes: dict[str, str], 105 *, 106 default: str | None = None, 107 error: bool = True, 108 ) -> Self | None: 109 """Load a code from a known key and value dict.""" 110 value: str | None 111 if not key: 112 return None 113 try: 114 value = codes[key] 115 except KeyError as exc: 116 if error: 117 msg = f"No code found for {key}" 118 raise KeyError(msg) from exc 119 value = default 120 return cls(key, value or "Unknown") 121 122 @classmethod 123 def from_list( 124 cls, 125 keys: str | None, 126 codes: dict[str, str], 127 *, 128 exclusive: bool = False, 129 ) -> list[Self]: 130 """Load a list of codes from string characters.""" 131 if not keys: 132 return [] 133 out = [] 134 for key in keys.strip(): 135 if value := codes.get(key): 136 out.append(cls(key, value)) 137 elif exclusive: 138 return [] 139 return out 140 141 142@dataclass 143class Coord: 144 lat: float 145 lon: float 146 repr: str | None = None 147 148 @property 149 def pair(self) -> tuple[float, float]: 150 return self.lat, self.lon 151 152 @property 153 def point(self) -> Point: 154 if Point is None: 155 extra = "shape" 156 raise MissingExtraModule(extra) 157 return Point(self.lat, self.lon) 158 159 @staticmethod 160 def to_dms(value: float) -> tuple[int, int, int]: 161 """Convert a coordinate decimal value to degree, minute, second.""" 162 minute, second = divmod(abs(value) * 3600, 60) 163 degree, minute = divmod(minute, 60) 164 if value < 0: 165 degree *= -1 166 return int(degree), int(minute), int(second) 167 168 169@dataclass 170class Cloud: 171 repr: str 172 type: str | None = None 173 base: int | None = None 174 top: int | None = None 175 modifier: str | None = None 176 177 178@dataclass 179class RunwayVisibility: 180 repr: str 181 runway: str 182 visibility: Number | None 183 variable_visibility: list[Number] 184 trend: Code | None 185 186 187@dataclass 188class Location: 189 repr: str 190 station: str | None 191 direction: Number | None 192 distance: Number | None 193 194 195@dataclass 196class PressureTendency: 197 repr: str 198 tendency: str 199 change: float 200 201 202@dataclass 203class FiveDigitCodes: 204 maximum_temperature_6: Number | None = None # 1 205 minimum_temperature_6: Number | None = None # 2 206 pressure_tendency: PressureTendency | None = None # 5 207 precip_36_hours: Number | None = None # 6 208 precip_24_hours: Number | None = None # 7 209 sunshine_minutes: Number | None = None # 9 210 211 212@dataclass 213class RemarksData(FiveDigitCodes): 214 codes: list[Code] = field(default_factory=list) 215 dewpoint_decimal: Number | None = None 216 maximum_temperature_24: Number | None = None 217 minimum_temperature_24: Number | None = None 218 precip_hourly: Number | None = None 219 sea_level_pressure: Number | None = None 220 snow_depth: Number | None = None 221 temperature_decimal: Number | None = None 222 223 224@dataclass 225class ReportData: 226 raw: str 227 sanitized: str 228 station: str | None 229 time: Timestamp | None 230 remarks: str | None 231 232 233@dataclass 234class SharedData: 235 altimeter: Number | None 236 clouds: list[Cloud] 237 flight_rules: str 238 other: list[str] 239 visibility: Number | None 240 wind_direction: Number | None 241 wind_gust: Number | None 242 wind_speed: Number | None 243 wx_codes: list[Code] 244 245 246@dataclass 247class MetarData(ReportData, SharedData): 248 dewpoint: Number | None 249 relative_humidity: float | None 250 remarks_info: RemarksData | None 251 runway_visibility: list[RunwayVisibility] 252 temperature: Number | None 253 wind_variable_direction: list[Number] 254 density_altitude: int | None = None 255 pressure_altitude: int | None = None 256 257 258@dataclass 259class TafLineData(SharedData): 260 end_time: Timestamp | None 261 icing: list[str] 262 probability: Number | None 263 raw: str 264 sanitized: str 265 start_time: Timestamp | None 266 transition_start: Timestamp | None 267 turbulence: list[str] 268 type: str 269 wind_shear: str | None 270 wind_variable_direction: list[Number] | None 271 272 273@dataclass 274class TafData(ReportData): 275 forecast: list[TafLineData] 276 start_time: Timestamp | None 277 end_time: Timestamp | None 278 is_amended: bool 279 is_correction: bool 280 max_temp: str | None = None 281 min_temp: str | None = None 282 alts: list[str] | None = None 283 temps: list[str] | None = None 284 remarks_info: RemarksData | None = None 285 286 287@dataclass 288class ReportTrans: 289 altimeter: str 290 clouds: str 291 wx_codes: str 292 visibility: str 293 294 295@dataclass 296class MetarTrans(ReportTrans): 297 dewpoint: str 298 remarks: dict 299 temperature: str 300 wind: str 301 302 303@dataclass 304class TafLineTrans(ReportTrans): 305 icing: str 306 turbulence: str 307 wind: str 308 wind_shear: str 309 310 311@dataclass 312class TafTrans: 313 forecast: list[TafLineTrans] 314 max_temp: str 315 min_temp: str 316 remarks: dict 317 318 319@dataclass 320class Turbulence: 321 severity: str 322 floor: Number | None = None 323 ceiling: Number | None = None 324 325 326@dataclass 327class Icing(Turbulence): 328 type: str | None = None 329 330 331@dataclass 332class PirepData(ReportData): 333 aircraft: Aircraft | str | None = None 334 altitude: Number | str | None = None 335 clouds: list[Cloud] | None = None 336 flight_visibility: Number | None = None 337 icing: Icing | None = None 338 location: Location | None = None 339 other: list[str] | None = None 340 temperature: Number | None = None 341 turbulence: Turbulence | None = None 342 type: str | None = None 343 wx_codes: list[Code] | None = None 344 345 346@dataclass 347class AirepData(ReportData): 348 pass 349 350 351@dataclass 352class Bulletin: 353 repr: str 354 type: Code 355 country: str 356 number: int 357 358 359@dataclass 360class Movement: 361 repr: str 362 direction: Number | None 363 speed: Number | None 364 365 366MIN_POLY_SIZE = 2 367 368 369@dataclass 370class AirSigObservation: 371 type: Code | None 372 start_time: Timestamp | None 373 end_time: Timestamp | None 374 position: Coord | None 375 floor: Number | None 376 ceiling: Number | None 377 coords: list[Coord] 378 bounds: list[str] 379 movement: Movement | None 380 intensity: Code | None 381 other: list[str] 382 383 @property 384 def poly(self) -> Polygon | None: 385 if Polygon is None: 386 extra = "shape" 387 raise MissingExtraModule(extra) 388 return Polygon([c.pair for c in self.coords]) if len(self.coords) > MIN_POLY_SIZE else None 389 390 391@dataclass 392class AirSigmetData(ReportData): 393 bulletin: Bulletin 394 issuer: str 395 correction: str | None 396 area: str 397 type: str 398 start_time: Timestamp | None 399 end_time: Timestamp | None 400 body: str 401 region: str 402 observation: AirSigObservation | None 403 forecast: AirSigObservation | None 404 405 406@dataclass 407class Qualifiers: 408 repr: str 409 fir: str 410 subject: Code | None 411 condition: Code | None 412 traffic: Code | None 413 purpose: list[Code] 414 scope: list[Code] 415 lower: Altitude | None 416 upper: Altitude | None 417 coord: Coord | None 418 radius: Number | None 419 420 421@dataclass 422class NotamData(ReportData): 423 number: str | None 424 replaces: str | None 425 type: Code | None 426 qualifiers: Qualifiers | None 427 start_time: Timestamp | Code | None 428 end_time: Timestamp | Code | None 429 schedule: str | None 430 body: str 431 lower: Altitude | None 432 upper: Altitude | None 433 434 435@dataclass 436class GfsPeriod: 437 time: Timestamp 438 temperature: Number 439 dewpoint: Number 440 cloud: Code 441 temperature_minmax: Number | None = None 442 precip_chance_12: Number | None = None 443 precip_amount_12: Code | None = None 444 thunderstorm_12: Number | None = None 445 severe_storm_12: Number | None = None 446 freezing_precip: Number | None = None 447 precip_type: Code | None = None 448 snow: Number | None = None 449 450 451@dataclass 452class MavPeriod(GfsPeriod): 453 wind_direction: Number | None = None 454 wind_speed: Number | None = None 455 precip_chance_6: Number | None = None 456 precip_amount_6: Code | None = None 457 thunderstorm_6: Number | None = None 458 severe_storm_6: Number | None = None 459 ceiling: Code | None = None 460 visibility: Code | None = None 461 vis_obstruction: Code | None = None 462 463 464@dataclass 465class MexPeriod(GfsPeriod): 466 precip_chance_24: Number | None = None 467 precip_amount_24: Code | None = None 468 thunderstorm_24: Number | None = None 469 severe_storm_24: Number | None = None 470 rain_snow_mix: Number | None = None 471 snow_amount_24: Code | None = None 472 473 474@dataclass 475class MavData(ReportData): 476 forecast: list[MavPeriod] 477 478 479@dataclass 480class MexData(ReportData): 481 forecast: list[MexPeriod] 482 483 484@dataclass 485class NbmUnits(Units): 486 duration: str 487 solar_radiation: str 488 wave_height: str 489 490 491@dataclass 492class NbmPeriod: 493 time: Timestamp 494 temperature: Number | None = None 495 dewpoint: Number | None = None 496 sky_cover: Number | None = None 497 wind_direction: Number | None = None 498 wind_speed: Number | None = None 499 wind_gust: Number | None = None 500 snow_level: Number | None = None 501 precip_duration: Number | None = None 502 freezing_precip: Number | None = None 503 snow: Number | None = None 504 sleet: Number | None = None 505 rain: Number | None = None 506 solar_radiation: Number | None = None 507 wave_height: Number | None = None 508 509 510@dataclass 511class NbhsShared(NbmPeriod): 512 ceiling: Number | None = None 513 visibility: Number | None = None 514 cloud_base: Number | None = None 515 mixing_height: Number | None = None 516 transport_wind_direction: Number | None = None 517 transport_wind_speed: Number | None = None 518 haines: list[Number] | None = None 519 520 521@dataclass 522class NbhPeriod(NbhsShared): 523 precip_chance_1: Number | None = None 524 precip_chance_6: Number | None = None 525 precip_amount_1: Number | None = None 526 thunderstorm_1: Number | None = None 527 snow_amount_1: Number | None = None 528 icing_amount_1: Number | None = None 529 530 531@dataclass 532class NbsPeriod(NbhsShared): 533 temperature_minmax: Number | None = None 534 precip_chance_6: Number | None = None 535 precip_chance_12: Number | None = None 536 precip_amount_6: Number | None = None 537 precip_amount_12: Number | None = None 538 precip_duration: Number | None = None 539 thunderstorm_3: Number | None = None 540 thunderstorm_6: Number | None = None 541 thunderstorm_12: Number | None = None 542 snow_amount_6: Number | None = None 543 icing_amount_6: Number | None = None 544 545 546@dataclass 547class NbePeriod(NbmPeriod): 548 temperature_minmax: Number | None = None 549 precip_chance_12: Number | None = None 550 precip_amount_12: Number | None = None 551 precip_amount_24: Number | None = None 552 thunderstorm_12: Number | None = None 553 snow_amount_12: Number | None = None 554 snow_amount_24: Number | None = None 555 icing_amount_12: Number | None = None 556 557 558@dataclass 559class NbxPeriod(NbmPeriod): 560 precip_chance_12: Number | None = None 561 precip_amount_12: Number | None = None 562 precip_amount_24: Number | None = None 563 snow_amount_12: Number | None = None 564 icing_amount_12: Number | None = None 565 566 567@dataclass 568class NbhData(ReportData): 569 forecast: list[NbhPeriod] 570 571 572@dataclass 573class NbsData(ReportData): 574 forecast: list[NbsPeriod] 575 576 577@dataclass 578class NbeData(ReportData): 579 forecast: list[NbePeriod] 580 581 582@dataclass 583class NbxData(ReportData): 584 forecast: list[NbxPeriod] 585 586 587# @dataclass 588# class GfsPeriodTrans: 589# temperature: str 590# dewpoint: str 591# cloud: str 592# precip_chance_12: str 593# precip_amount_12: str 594# thunderstorm_12: str 595# severe_storm_12: str 596# freezing_precip: str 597# precip_type: str 598# snow: str 599 600 601# @dataclass 602# class MavPeriodTrans(GfsPeriodTrans): 603# wind_direction: str 604# wind_speed: str 605# precip_chance_6: str 606# precip_amount_6: str 607# thunderstorm_6: str 608# severe_storm_6: str 609# ceiling: str 610# visibility: str 611# vis_obstruction: str 612 613 614# @dataclass 615# class MexPeriodTrans(GfsPeriodTrans): 616# precip_chance_24: str 617# precip_amount_24: str 618# thunderstorm_24: str 619# severe_storm_24: str 620# rain_snow_mix: str 621# snow_amount_24: str 622 623 624@dataclass 625class Sanitization: 626 """Tracks changes made during the sanitization process.""" 627 628 removed: list[str] = field(default_factory=list) 629 replaced: dict[str, str] = field(default_factory=dict) 630 duplicates_found: bool = False 631 extra_spaces_found: bool = False 632 extra_spaces_needed: bool = False 633 634 @property 635 def errors_found(self) -> bool: 636 return bool( 637 self.removed 638 or self.replaced 639 or self.duplicates_found 640 or self.extra_spaces_found 641 or self.extra_spaces_needed 642 ) 643 644 def log(self, item: str, replacement: str | None = None) -> None: 645 """Log a changed item. Calling without a replacement assumes removal.""" 646 item = item.strip() 647 if not item: 648 return 649 if replacement is None: 650 self.removed.insert(0, item) 651 return 652 replacement = replacement.strip() 653 if not replacement: 654 self.removed.insert(0, item) 655 elif item != replacement: 656 self.replaced[item] = replacement 657 658 def log_list(self, before: list[str], after: list[str]) -> None: 659 """Log list differences. Assumes that list length and order haven't changed.""" 660 for item, replacement in zip(before, after, strict=True): 661 if item != replacement: 662 self.log(item, replacement)
AIRCRAFT =
<avwx.load_utils.LazyLoad object>
@dataclass
class
Aircraft:
30@dataclass 31class Aircraft: 32 code: str 33 type: str 34 35 @classmethod 36 def from_icao(cls, code: str) -> Self: 37 """Load an Aircraft from an ICAO aircraft code.""" 38 try: 39 return cls(code=code, type=AIRCRAFT[code]) 40 except KeyError as key_error: 41 msg = f"{code} is not a known aircraft code" 42 raise ValueError(msg) from key_error
@classmethod
def
from_icao(cls, code: str) -> Self:
35 @classmethod 36 def from_icao(cls, code: str) -> Self: 37 """Load an Aircraft from an ICAO aircraft code.""" 38 try: 39 return cls(code=code, type=AIRCRAFT[code]) 40 except KeyError as key_error: 41 msg = f"{code} is not a known aircraft code" 42 raise ValueError(msg) from key_error
Load an Aircraft from an ICAO aircraft code.
@dataclass
class
Units:
45@dataclass 46class Units: 47 accumulation: str 48 altimeter: str 49 altitude: str 50 temperature: str 51 visibility: str 52 wind_speed: str 53 54 @classmethod 55 def international(cls) -> Self: 56 """Create default internation units.""" 57 return cls(**IN_UNITS) 58 59 @classmethod 60 def north_american(cls) -> Self: 61 """Create default North American units.""" 62 return cls(**NA_UNITS)
Units( accumulation: str, altimeter: str, altitude: str, temperature: str, visibility: str, wind_speed: str)
@dataclass
class
Number:
79@dataclass 80class Altitude(Number): 81 """A vertical limit that knows whether it is a flight level. 82 83 ``value`` is in hundreds of feet when ``flight_level`` is set, and in 84 ``Units.altitude`` otherwise. 85 """ 86 87 flight_level: bool = False
A vertical limit that knows whether it is a flight level.
value is in hundreds of feet when flight_level is set, and in
Units.altitude otherwise.
@dataclass
class
Timestamp:
@dataclass
class
Code:
96@dataclass 97class Code: 98 repr: str 99 value: str 100 101 @classmethod 102 def from_dict( 103 cls, 104 key: str | None, 105 codes: dict[str, str], 106 *, 107 default: str | None = None, 108 error: bool = True, 109 ) -> Self | None: 110 """Load a code from a known key and value dict.""" 111 value: str | None 112 if not key: 113 return None 114 try: 115 value = codes[key] 116 except KeyError as exc: 117 if error: 118 msg = f"No code found for {key}" 119 raise KeyError(msg) from exc 120 value = default 121 return cls(key, value or "Unknown") 122 123 @classmethod 124 def from_list( 125 cls, 126 keys: str | None, 127 codes: dict[str, str], 128 *, 129 exclusive: bool = False, 130 ) -> list[Self]: 131 """Load a list of codes from string characters.""" 132 if not keys: 133 return [] 134 out = [] 135 for key in keys.strip(): 136 if value := codes.get(key): 137 out.append(cls(key, value)) 138 elif exclusive: 139 return [] 140 return out
@classmethod
def
from_dict( cls, key: str | None, codes: dict[str, str], *, default: str | None = None, error: bool = True) -> Optional[Self]:
101 @classmethod 102 def from_dict( 103 cls, 104 key: str | None, 105 codes: dict[str, str], 106 *, 107 default: str | None = None, 108 error: bool = True, 109 ) -> Self | None: 110 """Load a code from a known key and value dict.""" 111 value: str | None 112 if not key: 113 return None 114 try: 115 value = codes[key] 116 except KeyError as exc: 117 if error: 118 msg = f"No code found for {key}" 119 raise KeyError(msg) from exc 120 value = default 121 return cls(key, value or "Unknown")
Load a code from a known key and value dict.
@classmethod
def
from_list( cls, keys: str | None, codes: dict[str, str], *, exclusive: bool = False) -> list[typing.Self]:
123 @classmethod 124 def from_list( 125 cls, 126 keys: str | None, 127 codes: dict[str, str], 128 *, 129 exclusive: bool = False, 130 ) -> list[Self]: 131 """Load a list of codes from string characters.""" 132 if not keys: 133 return [] 134 out = [] 135 for key in keys.strip(): 136 if value := codes.get(key): 137 out.append(cls(key, value)) 138 elif exclusive: 139 return [] 140 return out
Load a list of codes from string characters.
@dataclass
class
Coord:
143@dataclass 144class Coord: 145 lat: float 146 lon: float 147 repr: str | None = None 148 149 @property 150 def pair(self) -> tuple[float, float]: 151 return self.lat, self.lon 152 153 @property 154 def point(self) -> Point: 155 if Point is None: 156 extra = "shape" 157 raise MissingExtraModule(extra) 158 return Point(self.lat, self.lon) 159 160 @staticmethod 161 def to_dms(value: float) -> tuple[int, int, int]: 162 """Convert a coordinate decimal value to degree, minute, second.""" 163 minute, second = divmod(abs(value) * 3600, 60) 164 degree, minute = divmod(minute, 60) 165 if value < 0: 166 degree *= -1 167 return int(degree), int(minute), int(second)
@staticmethod
def
to_dms(value: float) -> tuple[int, int, int]:
160 @staticmethod 161 def to_dms(value: float) -> tuple[int, int, int]: 162 """Convert a coordinate decimal value to degree, minute, second.""" 163 minute, second = divmod(abs(value) * 3600, 60) 164 degree, minute = divmod(minute, 60) 165 if value < 0: 166 degree *= -1 167 return int(degree), int(minute), int(second)
Convert a coordinate decimal value to degree, minute, second.
@dataclass
class
Cloud:
170@dataclass 171class Cloud: 172 repr: str 173 type: str | None = None 174 base: int | None = None 175 top: int | None = None 176 modifier: str | None = None
@dataclass
class
RunwayVisibility:
179@dataclass 180class RunwayVisibility: 181 repr: str 182 runway: str 183 visibility: Number | None 184 variable_visibility: list[Number] 185 trend: Code | None
RunwayVisibility( repr: str, runway: str, visibility: Number | None, variable_visibility: list[Number], trend: Code | None)
visibility: Number | None
variable_visibility: list[Number]
trend: Code | None
@dataclass
class
Location:
188@dataclass 189class Location: 190 repr: str 191 station: str | None 192 direction: Number | None 193 distance: Number | None
direction: Number | None
distance: Number | None
@dataclass
class
PressureTendency:
@dataclass
class
FiveDigitCodes:
203@dataclass 204class FiveDigitCodes: 205 maximum_temperature_6: Number | None = None # 1 206 minimum_temperature_6: Number | None = None # 2 207 pressure_tendency: PressureTendency | None = None # 5 208 precip_36_hours: Number | None = None # 6 209 precip_24_hours: Number | None = None # 7 210 sunshine_minutes: Number | None = None # 9
FiveDigitCodes( maximum_temperature_6: Number | None = None, minimum_temperature_6: Number | None = None, pressure_tendency: PressureTendency | None = None, precip_36_hours: Number | None = None, precip_24_hours: Number | None = None, sunshine_minutes: Number | None = None)
213@dataclass 214class RemarksData(FiveDigitCodes): 215 codes: list[Code] = field(default_factory=list) 216 dewpoint_decimal: Number | None = None 217 maximum_temperature_24: Number | None = None 218 minimum_temperature_24: Number | None = None 219 precip_hourly: Number | None = None 220 sea_level_pressure: Number | None = None 221 snow_depth: Number | None = None 222 temperature_decimal: Number | None = None
RemarksData( maximum_temperature_6: Number | None = None, minimum_temperature_6: Number | None = None, pressure_tendency: PressureTendency | None = None, precip_36_hours: Number | None = None, precip_24_hours: Number | None = None, sunshine_minutes: Number | None = None, codes: list[Code] = <factory>, dewpoint_decimal: Number | None = None, maximum_temperature_24: Number | None = None, minimum_temperature_24: Number | None = None, precip_hourly: Number | None = None, sea_level_pressure: Number | None = None, snow_depth: Number | None = None, temperature_decimal: Number | None = None)
codes: list[Code]
@dataclass
class
ReportData:
225@dataclass 226class ReportData: 227 raw: str 228 sanitized: str 229 station: str | None 230 time: Timestamp | None 231 remarks: str | None
ReportData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None)
time: Timestamp | None
247@dataclass 248class MetarData(ReportData, SharedData): 249 dewpoint: Number | None 250 relative_humidity: float | None 251 remarks_info: RemarksData | None 252 runway_visibility: list[RunwayVisibility] 253 temperature: Number | None 254 wind_variable_direction: list[Number] 255 density_altitude: int | None = None 256 pressure_altitude: int | None = None
MetarData( altimeter: Number | None, clouds: list[Cloud], flight_rules: str, other: list[str], visibility: Number | None, wind_direction: Number | None, wind_gust: Number | None, wind_speed: Number | None, wx_codes: list[Code], raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, dewpoint: Number | None, relative_humidity: float | None, remarks_info: RemarksData | None, runway_visibility: list[RunwayVisibility], temperature: Number | None, wind_variable_direction: list[Number], density_altitude: int | None = None, pressure_altitude: int | None = None)
dewpoint: Number | None
remarks_info: RemarksData | None
runway_visibility: list[RunwayVisibility]
temperature: Number | None
wind_variable_direction: list[Number]
259@dataclass 260class TafLineData(SharedData): 261 end_time: Timestamp | None 262 icing: list[str] 263 probability: Number | None 264 raw: str 265 sanitized: str 266 start_time: Timestamp | None 267 transition_start: Timestamp | None 268 turbulence: list[str] 269 type: str 270 wind_shear: str | None 271 wind_variable_direction: list[Number] | None
TafLineData( altimeter: Number | None, clouds: list[Cloud], flight_rules: str, other: list[str], visibility: Number | None, wind_direction: Number | None, wind_gust: Number | None, wind_speed: Number | None, wx_codes: list[Code], end_time: Timestamp | None, icing: list[str], probability: Number | None, raw: str, sanitized: str, start_time: Timestamp | None, transition_start: Timestamp | None, turbulence: list[str], type: str, wind_shear: str | None, wind_variable_direction: list[Number] | None)
end_time: Timestamp | None
probability: Number | None
start_time: Timestamp | None
transition_start: Timestamp | None
wind_variable_direction: list[Number] | None
Inherited Members
274@dataclass 275class TafData(ReportData): 276 forecast: list[TafLineData] 277 start_time: Timestamp | None 278 end_time: Timestamp | None 279 is_amended: bool 280 is_correction: bool 281 max_temp: str | None = None 282 min_temp: str | None = None 283 alts: list[str] | None = None 284 temps: list[str] | None = None 285 remarks_info: RemarksData | None = None
TafData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, forecast: list[TafLineData], start_time: Timestamp | None, end_time: Timestamp | None, is_amended: bool, is_correction: bool, max_temp: str | None = None, min_temp: str | None = None, alts: list[str] | None = None, temps: list[str] | None = None, remarks_info: RemarksData | None = None)
forecast: list[TafLineData]
start_time: Timestamp | None
end_time: Timestamp | None
@dataclass
class
ReportTrans:
296@dataclass 297class MetarTrans(ReportTrans): 298 dewpoint: str 299 remarks: dict 300 temperature: str 301 wind: str
MetarTrans( altimeter: str, clouds: str, wx_codes: str, visibility: str, dewpoint: str, remarks: dict, temperature: str, wind: str)
Inherited Members
304@dataclass 305class TafLineTrans(ReportTrans): 306 icing: str 307 turbulence: str 308 wind: str 309 wind_shear: str
TafLineTrans( altimeter: str, clouds: str, wx_codes: str, visibility: str, icing: str, turbulence: str, wind: str, wind_shear: str)
Inherited Members
@dataclass
class
TafTrans:
312@dataclass 313class TafTrans: 314 forecast: list[TafLineTrans] 315 max_temp: str 316 min_temp: str 317 remarks: dict
TafTrans( forecast: list[TafLineTrans], max_temp: str, min_temp: str, remarks: dict)
forecast: list[TafLineTrans]
@dataclass
class
Turbulence:
320@dataclass 321class Turbulence: 322 severity: str 323 floor: Number | None = None 324 ceiling: Number | None = None
Icing( severity: str, floor: Number | None = None, ceiling: Number | None = None, type: str | None = None)
Inherited Members
332@dataclass 333class PirepData(ReportData): 334 aircraft: Aircraft | str | None = None 335 altitude: Number | str | None = None 336 clouds: list[Cloud] | None = None 337 flight_visibility: Number | None = None 338 icing: Icing | None = None 339 location: Location | None = None 340 other: list[str] | None = None 341 temperature: Number | None = None 342 turbulence: Turbulence | None = None 343 type: str | None = None 344 wx_codes: list[Code] | None = None
PirepData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, aircraft: Aircraft | str | None = None, altitude: Number | str | None = None, clouds: list[Cloud] | None = None, flight_visibility: Number | None = None, icing: Icing | None = None, location: Location | None = None, other: list[str] | None = None, temperature: Number | None = None, turbulence: Turbulence | None = None, type: str | None = None, wx_codes: list[Code] | None = None)
AirepData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None)
@dataclass
class
Bulletin:
Bulletin(repr: str, type: Code, country: str, number: int)
type: Code
@dataclass
class
Movement:
360@dataclass 361class Movement: 362 repr: str 363 direction: Number | None 364 speed: Number | None
direction: Number | None
speed: Number | None
MIN_POLY_SIZE =
2
@dataclass
class
AirSigObservation:
370@dataclass 371class AirSigObservation: 372 type: Code | None 373 start_time: Timestamp | None 374 end_time: Timestamp | None 375 position: Coord | None 376 floor: Number | None 377 ceiling: Number | None 378 coords: list[Coord] 379 bounds: list[str] 380 movement: Movement | None 381 intensity: Code | None 382 other: list[str] 383 384 @property 385 def poly(self) -> Polygon | None: 386 if Polygon is None: 387 extra = "shape" 388 raise MissingExtraModule(extra) 389 return Polygon([c.pair for c in self.coords]) if len(self.coords) > MIN_POLY_SIZE else None
AirSigObservation( type: Code | None, start_time: Timestamp | None, end_time: Timestamp | None, position: Coord | None, floor: Number | None, ceiling: Number | None, coords: list[Coord], bounds: list[str], movement: Movement | None, intensity: Code | None, other: list[str])
type: Code | None
start_time: Timestamp | None
end_time: Timestamp | None
position: Coord | None
floor: Number | None
ceiling: Number | None
coords: list[Coord]
movement: Movement | None
intensity: Code | None
392@dataclass 393class AirSigmetData(ReportData): 394 bulletin: Bulletin 395 issuer: str 396 correction: str | None 397 area: str 398 type: str 399 start_time: Timestamp | None 400 end_time: Timestamp | None 401 body: str 402 region: str 403 observation: AirSigObservation | None 404 forecast: AirSigObservation | None
AirSigmetData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, bulletin: Bulletin, issuer: str, correction: str | None, area: str, type: str, start_time: Timestamp | None, end_time: Timestamp | None, body: str, region: str, observation: AirSigObservation | None, forecast: AirSigObservation | None)
bulletin: Bulletin
start_time: Timestamp | None
end_time: Timestamp | None
observation: AirSigObservation | None
forecast: AirSigObservation | None
@dataclass
class
Qualifiers:
407@dataclass 408class Qualifiers: 409 repr: str 410 fir: str 411 subject: Code | None 412 condition: Code | None 413 traffic: Code | None 414 purpose: list[Code] 415 scope: list[Code] 416 lower: Altitude | None 417 upper: Altitude | None 418 coord: Coord | None 419 radius: Number | None
Qualifiers( repr: str, fir: str, subject: Code | None, condition: Code | None, traffic: Code | None, purpose: list[Code], scope: list[Code], lower: Altitude | None, upper: Altitude | None, coord: Coord | None, radius: Number | None)
subject: Code | None
condition: Code | None
traffic: Code | None
purpose: list[Code]
scope: list[Code]
lower: Altitude | None
upper: Altitude | None
coord: Coord | None
radius: Number | None
422@dataclass 423class NotamData(ReportData): 424 number: str | None 425 replaces: str | None 426 type: Code | None 427 qualifiers: Qualifiers | None 428 start_time: Timestamp | Code | None 429 end_time: Timestamp | Code | None 430 schedule: str | None 431 body: str 432 lower: Altitude | None 433 upper: Altitude | None
NotamData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, number: str | None, replaces: str | None, type: Code | None, qualifiers: Qualifiers | None, start_time: Timestamp | Code | None, end_time: Timestamp | Code | None, schedule: str | None, body: str, lower: Altitude | None, upper: Altitude | None)
type: Code | None
qualifiers: Qualifiers | None
lower: Altitude | None
upper: Altitude | None
@dataclass
class
GfsPeriod:
436@dataclass 437class GfsPeriod: 438 time: Timestamp 439 temperature: Number 440 dewpoint: Number 441 cloud: Code 442 temperature_minmax: Number | None = None 443 precip_chance_12: Number | None = None 444 precip_amount_12: Code | None = None 445 thunderstorm_12: Number | None = None 446 severe_storm_12: Number | None = None 447 freezing_precip: Number | None = None 448 precip_type: Code | None = None 449 snow: Number | None = None
GfsPeriod( time: Timestamp, temperature: Number, dewpoint: Number, cloud: Code, temperature_minmax: Number | None = None, precip_chance_12: Number | None = None, precip_amount_12: Code | None = None, thunderstorm_12: Number | None = None, severe_storm_12: Number | None = None, freezing_precip: Number | None = None, precip_type: Code | None = None, snow: Number | None = None)
time: Timestamp
temperature: Number
dewpoint: Number
cloud: Code
452@dataclass 453class MavPeriod(GfsPeriod): 454 wind_direction: Number | None = None 455 wind_speed: Number | None = None 456 precip_chance_6: Number | None = None 457 precip_amount_6: Code | None = None 458 thunderstorm_6: Number | None = None 459 severe_storm_6: Number | None = None 460 ceiling: Code | None = None 461 visibility: Code | None = None 462 vis_obstruction: Code | None = None
MavPeriod( time: Timestamp, temperature: Number, dewpoint: Number, cloud: Code, temperature_minmax: Number | None = None, precip_chance_12: Number | None = None, precip_amount_12: Code | None = None, thunderstorm_12: Number | None = None, severe_storm_12: Number | None = None, freezing_precip: Number | None = None, precip_type: Code | None = None, snow: Number | None = None, wind_direction: Number | None = None, wind_speed: Number | None = None, precip_chance_6: Number | None = None, precip_amount_6: Code | None = None, thunderstorm_6: Number | None = None, severe_storm_6: Number | None = None, ceiling: Code | None = None, visibility: Code | None = None, vis_obstruction: Code | None = None)
465@dataclass 466class MexPeriod(GfsPeriod): 467 precip_chance_24: Number | None = None 468 precip_amount_24: Code | None = None 469 thunderstorm_24: Number | None = None 470 severe_storm_24: Number | None = None 471 rain_snow_mix: Number | None = None 472 snow_amount_24: Code | None = None
MexPeriod( time: Timestamp, temperature: Number, dewpoint: Number, cloud: Code, temperature_minmax: Number | None = None, precip_chance_12: Number | None = None, precip_amount_12: Code | None = None, thunderstorm_12: Number | None = None, severe_storm_12: Number | None = None, freezing_precip: Number | None = None, precip_type: Code | None = None, snow: Number | None = None, precip_chance_24: Number | None = None, precip_amount_24: Code | None = None, thunderstorm_24: Number | None = None, severe_storm_24: Number | None = None, rain_snow_mix: Number | None = None, snow_amount_24: Code | None = None)
MavData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, forecast: list[MavPeriod])
forecast: list[MavPeriod]
MexData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, forecast: list[MexPeriod])
forecast: list[MexPeriod]
485@dataclass 486class NbmUnits(Units): 487 duration: str 488 solar_radiation: str 489 wave_height: str
NbmUnits( accumulation: str, altimeter: str, altitude: str, temperature: str, visibility: str, wind_speed: str, duration: str, solar_radiation: str, wave_height: str)
Inherited Members
@dataclass
class
NbmPeriod:
492@dataclass 493class NbmPeriod: 494 time: Timestamp 495 temperature: Number | None = None 496 dewpoint: Number | None = None 497 sky_cover: Number | None = None 498 wind_direction: Number | None = None 499 wind_speed: Number | None = None 500 wind_gust: Number | None = None 501 snow_level: Number | None = None 502 precip_duration: Number | None = None 503 freezing_precip: Number | None = None 504 snow: Number | None = None 505 sleet: Number | None = None 506 rain: Number | None = None 507 solar_radiation: Number | None = None 508 wave_height: Number | None = None
NbmPeriod( time: Timestamp, temperature: Number | None = None, dewpoint: Number | None = None, sky_cover: Number | None = None, wind_direction: Number | None = None, wind_speed: Number | None = None, wind_gust: Number | None = None, snow_level: Number | None = None, precip_duration: Number | None = None, freezing_precip: Number | None = None, snow: Number | None = None, sleet: Number | None = None, rain: Number | None = None, solar_radiation: Number | None = None, wave_height: Number | None = None)
time: Timestamp
522@dataclass 523class NbhPeriod(NbhsShared): 524 precip_chance_1: Number | None = None 525 precip_chance_6: Number | None = None 526 precip_amount_1: Number | None = None 527 thunderstorm_1: Number | None = None 528 snow_amount_1: Number | None = None 529 icing_amount_1: Number | None = None
NbhPeriod( time: Timestamp, temperature: Number | None = None, dewpoint: Number | None = None, sky_cover: Number | None = None, wind_direction: Number | None = None, wind_speed: Number | None = None, wind_gust: Number | None = None, snow_level: Number | None = None, precip_duration: Number | None = None, freezing_precip: Number | None = None, snow: Number | None = None, sleet: Number | None = None, rain: Number | None = None, solar_radiation: Number | None = None, wave_height: Number | None = None, ceiling: Number | None = None, visibility: Number | None = None, cloud_base: Number | None = None, mixing_height: Number | None = None, transport_wind_direction: Number | None = None, transport_wind_speed: Number | None = None, haines: list[Number] | None = None, precip_chance_1: Number | None = None, precip_chance_6: Number | None = None, precip_amount_1: Number | None = None, thunderstorm_1: Number | None = None, snow_amount_1: Number | None = None, icing_amount_1: Number | None = None)
532@dataclass 533class NbsPeriod(NbhsShared): 534 temperature_minmax: Number | None = None 535 precip_chance_6: Number | None = None 536 precip_chance_12: Number | None = None 537 precip_amount_6: Number | None = None 538 precip_amount_12: Number | None = None 539 precip_duration: Number | None = None 540 thunderstorm_3: Number | None = None 541 thunderstorm_6: Number | None = None 542 thunderstorm_12: Number | None = None 543 snow_amount_6: Number | None = None 544 icing_amount_6: Number | None = None
NbsPeriod( time: Timestamp, temperature: Number | None = None, dewpoint: Number | None = None, sky_cover: Number | None = None, wind_direction: Number | None = None, wind_speed: Number | None = None, wind_gust: Number | None = None, snow_level: Number | None = None, precip_duration: Number | None = None, freezing_precip: Number | None = None, snow: Number | None = None, sleet: Number | None = None, rain: Number | None = None, solar_radiation: Number | None = None, wave_height: Number | None = None, ceiling: Number | None = None, visibility: Number | None = None, cloud_base: Number | None = None, mixing_height: Number | None = None, transport_wind_direction: Number | None = None, transport_wind_speed: Number | None = None, haines: list[Number] | None = None, temperature_minmax: Number | None = None, precip_chance_6: Number | None = None, precip_chance_12: Number | None = None, precip_amount_6: Number | None = None, precip_amount_12: Number | None = None, thunderstorm_3: Number | None = None, thunderstorm_6: Number | None = None, thunderstorm_12: Number | None = None, snow_amount_6: Number | None = None, icing_amount_6: Number | None = None)
547@dataclass 548class NbePeriod(NbmPeriod): 549 temperature_minmax: Number | None = None 550 precip_chance_12: Number | None = None 551 precip_amount_12: Number | None = None 552 precip_amount_24: Number | None = None 553 thunderstorm_12: Number | None = None 554 snow_amount_12: Number | None = None 555 snow_amount_24: Number | None = None 556 icing_amount_12: Number | None = None
NbePeriod( time: Timestamp, temperature: Number | None = None, dewpoint: Number | None = None, sky_cover: Number | None = None, wind_direction: Number | None = None, wind_speed: Number | None = None, wind_gust: Number | None = None, snow_level: Number | None = None, precip_duration: Number | None = None, freezing_precip: Number | None = None, snow: Number | None = None, sleet: Number | None = None, rain: Number | None = None, solar_radiation: Number | None = None, wave_height: Number | None = None, temperature_minmax: Number | None = None, precip_chance_12: Number | None = None, precip_amount_12: Number | None = None, precip_amount_24: Number | None = None, thunderstorm_12: Number | None = None, snow_amount_12: Number | None = None, snow_amount_24: Number | None = None, icing_amount_12: Number | None = None)
559@dataclass 560class NbxPeriod(NbmPeriod): 561 precip_chance_12: Number | None = None 562 precip_amount_12: Number | None = None 563 precip_amount_24: Number | None = None 564 snow_amount_12: Number | None = None 565 icing_amount_12: Number | None = None
NbxPeriod( time: Timestamp, temperature: Number | None = None, dewpoint: Number | None = None, sky_cover: Number | None = None, wind_direction: Number | None = None, wind_speed: Number | None = None, wind_gust: Number | None = None, snow_level: Number | None = None, precip_duration: Number | None = None, freezing_precip: Number | None = None, snow: Number | None = None, sleet: Number | None = None, rain: Number | None = None, solar_radiation: Number | None = None, wave_height: Number | None = None, precip_chance_12: Number | None = None, precip_amount_12: Number | None = None, precip_amount_24: Number | None = None, snow_amount_12: Number | None = None, icing_amount_12: Number | None = None)
NbhData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, forecast: list[NbhPeriod])
forecast: list[NbhPeriod]
NbsData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, forecast: list[NbsPeriod])
forecast: list[NbsPeriod]
NbeData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, forecast: list[NbePeriod])
forecast: list[NbePeriod]
NbxData( raw: str, sanitized: str, station: str | None, time: Timestamp | None, remarks: str | None, forecast: list[NbxPeriod])
forecast: list[NbxPeriod]
@dataclass
class
Sanitization:
625@dataclass 626class Sanitization: 627 """Tracks changes made during the sanitization process.""" 628 629 removed: list[str] = field(default_factory=list) 630 replaced: dict[str, str] = field(default_factory=dict) 631 duplicates_found: bool = False 632 extra_spaces_found: bool = False 633 extra_spaces_needed: bool = False 634 635 @property 636 def errors_found(self) -> bool: 637 return bool( 638 self.removed 639 or self.replaced 640 or self.duplicates_found 641 or self.extra_spaces_found 642 or self.extra_spaces_needed 643 ) 644 645 def log(self, item: str, replacement: str | None = None) -> None: 646 """Log a changed item. Calling without a replacement assumes removal.""" 647 item = item.strip() 648 if not item: 649 return 650 if replacement is None: 651 self.removed.insert(0, item) 652 return 653 replacement = replacement.strip() 654 if not replacement: 655 self.removed.insert(0, item) 656 elif item != replacement: 657 self.replaced[item] = replacement 658 659 def log_list(self, before: list[str], after: list[str]) -> None: 660 """Log list differences. Assumes that list length and order haven't changed.""" 661 for item, replacement in zip(before, after, strict=True): 662 if item != replacement: 663 self.log(item, replacement)
Tracks changes made during the sanitization process.
Sanitization( removed: list[str] = <factory>, replaced: dict[str, str] = <factory>, duplicates_found: bool = False, extra_spaces_found: bool = False, extra_spaces_needed: bool = False)
def
log(self, item: str, replacement: str | None = None) -> None:
645 def log(self, item: str, replacement: str | None = None) -> None: 646 """Log a changed item. Calling without a replacement assumes removal.""" 647 item = item.strip() 648 if not item: 649 return 650 if replacement is None: 651 self.removed.insert(0, item) 652 return 653 replacement = replacement.strip() 654 if not replacement: 655 self.removed.insert(0, item) 656 elif item != replacement: 657 self.replaced[item] = replacement
Log a changed item. Calling without a replacement assumes removal.
def
log_list(self, before: list[str], after: list[str]) -> None:
659 def log_list(self, before: list[str], after: list[str]) -> None: 660 """Log list differences. Assumes that list length and order haven't changed.""" 661 for item, replacement in zip(before, after, strict=True): 662 if item != replacement: 663 self.log(item, replacement)
Log list differences. Assumes that list length and order haven't changed.