avwx.structs

Contains dataclasses to hold report data

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

Load an Aircraft from an ICAO aircraft code

@dataclass
class Units:
41@dataclass
42class Units:
43    accumulation: str
44    altimeter: str
45    altitude: str
46    temperature: str
47    visibility: str
48    wind_speed: str
49
50    @classmethod
51    def international(cls) -> "Units":
52        """Create default internation units"""
53        return cls(**IN_UNITS)
54
55    @classmethod
56    def north_american(cls) -> "Units":
57        """Create default North American units"""
58        return cls(**NA_UNITS)
Units( accumulation: str, altimeter: str, altitude: str, temperature: str, visibility: str, wind_speed: str)
accumulation: str
altimeter: str
altitude: str
temperature: str
visibility: str
wind_speed: str
@classmethod
def international(cls) -> Units:
50    @classmethod
51    def international(cls) -> "Units":
52        """Create default internation units"""
53        return cls(**IN_UNITS)

Create default internation units

@classmethod
def north_american(cls) -> Units:
55    @classmethod
56    def north_american(cls) -> "Units":
57        """Create default North American units"""
58        return cls(**NA_UNITS)

Create default North American units

@dataclass
class Number:
61@dataclass
62class Number:
63    repr: str
64    value: Union[int, float, None]
65    spoken: str
Number(repr: str, value: Union[int, float, NoneType], spoken: str)
repr: str
value: Union[int, float, NoneType]
spoken: str
@dataclass
class Fraction(Number):
68@dataclass
69class Fraction(Number):
70    numerator: int
71    denominator: int
72    normalized: str
Fraction( repr: str, value: Union[int, float, NoneType], spoken: str, numerator: int, denominator: int, normalized: str)
numerator: int
denominator: int
normalized: str
Inherited Members
Number
repr
value
spoken
@dataclass
class Timestamp:
75@dataclass
76class Timestamp:
77    # pylint: disable=invalid-name
78    repr: str
79    dt: Optional[datetime]
Timestamp(repr: str, dt: Optional[datetime.datetime])
repr: str
dt: Optional[datetime.datetime]
@dataclass
class Code:
 85@dataclass
 86class Code:
 87    repr: str
 88    value: str
 89
 90    @classmethod
 91    def from_dict(
 92        cls: Type[CodeType],
 93        key: Optional[str],
 94        codes: Dict[str, str],
 95        default: Optional[str] = None,
 96        error: bool = True,
 97    ) -> Optional[CodeType]:
 98        """Load a code from a known key and value dict"""
 99        value: Optional[str]
100        if not key:
101            return None
102        try:
103            value = codes[key]
104        except KeyError as exc:
105            if error:
106                raise KeyError(f"No code found for {key}") from exc
107            value = default
108        return cls(key, value or "Unknown")
109
110    @classmethod
111    def from_list(
112        cls: Type[CodeType],
113        keys: Optional[str],
114        codes: Dict[str, str],
115        exclusive: bool = False,
116    ) -> List[CodeType]:
117        """Load a list of codes from string characters"""
118        if not keys:
119            return []
120        out = []
121        for key in keys.strip():
122            if value := codes.get(key):
123                out.append(cls(key, value))
124            elif exclusive:
125                return []
126        return out
Code(repr: str, value: str)
repr: str
value: str
@classmethod
def from_dict( cls: Type[~CodeType], key: Optional[str], codes: Dict[str, str], default: Optional[str] = None, error: bool = True) -> Optional[~CodeType]:
 90    @classmethod
 91    def from_dict(
 92        cls: Type[CodeType],
 93        key: Optional[str],
 94        codes: Dict[str, str],
 95        default: Optional[str] = None,
 96        error: bool = True,
 97    ) -> Optional[CodeType]:
 98        """Load a code from a known key and value dict"""
 99        value: Optional[str]
100        if not key:
101            return None
102        try:
103            value = codes[key]
104        except KeyError as exc:
105            if error:
106                raise KeyError(f"No code found for {key}") from exc
107            value = default
108        return cls(key, value or "Unknown")

Load a code from a known key and value dict

@classmethod
def from_list( cls: Type[~CodeType], keys: Optional[str], codes: Dict[str, str], exclusive: bool = False) -> List[~CodeType]:
110    @classmethod
111    def from_list(
112        cls: Type[CodeType],
113        keys: Optional[str],
114        codes: Dict[str, str],
115        exclusive: bool = False,
116    ) -> List[CodeType]:
117        """Load a list of codes from string characters"""
118        if not keys:
119            return []
120        out = []
121        for key in keys.strip():
122            if value := codes.get(key):
123                out.append(cls(key, value))
124            elif exclusive:
125                return []
126        return out

Load a list of codes from string characters

@dataclass
class Coord:
129@dataclass
130class Coord:
131    lat: float
132    lon: float
133    repr: Optional[str] = None
134
135    @property
136    def pair(self) -> Tuple[float, float]:
137        return self.lat, self.lon
138
139    @property
140    def point(self) -> Point:
141        if Point is None:
142            raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature")
143        return Point(self.lat, self.lon)
144
145    @staticmethod
146    def to_dms(value: float) -> Tuple[int, int, int]:
147        """Convert a coordinate decimal value to degree, minute, second"""
148        minute, second = divmod(abs(value) * 3600, 60)
149        degree, minute = divmod(minute, 60)
150        if value < 0:
151            degree *= -1
152        return int(degree), int(minute), int(second)
Coord(lat: float, lon: float, repr: Optional[str] = None)
lat: float
lon: float
repr: Optional[str] = None
pair: Tuple[float, float]
135    @property
136    def pair(self) -> Tuple[float, float]:
137        return self.lat, self.lon
point: shapely.geometry.point.Point
139    @property
140    def point(self) -> Point:
141        if Point is None:
142            raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature")
143        return Point(self.lat, self.lon)
@staticmethod
def to_dms(value: float) -> Tuple[int, int, int]:
145    @staticmethod
146    def to_dms(value: float) -> Tuple[int, int, int]:
147        """Convert a coordinate decimal value to degree, minute, second"""
148        minute, second = divmod(abs(value) * 3600, 60)
149        degree, minute = divmod(minute, 60)
150        if value < 0:
151            degree *= -1
152        return int(degree), int(minute), int(second)

Convert a coordinate decimal value to degree, minute, second

@dataclass
class Cloud:
155@dataclass
156class Cloud:
157    repr: str
158    type: Optional[str] = None
159    base: Optional[int] = None
160    top: Optional[int] = None
161    modifier: Optional[str] = None
Cloud( repr: str, type: Optional[str] = None, base: Optional[int] = None, top: Optional[int] = None, modifier: Optional[str] = None)
repr: str
type: Optional[str] = None
base: Optional[int] = None
top: Optional[int] = None
modifier: Optional[str] = None
@dataclass
class RunwayVisibility:
164@dataclass
165class RunwayVisibility:
166    repr: str
167    runway: str
168    visibility: Optional[Number]
169    variable_visibility: List[Number]
170    trend: Optional[Code]
RunwayVisibility( repr: str, runway: str, visibility: Optional[Number], variable_visibility: List[Number], trend: Optional[Code])
repr: str
runway: str
visibility: Optional[Number]
variable_visibility: List[Number]
trend: Optional[Code]
@dataclass
class Location:
173@dataclass
174class Location:
175    repr: str
176    station: Optional[str]
177    direction: Optional[Number]
178    distance: Optional[Number]
Location( repr: str, station: Optional[str], direction: Optional[Number], distance: Optional[Number])
repr: str
station: Optional[str]
direction: Optional[Number]
distance: Optional[Number]
@dataclass
class PressureTendency:
181@dataclass
182class PressureTendency:
183    repr: str
184    tendency: str
185    change: float
PressureTendency(repr: str, tendency: str, change: float)
repr: str
tendency: str
change: float
@dataclass
class FiveDigitCodes:
188@dataclass
189class FiveDigitCodes:
190    maximum_temperature_6: Optional[Number] = None  # 1
191    minimum_temperature_6: Optional[Number] = None  # 2
192    pressure_tendency: Optional[PressureTendency] = None  # 5
193    precip_36_hours: Optional[Number] = None  # 6
194    precip_24_hours: Optional[Number] = None  # 7
195    sunshine_minutes: Optional[Number] = None  # 9
FiveDigitCodes( maximum_temperature_6: Optional[Number] = None, minimum_temperature_6: Optional[Number] = None, pressure_tendency: Optional[PressureTendency] = None, precip_36_hours: Optional[Number] = None, precip_24_hours: Optional[Number] = None, sunshine_minutes: Optional[Number] = None)
maximum_temperature_6: Optional[Number] = None
minimum_temperature_6: Optional[Number] = None
pressure_tendency: Optional[PressureTendency] = None
precip_36_hours: Optional[Number] = None
precip_24_hours: Optional[Number] = None
sunshine_minutes: Optional[Number] = None
@dataclass
class RemarksData(FiveDigitCodes):
198@dataclass
199class RemarksData(FiveDigitCodes):
200    codes: List[Code] = field(default_factory=[])  # type: ignore
201    dewpoint_decimal: Optional[Number] = None
202    maximum_temperature_24: Optional[Number] = None
203    minimum_temperature_24: Optional[Number] = None
204    precip_hourly: Optional[Number] = None
205    sea_level_pressure: Optional[Number] = None
206    snow_depth: Optional[Number] = None
207    temperature_decimal: Optional[Number] = None
RemarksData( maximum_temperature_6: Optional[Number] = None, minimum_temperature_6: Optional[Number] = None, pressure_tendency: Optional[PressureTendency] = None, precip_36_hours: Optional[Number] = None, precip_24_hours: Optional[Number] = None, sunshine_minutes: Optional[Number] = None, codes: List[Code] = <factory>, dewpoint_decimal: Optional[Number] = None, maximum_temperature_24: Optional[Number] = None, minimum_temperature_24: Optional[Number] = None, precip_hourly: Optional[Number] = None, sea_level_pressure: Optional[Number] = None, snow_depth: Optional[Number] = None, temperature_decimal: Optional[Number] = None)
codes: List[Code]
dewpoint_decimal: Optional[Number] = None
maximum_temperature_24: Optional[Number] = None
minimum_temperature_24: Optional[Number] = None
precip_hourly: Optional[Number] = None
sea_level_pressure: Optional[Number] = None
snow_depth: Optional[Number] = None
temperature_decimal: Optional[Number] = None
@dataclass
class ReportData:
210@dataclass
211class ReportData:
212    raw: str
213    sanitized: str
214    station: Optional[str]
215    time: Optional[Timestamp]
216    remarks: Optional[str]
ReportData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str])
raw: str
sanitized: str
station: Optional[str]
time: Optional[Timestamp]
remarks: Optional[str]
@dataclass
class SharedData:
219@dataclass
220class SharedData:
221    altimeter: Optional[Number]
222    clouds: List[Cloud]
223    flight_rules: str
224    other: List[str]
225    visibility: Optional[Number]
226    wind_direction: Optional[Number]
227    wind_gust: Optional[Number]
228    wind_speed: Optional[Number]
229    wx_codes: List[Code]
SharedData( altimeter: Optional[Number], clouds: List[Cloud], flight_rules: str, other: List[str], visibility: Optional[Number], wind_direction: Optional[Number], wind_gust: Optional[Number], wind_speed: Optional[Number], wx_codes: List[Code])
altimeter: Optional[Number]
clouds: List[Cloud]
flight_rules: str
other: List[str]
visibility: Optional[Number]
wind_direction: Optional[Number]
wind_gust: Optional[Number]
wind_speed: Optional[Number]
wx_codes: List[Code]
@dataclass
class MetarData(ReportData, SharedData):
232@dataclass
233class MetarData(ReportData, SharedData):
234    dewpoint: Optional[Number]
235    relative_humidity: Optional[float]
236    remarks_info: Optional[RemarksData]
237    runway_visibility: List[RunwayVisibility]
238    temperature: Optional[Number]
239    wind_variable_direction: List[Number]
240    density_altitude: Optional[int] = None
241    pressure_altitude: Optional[int] = None
MetarData( altimeter: Optional[Number], clouds: List[Cloud], flight_rules: str, other: List[str], visibility: Optional[Number], wind_direction: Optional[Number], wind_gust: Optional[Number], wind_speed: Optional[Number], wx_codes: List[Code], raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], dewpoint: Optional[Number], relative_humidity: Optional[float], remarks_info: Optional[RemarksData], runway_visibility: List[RunwayVisibility], temperature: Optional[Number], wind_variable_direction: List[Number], density_altitude: Optional[int] = None, pressure_altitude: Optional[int] = None)
dewpoint: Optional[Number]
relative_humidity: Optional[float]
remarks_info: Optional[RemarksData]
runway_visibility: List[RunwayVisibility]
temperature: Optional[Number]
wind_variable_direction: List[Number]
density_altitude: Optional[int] = None
pressure_altitude: Optional[int] = None
@dataclass
class TafLineData(SharedData):
244@dataclass
245class TafLineData(SharedData):
246    end_time: Optional[Timestamp]
247    icing: List[str]
248    probability: Optional[Number]
249    raw: str
250    sanitized: str
251    start_time: Optional[Timestamp]
252    transition_start: Optional[Timestamp]
253    turbulence: List[str]
254    type: str
255    wind_shear: Optional[str]
256    wind_variable_direction: Optional[List[Number]]
TafLineData( altimeter: Optional[Number], clouds: List[Cloud], flight_rules: str, other: List[str], visibility: Optional[Number], wind_direction: Optional[Number], wind_gust: Optional[Number], wind_speed: Optional[Number], wx_codes: List[Code], end_time: Optional[Timestamp], icing: List[str], probability: Optional[Number], raw: str, sanitized: str, start_time: Optional[Timestamp], transition_start: Optional[Timestamp], turbulence: List[str], type: str, wind_shear: Optional[str], wind_variable_direction: Optional[List[Number]])
end_time: Optional[Timestamp]
icing: List[str]
probability: Optional[Number]
raw: str
sanitized: str
start_time: Optional[Timestamp]
transition_start: Optional[Timestamp]
turbulence: List[str]
type: str
wind_shear: Optional[str]
wind_variable_direction: Optional[List[Number]]
@dataclass
class TafData(ReportData):
259@dataclass
260class TafData(ReportData):
261    forecast: List[TafLineData]
262    start_time: Optional[Timestamp]
263    end_time: Optional[Timestamp]
264    max_temp: Optional[str] = None
265    min_temp: Optional[str] = None
266    alts: Optional[List[str]] = None
267    temps: Optional[List[str]] = None
268    remarks_info: Optional[RemarksData] = None
TafData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[TafLineData], start_time: Optional[Timestamp], end_time: Optional[Timestamp], max_temp: Optional[str] = None, min_temp: Optional[str] = None, alts: Optional[List[str]] = None, temps: Optional[List[str]] = None, remarks_info: Optional[RemarksData] = None)
forecast: List[TafLineData]
start_time: Optional[Timestamp]
end_time: Optional[Timestamp]
max_temp: Optional[str] = None
min_temp: Optional[str] = None
alts: Optional[List[str]] = None
temps: Optional[List[str]] = None
remarks_info: Optional[RemarksData] = None
@dataclass
class ReportTrans:
271@dataclass
272class ReportTrans:
273    altimeter: str
274    clouds: str
275    wx_codes: str
276    visibility: str
ReportTrans(altimeter: str, clouds: str, wx_codes: str, visibility: str)
altimeter: str
clouds: str
wx_codes: str
visibility: str
@dataclass
class MetarTrans(ReportTrans):
279@dataclass
280class MetarTrans(ReportTrans):
281    dewpoint: str
282    remarks: dict
283    temperature: str
284    wind: str
MetarTrans( altimeter: str, clouds: str, wx_codes: str, visibility: str, dewpoint: str, remarks: dict, temperature: str, wind: str)
dewpoint: str
remarks: dict
temperature: str
wind: str
@dataclass
class TafLineTrans(ReportTrans):
287@dataclass
288class TafLineTrans(ReportTrans):
289    icing: str
290    turbulence: str
291    wind: str
292    wind_shear: str
TafLineTrans( altimeter: str, clouds: str, wx_codes: str, visibility: str, icing: str, turbulence: str, wind: str, wind_shear: str)
icing: str
turbulence: str
wind: str
wind_shear: str
@dataclass
class TafTrans:
295@dataclass
296class TafTrans:
297    forecast: List[TafLineTrans]
298    max_temp: str
299    min_temp: str
300    remarks: dict
TafTrans( forecast: List[TafLineTrans], max_temp: str, min_temp: str, remarks: dict)
forecast: List[TafLineTrans]
max_temp: str
min_temp: str
remarks: dict
@dataclass
class Turbulence:
303@dataclass
304class Turbulence:
305    severity: str
306    floor: Optional[Number] = None
307    ceiling: Optional[Number] = None
Turbulence( severity: str, floor: Optional[Number] = None, ceiling: Optional[Number] = None)
severity: str
floor: Optional[Number] = None
ceiling: Optional[Number] = None
@dataclass
class Icing(Turbulence):
310@dataclass
311class Icing(Turbulence):
312    type: Optional[str] = None
Icing( severity: str, floor: Optional[Number] = None, ceiling: Optional[Number] = None, type: Optional[str] = None)
type: Optional[str] = None
Inherited Members
Turbulence
severity
floor
ceiling
@dataclass
class PirepData(ReportData):
315@dataclass
316class PirepData(ReportData):
317    # pylint: disable=invalid-name
318    aircraft: Union[Aircraft, str, None] = None
319    altitude: Union[Number, str, None] = None
320    clouds: Optional[List[Cloud]] = None
321    flight_visibility: Optional[Number] = None
322    icing: Optional[Icing] = None
323    location: Optional[Location] = None
324    other: Optional[List[str]] = None
325    temperature: Optional[Number] = None
326    turbulence: Optional[Turbulence] = None
327    type: Optional[str] = None
328    wx_codes: Optional[List[Code]] = None
PirepData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], aircraft: Union[Aircraft, str, NoneType] = None, altitude: Union[Number, str, NoneType] = None, clouds: Optional[List[Cloud]] = None, flight_visibility: Optional[Number] = None, icing: Optional[Icing] = None, location: Optional[Location] = None, other: Optional[List[str]] = None, temperature: Optional[Number] = None, turbulence: Optional[Turbulence] = None, type: Optional[str] = None, wx_codes: Optional[List[Code]] = None)
aircraft: Union[Aircraft, str, NoneType] = None
altitude: Union[Number, str, NoneType] = None
clouds: Optional[List[Cloud]] = None
flight_visibility: Optional[Number] = None
icing: Optional[Icing] = None
location: Optional[Location] = None
other: Optional[List[str]] = None
temperature: Optional[Number] = None
turbulence: Optional[Turbulence] = None
type: Optional[str] = None
wx_codes: Optional[List[Code]] = None
@dataclass
class AirepData(ReportData):
331@dataclass
332class AirepData(ReportData):
333    pass
AirepData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str])
@dataclass
class Bulletin:
336@dataclass
337class Bulletin:
338    repr: str
339    type: Code
340    country: str
341    number: int
Bulletin(repr: str, type: Code, country: str, number: int)
repr: str
type: Code
country: str
number: int
@dataclass
class Movement:
344@dataclass
345class Movement:
346    repr: str
347    direction: Optional[Number]
348    speed: Optional[Number]
Movement( repr: str, direction: Optional[Number], speed: Optional[Number])
repr: str
direction: Optional[Number]
speed: Optional[Number]
@dataclass
class AirSigObservation:
351@dataclass
352class AirSigObservation:
353    type: Optional[Code]
354    start_time: Optional[Timestamp]
355    end_time: Optional[Timestamp]
356    position: Optional[Coord]
357    floor: Optional[Number]
358    ceiling: Optional[Number]
359    coords: List[Coord]
360    bounds: List[str]
361    movement: Optional[Movement]
362    intensity: Optional[Code]
363    other: List[str]
364
365    @property
366    def poly(self) -> Optional[Polygon]:
367        if Polygon is None:
368            raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature")
369        return Polygon([c.pair for c in self.coords]) if len(self.coords) > 2 else None
AirSigObservation( type: Optional[Code], start_time: Optional[Timestamp], end_time: Optional[Timestamp], position: Optional[Coord], floor: Optional[Number], ceiling: Optional[Number], coords: List[Coord], bounds: List[str], movement: Optional[Movement], intensity: Optional[Code], other: List[str])
type: Optional[Code]
start_time: Optional[Timestamp]
end_time: Optional[Timestamp]
position: Optional[Coord]
floor: Optional[Number]
ceiling: Optional[Number]
coords: List[Coord]
bounds: List[str]
movement: Optional[Movement]
intensity: Optional[Code]
other: List[str]
poly: Optional[shapely.geometry.polygon.Polygon]
365    @property
366    def poly(self) -> Optional[Polygon]:
367        if Polygon is None:
368            raise ModuleNotFoundError("Install avwx-engine[shape] to use this feature")
369        return Polygon([c.pair for c in self.coords]) if len(self.coords) > 2 else None
@dataclass
class AirSigmetData(ReportData):
372@dataclass
373class AirSigmetData(ReportData):
374    bulletin: Bulletin
375    issuer: str
376    correction: Optional[str]
377    area: str
378    type: str
379    start_time: Optional[Timestamp]
380    end_time: Optional[Timestamp]
381    body: str
382    region: str
383    observation: Optional[AirSigObservation]
384    forecast: Optional[AirSigObservation]
AirSigmetData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], bulletin: Bulletin, issuer: str, correction: Optional[str], area: str, type: str, start_time: Optional[Timestamp], end_time: Optional[Timestamp], body: str, region: str, observation: Optional[AirSigObservation], forecast: Optional[AirSigObservation])
bulletin: Bulletin
issuer: str
correction: Optional[str]
area: str
type: str
start_time: Optional[Timestamp]
end_time: Optional[Timestamp]
body: str
region: str
observation: Optional[AirSigObservation]
forecast: Optional[AirSigObservation]
@dataclass
class Qualifiers:
387@dataclass
388class Qualifiers:
389    repr: str
390    fir: str
391    subject: Optional[Code]
392    condition: Optional[Code]
393    traffic: Optional[Code]
394    purpose: List[Code]
395    scope: List[Code]
396    lower: Optional[Number]
397    upper: Optional[Number]
398    coord: Optional[Coord]
399    radius: Optional[Number]
Qualifiers( repr: str, fir: str, subject: Optional[Code], condition: Optional[Code], traffic: Optional[Code], purpose: List[Code], scope: List[Code], lower: Optional[Number], upper: Optional[Number], coord: Optional[Coord], radius: Optional[Number])
repr: str
fir: str
subject: Optional[Code]
condition: Optional[Code]
traffic: Optional[Code]
purpose: List[Code]
scope: List[Code]
lower: Optional[Number]
upper: Optional[Number]
coord: Optional[Coord]
radius: Optional[Number]
@dataclass
class NotamData(ReportData):
402@dataclass
403class NotamData(ReportData):
404    number: Optional[str]
405    replaces: Optional[str]
406    type: Optional[Code]
407    qualifiers: Optional[Qualifiers]
408    start_time: Union[Timestamp, Code, None]
409    end_time: Union[Timestamp, Code, None]
410    schedule: Optional[str]
411    body: str
412    lower: Optional[Number]
413    upper: Optional[Number]
NotamData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], number: Optional[str], replaces: Optional[str], type: Optional[Code], qualifiers: Optional[Qualifiers], start_time: Union[Timestamp, Code, NoneType], end_time: Union[Timestamp, Code, NoneType], schedule: Optional[str], body: str, lower: Optional[Number], upper: Optional[Number])
number: Optional[str]
replaces: Optional[str]
type: Optional[Code]
qualifiers: Optional[Qualifiers]
start_time: Union[Timestamp, Code, NoneType]
end_time: Union[Timestamp, Code, NoneType]
schedule: Optional[str]
body: str
lower: Optional[Number]
upper: Optional[Number]
@dataclass
class GfsPeriod:
416@dataclass
417class GfsPeriod:
418    time: Timestamp
419    temperature: Number
420    dewpoint: Number
421    cloud: Code
422    temperature_minmax: Optional[Number] = None
423    precip_chance_12: Optional[Number] = None
424    precip_amount_12: Optional[Code] = None
425    thunderstorm_12: Optional[Number] = None
426    severe_storm_12: Optional[Number] = None
427    freezing_precip: Optional[Number] = None
428    precip_type: Optional[Code] = None
429    snow: Optional[Number] = None
GfsPeriod( time: Timestamp, temperature: Number, dewpoint: Number, cloud: Code, temperature_minmax: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_12: Optional[Code] = None, thunderstorm_12: Optional[Number] = None, severe_storm_12: Optional[Number] = None, freezing_precip: Optional[Number] = None, precip_type: Optional[Code] = None, snow: Optional[Number] = None)
time: Timestamp
temperature: Number
dewpoint: Number
cloud: Code
temperature_minmax: Optional[Number] = None
precip_chance_12: Optional[Number] = None
precip_amount_12: Optional[Code] = None
thunderstorm_12: Optional[Number] = None
severe_storm_12: Optional[Number] = None
freezing_precip: Optional[Number] = None
precip_type: Optional[Code] = None
snow: Optional[Number] = None
@dataclass
class MavPeriod(GfsPeriod):
432@dataclass
433class MavPeriod(GfsPeriod):
434    wind_direction: Optional[Number] = None
435    wind_speed: Optional[Number] = None
436    precip_chance_6: Optional[Number] = None
437    precip_amount_6: Optional[Code] = None
438    thunderstorm_6: Optional[Number] = None
439    severe_storm_6: Optional[Number] = None
440    ceiling: Optional[Code] = None
441    visibility: Optional[Code] = None
442    vis_obstruction: Optional[Code] = None
MavPeriod( time: Timestamp, temperature: Number, dewpoint: Number, cloud: Code, temperature_minmax: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_12: Optional[Code] = None, thunderstorm_12: Optional[Number] = None, severe_storm_12: Optional[Number] = None, freezing_precip: Optional[Number] = None, precip_type: Optional[Code] = None, snow: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, precip_chance_6: Optional[Number] = None, precip_amount_6: Optional[Code] = None, thunderstorm_6: Optional[Number] = None, severe_storm_6: Optional[Number] = None, ceiling: Optional[Code] = None, visibility: Optional[Code] = None, vis_obstruction: Optional[Code] = None)
wind_direction: Optional[Number] = None
wind_speed: Optional[Number] = None
precip_chance_6: Optional[Number] = None
precip_amount_6: Optional[Code] = None
thunderstorm_6: Optional[Number] = None
severe_storm_6: Optional[Number] = None
ceiling: Optional[Code] = None
visibility: Optional[Code] = None
vis_obstruction: Optional[Code] = None
@dataclass
class MexPeriod(GfsPeriod):
445@dataclass
446class MexPeriod(GfsPeriod):
447    precip_chance_24: Optional[Number] = None
448    precip_amount_24: Optional[Code] = None
449    thunderstorm_24: Optional[Number] = None
450    severe_storm_24: Optional[Number] = None
451    rain_snow_mix: Optional[Number] = None
452    snow_amount_24: Optional[Code] = None
MexPeriod( time: Timestamp, temperature: Number, dewpoint: Number, cloud: Code, temperature_minmax: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_12: Optional[Code] = None, thunderstorm_12: Optional[Number] = None, severe_storm_12: Optional[Number] = None, freezing_precip: Optional[Number] = None, precip_type: Optional[Code] = None, snow: Optional[Number] = None, precip_chance_24: Optional[Number] = None, precip_amount_24: Optional[Code] = None, thunderstorm_24: Optional[Number] = None, severe_storm_24: Optional[Number] = None, rain_snow_mix: Optional[Number] = None, snow_amount_24: Optional[Code] = None)
precip_chance_24: Optional[Number] = None
precip_amount_24: Optional[Code] = None
thunderstorm_24: Optional[Number] = None
severe_storm_24: Optional[Number] = None
rain_snow_mix: Optional[Number] = None
snow_amount_24: Optional[Code] = None
@dataclass
class MavData(ReportData):
455@dataclass
456class MavData(ReportData):
457    forecast: List[MavPeriod]
MavData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[MavPeriod])
forecast: List[MavPeriod]
@dataclass
class MexData(ReportData):
460@dataclass
461class MexData(ReportData):
462    forecast: List[MexPeriod]
MexData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[MexPeriod])
forecast: List[MexPeriod]
@dataclass
class NbmUnits(Units):
465@dataclass
466class NbmUnits(Units):
467    duration: str
468    solar_radiation: str
469    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)
duration: str
solar_radiation: str
wave_height: str
@dataclass
class NbmPeriod:
472@dataclass
473class NbmPeriod:
474    time: Timestamp
475    temperature: Optional[Number] = None
476    dewpoint: Optional[Number] = None
477    sky_cover: Optional[Number] = None
478    wind_direction: Optional[Number] = None
479    wind_speed: Optional[Number] = None
480    wind_gust: Optional[Number] = None
481    snow_level: Optional[Number] = None
482    precip_duration: Optional[Number] = None
483    freezing_precip: Optional[Number] = None
484    snow: Optional[Number] = None
485    sleet: Optional[Number] = None
486    rain: Optional[Number] = None
487    solar_radiation: Optional[Number] = None
488    wave_height: Optional[Number] = None
NbmPeriod( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None)
time: Timestamp
temperature: Optional[Number] = None
dewpoint: Optional[Number] = None
sky_cover: Optional[Number] = None
wind_direction: Optional[Number] = None
wind_speed: Optional[Number] = None
wind_gust: Optional[Number] = None
snow_level: Optional[Number] = None
precip_duration: Optional[Number] = None
freezing_precip: Optional[Number] = None
snow: Optional[Number] = None
sleet: Optional[Number] = None
rain: Optional[Number] = None
solar_radiation: Optional[Number] = None
wave_height: Optional[Number] = None
@dataclass
class NbhsShared(NbmPeriod):
491@dataclass
492class NbhsShared(NbmPeriod):
493    ceiling: Optional[Number] = None
494    visibility: Optional[Number] = None
495    cloud_base: Optional[Number] = None
496    mixing_height: Optional[Number] = None
497    transport_wind_direction: Optional[Number] = None
498    transport_wind_speed: Optional[Number] = None
499    haines: Optional[List[Number]] = None
NbhsShared( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None, ceiling: Optional[Number] = None, visibility: Optional[Number] = None, cloud_base: Optional[Number] = None, mixing_height: Optional[Number] = None, transport_wind_direction: Optional[Number] = None, transport_wind_speed: Optional[Number] = None, haines: Optional[List[Number]] = None)
ceiling: Optional[Number] = None
visibility: Optional[Number] = None
cloud_base: Optional[Number] = None
mixing_height: Optional[Number] = None
transport_wind_direction: Optional[Number] = None
transport_wind_speed: Optional[Number] = None
haines: Optional[List[Number]] = None
@dataclass
class NbhPeriod(NbhsShared):
502@dataclass
503class NbhPeriod(NbhsShared):
504    precip_chance_1: Optional[Number] = None
505    precip_chance_6: Optional[Number] = None
506    precip_amount_1: Optional[Number] = None
507    thunderstorm_1: Optional[Number] = None
508    snow_amount_1: Optional[Number] = None
509    icing_amount_1: Optional[Number] = None
NbhPeriod( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None, ceiling: Optional[Number] = None, visibility: Optional[Number] = None, cloud_base: Optional[Number] = None, mixing_height: Optional[Number] = None, transport_wind_direction: Optional[Number] = None, transport_wind_speed: Optional[Number] = None, haines: Optional[List[Number]] = None, precip_chance_1: Optional[Number] = None, precip_chance_6: Optional[Number] = None, precip_amount_1: Optional[Number] = None, thunderstorm_1: Optional[Number] = None, snow_amount_1: Optional[Number] = None, icing_amount_1: Optional[Number] = None)
precip_chance_1: Optional[Number] = None
precip_chance_6: Optional[Number] = None
precip_amount_1: Optional[Number] = None
thunderstorm_1: Optional[Number] = None
snow_amount_1: Optional[Number] = None
icing_amount_1: Optional[Number] = None
@dataclass
class NbsPeriod(NbhsShared):
512@dataclass
513class NbsPeriod(NbhsShared):
514    temperature_minmax: Optional[Number] = None
515    precip_chance_6: Optional[Number] = None
516    precip_chance_12: Optional[Number] = None
517    precip_amount_6: Optional[Number] = None
518    precip_amount_12: Optional[Number] = None
519    precip_duration: Optional[Number] = None
520    thunderstorm_3: Optional[Number] = None
521    thunderstorm_6: Optional[Number] = None
522    thunderstorm_12: Optional[Number] = None
523    snow_amount_6: Optional[Number] = None
524    icing_amount_6: Optional[Number] = None
NbsPeriod( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None, ceiling: Optional[Number] = None, visibility: Optional[Number] = None, cloud_base: Optional[Number] = None, mixing_height: Optional[Number] = None, transport_wind_direction: Optional[Number] = None, transport_wind_speed: Optional[Number] = None, haines: Optional[List[Number]] = None, temperature_minmax: Optional[Number] = None, precip_chance_6: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_6: Optional[Number] = None, precip_amount_12: Optional[Number] = None, thunderstorm_3: Optional[Number] = None, thunderstorm_6: Optional[Number] = None, thunderstorm_12: Optional[Number] = None, snow_amount_6: Optional[Number] = None, icing_amount_6: Optional[Number] = None)
temperature_minmax: Optional[Number] = None
precip_chance_6: Optional[Number] = None
precip_chance_12: Optional[Number] = None
precip_amount_6: Optional[Number] = None
precip_amount_12: Optional[Number] = None
precip_duration: Optional[Number] = None
thunderstorm_3: Optional[Number] = None
thunderstorm_6: Optional[Number] = None
thunderstorm_12: Optional[Number] = None
snow_amount_6: Optional[Number] = None
icing_amount_6: Optional[Number] = None
@dataclass
class NbePeriod(NbmPeriod):
527@dataclass
528class NbePeriod(NbmPeriod):
529    temperature_minmax: Optional[Number] = None
530    precip_chance_12: Optional[Number] = None
531    precip_amount_12: Optional[Number] = None
532    precip_amount_24: Optional[Number] = None
533    thunderstorm_12: Optional[Number] = None
534    snow_amount_12: Optional[Number] = None
535    snow_amount_24: Optional[Number] = None
536    icing_amount_12: Optional[Number] = None
NbePeriod( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None, temperature_minmax: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_12: Optional[Number] = None, precip_amount_24: Optional[Number] = None, thunderstorm_12: Optional[Number] = None, snow_amount_12: Optional[Number] = None, snow_amount_24: Optional[Number] = None, icing_amount_12: Optional[Number] = None)
temperature_minmax: Optional[Number] = None
precip_chance_12: Optional[Number] = None
precip_amount_12: Optional[Number] = None
precip_amount_24: Optional[Number] = None
thunderstorm_12: Optional[Number] = None
snow_amount_12: Optional[Number] = None
snow_amount_24: Optional[Number] = None
icing_amount_12: Optional[Number] = None
@dataclass
class NbxPeriod(NbmPeriod):
539@dataclass
540class NbxPeriod(NbmPeriod):
541    precip_chance_12: Optional[Number] = None
542    precip_amount_12: Optional[Number] = None
543    precip_amount_24: Optional[Number] = None
544    snow_amount_12: Optional[Number] = None
545    icing_amount_12: Optional[Number] = None
NbxPeriod( time: Timestamp, temperature: Optional[Number] = None, dewpoint: Optional[Number] = None, sky_cover: Optional[Number] = None, wind_direction: Optional[Number] = None, wind_speed: Optional[Number] = None, wind_gust: Optional[Number] = None, snow_level: Optional[Number] = None, precip_duration: Optional[Number] = None, freezing_precip: Optional[Number] = None, snow: Optional[Number] = None, sleet: Optional[Number] = None, rain: Optional[Number] = None, solar_radiation: Optional[Number] = None, wave_height: Optional[Number] = None, precip_chance_12: Optional[Number] = None, precip_amount_12: Optional[Number] = None, precip_amount_24: Optional[Number] = None, snow_amount_12: Optional[Number] = None, icing_amount_12: Optional[Number] = None)
precip_chance_12: Optional[Number] = None
precip_amount_12: Optional[Number] = None
precip_amount_24: Optional[Number] = None
snow_amount_12: Optional[Number] = None
icing_amount_12: Optional[Number] = None
@dataclass
class NbhData(ReportData):
548@dataclass
549class NbhData(ReportData):
550    forecast: List[NbhPeriod]
NbhData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[NbhPeriod])
forecast: List[NbhPeriod]
@dataclass
class NbsData(ReportData):
553@dataclass
554class NbsData(ReportData):
555    forecast: List[NbsPeriod]
NbsData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[NbsPeriod])
forecast: List[NbsPeriod]
@dataclass
class NbeData(ReportData):
558@dataclass
559class NbeData(ReportData):
560    forecast: List[NbePeriod]
NbeData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[NbePeriod])
forecast: List[NbePeriod]
@dataclass
class NbxData(ReportData):
563@dataclass
564class NbxData(ReportData):
565    forecast: List[NbxPeriod]
NbxData( raw: str, sanitized: str, station: Optional[str], time: Optional[Timestamp], remarks: Optional[str], forecast: List[NbxPeriod])
forecast: List[NbxPeriod]
@dataclass
class Sanitization:
605@dataclass
606class Sanitization:
607    removed: List[str] = field(default_factory=list)
608    replaced: Dict[str, str] = field(default_factory=dict)
609    duplicates_found: bool = False
610    extra_spaces_found: bool = False
611    extra_spaces_needed: bool = False
612
613    @property
614    def errors_found(self) -> bool:
615        return bool(
616            self.removed
617            or self.replaced
618            or self.duplicates_found
619            or self.extra_spaces_found
620            or self.extra_spaces_needed
621        )
622
623    def log(self, item: str, replacement: Optional[str] = None) -> None:
624        """Log a changed item. Calling without a replacement assumes removal"""
625        item = item.strip()
626        if not item:
627            return
628        if replacement is None:
629            self.removed.insert(0, item)
630            return
631        replacement = replacement.strip()
632        if not replacement:
633            self.removed.insert(0, item)
634        elif item != replacement:
635            self.replaced[item] = replacement
636
637    def log_list(self, before: List[str], after: List[str]) -> None:
638        """Log list differences. Assumes that list length and order haven't changed"""
639        for item, replacement in zip(before, after):
640            if item != replacement:
641                self.log(item, replacement)
Sanitization( removed: List[str] = <factory>, replaced: Dict[str, str] = <factory>, duplicates_found: bool = False, extra_spaces_found: bool = False, extra_spaces_needed: bool = False)
removed: List[str]
replaced: Dict[str, str]
duplicates_found: bool = False
extra_spaces_found: bool = False
extra_spaces_needed: bool = False
errors_found: bool
613    @property
614    def errors_found(self) -> bool:
615        return bool(
616            self.removed
617            or self.replaced
618            or self.duplicates_found
619            or self.extra_spaces_found
620            or self.extra_spaces_needed
621        )
def log(self, item: str, replacement: Optional[str] = None) -> None:
623    def log(self, item: str, replacement: Optional[str] = None) -> None:
624        """Log a changed item. Calling without a replacement assumes removal"""
625        item = item.strip()
626        if not item:
627            return
628        if replacement is None:
629            self.removed.insert(0, item)
630            return
631        replacement = replacement.strip()
632        if not replacement:
633            self.removed.insert(0, item)
634        elif item != replacement:
635            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:
637    def log_list(self, before: List[str], after: List[str]) -> None:
638        """Log list differences. Assumes that list length and order haven't changed"""
639        for item, replacement in zip(before, after):
640            if item != replacement:
641                self.log(item, replacement)

Log list differences. Assumes that list length and order haven't changed