Skip to main content

nea_rs/
types.rs

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