Skip to main content

nea_rs/
types.rs

1//! @generated by satay. Do not edit by hand.
2
3use std::fmt;
4#[derive(Debug, Clone, PartialEq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct PsiResponse {
7    /// Response status code (0 for success)
8    pub code: NeaSuccessCode,
9    /// Error message (empty string for success)
10    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
11    pub error_msg: String,
12    pub data: PsiData,
13}
14#[derive(Debug, Clone, PartialEq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct Pm25Response {
17    /// Response status code (0 for success)
18    pub code: NeaSuccessCode,
19    /// Error message (empty string for success)
20    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
21    pub error_msg: String,
22    pub data: Pm25Data,
23}
24#[derive(Debug, Clone, PartialEq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct AirTemperatureResponse {
27    /// Response status code (0 for success)
28    pub code: NeaSuccessCode,
29    /// Error message (empty string for success)
30    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
31    pub error_msg: String,
32    pub data: AirTemperatureData,
33}
34#[derive(Debug, Clone, PartialEq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub struct RelativeHumidityResponse {
37    /// Response status code (0 for success)
38    pub code: NeaSuccessCode,
39    /// Error message (empty string for success)
40    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
41    pub error_msg: String,
42    pub data: RelativeHumidityData,
43}
44#[derive(Debug, Clone, PartialEq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub struct WindSpeedResponse {
47    /// Response status code (0 for success)
48    pub code: NeaSuccessCode,
49    /// Error message (empty string for success)
50    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
51    pub error_msg: String,
52    pub data: WindSpeedData,
53}
54#[derive(Debug, Clone, PartialEq)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56pub struct WindDirectionResponse {
57    /// Response status code (0 for success)
58    pub code: NeaSuccessCode,
59    /// Error message (empty string for success)
60    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
61    pub error_msg: String,
62    pub data: WindDirectionData,
63}
64#[derive(Debug, Clone, PartialEq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66pub struct RainfallResponse {
67    /// Response status code (0 for success)
68    pub code: NeaSuccessCode,
69    /// Error message (empty string for success)
70    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
71    pub error_msg: String,
72    pub data: RainfallData,
73}
74#[derive(Debug, Clone, PartialEq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub struct TwoHrForecastResponse {
77    /// Response status code (0 for success)
78    pub code: NeaSuccessCode,
79    /// Error message (empty string for success)
80    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
81    pub error_msg: String,
82    pub data: TwoHrForecastData,
83}
84#[derive(Debug, Clone, PartialEq)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
86pub struct TwentyFourHrForecastResponse {
87    /// Response status code (0 for success)
88    pub code: NeaSuccessCode,
89    /// Error message (empty string for success)
90    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
91    pub error_msg: String,
92    pub data: TwentyFourHrForecastData,
93}
94#[derive(Debug, Clone, PartialEq)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96pub struct FourDayOutlookResponse {
97    /// Response status code (0 for success)
98    pub code: NeaSuccessCode,
99    /// Error message (empty string for success)
100    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
101    pub error_msg: String,
102    /// Chronologically ordered forecasts for the next 4 days
103    pub data: FourDayOutlookData,
104}
105#[derive(Debug, Clone, PartialEq)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107pub struct UvResponse {
108    /// Response status code (0 for success)
109    pub code: NeaSuccessCode,
110    /// Error message (empty string for success)
111    #[cfg_attr(feature = "serde", serde(rename = "errorMsg"))]
112    pub error_msg: String,
113    pub data: UvData,
114}
115#[derive(Debug, Clone, PartialEq)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
117pub struct WeatherSubApiResponse {
118    /// Response status code (0 for success)
119    #[cfg_attr(
120        feature = "serde",
121        serde(default, skip_serializing_if = "Option::is_none")
122    )]
123    pub code: Option<NeaSuccessCode>,
124    /// Error message (empty string for success)
125    #[cfg_attr(
126        feature = "serde",
127        serde(rename = "errorMsg", default, skip_serializing_if = "Option::is_none")
128    )]
129    pub error_msg: Option<String>,
130    #[cfg_attr(
131        feature = "serde",
132        serde(default, skip_serializing_if = "Option::is_none")
133    )]
134    pub data: Option<WeatherSubApiData>,
135}
136#[derive(Debug, Clone, PartialEq)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
138pub struct InvalidParamsError {
139    #[cfg_attr(
140        feature = "serde",
141        serde(default, skip_serializing_if = "Option::is_none")
142    )]
143    pub code: Option<f64>,
144    #[cfg_attr(
145        feature = "serde",
146        serde(default, skip_serializing_if = "Option::is_none")
147    )]
148    pub name: Option<String>,
149    /// Bad-request error message for invalid date or pagination token
150    #[cfg_attr(
151        feature = "serde",
152        serde(rename = "errorMsg", default, skip_serializing_if = "Option::is_none")
153    )]
154    pub error_msg: Option<NeaInvalidParamsErrorMsg>,
155}
156#[derive(Debug, Clone, PartialEq)]
157#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
158pub struct InvalidParamsError22 {
159    #[cfg_attr(
160        feature = "serde",
161        serde(default, skip_serializing_if = "Option::is_none")
162    )]
163    pub code: Option<f64>,
164    #[cfg_attr(
165        feature = "serde",
166        serde(default, skip_serializing_if = "Option::is_none")
167    )]
168    pub name: Option<String>,
169    #[cfg_attr(
170        feature = "serde",
171        serde(rename = "errorMsg", default, skip_serializing_if = "Option::is_none")
172    )]
173    pub error_msg: Option<String>,
174}
175#[derive(Debug, Clone, PartialEq)]
176#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
177pub struct DataNotFoundError {
178    #[cfg_attr(
179        feature = "serde",
180        serde(default, skip_serializing_if = "Option::is_none")
181    )]
182    pub code: Option<f64>,
183    #[cfg_attr(
184        feature = "serde",
185        serde(default, skip_serializing_if = "Option::is_none")
186    )]
187    pub name: Option<String>,
188    #[cfg_attr(
189        feature = "serde",
190        serde(rename = "errorMsg", default, skip_serializing_if = "Option::is_none")
191    )]
192    pub error_msg: Option<String>,
193}
194#[derive(Debug, Clone, PartialEq)]
195#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
196pub struct RateLimitError {
197    #[cfg_attr(
198        feature = "serde",
199        serde(default, skip_serializing_if = "Option::is_none")
200    )]
201    pub code: Option<f64>,
202    #[cfg_attr(
203        feature = "serde",
204        serde(default, skip_serializing_if = "Option::is_none")
205    )]
206    pub name: Option<String>,
207    #[cfg_attr(
208        feature = "serde",
209        serde(rename = "errorMsg", default, skip_serializing_if = "Option::is_none")
210    )]
211    pub error_msg: Option<String>,
212}
213#[derive(Debug, Clone, PartialEq)]
214#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
215pub struct MissingAuthResponse {
216    #[cfg_attr(
217        feature = "serde",
218        serde(default, skip_serializing_if = "Option::is_none")
219    )]
220    pub message: Option<String>,
221}
222/// Response status code (0 for success)
223#[nutype::nutype(
224    validate(less_or_equal = 0),
225    derive(
226        Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, AsRef, Deref, TryFrom, Into, Display
227    ),
228    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
229)]
230pub struct NeaSuccessCode(u8);
231/// ISO 8601 date or date-time in Singapore Time (SGT)
232pub type NeaOffsetDateTime = satay_runtime::OffsetDateTime;
233/// SGT calendar date (YYYY-MM-DD)
234pub type NeaDate = satay_runtime::Date;
235/// WGS84 latitude for Singapore
236#[nutype::nutype(
237    validate(finite, greater_or_equal = 1.0, less_or_equal = 1.5),
238    derive(
239        Debug, Clone, Copy, PartialEq, PartialOrd, AsRef, Deref, TryFrom, Into, Display
240    ),
241    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
242)]
243pub struct NeaLatitude(f64);
244/// WGS84 longitude for Singapore
245#[nutype::nutype(
246    validate(finite, greater_or_equal = 103.5, less_or_equal = 104.2),
247    derive(
248        Debug, Clone, Copy, PartialEq, PartialOrd, AsRef, Deref, TryFrom, Into, Display
249    ),
250    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
251)]
252pub struct NeaLongitude(f64);
253/// Regional air-quality or index reading
254#[nutype::nutype(
255    validate(less_or_equal = 500),
256    derive(
257        Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, AsRef, Deref, TryFrom, Into, Display
258    ),
259    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
260)]
261pub struct NeaRegionalReading(u16);
262/// Regional PM2.5 one-hour reading (µg/m³)
263#[nutype::nutype(
264    validate(less_or_equal = 500),
265    derive(
266        Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, AsRef, Deref, TryFrom, Into, Display
267    ),
268    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
269)]
270pub struct NeaPm25HourlyReading(u16);
271/// Wind direction in degrees
272#[nutype::nutype(
273    validate(less_or_equal = 360),
274    derive(
275        Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, AsRef, Deref, TryFrom, Into, Display
276    ),
277    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
278)]
279pub struct NeaWindDirectionDegrees(u16);
280/// UV index for the hour
281#[nutype::nutype(
282    validate(less_or_equal = 16),
283    derive(
284        Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, AsRef, Deref, TryFrom, Into, Display
285    ),
286    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
287)]
288pub struct NeaUvIndex(u8);
289/// Relative humidity percentage
290#[nutype::nutype(
291    validate(finite, greater_or_equal = 0.0, less_or_equal = 100.0),
292    derive(
293        Debug, Clone, Copy, PartialEq, PartialOrd, AsRef, Deref, TryFrom, Into, Display
294    ),
295    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
296)]
297pub struct NeaHumidityPercent(f64);
298/// Air temperature in degrees Celsius
299#[nutype::nutype(
300    validate(finite, greater_or_equal = 15.0, less_or_equal = 45.0),
301    derive(
302        Debug, Clone, Copy, PartialEq, PartialOrd, AsRef, Deref, TryFrom, Into, Display
303    ),
304    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
305)]
306pub struct NeaTemperatureCelsius(f64);
307/// Wind speed in km/h
308#[nutype::nutype(
309    validate(finite, greater_or_equal = 0.0, less_or_equal = 120.0),
310    derive(
311        Debug, Clone, Copy, PartialEq, PartialOrd, AsRef, Deref, TryFrom, Into, Display
312    ),
313    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
314)]
315pub struct NeaWindSpeedKmh(f64);
316/// NEA MSS short weather forecast code
317#[derive(Debug, Clone, PartialEq, Eq, Default)]
318#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
319pub enum NeaForecastCode {
320    #[cfg_attr(feature = "serde", serde(rename = "FA"))]
321    FairDay,
322    #[cfg_attr(feature = "serde", serde(rename = "FN"))]
323    FairNight,
324    #[cfg_attr(feature = "serde", serde(rename = "FW"))]
325    FairAndWarm,
326    #[cfg_attr(feature = "serde", serde(rename = "CL"))]
327    Cloudy,
328    #[cfg_attr(feature = "serde", serde(rename = "PC"))]
329    PartlyCloudyDay,
330    #[cfg_attr(feature = "serde", serde(rename = "PN"))]
331    PartlyCloudyNight,
332    #[cfg_attr(feature = "serde", serde(rename = "HZ"))]
333    Hazy,
334    #[cfg_attr(feature = "serde", serde(rename = "SH"))]
335    SlightlyHazy,
336    #[cfg_attr(feature = "serde", serde(rename = "WI"))]
337    Windy,
338    #[cfg_attr(feature = "serde", serde(rename = "MS"))]
339    Mist,
340    #[cfg_attr(feature = "serde", serde(rename = "FG"))]
341    Fog,
342    #[cfg_attr(feature = "serde", serde(rename = "LR"))]
343    LightRain,
344    #[cfg_attr(feature = "serde", serde(rename = "MR"))]
345    ModerateRain,
346    #[cfg_attr(feature = "serde", serde(rename = "HR"))]
347    HeavyRain,
348    #[cfg_attr(feature = "serde", serde(rename = "PS"))]
349    PassingShowers,
350    #[cfg_attr(feature = "serde", serde(rename = "LS"))]
351    LightShowers,
352    #[cfg_attr(feature = "serde", serde(rename = "HS"))]
353    HeavyShowers,
354    #[cfg_attr(feature = "serde", serde(rename = "TS"))]
355    ThunderyShowers,
356    #[cfg_attr(feature = "serde", serde(rename = "HT"))]
357    HeavyThunderyShowers,
358    #[cfg_attr(feature = "serde", serde(rename = "HG"))]
359    HeavyThunderyShowersWithGustyWinds,
360    #[cfg_attr(feature = "serde", serde(rename = "TL"))]
361    ThunderyShowersAlt,
362    #[default]
363    #[cfg_attr(feature = "serde", serde(other))]
364    Unknown,
365}
366impl NeaForecastCode {
367    pub const fn as_str(&self) -> &'static str {
368        match self {
369            Self::FairDay => "FA",
370            Self::FairNight => "FN",
371            Self::FairAndWarm => "FW",
372            Self::Cloudy => "CL",
373            Self::PartlyCloudyDay => "PC",
374            Self::PartlyCloudyNight => "PN",
375            Self::Hazy => "HZ",
376            Self::SlightlyHazy => "SH",
377            Self::Windy => "WI",
378            Self::Mist => "MS",
379            Self::Fog => "FG",
380            Self::LightRain => "LR",
381            Self::ModerateRain => "MR",
382            Self::HeavyRain => "HR",
383            Self::PassingShowers => "PS",
384            Self::LightShowers => "LS",
385            Self::HeavyShowers => "HS",
386            Self::ThunderyShowers => "TS",
387            Self::HeavyThunderyShowers => "HT",
388            Self::HeavyThunderyShowersWithGustyWinds => "HG",
389            Self::ThunderyShowersAlt => "TL",
390            Self::Unknown => "",
391        }
392    }
393}
394impl AsRef<str> for NeaForecastCode {
395    fn as_ref(&self) -> &str {
396        self.as_str()
397    }
398}
399impl fmt::Display for NeaForecastCode {
400    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401        f.write_str(self.as_str())
402    }
403}
404/// Lightning event type (C = cloud-to-cloud, G = cloud-to-ground)
405#[derive(Debug, Clone, PartialEq, Eq, Default)]
406#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
407pub enum NeaLightningType {
408    #[cfg_attr(feature = "serde", serde(rename = "C"))]
409    CloudToCloud,
410    #[cfg_attr(feature = "serde", serde(rename = "G"))]
411    CloudToGround,
412    #[default]
413    #[cfg_attr(feature = "serde", serde(other))]
414    Unknown,
415}
416impl NeaLightningType {
417    pub const fn as_str(&self) -> &'static str {
418        match self {
419            Self::CloudToCloud => "C",
420            Self::CloudToGround => "G",
421            Self::Unknown => "",
422        }
423    }
424}
425impl AsRef<str> for NeaLightningType {
426    fn as_ref(&self) -> &str {
427        self.as_str()
428    }
429}
430impl fmt::Display for NeaLightningType {
431    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432        f.write_str(self.as_str())
433    }
434}
435/// NEA MSS human-readable weather forecast text
436#[derive(Debug, Clone, PartialEq, Eq, Default)]
437#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
438pub enum NeaForecastText {
439    Fair,
440    #[cfg_attr(feature = "serde", serde(rename = "Fair (Day)"))]
441    FairDay,
442    #[cfg_attr(feature = "serde", serde(rename = "Fair (Night)"))]
443    FairNight,
444    #[cfg_attr(feature = "serde", serde(rename = "Fair and Warm"))]
445    FairAndWarm,
446    #[cfg_attr(feature = "serde", serde(rename = "Partly Cloudy"))]
447    PartlyCloudy,
448    #[cfg_attr(feature = "serde", serde(rename = "Partly Cloudy (Day)"))]
449    PartlyCloudyDay,
450    #[cfg_attr(feature = "serde", serde(rename = "Partly Cloudy (Night)"))]
451    PartlyCloudyNight,
452    Cloudy,
453    Hazy,
454    #[cfg_attr(feature = "serde", serde(rename = "Slightly Hazy"))]
455    SlightlyHazy,
456    Windy,
457    Mist,
458    Fog,
459    #[cfg_attr(feature = "serde", serde(rename = "Light Rain"))]
460    LightRain,
461    #[cfg_attr(feature = "serde", serde(rename = "Moderate Rain"))]
462    ModerateRain,
463    #[cfg_attr(feature = "serde", serde(rename = "Heavy Rain"))]
464    HeavyRain,
465    #[cfg_attr(feature = "serde", serde(rename = "Passing Showers"))]
466    PassingShowers,
467    #[cfg_attr(feature = "serde", serde(rename = "Light Showers"))]
468    LightShowers,
469    Showers,
470    #[cfg_attr(feature = "serde", serde(rename = "Heavy Showers"))]
471    HeavyShowers,
472    #[cfg_attr(feature = "serde", serde(rename = "Thundery Showers"))]
473    ThunderyShowers,
474    #[cfg_attr(feature = "serde", serde(rename = "Heavy Thundery Showers"))]
475    HeavyThunderyShowers,
476    #[cfg_attr(
477        feature = "serde",
478        serde(rename = "Heavy Thundery Showers with Gusty Winds")
479    )]
480    HeavyThunderyShowersWithGustyWinds,
481    #[default]
482    #[cfg_attr(feature = "serde", serde(other))]
483    Unknown,
484}
485impl NeaForecastText {
486    pub const fn as_str(&self) -> &'static str {
487        match self {
488            Self::Fair => "Fair",
489            Self::FairDay => "Fair (Day)",
490            Self::FairNight => "Fair (Night)",
491            Self::FairAndWarm => "Fair and Warm",
492            Self::PartlyCloudy => "Partly Cloudy",
493            Self::PartlyCloudyDay => "Partly Cloudy (Day)",
494            Self::PartlyCloudyNight => "Partly Cloudy (Night)",
495            Self::Cloudy => "Cloudy",
496            Self::Hazy => "Hazy",
497            Self::SlightlyHazy => "Slightly Hazy",
498            Self::Windy => "Windy",
499            Self::Mist => "Mist",
500            Self::Fog => "Fog",
501            Self::LightRain => "Light Rain",
502            Self::ModerateRain => "Moderate Rain",
503            Self::HeavyRain => "Heavy Rain",
504            Self::PassingShowers => "Passing Showers",
505            Self::LightShowers => "Light Showers",
506            Self::Showers => "Showers",
507            Self::HeavyShowers => "Heavy Showers",
508            Self::ThunderyShowers => "Thundery Showers",
509            Self::HeavyThunderyShowers => "Heavy Thundery Showers",
510            Self::HeavyThunderyShowersWithGustyWinds => "Heavy Thundery Showers with Gusty Winds",
511            Self::Unknown => "",
512        }
513    }
514}
515impl AsRef<str> for NeaForecastText {
516    fn as_ref(&self) -> &str {
517        self.as_str()
518    }
519}
520impl fmt::Display for NeaForecastText {
521    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522        f.write_str(self.as_str())
523    }
524}
525/// Day of week for multi-day outlook forecasts
526#[derive(Debug, Clone, PartialEq, Eq, Default)]
527#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
528pub enum NeaDayOfWeek {
529    Monday,
530    Tuesday,
531    Wednesday,
532    Thursday,
533    Friday,
534    Saturday,
535    Sunday,
536    #[default]
537    #[cfg_attr(feature = "serde", serde(other))]
538    Unknown,
539}
540impl NeaDayOfWeek {
541    pub const fn as_str(&self) -> &'static str {
542        match self {
543            Self::Monday => "Monday",
544            Self::Tuesday => "Tuesday",
545            Self::Wednesday => "Wednesday",
546            Self::Thursday => "Thursday",
547            Self::Friday => "Friday",
548            Self::Saturday => "Saturday",
549            Self::Sunday => "Sunday",
550            Self::Unknown => "",
551        }
552    }
553}
554impl AsRef<str> for NeaDayOfWeek {
555    fn as_ref(&self) -> &str {
556        self.as_str()
557    }
558}
559impl fmt::Display for NeaDayOfWeek {
560    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
561        f.write_str(self.as_str())
562    }
563}
564/// Bad-request error message for invalid date or pagination token
565#[derive(Debug, Clone, PartialEq, Eq, Default)]
566#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
567pub enum NeaInvalidParamsErrorMsg {
568    #[cfg_attr(
569        feature = "serde",
570        serde(
571            rename = "Invalid date format. Date format must be YYYY-MM-DD (2024-06-01) or YYYY-MM-DDTHH:mm:ss (2024-06-01T08:30:00)."
572        )
573    )]
574    InvalidDateFormat,
575    #[cfg_attr(feature = "serde", serde(rename = "Invalid pagination token."))]
576    InvalidPaginationToken,
577    #[default]
578    #[cfg_attr(feature = "serde", serde(other))]
579    Unknown,
580}
581impl NeaInvalidParamsErrorMsg {
582    pub const fn as_str(&self) -> &'static str {
583        match self {
584            Self::InvalidDateFormat => {
585                "Invalid date format. Date format must be YYYY-MM-DD (2024-06-01) or YYYY-MM-DDTHH:mm:ss (2024-06-01T08:30:00)."
586            }
587            Self::InvalidPaginationToken => "Invalid pagination token.",
588            Self::Unknown => "",
589        }
590    }
591}
592impl AsRef<str> for NeaInvalidParamsErrorMsg {
593    fn as_ref(&self) -> &str {
594        self.as_str()
595    }
596}
597impl fmt::Display for NeaInvalidParamsErrorMsg {
598    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
599        f.write_str(self.as_str())
600    }
601}
602/// Weather sub-API selector (lightning or wbgt)
603#[derive(Debug, Clone, PartialEq, Eq, Default)]
604#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
605pub enum NeaWeatherSubApi {
606    #[cfg_attr(feature = "serde", serde(rename = "lightning"))]
607    Lightning,
608    #[cfg_attr(feature = "serde", serde(rename = "wbgt"))]
609    WetBulbGlobeTemperature,
610    #[default]
611    #[cfg_attr(feature = "serde", serde(other))]
612    Unknown,
613}
614impl NeaWeatherSubApi {
615    pub const fn as_str(&self) -> &'static str {
616        match self {
617            Self::Lightning => "lightning",
618            Self::WetBulbGlobeTemperature => "wbgt",
619            Self::Unknown => "",
620        }
621    }
622}
623impl AsRef<str> for NeaWeatherSubApi {
624    fn as_ref(&self) -> &str {
625        self.as_str()
626    }
627}
628impl fmt::Display for NeaWeatherSubApi {
629    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630        f.write_str(self.as_str())
631    }
632}
633/// NEA weather station identifier (S + 2–3 digits)
634#[nutype::nutype(
635    validate(regex = "^S[0-9]{2,3}$", len_char_min = 3, len_char_max = 4),
636    derive(
637        Debug, Clone, PartialEq, Eq, PartialOrd, Ord, AsRef, Deref, TryFrom, Into, Display, Hash
638    ),
639    cfg_attr(feature = "serde", derive(Serialize, Deserialize))
640)]
641pub struct NeaStationId(String);
642/// WGS84 coordinates for a weather station
643#[derive(Debug, Clone, PartialEq)]
644#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
645pub struct NeaStationLocation {
646    /// WGS84 latitude for Singapore
647    pub latitude: NeaLatitude,
648    /// WGS84 longitude for Singapore
649    pub longitude: NeaLongitude,
650}
651/// 16-point compass wind direction for outlook forecasts
652#[derive(Debug, Clone, PartialEq, Eq, Default)]
653#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
654pub enum NeaWindDirection16 {
655    N,
656    #[cfg_attr(feature = "serde", serde(rename = "NNE"))]
657    Nne,
658    #[cfg_attr(feature = "serde", serde(rename = "NE"))]
659    Ne,
660    #[cfg_attr(feature = "serde", serde(rename = "ENE"))]
661    Ene,
662    E,
663    #[cfg_attr(feature = "serde", serde(rename = "ESE"))]
664    Ese,
665    #[cfg_attr(feature = "serde", serde(rename = "SE"))]
666    Se,
667    #[cfg_attr(feature = "serde", serde(rename = "SSE"))]
668    Sse,
669    S,
670    #[cfg_attr(feature = "serde", serde(rename = "SSW"))]
671    Ssw,
672    #[cfg_attr(feature = "serde", serde(rename = "SW"))]
673    Sw,
674    #[cfg_attr(feature = "serde", serde(rename = "WSW"))]
675    Wsw,
676    W,
677    #[cfg_attr(feature = "serde", serde(rename = "WNW"))]
678    Wnw,
679    #[cfg_attr(feature = "serde", serde(rename = "NW"))]
680    Nw,
681    #[cfg_attr(feature = "serde", serde(rename = "NNW"))]
682    Nnw,
683    #[default]
684    #[cfg_attr(feature = "serde", serde(other))]
685    Unknown,
686}
687impl NeaWindDirection16 {
688    pub const fn as_str(&self) -> &'static str {
689        match self {
690            Self::N => "N",
691            Self::Nne => "NNE",
692            Self::Ne => "NE",
693            Self::Ene => "ENE",
694            Self::E => "E",
695            Self::Ese => "ESE",
696            Self::Se => "SE",
697            Self::Sse => "SSE",
698            Self::S => "S",
699            Self::Ssw => "SSW",
700            Self::Sw => "SW",
701            Self::Wsw => "WSW",
702            Self::W => "W",
703            Self::Wnw => "WNW",
704            Self::Nw => "NW",
705            Self::Nnw => "NNW",
706            Self::Unknown => "",
707        }
708    }
709}
710impl AsRef<str> for NeaWindDirection16 {
711    fn as_ref(&self) -> &str {
712        self.as_str()
713    }
714}
715impl fmt::Display for NeaWindDirection16 {
716    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
717        f.write_str(self.as_str())
718    }
719}
720/// WGS84 latitude as a decimal string
721pub type NeaStringLatitude = f64;
722/// WGS84 longitude as a decimal string
723pub type NeaStringLongitude = f64;
724#[derive(Debug, Clone, PartialEq)]
725#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
726pub struct NeaWeatherStation {
727    /// NEA weather station identifier (S + 2–3 digits)
728    pub id: NeaStationId,
729    /// NEA weather station identifier (S + 2–3 digits)
730    #[cfg_attr(feature = "serde", serde(rename = "deviceId"))]
731    pub device_id: NeaStationId,
732    /// Station's name
733    pub name: String,
734    /// WGS84 coordinates for a weather station
735    pub location: NeaStationLocation,
736}
737#[derive(Debug, Clone, PartialEq)]
738#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
739pub struct NeaReadingSnapshot {
740    /// ISO 8601 date or date-time in Singapore Time (SGT)
741    #[cfg_attr(
742        feature = "serde",
743        serde(with = "satay_runtime::serde_string::as_offset_datetime")
744    )]
745    pub timestamp: satay_runtime::OffsetDateTime,
746    pub data: Vec<NeaStationReading>,
747}
748#[derive(Debug, Clone, PartialEq)]
749#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
750pub struct NeaStationReading {
751    /// NEA weather station identifier (S + 2–3 digits)
752    #[cfg_attr(feature = "serde", serde(rename = "stationId"))]
753    pub station_id: NeaStationId,
754    pub value: f64,
755}
756/// Unit of measure - Percentage
757#[derive(Debug, Clone, PartialEq)]
758#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
759pub struct NeaHumidityRange {
760    /// Relative humidity percentage
761    pub low: NeaHumidityPercent,
762    /// Relative humidity percentage
763    pub high: NeaHumidityPercent,
764    /// Unit of measure for NEA station readings and outlook ranges
765    pub unit: NeaMeasurementUnit,
766}
767/// Unit of measure - Degrees Celsius
768#[derive(Debug, Clone, PartialEq)]
769#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
770pub struct NeaTemperatureRange {
771    /// Air temperature in degrees Celsius
772    pub low: NeaTemperatureCelsius,
773    /// Air temperature in degrees Celsius
774    pub high: NeaTemperatureCelsius,
775    /// Unit of measure for NEA station readings and outlook ranges
776    pub unit: NeaMeasurementUnit,
777}
778/// Unit of measure - Kilometeres per hour
779#[derive(Debug, Clone, PartialEq)]
780#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
781pub struct NeaWindSpeedRange {
782    /// Wind speed in km/h
783    pub low: NeaWindSpeedKmh,
784    /// Wind speed in km/h
785    pub high: NeaWindSpeedKmh,
786}
787#[derive(Debug, Clone, PartialEq)]
788#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
789pub struct NeaOutlookWind {
790    /// Unit of measure - Kilometeres per hour
791    pub speed: NeaWindSpeedRange,
792    /// 16-point compass wind direction for outlook forecasts
793    pub direction: NeaWindDirection16,
794}
795#[derive(Debug, Clone, PartialEq)]
796#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
797pub struct NeaMssForecast {
798    /// NEA MSS short weather forecast code
799    pub code: NeaForecastCode,
800    /// NEA MSS human-readable weather forecast text
801    pub text: NeaForecastText,
802}
803#[derive(Debug, Clone, PartialEq)]
804#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
805pub struct NeaGeoPoint {
806    /// WGS84 latitude for Singapore
807    pub latitude: NeaLatitude,
808    /// WGS84 longitude for Singapore
809    pub longitude: NeaLongitude,
810}
811#[derive(Debug, Clone, PartialEq)]
812#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
813pub struct AirTemperatureData {
814    pub stations: Vec<NeaWeatherStation>,
815    pub readings: Vec<NeaReadingSnapshot>,
816    /// Information about the reading
817    #[cfg_attr(feature = "serde", serde(rename = "readingType"))]
818    pub reading_type: String,
819    /// Unit of measure for NEA station readings and outlook ranges
820    #[cfg_attr(feature = "serde", serde(rename = "readingUnit"))]
821    pub reading_unit: NeaMeasurementUnit,
822    /// Token to retrieve next page if exists
823    #[cfg_attr(
824        feature = "serde",
825        serde(
826            rename = "paginationToken",
827            default,
828            skip_serializing_if = "Option::is_none"
829        )
830    )]
831    pub pagination_token: Option<String>,
832}
833#[derive(Debug, Clone, PartialEq)]
834#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
835pub struct RelativeHumidityData {
836    pub stations: Vec<NeaWeatherStation>,
837    pub readings: Vec<NeaReadingSnapshot>,
838    /// Information about the reading
839    #[cfg_attr(feature = "serde", serde(rename = "readingType"))]
840    pub reading_type: String,
841    /// Unit of measure for NEA station readings and outlook ranges
842    #[cfg_attr(feature = "serde", serde(rename = "readingUnit"))]
843    pub reading_unit: NeaMeasurementUnit,
844    /// Token to retrieve next page if exists
845    #[cfg_attr(
846        feature = "serde",
847        serde(
848            rename = "paginationToken",
849            default,
850            skip_serializing_if = "Option::is_none"
851        )
852    )]
853    pub pagination_token: Option<String>,
854}
855#[derive(Debug, Clone, PartialEq)]
856#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
857pub struct WindSpeedData {
858    pub stations: Vec<NeaWeatherStation>,
859    pub readings: Vec<NeaReadingSnapshot>,
860    /// Information about the reading
861    #[cfg_attr(feature = "serde", serde(rename = "readingType"))]
862    pub reading_type: String,
863    /// Unit of measure for NEA station readings and outlook ranges
864    #[cfg_attr(feature = "serde", serde(rename = "readingUnit"))]
865    pub reading_unit: NeaMeasurementUnit,
866    /// Token to retrieve next page if exists
867    #[cfg_attr(
868        feature = "serde",
869        serde(
870            rename = "paginationToken",
871            default,
872            skip_serializing_if = "Option::is_none"
873        )
874    )]
875    pub pagination_token: Option<String>,
876}
877#[derive(Debug, Clone, PartialEq)]
878#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
879pub struct WindDirectionData {
880    pub stations: Vec<NeaWeatherStation>,
881    pub readings: Vec<NeaReadingSnapshot>,
882    /// Information about the reading
883    #[cfg_attr(feature = "serde", serde(rename = "readingType"))]
884    pub reading_type: String,
885    /// Unit of measure for NEA station readings and outlook ranges
886    #[cfg_attr(feature = "serde", serde(rename = "readingUnit"))]
887    pub reading_unit: NeaMeasurementUnit,
888    /// Token to retrieve next page if exists
889    #[cfg_attr(
890        feature = "serde",
891        serde(
892            rename = "paginationToken",
893            default,
894            skip_serializing_if = "Option::is_none"
895        )
896    )]
897    pub pagination_token: Option<String>,
898}
899#[derive(Debug, Clone, PartialEq)]
900#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
901pub struct RainfallData {
902    pub stations: Vec<NeaWeatherStation>,
903    pub readings: Vec<NeaReadingSnapshot>,
904    /// Information about the reading
905    #[cfg_attr(feature = "serde", serde(rename = "readingType"))]
906    pub reading_type: String,
907    /// Unit of measure for NEA station readings and outlook ranges
908    #[cfg_attr(feature = "serde", serde(rename = "readingUnit"))]
909    pub reading_unit: NeaMeasurementUnit,
910    /// Token to retrieve next page if exists
911    #[cfg_attr(
912        feature = "serde",
913        serde(
914            rename = "paginationToken",
915            default,
916            skip_serializing_if = "Option::is_none"
917        )
918    )]
919    pub pagination_token: Option<String>,
920}
921/// Chronologically ordered forecasts for the next 4 days
922#[derive(Debug, Clone, PartialEq)]
923#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
924pub struct FourDayOutlookData {
925    pub records: Vec<FourDayOutlookDay>,
926}
927#[derive(Debug, Clone, PartialEq)]
928#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
929pub struct FourDayOutlookDay {
930    /// SGT calendar date (YYYY-MM-DD)
931    #[cfg_attr(
932        feature = "serde",
933        serde(with = "satay_runtime::serde_string::as_date")
934    )]
935    pub date: satay_runtime::Date,
936    /// ISO 8601 date or date-time in Singapore Time (SGT)
937    #[cfg_attr(
938        feature = "serde",
939        serde(
940            rename = "updatedTimestamp",
941            with = "satay_runtime::serde_string::as_offset_datetime"
942        )
943    )]
944    pub updated_timestamp: satay_runtime::OffsetDateTime,
945    /// ISO 8601 date or date-time in Singapore Time (SGT)
946    #[cfg_attr(
947        feature = "serde",
948        serde(with = "satay_runtime::serde_string::as_offset_datetime")
949    )]
950    pub timestamp: satay_runtime::OffsetDateTime,
951    /// Forecast summary for the day
952    pub forecasts: Vec<FourDayOutlookPeriod>,
953}
954#[derive(Debug, Clone, PartialEq)]
955#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
956pub struct FourDayOutlookPeriod {
957    /// ISO 8601 date or date-time in Singapore Time (SGT)
958    #[cfg_attr(
959        feature = "serde",
960        serde(with = "satay_runtime::serde_string::as_offset_datetime")
961    )]
962    pub timestamp: satay_runtime::OffsetDateTime,
963    /// Unit of measure - Degrees Celsius
964    pub temperature: NeaTemperatureRange,
965    /// Unit of measure - Percentage
966    #[cfg_attr(feature = "serde", serde(rename = "relativeHumidity"))]
967    pub relative_humidity: NeaHumidityRange,
968    pub forecast: NeaOutlookForecastDetail,
969    /// Day of week for multi-day outlook forecasts
970    pub day: NeaDayOfWeek,
971    pub wind: NeaOutlookWind,
972}
973#[derive(Debug, Clone, PartialEq)]
974#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
975pub struct NeaOutlookForecastDetail {
976    pub summary: String,
977    /// NEA MSS short weather forecast code
978    pub code: NeaForecastCode,
979    /// NEA MSS human-readable weather forecast text
980    pub text: NeaForecastText,
981}
982#[derive(Debug, Clone, PartialEq)]
983#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
984pub struct TwentyFourHrForecastData {
985    #[cfg_attr(
986        feature = "serde",
987        serde(default, skip_serializing_if = "Option::is_none")
988    )]
989    pub area_metadata: Option<Vec<NeaForecastArea>>,
990    pub records: Vec<TwentyFourHrForecastDay>,
991    /// Token to retrieve next page if exists
992    #[cfg_attr(
993        feature = "serde",
994        serde(
995            rename = "paginationToken",
996            default,
997            skip_serializing_if = "Option::is_none"
998        )
999    )]
1000    pub pagination_token: Option<String>,
1001}
1002#[derive(Debug, Clone, PartialEq)]
1003#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1004pub struct TwentyFourHrForecastDay {
1005    /// SGT calendar date (YYYY-MM-DD)
1006    #[cfg_attr(
1007        feature = "serde",
1008        serde(with = "satay_runtime::serde_string::as_date")
1009    )]
1010    pub date: satay_runtime::Date,
1011    /// ISO 8601 date or date-time in Singapore Time (SGT)
1012    #[cfg_attr(
1013        feature = "serde",
1014        serde(
1015            rename = "updatedTimestamp",
1016            with = "satay_runtime::serde_string::as_offset_datetime"
1017        )
1018    )]
1019    pub updated_timestamp: satay_runtime::OffsetDateTime,
1020    /// ISO 8601 date or date-time in Singapore Time (SGT)
1021    #[cfg_attr(
1022        feature = "serde",
1023        serde(with = "satay_runtime::serde_string::as_offset_datetime")
1024    )]
1025    pub timestamp: satay_runtime::OffsetDateTime,
1026    /// A general weather forecast for the 24 hour period
1027    pub general: TwentyFourHrForecastGeneral,
1028    /// Forecasts for various areas in Singapore
1029    pub periods: Vec<TwentyFourHrForecastPeriod>,
1030}
1031/// A general weather forecast for the 24 hour period
1032#[derive(Debug, Clone, PartialEq)]
1033#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1034pub struct TwentyFourHrForecastGeneral {
1035    /// Period of time the forecast is valid for
1036    #[cfg_attr(feature = "serde", serde(rename = "validPeriod"))]
1037    pub valid_period: NeaValidPeriod,
1038    /// Unit of measure - Degrees Celsius
1039    pub temperature: NeaTemperatureRange,
1040    /// Unit of measure - Percentage
1041    #[cfg_attr(feature = "serde", serde(rename = "relativeHumidity"))]
1042    pub relative_humidity: NeaHumidityRange,
1043    pub forecast: NeaMssForecast,
1044    pub wind: NeaOutlookWind,
1045}
1046#[derive(Debug, Clone, PartialEq)]
1047#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1048pub struct TwentyFourHrForecastPeriod {
1049    /// Period of time the forecast is valid for
1050    #[cfg_attr(feature = "serde", serde(rename = "timePeriod"))]
1051    pub time_period: NeaValidPeriod,
1052    pub regions: TwentyFourHrRegionalForecast,
1053}
1054#[derive(Debug, Clone, PartialEq)]
1055#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1056pub struct TwentyFourHrRegionalForecast {
1057    pub west: NeaMssForecast,
1058    pub east: NeaMssForecast,
1059    pub central: NeaMssForecast,
1060    pub north: NeaMssForecast,
1061    pub south: NeaMssForecast,
1062}
1063#[derive(Debug, Clone, PartialEq)]
1064#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1065pub struct TwoHrForecastData {
1066    pub area_metadata: Vec<NeaForecastArea>,
1067    pub items: Vec<TwoHrForecastSnapshot>,
1068    /// Token to retrieve next page if exists
1069    #[cfg_attr(
1070        feature = "serde",
1071        serde(
1072            rename = "paginationToken",
1073            default,
1074            skip_serializing_if = "Option::is_none"
1075        )
1076    )]
1077    pub pagination_token: Option<String>,
1078}
1079#[derive(Debug, Clone, PartialEq)]
1080#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1081pub struct NeaForecastArea {
1082    /// Name of the area
1083    pub name: String,
1084    pub label_location: NeaGeoPoint,
1085}
1086#[derive(Debug, Clone, PartialEq)]
1087#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1088pub struct TwoHrForecastSnapshot {
1089    /// ISO 8601 date or date-time in Singapore Time (SGT)
1090    #[cfg_attr(
1091        feature = "serde",
1092        serde(with = "satay_runtime::serde_string::as_offset_datetime")
1093    )]
1094    pub update_timestamp: satay_runtime::OffsetDateTime,
1095    /// ISO 8601 date or date-time in Singapore Time (SGT)
1096    #[cfg_attr(
1097        feature = "serde",
1098        serde(with = "satay_runtime::serde_string::as_offset_datetime")
1099    )]
1100    pub timestamp: satay_runtime::OffsetDateTime,
1101    /// Period of time the forecast is valid for
1102    pub valid_period: NeaValidPeriod,
1103    /// Forecasts for various areas in Singapore
1104    pub forecasts: Vec<TwoHrAreaForecast>,
1105}
1106#[derive(Debug, Clone, PartialEq)]
1107#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1108pub struct TwoHrAreaForecast {
1109    pub area: String,
1110    /// NEA MSS human-readable weather forecast text
1111    pub forecast: NeaForecastText,
1112}
1113/// Period of time the forecast is valid for
1114#[derive(Debug, Clone, PartialEq)]
1115#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1116pub struct NeaValidPeriod {
1117    /// ISO 8601 date or date-time in Singapore Time (SGT)
1118    #[cfg_attr(
1119        feature = "serde",
1120        serde(with = "satay_runtime::serde_string::as_offset_datetime")
1121    )]
1122    pub start: satay_runtime::OffsetDateTime,
1123    /// ISO 8601 date or date-time in Singapore Time (SGT)
1124    #[cfg_attr(
1125        feature = "serde",
1126        serde(with = "satay_runtime::serde_string::as_offset_datetime")
1127    )]
1128    pub end: satay_runtime::OffsetDateTime,
1129    pub text: String,
1130}
1131#[derive(Debug, Clone, PartialEq)]
1132#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1133pub struct PsiData {
1134    #[cfg_attr(feature = "serde", serde(rename = "regionMetadata"))]
1135    pub region_metadata: Vec<NeaRegionMetadata>,
1136    pub items: Vec<PsiSnapshot>,
1137    /// Token to retrieve next page if exists
1138    #[cfg_attr(
1139        feature = "serde",
1140        serde(
1141            rename = "paginationToken",
1142            default,
1143            skip_serializing_if = "Option::is_none"
1144        )
1145    )]
1146    pub pagination_token: Option<String>,
1147}
1148#[derive(Debug, Clone, PartialEq)]
1149#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1150pub struct PsiSnapshot {
1151    /// SGT calendar date (YYYY-MM-DD)
1152    #[cfg_attr(
1153        feature = "serde",
1154        serde(with = "satay_runtime::serde_string::as_date")
1155    )]
1156    pub date: satay_runtime::Date,
1157    /// ISO 8601 date or date-time in Singapore Time (SGT)
1158    #[cfg_attr(
1159        feature = "serde",
1160        serde(
1161            rename = "updatedTimestamp",
1162            with = "satay_runtime::serde_string::as_offset_datetime"
1163        )
1164    )]
1165    pub updated_timestamp: satay_runtime::OffsetDateTime,
1166    /// ISO 8601 date or date-time in Singapore Time (SGT)
1167    #[cfg_attr(
1168        feature = "serde",
1169        serde(with = "satay_runtime::serde_string::as_offset_datetime")
1170    )]
1171    pub timestamp: satay_runtime::OffsetDateTime,
1172    /// Overall and regional PSI data including pollutant concentrations and sub-indices
1173    pub readings: PsiReadings,
1174}
1175/// Overall and regional PSI data including pollutant concentrations and sub-indices
1176#[derive(Debug, Clone, PartialEq)]
1177#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1178pub struct PsiReadings {
1179    /// Concentration is measured in micrograms per cubic metre
1180    pub co_sub_index: PsiCoSubIndexRegional,
1181    /// Concentration is measured in micrograms per cubic metre
1182    pub so2_twenty_four_hourly: PsiSo2TwentyFourHourRegional,
1183    /// Concentration is measured in micrograms per cubic metre
1184    pub so2_sub_index: PsiSo2SubIndexRegional,
1185    /// Concentration is measured in micrograms per cubic metre
1186    #[cfg_attr(
1187        feature = "serde",
1188        serde(default, skip_serializing_if = "Option::is_none")
1189    )]
1190    pub psi_three_hourly: Option<PsiThreeHourRegional>,
1191    /// Concentration is measured in micrograms per cubic metre
1192    pub co_eight_hour_max: PsiCoEightHourMaxRegional,
1193    /// Concentration is measured in micrograms per cubic metre
1194    pub no2_one_hour_max: PsiNo2OneHourMaxRegional,
1195    /// Concentration is measured in micrograms per cubic metre
1196    pub pm10_sub_index: PsiPm10SubIndexRegional,
1197    /// Concentration is measured in micrograms per cubic metre
1198    pub pm25_sub_index: PsiPm25SubIndexRegional,
1199    /// Concentration is measured in micrograms per cubic metre
1200    pub o3_eight_hour_max: PsiO3EightHourMaxRegional,
1201    /// Concentration is measured in micrograms per cubic metre
1202    pub psi_twenty_four_hourly: PsiTwentyFourHourRegional,
1203    /// Concentration is measured in micrograms per cubic metre
1204    pub o3_sub_index: PsiO3SubIndexRegional,
1205    /// Concentration is measured in micrograms per cubic metre
1206    pub pm25_twenty_four_hourly: PsiPm25TwentyFourHourRegional,
1207    /// Concentration is measured in micrograms per cubic metre
1208    pub pm10_twenty_four_hourly: PsiPm10TwentyFourHourRegional,
1209}
1210#[derive(Debug, Clone, PartialEq)]
1211#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1212pub struct NeaRegionMetadata {
1213    /// Region name
1214    pub name: NeaRegionMetadataName,
1215    #[cfg_attr(feature = "serde", serde(rename = "labelLocation"))]
1216    pub label_location: NeaGeoPoint,
1217}
1218/// Concentration is measured in micrograms per cubic metre
1219#[derive(Debug, Clone, PartialEq)]
1220#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1221pub struct PsiCoEightHourMaxRegional {
1222    /// Regional air-quality or index reading
1223    pub east: NeaRegionalReading,
1224    /// Regional air-quality or index reading
1225    pub west: NeaRegionalReading,
1226    /// Regional air-quality or index reading
1227    pub north: NeaRegionalReading,
1228    /// Regional air-quality or index reading
1229    pub south: NeaRegionalReading,
1230    /// Regional air-quality or index reading
1231    pub central: NeaRegionalReading,
1232}
1233/// Concentration is measured in micrograms per cubic metre
1234#[derive(Debug, Clone, PartialEq)]
1235#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1236pub struct PsiCoSubIndexRegional {
1237    /// Regional air-quality or index reading
1238    pub east: NeaRegionalReading,
1239    /// Regional air-quality or index reading
1240    pub west: NeaRegionalReading,
1241    /// Regional air-quality or index reading
1242    pub north: NeaRegionalReading,
1243    /// Regional air-quality or index reading
1244    pub south: NeaRegionalReading,
1245    /// Regional air-quality or index reading
1246    pub central: NeaRegionalReading,
1247}
1248/// Concentration is measured in micrograms per cubic metre
1249#[derive(Debug, Clone, PartialEq)]
1250#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1251pub struct PsiNo2OneHourMaxRegional {
1252    /// Regional air-quality or index reading
1253    pub east: NeaRegionalReading,
1254    /// Regional air-quality or index reading
1255    pub west: NeaRegionalReading,
1256    /// Regional air-quality or index reading
1257    pub north: NeaRegionalReading,
1258    /// Regional air-quality or index reading
1259    pub south: NeaRegionalReading,
1260    /// Regional air-quality or index reading
1261    pub central: NeaRegionalReading,
1262}
1263/// Concentration is measured in micrograms per cubic metre
1264#[derive(Debug, Clone, PartialEq)]
1265#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1266pub struct PsiO3EightHourMaxRegional {
1267    /// Regional air-quality or index reading
1268    pub east: NeaRegionalReading,
1269    /// Regional air-quality or index reading
1270    pub west: NeaRegionalReading,
1271    /// Regional air-quality or index reading
1272    pub north: NeaRegionalReading,
1273    /// Regional air-quality or index reading
1274    pub south: NeaRegionalReading,
1275    /// Regional air-quality or index reading
1276    pub central: NeaRegionalReading,
1277}
1278/// Concentration is measured in micrograms per cubic metre
1279#[derive(Debug, Clone, PartialEq)]
1280#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1281pub struct PsiO3SubIndexRegional {
1282    /// Regional air-quality or index reading
1283    pub east: NeaRegionalReading,
1284    /// Regional air-quality or index reading
1285    pub west: NeaRegionalReading,
1286    /// Regional air-quality or index reading
1287    pub north: NeaRegionalReading,
1288    /// Regional air-quality or index reading
1289    pub south: NeaRegionalReading,
1290    /// Regional air-quality or index reading
1291    pub central: NeaRegionalReading,
1292}
1293/// Concentration is measured in micrograms per cubic metre
1294#[derive(Debug, Clone, PartialEq)]
1295#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1296pub struct PsiPm10SubIndexRegional {
1297    /// Regional air-quality or index reading
1298    pub east: NeaRegionalReading,
1299    /// Regional air-quality or index reading
1300    pub west: NeaRegionalReading,
1301    /// Regional air-quality or index reading
1302    pub north: NeaRegionalReading,
1303    /// Regional air-quality or index reading
1304    pub south: NeaRegionalReading,
1305    /// Regional air-quality or index reading
1306    pub central: NeaRegionalReading,
1307}
1308/// Concentration is measured in micrograms per cubic metre
1309#[derive(Debug, Clone, PartialEq)]
1310#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1311pub struct PsiPm10TwentyFourHourRegional {
1312    /// Regional air-quality or index reading
1313    pub east: NeaRegionalReading,
1314    /// Regional air-quality or index reading
1315    pub west: NeaRegionalReading,
1316    /// Regional air-quality or index reading
1317    pub north: NeaRegionalReading,
1318    /// Regional air-quality or index reading
1319    pub south: NeaRegionalReading,
1320    /// Regional air-quality or index reading
1321    pub central: NeaRegionalReading,
1322}
1323/// Concentration is measured in micrograms per cubic metre
1324#[derive(Debug, Clone, PartialEq)]
1325#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1326pub struct PsiPm25SubIndexRegional {
1327    /// Regional air-quality or index reading
1328    pub east: NeaRegionalReading,
1329    /// Regional air-quality or index reading
1330    pub west: NeaRegionalReading,
1331    /// Regional air-quality or index reading
1332    pub north: NeaRegionalReading,
1333    /// Regional air-quality or index reading
1334    pub south: NeaRegionalReading,
1335    /// Regional air-quality or index reading
1336    pub central: NeaRegionalReading,
1337}
1338/// Concentration is measured in micrograms per cubic metre
1339#[derive(Debug, Clone, PartialEq)]
1340#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1341pub struct PsiPm25TwentyFourHourRegional {
1342    /// Regional air-quality or index reading
1343    pub east: NeaRegionalReading,
1344    /// Regional air-quality or index reading
1345    pub west: NeaRegionalReading,
1346    /// Regional air-quality or index reading
1347    pub north: NeaRegionalReading,
1348    /// Regional air-quality or index reading
1349    pub south: NeaRegionalReading,
1350    /// Regional air-quality or index reading
1351    pub central: NeaRegionalReading,
1352}
1353/// Concentration is measured in micrograms per cubic metre
1354#[derive(Debug, Clone, PartialEq)]
1355#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1356pub struct PsiThreeHourRegional {
1357    /// Regional air-quality or index reading
1358    #[cfg_attr(
1359        feature = "serde",
1360        serde(default, skip_serializing_if = "Option::is_none")
1361    )]
1362    pub east: Option<NeaRegionalReading>,
1363    /// Regional air-quality or index reading
1364    #[cfg_attr(
1365        feature = "serde",
1366        serde(default, skip_serializing_if = "Option::is_none")
1367    )]
1368    pub west: Option<NeaRegionalReading>,
1369    /// Regional air-quality or index reading
1370    #[cfg_attr(
1371        feature = "serde",
1372        serde(default, skip_serializing_if = "Option::is_none")
1373    )]
1374    pub north: Option<NeaRegionalReading>,
1375    /// Regional air-quality or index reading
1376    #[cfg_attr(
1377        feature = "serde",
1378        serde(default, skip_serializing_if = "Option::is_none")
1379    )]
1380    pub south: Option<NeaRegionalReading>,
1381    /// Regional air-quality or index reading
1382    #[cfg_attr(
1383        feature = "serde",
1384        serde(default, skip_serializing_if = "Option::is_none")
1385    )]
1386    pub central: Option<NeaRegionalReading>,
1387}
1388/// Concentration is measured in micrograms per cubic metre
1389#[derive(Debug, Clone, PartialEq)]
1390#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1391pub struct PsiTwentyFourHourRegional {
1392    /// Regional air-quality or index reading
1393    pub east: NeaRegionalReading,
1394    /// Regional air-quality or index reading
1395    pub west: NeaRegionalReading,
1396    /// Regional air-quality or index reading
1397    pub north: NeaRegionalReading,
1398    /// Regional air-quality or index reading
1399    pub south: NeaRegionalReading,
1400    /// Regional air-quality or index reading
1401    pub central: NeaRegionalReading,
1402}
1403/// Concentration is measured in micrograms per cubic metre
1404#[derive(Debug, Clone, PartialEq)]
1405#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1406pub struct PsiSo2SubIndexRegional {
1407    /// Regional air-quality or index reading
1408    pub east: NeaRegionalReading,
1409    /// Regional air-quality or index reading
1410    pub west: NeaRegionalReading,
1411    /// Regional air-quality or index reading
1412    pub north: NeaRegionalReading,
1413    /// Regional air-quality or index reading
1414    pub south: NeaRegionalReading,
1415    /// Regional air-quality or index reading
1416    pub central: NeaRegionalReading,
1417}
1418/// Concentration is measured in micrograms per cubic metre
1419#[derive(Debug, Clone, PartialEq)]
1420#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1421pub struct PsiSo2TwentyFourHourRegional {
1422    /// Regional air-quality or index reading
1423    pub east: NeaRegionalReading,
1424    /// Regional air-quality or index reading
1425    pub west: NeaRegionalReading,
1426    /// Regional air-quality or index reading
1427    pub north: NeaRegionalReading,
1428    /// Regional air-quality or index reading
1429    pub south: NeaRegionalReading,
1430    /// Regional air-quality or index reading
1431    pub central: NeaRegionalReading,
1432}
1433#[derive(Debug, Clone, PartialEq)]
1434#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1435pub struct Pm25Data {
1436    #[cfg_attr(feature = "serde", serde(rename = "regionMetadata"))]
1437    pub region_metadata: Vec<NeaRegionMetadata>,
1438    pub items: Vec<Pm25Snapshot>,
1439    /// Token to retrieve next page if exists
1440    #[cfg_attr(
1441        feature = "serde",
1442        serde(
1443            rename = "paginationToken",
1444            default,
1445            skip_serializing_if = "Option::is_none"
1446        )
1447    )]
1448    pub pagination_token: Option<String>,
1449}
1450#[derive(Debug, Clone, PartialEq)]
1451#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1452pub struct Pm25Snapshot {
1453    /// SGT calendar date (YYYY-MM-DD)
1454    #[cfg_attr(
1455        feature = "serde",
1456        serde(with = "satay_runtime::serde_string::as_date")
1457    )]
1458    pub date: satay_runtime::Date,
1459    /// ISO 8601 date or date-time in Singapore Time (SGT)
1460    #[cfg_attr(
1461        feature = "serde",
1462        serde(
1463            rename = "updatedTimestamp",
1464            with = "satay_runtime::serde_string::as_offset_datetime"
1465        )
1466    )]
1467    pub updated_timestamp: satay_runtime::OffsetDateTime,
1468    /// ISO 8601 date or date-time in Singapore Time (SGT)
1469    #[cfg_attr(
1470        feature = "serde",
1471        serde(with = "satay_runtime::serde_string::as_offset_datetime")
1472    )]
1473    pub timestamp: satay_runtime::OffsetDateTime,
1474    /// Overall and regional PSI data including pollutant concentrations and sub-indices
1475    pub readings: Pm25Readings,
1476}
1477/// Overall and regional PSI data including pollutant concentrations and sub-indices
1478#[derive(Debug, Clone, PartialEq)]
1479#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1480pub struct Pm25Readings {
1481    pub pm25_one_hourly: Pm25OneHourRegional,
1482}
1483#[derive(Debug, Clone, PartialEq)]
1484#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1485pub struct Pm25OneHourRegional {
1486    /// Regional PM2.5 one-hour reading (µg/m³)
1487    pub east: NeaPm25HourlyReading,
1488    /// Regional PM2.5 one-hour reading (µg/m³)
1489    pub west: NeaPm25HourlyReading,
1490    /// Regional PM2.5 one-hour reading (µg/m³)
1491    pub north: NeaPm25HourlyReading,
1492    /// Regional PM2.5 one-hour reading (µg/m³)
1493    pub south: NeaPm25HourlyReading,
1494    /// Regional PM2.5 one-hour reading (µg/m³)
1495    pub central: NeaPm25HourlyReading,
1496}
1497#[derive(Debug, Clone, PartialEq)]
1498#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1499pub struct UvData {
1500    pub records: Vec<UvDayRecord>,
1501    /// Token to retrieve next page if exists
1502    #[cfg_attr(
1503        feature = "serde",
1504        serde(
1505            rename = "paginationToken",
1506            default,
1507            skip_serializing_if = "Option::is_none"
1508        )
1509    )]
1510    pub pagination_token: Option<String>,
1511}
1512#[derive(Debug, Clone, PartialEq)]
1513#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1514pub struct UvDayRecord {
1515    pub index: Vec<UvHourlyIndex>,
1516    /// SGT calendar date (YYYY-MM-DD)
1517    #[cfg_attr(
1518        feature = "serde",
1519        serde(with = "satay_runtime::serde_string::as_date")
1520    )]
1521    pub date: satay_runtime::Date,
1522    /// ISO 8601 date or date-time in Singapore Time (SGT)
1523    #[cfg_attr(
1524        feature = "serde",
1525        serde(
1526            rename = "updatedTimestamp",
1527            with = "satay_runtime::serde_string::as_offset_datetime"
1528        )
1529    )]
1530    pub updated_timestamp: satay_runtime::OffsetDateTime,
1531    /// ISO 8601 date or date-time in Singapore Time (SGT)
1532    #[cfg_attr(
1533        feature = "serde",
1534        serde(with = "satay_runtime::serde_string::as_offset_datetime")
1535    )]
1536    pub timestamp: satay_runtime::OffsetDateTime,
1537}
1538/// Reverse-chronologically ordered indexes
1539#[derive(Debug, Clone, PartialEq)]
1540#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1541pub struct UvHourlyIndex {
1542    /// ISO 8601 date or date-time in Singapore Time (SGT)
1543    #[cfg_attr(
1544        feature = "serde",
1545        serde(with = "satay_runtime::serde_string::as_offset_datetime")
1546    )]
1547    pub hour: satay_runtime::OffsetDateTime,
1548    /// UV index for the hour
1549    pub value: NeaUvIndex,
1550}
1551#[derive(Debug, Clone, PartialEq)]
1552#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1553pub struct WeatherSubApiData {
1554    #[cfg_attr(
1555        feature = "serde",
1556        serde(default, skip_serializing_if = "Option::is_none")
1557    )]
1558    pub records: Option<Vec<WeatherSubApiDayRecord>>,
1559    /// Token to retrieve next page if exists
1560    #[cfg_attr(
1561        feature = "serde",
1562        serde(
1563            rename = "paginationToken",
1564            default,
1565            skip_serializing_if = "Option::is_none"
1566        )
1567    )]
1568    pub pagination_token: Option<String>,
1569}
1570#[derive(Debug, Clone, PartialEq)]
1571#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1572pub struct WeatherSubApiDayRecord {
1573    /// ISO 8601 date or date-time in Singapore Time (SGT)
1574    #[cfg_attr(
1575        feature = "serde",
1576        serde(
1577            with = "satay_runtime::serde_string::as_offset_datetime::option",
1578            default,
1579            skip_serializing_if = "Option::is_none"
1580        )
1581    )]
1582    pub datetime: Option<satay_runtime::OffsetDateTime>,
1583    /// Weather sub-API observation (lightning strikes or WBGT station readings)
1584    #[cfg_attr(
1585        feature = "serde",
1586        serde(default, skip_serializing_if = "Option::is_none")
1587    )]
1588    pub item: Option<WeatherSubApiObservation>,
1589    /// ISO 8601 date or date-time in Singapore Time (SGT)
1590    #[cfg_attr(
1591        feature = "serde",
1592        serde(
1593            rename = "updatedTimestamp",
1594            with = "satay_runtime::serde_string::as_offset_datetime::option",
1595            default,
1596            skip_serializing_if = "Option::is_none"
1597        )
1598    )]
1599    pub updated_timestamp: Option<satay_runtime::OffsetDateTime>,
1600}
1601/// Weather sub-API observation (lightning strikes or WBGT station readings)
1602#[derive(Debug, Clone, PartialEq)]
1603#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1604pub struct WeatherSubApiObservation {
1605    /// Type of the weather information (`observation` or `forecast`). Lightning is always `observation.
1606    #[cfg_attr(
1607        feature = "serde",
1608        serde(rename = "type", default, skip_serializing_if = "Option::is_none")
1609    )]
1610    pub type_: Option<String>,
1611    /// Indicates whether the weather information includes station information.
1612    #[cfg_attr(
1613        feature = "serde",
1614        serde(
1615            rename = "isStationData",
1616            default,
1617            skip_serializing_if = "Option::is_none"
1618        )
1619    )]
1620    pub is_station_data: Option<bool>,
1621    #[cfg_attr(
1622        feature = "serde",
1623        serde(default, skip_serializing_if = "Option::is_none")
1624    )]
1625    pub readings: Option<Vec<WeatherSubApiLightningReading>>,
1626}
1627#[derive(Debug, Clone, PartialEq)]
1628#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1629pub struct WeatherSubApiLightningReading {
1630    /// WGS84 coordinates (API returns lat/long as decimal strings)
1631    #[cfg_attr(
1632        feature = "serde",
1633        serde(default, skip_serializing_if = "Option::is_none")
1634    )]
1635    pub location: Option<NeaLightningGeoPoint>,
1636    /// ISO 8601 date or date-time in Singapore Time (SGT)
1637    #[cfg_attr(
1638        feature = "serde",
1639        serde(
1640            with = "satay_runtime::serde_string::as_offset_datetime::option",
1641            default,
1642            skip_serializing_if = "Option::is_none"
1643        )
1644    )]
1645    pub datetime: Option<satay_runtime::OffsetDateTime>,
1646    /// A textual description of the weather event, which can include types such as "Cloud to Cloud" or "Cloud to Ground."
1647    #[cfg_attr(
1648        feature = "serde",
1649        serde(default, skip_serializing_if = "Option::is_none")
1650    )]
1651    pub text: Option<String>,
1652    /// Lightning event type (C = cloud-to-cloud, G = cloud-to-ground)
1653    #[cfg_attr(
1654        feature = "serde",
1655        serde(rename = "type", default, skip_serializing_if = "Option::is_none")
1656    )]
1657    pub type_: Option<NeaLightningType>,
1658    /// WBGT monitoring station metadata
1659    #[cfg_attr(
1660        feature = "serde",
1661        serde(default, skip_serializing_if = "Option::is_none")
1662    )]
1663    pub station: Option<NeaWbgtStation>,
1664    /// 15-minute average WBGT (°C) as a decimal string
1665    #[cfg_attr(
1666        feature = "serde",
1667        serde(
1668            with = "satay_runtime::serde_string::as_f64::option",
1669            default,
1670            skip_serializing_if = "Option::is_none"
1671        )
1672    )]
1673    pub wbgt: Option<f64>,
1674    /// Heat stress advisory level from WBGT
1675    #[cfg_attr(
1676        feature = "serde",
1677        serde(
1678            rename = "heatStress",
1679            default,
1680            skip_serializing_if = "Option::is_none"
1681        )
1682    )]
1683    pub heat_stress: Option<NeaHeatStressLevel>,
1684}
1685#[derive(Debug, Clone, PartialEq)]
1686#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1687pub struct WeatherSubApiInvalidParamsError {
1688    #[cfg_attr(
1689        feature = "serde",
1690        serde(default, skip_serializing_if = "Option::is_none")
1691    )]
1692    pub code: Option<f64>,
1693    #[cfg_attr(
1694        feature = "serde",
1695        serde(default, skip_serializing_if = "Option::is_none")
1696    )]
1697    pub name: Option<String>,
1698    #[cfg_attr(
1699        feature = "serde",
1700        serde(rename = "errorMsg", default, skip_serializing_if = "Option::is_none")
1701    )]
1702    pub error_msg: Option<String>,
1703}
1704/// Unit of measure for NEA station readings and outlook ranges
1705#[derive(Debug, Clone, PartialEq, Eq, Default)]
1706#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1707pub enum NeaMeasurementUnit {
1708    #[cfg_attr(feature = "serde", serde(rename = "deg C"))]
1709    DegC,
1710    #[cfg_attr(feature = "serde", serde(rename = "Degrees Celsius"))]
1711    DegreesCelsius,
1712    Percentage,
1713    #[cfg_attr(feature = "serde", serde(rename = "percentage"))]
1714    PercentLower,
1715    #[cfg_attr(feature = "serde", serde(rename = "knots"))]
1716    Knots,
1717    #[cfg_attr(feature = "serde", serde(rename = "degrees"))]
1718    Degrees,
1719    #[cfg_attr(feature = "serde", serde(rename = "mm"))]
1720    Millimeters,
1721    #[default]
1722    #[cfg_attr(feature = "serde", serde(other))]
1723    Unknown,
1724}
1725impl NeaMeasurementUnit {
1726    pub const fn as_str(&self) -> &'static str {
1727        match self {
1728            Self::DegC => "deg C",
1729            Self::DegreesCelsius => "Degrees Celsius",
1730            Self::Percentage => "Percentage",
1731            Self::PercentLower => "percentage",
1732            Self::Knots => "knots",
1733            Self::Degrees => "degrees",
1734            Self::Millimeters => "mm",
1735            Self::Unknown => "",
1736        }
1737    }
1738}
1739impl AsRef<str> for NeaMeasurementUnit {
1740    fn as_ref(&self) -> &str {
1741        self.as_str()
1742    }
1743}
1744impl fmt::Display for NeaMeasurementUnit {
1745    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1746        f.write_str(self.as_str())
1747    }
1748}
1749/// WGS84 coordinates (API returns lat/long as decimal strings)
1750#[derive(Debug, Clone, PartialEq)]
1751#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1752pub struct NeaLightningGeoPoint {
1753    /// WGS84 latitude as a decimal string
1754    #[cfg_attr(feature = "serde", serde(with = "satay_runtime::serde_string::as_f64"))]
1755    pub latitude: f64,
1756    /// WGS84 longitude as a decimal string
1757    #[cfg_attr(feature = "serde", serde(with = "satay_runtime::serde_string::as_f64"))]
1758    pub longitude: f64,
1759}
1760/// WBGT monitoring station metadata
1761#[derive(Debug, Clone, PartialEq)]
1762#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1763pub struct NeaWbgtStation {
1764    /// NEA weather station identifier (S + 2–3 digits)
1765    pub id: NeaStationId,
1766    /// Station name
1767    pub name: String,
1768    /// Town centre or site label for the station
1769    #[cfg_attr(feature = "serde", serde(rename = "townCenter"))]
1770    pub town_center: String,
1771}
1772/// 15-minute average WBGT (°C) as a decimal string
1773pub type NeaWbgt = f64;
1774/// Heat stress advisory level from WBGT
1775#[derive(Debug, Clone, PartialEq, Eq, Default)]
1776#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1777pub enum NeaHeatStressLevel {
1778    Low,
1779    Moderate,
1780    High,
1781    #[default]
1782    #[cfg_attr(feature = "serde", serde(other))]
1783    Unknown,
1784}
1785impl NeaHeatStressLevel {
1786    pub const fn as_str(&self) -> &'static str {
1787        match self {
1788            Self::Low => "Low",
1789            Self::Moderate => "Moderate",
1790            Self::High => "High",
1791            Self::Unknown => "",
1792        }
1793    }
1794}
1795impl AsRef<str> for NeaHeatStressLevel {
1796    fn as_ref(&self) -> &str {
1797        self.as_str()
1798    }
1799}
1800impl fmt::Display for NeaHeatStressLevel {
1801    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1802        f.write_str(self.as_str())
1803    }
1804}
1805/// Region name
1806#[derive(Debug, Clone, PartialEq, Eq, Default)]
1807#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1808pub enum NeaRegionMetadataName {
1809    #[cfg_attr(feature = "serde", serde(rename = "west"))]
1810    West,
1811    #[cfg_attr(feature = "serde", serde(rename = "east"))]
1812    East,
1813    #[cfg_attr(feature = "serde", serde(rename = "north"))]
1814    North,
1815    #[cfg_attr(feature = "serde", serde(rename = "south"))]
1816    South,
1817    #[cfg_attr(feature = "serde", serde(rename = "central"))]
1818    Central,
1819    #[default]
1820    #[cfg_attr(feature = "serde", serde(other))]
1821    Unknown,
1822}
1823impl NeaRegionMetadataName {
1824    pub const fn as_str(&self) -> &'static str {
1825        match self {
1826            Self::West => "west",
1827            Self::East => "east",
1828            Self::North => "north",
1829            Self::South => "south",
1830            Self::Central => "central",
1831            Self::Unknown => "",
1832        }
1833    }
1834}
1835impl AsRef<str> for NeaRegionMetadataName {
1836    fn as_ref(&self) -> &str {
1837        self.as_str()
1838    }
1839}
1840impl fmt::Display for NeaRegionMetadataName {
1841    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1842        f.write_str(self.as_str())
1843    }
1844}