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