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 is_amended: bool 268 is_correction: bool 269 max_temp: str | None = None 270 min_temp: str | None = None 271 alts: list[str] | None = None 272 temps: list[str] | None = None 273 remarks_info: RemarksData | None = None 274 275 276@dataclass 277class ReportTrans: 278 altimeter: str 279 clouds: str 280 wx_codes: str 281 visibility: str 282 283 284@dataclass 285class MetarTrans(ReportTrans): 286 dewpoint: str 287 remarks: dict 288 temperature: str 289 wind: str 290 291 292@dataclass 293class TafLineTrans(ReportTrans): 294 icing: str 295 turbulence: str 296 wind: str 297 wind_shear: str 298 299 300@dataclass 301class TafTrans: 302 forecast: list[TafLineTrans] 303 max_temp: str 304 min_temp: str 305 remarks: dict 306 307 308@dataclass 309class Turbulence: 310 severity: str 311 floor: Number | None = None 312 ceiling: Number | None = None 313 314 315@dataclass 316class Icing(Turbulence): 317 type: str | None = None 318 319 320@dataclass 321class PirepData(ReportData): 322 aircraft: Aircraft | str | None = None 323 altitude: Number | str | None = None 324 clouds: list[Cloud] | None = None 325 flight_visibility: Number | None = None 326 icing: Icing | None = None 327 location: Location | None = None 328 other: list[str] | None = None 329 temperature: Number | None = None 330 turbulence: Turbulence | None = None 331 type: str | None = None 332 wx_codes: list[Code] | None = None 333 334 335@dataclass 336class AirepData(ReportData): 337 pass 338 339 340@dataclass 341class Bulletin: 342 repr: str 343 type: Code 344 country: str 345 number: int 346 347 348@dataclass 349class Movement: 350 repr: str 351 direction: Number | None 352 speed: Number | None 353 354 355MIN_POLY_SIZE = 2 356 357 358@dataclass 359class AirSigObservation: 360 type: Code | None 361 start_time: Timestamp | None 362 end_time: Timestamp | None 363 position: Coord | None 364 floor: Number | None 365 ceiling: Number | None 366 coords: list[Coord] 367 bounds: list[str] 368 movement: Movement | None 369 intensity: Code | None 370 other: list[str] 371 372 @property 373 def poly(self) -> Polygon | None: 374 if Polygon is None: 375 extra = "shape" 376 raise MissingExtraModule(extra) 377 return Polygon([c.pair for c in self.coords]) if len(self.coords) > MIN_POLY_SIZE else None 378 379 380@dataclass 381class AirSigmetData(ReportData): 382 bulletin: Bulletin 383 issuer: str 384 correction: str | None 385 area: str 386 type: str 387 start_time: Timestamp | None 388 end_time: Timestamp | None 389 body: str 390 region: str 391 observation: AirSigObservation | None 392 forecast: AirSigObservation | None 393 394 395@dataclass 396class Qualifiers: 397 repr: str 398 fir: str 399 subject: Code | None 400 condition: Code | None 401 traffic: Code | None 402 purpose: list[Code] 403 scope: list[Code] 404 lower: Number | None 405 upper: Number | None 406 coord: Coord | None 407 radius: Number | None 408 409 410@dataclass 411class NotamData(ReportData): 412 number: str | None 413 replaces: str | None 414 type: Code | None 415 qualifiers: Qualifiers | None 416 start_time: Timestamp | Code | None 417 end_time: Timestamp | Code | None 418 schedule: str | None 419 body: str 420 lower: Number | None 421 upper: Number | None 422 423 424@dataclass 425class GfsPeriod: 426 time: Timestamp 427 temperature: Number 428 dewpoint: Number 429 cloud: Code 430 temperature_minmax: Number | None = None 431 precip_chance_12: Number | None = None 432 precip_amount_12: Code | None = None 433 thunderstorm_12: Number | None = None 434 severe_storm_12: Number | None = None 435 freezing_precip: Number | None = None 436 precip_type: Code | None = None 437 snow: Number | None = None 438 439 440@dataclass 441class MavPeriod(GfsPeriod): 442 wind_direction: Number | None = None 443 wind_speed: Number | None = None 444 precip_chance_6: Number | None = None 445 precip_amount_6: Code | None = None 446 thunderstorm_6: Number | None = None 447 severe_storm_6: Number | None = None 448 ceiling: Code | None = None 449 visibility: Code | None = None 450 vis_obstruction: Code | None = None 451 452 453@dataclass 454class MexPeriod(GfsPeriod): 455 precip_chance_24: Number | None = None 456 precip_amount_24: Code | None = None 457 thunderstorm_24: Number | None = None 458 severe_storm_24: Number | None = None 459 rain_snow_mix: Number | None = None 460 snow_amount_24: Code | None = None 461 462 463@dataclass 464class MavData(ReportData): 465 forecast: list[MavPeriod] 466 467 468@dataclass 469class MexData(ReportData): 470 forecast: list[MexPeriod] 471 472 473@dataclass 474class NbmUnits(Units): 475 duration: str 476 solar_radiation: str 477 wave_height: str 478 479 480@dataclass 481class NbmPeriod: 482 time: Timestamp 483 temperature: Number | None = None 484 dewpoint: Number | None = None 485 sky_cover: Number | None = None 486 wind_direction: Number | None = None 487 wind_speed: Number | None = None 488 wind_gust: Number | None = None 489 snow_level: Number | None = None 490 precip_duration: Number | None = None 491 freezing_precip: Number | None = None 492 snow: Number | None = None 493 sleet: Number | None = None 494 rain: Number | None = None 495 solar_radiation: Number | None = None 496 wave_height: Number | None = None 497 498 499@dataclass 500class NbhsShared(NbmPeriod): 501 ceiling: Number | None = None 502 visibility: Number | None = None 503 cloud_base: Number | None = None 504 mixing_height: Number | None = None 505 transport_wind_direction: Number | None = None 506 transport_wind_speed: Number | None = None 507 haines: list[Number] | None = None 508 509 510@dataclass 511class NbhPeriod(NbhsShared): 512 precip_chance_1: Number | None = None 513 precip_chance_6: Number | None = None 514 precip_amount_1: Number | None = None 515 thunderstorm_1: Number | None = None 516 snow_amount_1: Number | None = None 517 icing_amount_1: Number | None = None 518 519 520@dataclass 521class NbsPeriod(NbhsShared): 522 temperature_minmax: Number | None = None 523 precip_chance_6: Number | None = None 524 precip_chance_12: Number | None = None 525 precip_amount_6: Number | None = None 526 precip_amount_12: Number | None = None 527 precip_duration: Number | None = None 528 thunderstorm_3: Number | None = None 529 thunderstorm_6: Number | None = None 530 thunderstorm_12: Number | None = None 531 snow_amount_6: Number | None = None 532 icing_amount_6: Number | None = None 533 534 535@dataclass 536class NbePeriod(NbmPeriod): 537 temperature_minmax: Number | None = None 538 precip_chance_12: Number | None = None 539 precip_amount_12: Number | None = None 540 precip_amount_24: Number | None = None 541 thunderstorm_12: Number | None = None 542 snow_amount_12: Number | None = None 543 snow_amount_24: Number | None = None 544 icing_amount_12: Number | None = None 545 546 547@dataclass 548class NbxPeriod(NbmPeriod): 549 precip_chance_12: Number | None = None 550 precip_amount_12: Number | None = None 551 precip_amount_24: Number | None = None 552 snow_amount_12: Number | None = None 553 icing_amount_12: Number | None = None 554 555 556@dataclass 557class NbhData(ReportData): 558 forecast: list[NbhPeriod] 559 560 561@dataclass 562class NbsData(ReportData): 563 forecast: list[NbsPeriod] 564 565 566@dataclass 567class NbeData(ReportData): 568 forecast: list[NbePeriod] 569 570 571@dataclass 572class NbxData(ReportData): 573 forecast: list[NbxPeriod] 574 575 576# @dataclass 577# class GfsPeriodTrans: 578# temperature: str 579# dewpoint: str 580# cloud: str 581# precip_chance_12: str 582# precip_amount_12: str 583# thunderstorm_12: str 584# severe_storm_12: str 585# freezing_precip: str 586# precip_type: str 587# snow: str 588 589 590# @dataclass 591# class MavPeriodTrans(GfsPeriodTrans): 592# wind_direction: str 593# wind_speed: str 594# precip_chance_6: str 595# precip_amount_6: str 596# thunderstorm_6: str 597# severe_storm_6: str 598# ceiling: str 599# visibility: str 600# vis_obstruction: str 601 602 603# @dataclass 604# class MexPeriodTrans(GfsPeriodTrans): 605# precip_chance_24: str 606# precip_amount_24: str 607# thunderstorm_24: str 608# severe_storm_24: str 609# rain_snow_mix: str 610# snow_amount_24: str 611 612 613@dataclass 614class Sanitization: 615 """Tracks changes made during the sanitization process.""" 616 617 removed: list[str] = field(default_factory=list) 618 replaced: dict[str, str] = field(default_factory=dict) 619 duplicates_found: bool = False 620 extra_spaces_found: bool = False 621 extra_spaces_needed: bool = False 622 623 @property 624 def errors_found(self) -> bool: 625 return bool( 626 self.removed 627 or self.replaced 628 or self.duplicates_found 629 or self.extra_spaces_found 630 or self.extra_spaces_needed 631 ) 632 633 def log(self, item: str, replacement: str | None = None) -> None: 634 """Log a changed item. Calling without a replacement assumes removal.""" 635 item = item.strip() 636 if not item: 637 return 638 if replacement is None: 639 self.removed.insert(0, item) 640 return 641 replacement = replacement.strip() 642 if not replacement: 643 self.removed.insert(0, item) 644 elif item != replacement: 645 self.replaced[item] = replacement 646 647 def log_list(self, before: list[str], after: list[str]) -> None: 648 """Log list differences. Assumes that list length and order haven't changed.""" 649 for item, replacement in zip(before, after, strict=True): 650 if item != replacement: 651 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 is_amended: bool 269 is_correction: bool 270 max_temp: str | None = None 271 min_temp: str | None = None 272 alts: list[str] | None = None 273 temps: list[str] | None = None 274 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:
285@dataclass 286class MetarTrans(ReportTrans): 287 dewpoint: str 288 remarks: dict 289 temperature: str 290 wind: str
MetarTrans( altimeter: str, clouds: str, wx_codes: str, visibility: str, dewpoint: str, remarks: dict, temperature: str, wind: str)
Inherited Members
293@dataclass 294class TafLineTrans(ReportTrans): 295 icing: str 296 turbulence: str 297 wind: str 298 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:
301@dataclass 302class TafTrans: 303 forecast: list[TafLineTrans] 304 max_temp: str 305 min_temp: str 306 remarks: dict
TafTrans( forecast: list[TafLineTrans], max_temp: str, min_temp: str, remarks: dict)
forecast: list[TafLineTrans]
@dataclass
class
Turbulence:
309@dataclass 310class Turbulence: 311 severity: str 312 floor: Number | None = None 313 ceiling: Number | None = None
Icing( severity: str, floor: Number | None = None, ceiling: Number | None = None, type: str | None = None)
Inherited Members
321@dataclass 322class PirepData(ReportData): 323 aircraft: Aircraft | str | None = None 324 altitude: Number | str | None = None 325 clouds: list[Cloud] | None = None 326 flight_visibility: Number | None = None 327 icing: Icing | None = None 328 location: Location | None = None 329 other: list[str] | None = None 330 temperature: Number | None = None 331 turbulence: Turbulence | None = None 332 type: str | None = None 333 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:
349@dataclass 350class Movement: 351 repr: str 352 direction: Number | None 353 speed: Number | None
direction: Number | None
speed: Number | None
MIN_POLY_SIZE =
2
@dataclass
class
AirSigObservation:
359@dataclass 360class AirSigObservation: 361 type: Code | None 362 start_time: Timestamp | None 363 end_time: Timestamp | None 364 position: Coord | None 365 floor: Number | None 366 ceiling: Number | None 367 coords: list[Coord] 368 bounds: list[str] 369 movement: Movement | None 370 intensity: Code | None 371 other: list[str] 372 373 @property 374 def poly(self) -> Polygon | None: 375 if Polygon is None: 376 extra = "shape" 377 raise MissingExtraModule(extra) 378 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
381@dataclass 382class AirSigmetData(ReportData): 383 bulletin: Bulletin 384 issuer: str 385 correction: str | None 386 area: str 387 type: str 388 start_time: Timestamp | None 389 end_time: Timestamp | None 390 body: str 391 region: str 392 observation: AirSigObservation | None 393 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:
396@dataclass 397class Qualifiers: 398 repr: str 399 fir: str 400 subject: Code | None 401 condition: Code | None 402 traffic: Code | None 403 purpose: list[Code] 404 scope: list[Code] 405 lower: Number | None 406 upper: Number | None 407 coord: Coord | None 408 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
411@dataclass 412class NotamData(ReportData): 413 number: str | None 414 replaces: str | None 415 type: Code | None 416 qualifiers: Qualifiers | None 417 start_time: Timestamp | Code | None 418 end_time: Timestamp | Code | None 419 schedule: str | None 420 body: str 421 lower: Number | None 422 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:
425@dataclass 426class GfsPeriod: 427 time: Timestamp 428 temperature: Number 429 dewpoint: Number 430 cloud: Code 431 temperature_minmax: Number | None = None 432 precip_chance_12: Number | None = None 433 precip_amount_12: Code | None = None 434 thunderstorm_12: Number | None = None 435 severe_storm_12: Number | None = None 436 freezing_precip: Number | None = None 437 precip_type: Code | None = None 438 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
441@dataclass 442class MavPeriod(GfsPeriod): 443 wind_direction: Number | None = None 444 wind_speed: Number | None = None 445 precip_chance_6: Number | None = None 446 precip_amount_6: Code | None = None 447 thunderstorm_6: Number | None = None 448 severe_storm_6: Number | None = None 449 ceiling: Code | None = None 450 visibility: Code | None = None 451 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)
454@dataclass 455class MexPeriod(GfsPeriod): 456 precip_chance_24: Number | None = None 457 precip_amount_24: Code | None = None 458 thunderstorm_24: Number | None = None 459 severe_storm_24: Number | None = None 460 rain_snow_mix: Number | None = None 461 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]
474@dataclass 475class NbmUnits(Units): 476 duration: str 477 solar_radiation: str 478 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:
481@dataclass 482class NbmPeriod: 483 time: Timestamp 484 temperature: Number | None = None 485 dewpoint: Number | None = None 486 sky_cover: Number | None = None 487 wind_direction: Number | None = None 488 wind_speed: Number | None = None 489 wind_gust: Number | None = None 490 snow_level: Number | None = None 491 precip_duration: Number | None = None 492 freezing_precip: Number | None = None 493 snow: Number | None = None 494 sleet: Number | None = None 495 rain: Number | None = None 496 solar_radiation: Number | None = None 497 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
511@dataclass 512class NbhPeriod(NbhsShared): 513 precip_chance_1: Number | None = None 514 precip_chance_6: Number | None = None 515 precip_amount_1: Number | None = None 516 thunderstorm_1: Number | None = None 517 snow_amount_1: Number | None = None 518 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)
521@dataclass 522class NbsPeriod(NbhsShared): 523 temperature_minmax: Number | None = None 524 precip_chance_6: Number | None = None 525 precip_chance_12: Number | None = None 526 precip_amount_6: Number | None = None 527 precip_amount_12: Number | None = None 528 precip_duration: Number | None = None 529 thunderstorm_3: Number | None = None 530 thunderstorm_6: Number | None = None 531 thunderstorm_12: Number | None = None 532 snow_amount_6: Number | None = None 533 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)
536@dataclass 537class NbePeriod(NbmPeriod): 538 temperature_minmax: Number | None = None 539 precip_chance_12: Number | None = None 540 precip_amount_12: Number | None = None 541 precip_amount_24: Number | None = None 542 thunderstorm_12: Number | None = None 543 snow_amount_12: Number | None = None 544 snow_amount_24: Number | None = None 545 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)
548@dataclass 549class NbxPeriod(NbmPeriod): 550 precip_chance_12: Number | None = None 551 precip_amount_12: Number | None = None 552 precip_amount_24: Number | None = None 553 snow_amount_12: Number | None = None 554 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:
614@dataclass 615class Sanitization: 616 """Tracks changes made during the sanitization process.""" 617 618 removed: list[str] = field(default_factory=list) 619 replaced: dict[str, str] = field(default_factory=dict) 620 duplicates_found: bool = False 621 extra_spaces_found: bool = False 622 extra_spaces_needed: bool = False 623 624 @property 625 def errors_found(self) -> bool: 626 return bool( 627 self.removed 628 or self.replaced 629 or self.duplicates_found 630 or self.extra_spaces_found 631 or self.extra_spaces_needed 632 ) 633 634 def log(self, item: str, replacement: str | None = None) -> None: 635 """Log a changed item. Calling without a replacement assumes removal.""" 636 item = item.strip() 637 if not item: 638 return 639 if replacement is None: 640 self.removed.insert(0, item) 641 return 642 replacement = replacement.strip() 643 if not replacement: 644 self.removed.insert(0, item) 645 elif item != replacement: 646 self.replaced[item] = replacement 647 648 def log_list(self, before: list[str], after: list[str]) -> None: 649 """Log list differences. Assumes that list length and order haven't changed.""" 650 for item, replacement in zip(before, after, strict=True): 651 if item != replacement: 652 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:
634 def log(self, item: str, replacement: str | None = None) -> None: 635 """Log a changed item. Calling without a replacement assumes removal.""" 636 item = item.strip() 637 if not item: 638 return 639 if replacement is None: 640 self.removed.insert(0, item) 641 return 642 replacement = replacement.strip() 643 if not replacement: 644 self.removed.insert(0, item) 645 elif item != replacement: 646 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:
648 def log_list(self, before: list[str], after: list[str]) -> None: 649 """Log list differences. Assumes that list length and order haven't changed.""" 650 for item, replacement in zip(before, after, strict=True): 651 if item != replacement: 652 self.log(item, replacement)
Log list differences. Assumes that list length and order haven't changed.