1use crate::{coords::Coords, errors::BufkitDataErr, site::StationNumber};
4
5#[cfg(feature = "pylib")]
6use pyo3::prelude::*;
7
8use std::convert::TryFrom;
9
10#[cfg_attr(feature = "pylib", pyclass(module = "bufkit_data"))]
12#[derive(Debug)]
13pub struct Archive {
14 root: std::path::PathBuf, db_conn: rusqlite::Connection, }
17
18mod clean;
19mod modify;
20
21mod query;
22pub use query::StationSummary;
23
24mod root;
25
26struct InternalSiteInfo {
27 station_num: StationNumber,
28 id: Option<String>,
29 init_time: chrono::NaiveDateTime,
30 end_time: chrono::NaiveDateTime,
31 coords: Coords,
32 elevation: metfor::Meters,
33}
34
35impl Archive {
36 fn parse_site_info(text: &str) -> Result<InternalSiteInfo, BufkitDataErr> {
37 let bdata = sounding_bufkit::BufkitData::init(text, "")?;
38 let mut iter = bdata.into_iter();
39
40 let first = iter.next().ok_or(BufkitDataErr::NotEnoughData)?.0;
41 let last = iter.last().ok_or(BufkitDataErr::NotEnoughData)?.0;
42
43 let init_time: chrono::NaiveDateTime =
44 first.valid_time().ok_or(BufkitDataErr::MissingValidTime)?;
45 let end_time: chrono::NaiveDateTime =
46 last.valid_time().ok_or(BufkitDataErr::MissingValidTime)?;
47 let coords: Coords = first
48 .station_info()
49 .location()
50 .map(Coords::from)
51 .ok_or(BufkitDataErr::MissingStationData)?;
52
53 let elevation = match first.station_info().elevation().into_option() {
54 Some(elev) => elev,
55 None => return Err(BufkitDataErr::MissingStationData),
56 };
57
58 let station_num: i32 = first
59 .station_info()
60 .station_num()
61 .ok_or(BufkitDataErr::MissingStationData)?;
62 let station_num: StationNumber = u32::try_from(station_num)
63 .map_err(|_| BufkitDataErr::GeneralError("negative station number?".to_owned()))
64 .map(StationNumber::from)?;
65
66 Ok(InternalSiteInfo {
67 station_num,
68 id: first
69 .station_info()
70 .station_id()
71 .map(|id| id.to_uppercase()),
72 init_time,
73 end_time,
74 coords,
75 elevation,
76 })
77 }
78}
79
80#[cfg(test)]
81mod unit {
82 use super::*;
83 use crate::{Model, SiteInfo, StateProv, StationNumber};
84
85 use tempdir::TempDir;
86
87 pub(super) struct TestArchive {
89 pub tmp: TempDir,
90 pub arch: Archive,
91 }
92
93 pub(super) fn create_test_archive() -> Result<TestArchive, BufkitDataErr> {
95 let tmp = TempDir::new("bufkit-data-test-archive")?;
96 let arch = Archive::create(&tmp.path())?;
97
98 Ok(TestArchive { tmp, arch })
99 }
100
101 pub(super) fn get_test_data() -> [(String, Model, String); 7] {
103 [
104 (
105 "KMSO".to_owned(),
106 Model::NAM,
107 include_str!("../example_data/2017040100Z_nam_kmso.buf").to_owned(),
108 ),
109 (
110 "KMSO".to_owned(),
111 Model::GFS,
112 include_str!("../example_data/2017040106Z_gfs3_kmso.buf").to_owned(),
113 ),
114 (
115 "KMSO".to_owned(),
116 Model::GFS,
117 include_str!("../example_data/2017040112Z_gfs3_kmso.buf").to_owned(),
118 ),
119 (
120 "KMSO".to_owned(),
121 Model::NAM,
122 include_str!("../example_data/2017040112Z_nam_kmso.buf").to_owned(),
123 ),
124 (
125 "KMSO".to_owned(),
126 Model::GFS,
127 include_str!("../example_data/2017040118Z_gfs3_kmso.buf").to_owned(),
128 ),
129 (
130 "KMSO".to_owned(),
131 Model::GFS,
132 include_str!("../example_data/2017040118Z_gfs_kmso.buf").to_owned(),
133 ),
134 (
135 "KMSO".to_owned(),
136 Model::NAM,
137 include_str!("../example_data/2017040118Z_namm_kmso.buf").to_owned(),
138 ),
139 ]
140 }
141
142 pub(super) fn fill_test_archive(arch: &mut Archive) {
144 for (site, model, raw_data) in get_test_data().iter() {
145 match arch.add(site, None, None, *model, raw_data) {
146 Ok(_) => {}
147 Err(err) => {
148 println!("{:?}", err);
149 panic!("Test archive error filling.");
150 }
151 }
152 }
153 }
154
155 pub(super) fn get_test_sites() -> [SiteInfo; 3] {
157 [
158 SiteInfo {
159 station_num: StationNumber::from(1),
160 name: Some("Chicago/O'Hare".to_owned()),
161 notes: Some("Major air travel hub.".to_owned()),
162 state: Some(StateProv::IL),
163 time_zone: None,
164 },
165 SiteInfo {
166 station_num: StationNumber::from(2),
167 name: Some("Seattle".to_owned()),
168 notes: Some("A coastal city with coffe and rain".to_owned()),
169 state: Some(StateProv::WA),
170 time_zone: Some(chrono::FixedOffset::west_opt(8 * 3600).unwrap()),
171 },
172 SiteInfo {
173 station_num: StationNumber::from(3),
174 name: Some("Missoula".to_owned()),
175 notes: Some("In a valley.".to_owned()),
176 state: None,
177 time_zone: Some(chrono::FixedOffset::west_opt(7 * 3600).unwrap()),
178 },
179 ]
180 }
181
182 #[test]
183 fn test_sites_round_trip() {
184 let TestArchive { tmp: _tmp, arch } =
185 create_test_archive().expect("Failed to create test archive.");
186
187 let test_sites = &get_test_sites();
188
189 for site in test_sites {
190 arch.add_site(site).expect("Error adding site.");
191 }
192
193 let retrieved_sites = arch.sites().expect("Error retrieving sites.");
194
195 for site in retrieved_sites {
196 println!("{:#?}", site);
197 assert!(test_sites
198 .iter()
199 .find(|st| st.station_num == site.station_num)
200 .is_some());
201 }
202 }
203
204 #[test]
205 fn test_files_round_trip() {
206 let TestArchive { tmp: _tmp, arch } =
207 create_test_archive().expect("Failed to create test archive.");
208
209 let test_data = get_test_data();
210
211 for (site, model, raw_data) in test_data.iter() {
212 let init_time = sounding_bufkit::BufkitData::init(&raw_data, "x")
213 .unwrap()
214 .into_iter()
215 .next()
216 .unwrap()
217 .0
218 .valid_time()
219 .unwrap();
220
221 dbg!(init_time);
222 dbg!(&site);
223
224 let site = match arch.add(site, None, None, *model, raw_data) {
225 Ok(site) => site,
226 x => panic!("Error adding site: {:?}", x),
227 };
228
229 dbg!(&site);
230
231 let recovered_str = arch
232 .retrieve(site, *model, init_time)
233 .expect("Failure to load");
234
235 assert!(raw_data == &recovered_str);
236 }
237 }
238
239 #[test]
240 fn test_adding_duplicates() {
241 let TestArchive {
242 tmp: _tmp,
243 mut arch,
244 } = create_test_archive().expect("Failed to create test archive.");
245
246 let kmso = StationNumber::from(727730); fill_test_archive(&mut arch);
249
250 assert_eq!(
251 arch.inventory(kmso, Model::GFS)
252 .expect("db error")
253 .iter()
254 .count(),
255 3
256 );
257 assert_eq!(
258 arch.inventory(kmso, Model::NAM)
259 .expect("db error")
260 .iter()
261 .count(),
262 3
263 );
264
265 fill_test_archive(&mut arch);
267
268 assert_eq!(
269 arch.inventory(kmso, Model::GFS)
270 .expect("db error")
271 .iter()
272 .count(),
273 3
274 );
275 assert_eq!(
276 arch.inventory(kmso, Model::NAM)
277 .expect("db error")
278 .iter()
279 .count(),
280 3
281 );
282 }
283}