bufkit_data/archive/query/
station_summary.rs

1use crate::{
2    errors::BufkitDataErr,
3    models::Model,
4    site::{StateProv, StationNumber},
5};
6use chrono::FixedOffset;
7use std::{collections::HashMap, str::FromStr};
8
9/// A summary of the information about a station.
10#[derive(Debug)]
11pub struct StationSummary {
12    /// Station number
13    pub station_num: StationNumber,
14    /// List of ids associated with this site
15    pub ids: Vec<String>,
16    /// All the models in the archive associated with this site
17    pub models: Vec<Model>,
18    /// Station name, common name
19    pub name: Option<String>,
20    /// Notes related to the site
21    pub notes: Option<String>,
22    /// The state-province associated with the site.
23    pub state: Option<StateProv>,
24    /// The time zone offset to local standard time.
25    pub time_zone: Option<FixedOffset>,
26    /// The number of files in the archive related to this site.
27    pub number_of_files: u32,
28}
29
30struct StationEntry {
31    station_num: StationNumber,
32    id: Option<String>,
33    model: Option<Model>,
34    name: Option<String>,
35    notes: Option<String>,
36    state: Option<StateProv>,
37    time_zone: Option<FixedOffset>,
38    number_of_files: u32,
39}
40
41impl StationSummary {
42    /// Concantenate the ids into a comma separated list.
43    pub fn ids_as_string(&self) -> String {
44        self.ids.join(", ")
45    }
46
47    /// Concatenate the models into a comma separated list.
48    pub fn models_as_string(&self) -> String {
49        self.models
50            .iter()
51            .map(|m| m.as_static_str().to_owned())
52            .collect::<Vec<_>>()
53            .join(", ")
54    }
55}
56
57impl From<StationEntry> for StationSummary {
58    fn from(entry: StationEntry) -> Self {
59        let StationEntry {
60            station_num,
61            id,
62            model,
63            name,
64            notes,
65            state,
66            time_zone,
67            number_of_files,
68        } = entry;
69
70        let mut models = vec![];
71        if let Some(model) = model {
72            models.push(model);
73        }
74
75        let mut ids = vec![];
76        if let Some(id) = id {
77            ids.push(id);
78        }
79
80        StationSummary {
81            station_num,
82            ids,
83            models,
84            name,
85            notes,
86            state,
87            time_zone,
88            number_of_files,
89        }
90    }
91}
92
93impl crate::Archive {
94    /// Get a summary of all the stations in the archive.
95    pub fn station_summaries(&self) -> Result<Vec<StationSummary>, BufkitDataErr> {
96        let mut vals: HashMap<StationNumber, StationSummary> = HashMap::new();
97
98        let mut stmt = self.db_conn.prepare(include_str!("station_summary.sql"))?;
99
100        stmt.query_and_then([], Self::parse_row_to_entry)?
101            .for_each(|stn_entry| {
102                if let Ok(stn_entry) = stn_entry {
103                    if let Some(summary) = vals.get_mut(&stn_entry.station_num) {
104                        if let Some(id) = stn_entry.id {
105                            summary.ids.push(id);
106                        }
107
108                        if let Some(model) = stn_entry.model {
109                            summary.models.push(model);
110                        }
111
112                        summary.number_of_files += stn_entry.number_of_files;
113                    } else {
114                        vals.insert(stn_entry.station_num, StationSummary::from(stn_entry));
115                    }
116                }
117            });
118
119        let mut vals: Vec<StationSummary> = vals.into_iter().map(|(_, v)| v).collect();
120
121        vals.iter_mut().for_each(|summary| {
122            summary.ids.sort_unstable();
123            summary.ids.dedup();
124            summary.models.sort_unstable();
125            summary.models.dedup();
126        });
127
128        Ok(vals)
129    }
130
131    fn parse_row_to_entry(row: &rusqlite::Row) -> Result<StationEntry, rusqlite::Error> {
132        let station_num: StationNumber = row.get::<_, u32>(0).map(StationNumber::from)?;
133        let id: Option<String> = row.get(1)?;
134
135        let model: Option<Model> = row.get::<_, Option<String>>(2).and_then(|string_opt| {
136            string_opt
137                .map(|string| Model::from_str(&string).map_err(|_| rusqlite::Error::InvalidQuery))
138                .transpose()
139        })?;
140
141        let name: Option<String> = row.get(3)?;
142
143        let state: Option<StateProv> = row
144            .get::<_, String>(4)
145            .ok()
146            .and_then(|a_string| StateProv::from_str(&a_string).ok());
147
148        let notes: Option<String> = row.get(5)?;
149
150        let time_zone: Option<chrono::FixedOffset> =
151            row.get::<_, i32>(6).ok().and_then(|offset: i32| {
152                if offset < 0 {
153                    chrono::FixedOffset::west_opt(offset.abs())
154                } else {
155                    chrono::FixedOffset::east_opt(offset)
156                }
157            });
158
159        let number_of_files: u32 = row.get(7)?;
160
161        Ok(StationEntry {
162            station_num,
163            id,
164            model,
165            name,
166            state,
167            notes,
168            time_zone,
169            number_of_files,
170        })
171    }
172}
173
174#[cfg(test)]
175mod unit {
176    use crate::archive::unit::*; // test helpers.
177    use crate::{Model, StationNumber};
178
179    #[test]
180    fn test_summaries() {
181        let TestArchive {
182            tmp: _tmp,
183            mut arch,
184        } = create_test_archive().expect("Failed to create test archive.");
185
186        fill_test_archive(&mut arch);
187
188        let sums = arch.station_summaries().unwrap();
189
190        for sum in sums {
191            println!("{:?}", sum);
192
193            assert_eq!(sum.ids.len(), 1);
194            assert_eq!(sum.ids[0], "KMSO");
195
196            assert_eq!(sum.models.len(), 2);
197            assert!(sum.models.contains(&Model::GFS));
198            assert!(sum.models.contains(&Model::NAM));
199
200            assert_eq!(sum.station_num, StationNumber::new(727730));
201            assert_eq!(sum.number_of_files, 6);
202            assert!(sum.name.is_none());
203            assert!(sum.notes.is_none());
204            assert!(sum.time_zone.is_none());
205            assert!(sum.state.is_none());
206        }
207    }
208}