bufkit_data/archive/query/
station_summary.rs1use crate::{
2 errors::BufkitDataErr,
3 models::Model,
4 site::{StateProv, StationNumber},
5};
6use chrono::FixedOffset;
7use std::{collections::HashMap, str::FromStr};
8
9#[derive(Debug)]
11pub struct StationSummary {
12 pub station_num: StationNumber,
14 pub ids: Vec<String>,
16 pub models: Vec<Model>,
18 pub name: Option<String>,
20 pub notes: Option<String>,
22 pub state: Option<StateProv>,
24 pub time_zone: Option<FixedOffset>,
26 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 pub fn ids_as_string(&self) -> String {
44 self.ids.join(", ")
45 }
46
47 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 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::*; 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}