Skip to content

Commit cefe6dc

Browse files
committed
introduce Timeseries abstraction to simplify types
1 parent 5e5424b commit cefe6dc

10 files changed

+347
-167
lines changed

src/checks/series/flatline_check.rs

+42-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{DataCache, Error, Flag};
1+
use crate::{util::Timeseries, DataCache, Error, Flag};
22

33
/// Timeseries check that looks for streaks of repeating values.
44
///
@@ -37,11 +37,11 @@ pub fn flatline_check(data: &[Option<f32>]) -> Flag {
3737
pub fn flatline_check_cache(
3838
cache: &DataCache,
3939
num_points: u8,
40-
) -> Result<Vec<(String, Vec<Flag>)>, Error> {
40+
) -> Result<Vec<Timeseries<Flag>>, Error> {
4141
let num_series = cache.data.len();
4242
let mut result_vec = Vec::with_capacity(cache.data.len());
4343
let series_len = match cache.data.first() {
44-
Some(ts) => ts.1.len(),
44+
Some(ts) => ts.values.len(),
4545
// if this is none, the cache is empty, so we can just return an empty result vec
4646
None => return Ok(result_vec),
4747
};
@@ -56,15 +56,15 @@ pub fn flatline_check_cache(
5656
}
5757

5858
for i in 0..num_series {
59-
let trimmed = &cache.data[i].1
59+
let trimmed = &cache.data[i].values
6060
[leading_trim as usize..(series_len - cache.num_trailing_points as usize)];
6161

6262
let windows = trimmed.windows(num_points as usize);
6363

64-
result_vec.push((
65-
cache.data[i].0.clone(),
66-
windows.map(flatline_check).collect(),
67-
));
64+
result_vec.push(Timeseries {
65+
tag: cache.data[i].tag.clone(),
66+
values: windows.map(flatline_check).collect(),
67+
});
6868
}
6969

7070
Ok(result_vec)
@@ -80,28 +80,52 @@ mod tests {
8080
assert_eq!(
8181
flatline_check_cache(
8282
&DataCache::new(
83+
vec![
84+
Timeseries {
85+
tag: "blindern1".to_string(),
86+
values: vec![Some(0.), Some(1.), Some(1.)]
87+
},
88+
Timeseries {
89+
tag: "blindern2".to_string(),
90+
values: vec![Some(0.), Some(0.), None]
91+
},
92+
Timeseries {
93+
tag: "blindern3".to_string(),
94+
values: vec![Some(1.), None, Some(1.)]
95+
},
96+
Timeseries {
97+
tag: "blindern4".to_string(),
98+
values: vec![None, Some(1.), Some(1.)]
99+
},
100+
],
83101
vec![0., 1., 2., 3.],
84102
vec![0., 1., 2., 3.],
85103
vec![0., 0., 0., 0.],
86104
crate::util::Timestamp(0),
87105
RelativeDuration::minutes(10),
88106
1,
89107
1,
90-
vec![
91-
("blindern1".to_string(), vec![Some(0.), Some(1.), Some(1.)]),
92-
("blindern2".to_string(), vec![Some(0.), Some(0.), None]),
93-
("blindern3".to_string(), vec![Some(1.), None, Some(1.)]),
94-
("blindern4".to_string(), vec![None, Some(1.), Some(1.)]),
95-
],
96108
),
97109
2,
98110
)
99111
.unwrap(),
100112
vec![
101-
("blindern1".to_string(), vec![Flag::Pass]),
102-
("blindern2".to_string(), vec![Flag::Fail]),
103-
("blindern3".to_string(), vec![Flag::DataMissing]),
104-
("blindern4".to_string(), vec![Flag::DataMissing]),
113+
Timeseries {
114+
tag: "blindern1".to_string(),
115+
values: vec![Flag::Pass]
116+
},
117+
Timeseries {
118+
tag: "blindern2".to_string(),
119+
values: vec![Flag::Fail]
120+
},
121+
Timeseries {
122+
tag: "blindern3".to_string(),
123+
values: vec![Flag::DataMissing]
124+
},
125+
Timeseries {
126+
tag: "blindern4".to_string(),
127+
values: vec![Flag::DataMissing]
128+
},
105129
]
106130
)
107131
}

src/checks/series/spike_check.rs

+60-27
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{DataCache, Error, Flag};
1+
use crate::{util::Timeseries, DataCache, Error, Flag};
22

33
/// Number of leading values a [`DataCache`] must contain to QC all its
44
/// intended values with spike check
@@ -49,11 +49,11 @@ pub fn spike_check(data: &[Option<f32>; 3], max: f32) -> Flag {
4949
/// - data is invalid
5050
/// - data has `num_leading_points` <= 1
5151
/// - data has `num_trailing_points` <= 1
52-
pub fn spike_check_cache(cache: &DataCache, max: f32) -> Result<Vec<(String, Vec<Flag>)>, Error> {
52+
pub fn spike_check_cache(cache: &DataCache, max: f32) -> Result<Vec<Timeseries<Flag>>, Error> {
5353
let num_series = cache.data.len();
5454
let mut result_vec = Vec::with_capacity(num_series);
5555
let series_len = match cache.data.first() {
56-
Some(ts) => ts.1.len(),
56+
Some(ts) => ts.values.len(),
5757
// if this is none, the cache is empty, so we can just return an empty result vec
5858
None => return Ok(result_vec),
5959
};
@@ -77,16 +77,16 @@ pub fn spike_check_cache(cache: &DataCache, max: f32) -> Result<Vec<(String, Vec
7777

7878
for i in 0..num_series {
7979
let trimmed =
80-
&cache.data[i].1[leading_trim as usize..(series_len - trailing_trim as usize)];
80+
&cache.data[i].values[leading_trim as usize..(series_len - trailing_trim as usize)];
8181

8282
let windows = trimmed.windows(3);
8383

84-
result_vec.push((
85-
cache.data[i].0.clone(),
86-
windows
84+
result_vec.push(Timeseries {
85+
tag: cache.data[i].tag.clone(),
86+
values: windows
8787
.map(|data| spike_check(data.try_into().unwrap(), max))
8888
.collect(),
89-
));
89+
});
9090
}
9191

9292
Ok(result_vec)
@@ -102,37 +102,70 @@ mod tests {
102102
assert_eq!(
103103
spike_check_cache(
104104
&DataCache::new(
105+
vec![
106+
Timeseries {
107+
tag: "blindern1".to_string(),
108+
values: vec![Some(0.), Some(0.), Some(0.)]
109+
},
110+
Timeseries {
111+
tag: "blindern2".to_string(),
112+
values: vec![Some(0.), Some(1.), Some(1.)]
113+
},
114+
// This one passes because although the diffsum is enough to be spike,
115+
// the diffdiff is big enough to override it
116+
Timeseries {
117+
tag: "blindern3".to_string(),
118+
values: vec![Some(0.), Some(1.6), Some(1.)]
119+
},
120+
Timeseries {
121+
tag: "blindern4".to_string(),
122+
values: vec![Some(0.), Some(-1.1), Some(0.)]
123+
},
124+
Timeseries {
125+
tag: "blindern5".to_string(),
126+
values: vec![Some(1.), None, Some(1.)]
127+
},
128+
Timeseries {
129+
tag: "blindern6".to_string(),
130+
values: vec![None, Some(1.), Some(1.)]
131+
},
132+
],
105133
vec![0., 1., 2., 3.],
106134
vec![0., 1., 2., 3.],
107135
vec![0., 0., 0., 0.],
108136
crate::util::Timestamp(0),
109137
RelativeDuration::minutes(10),
110138
1,
111139
1,
112-
vec![
113-
("blindern1".to_string(), vec![Some(0.), Some(0.), Some(0.)]),
114-
("blindern2".to_string(), vec![Some(0.), Some(1.), Some(1.)]),
115-
// This one passes because although the diffsum is enough to be spike,
116-
// the diffdiff is big enough to override it
117-
("blindern3".to_string(), vec![Some(0.), Some(1.6), Some(1.)]),
118-
(
119-
"blindern4".to_string(),
120-
vec![Some(0.), Some(-1.1), Some(0.)]
121-
),
122-
("blindern5".to_string(), vec![Some(1.), None, Some(1.)]),
123-
("blindern6".to_string(), vec![None, Some(1.), Some(1.)]),
124-
],
125140
),
126141
1.,
127142
)
128143
.unwrap(),
129144
vec![
130-
("blindern1".to_string(), vec![Flag::Pass]),
131-
("blindern2".to_string(), vec![Flag::Pass]),
132-
("blindern3".to_string(), vec![Flag::Pass]),
133-
("blindern4".to_string(), vec![Flag::Fail]),
134-
("blindern5".to_string(), vec![Flag::DataMissing]),
135-
("blindern6".to_string(), vec![Flag::DataMissing]),
145+
Timeseries {
146+
tag: "blindern1".to_string(),
147+
values: vec![Flag::Pass]
148+
},
149+
Timeseries {
150+
tag: "blindern2".to_string(),
151+
values: vec![Flag::Pass]
152+
},
153+
Timeseries {
154+
tag: "blindern3".to_string(),
155+
values: vec![Flag::Pass]
156+
},
157+
Timeseries {
158+
tag: "blindern4".to_string(),
159+
values: vec![Flag::Fail]
160+
},
161+
Timeseries {
162+
tag: "blindern5".to_string(),
163+
values: vec![Flag::DataMissing]
164+
},
165+
Timeseries {
166+
tag: "blindern6".to_string(),
167+
values: vec![Flag::DataMissing]
168+
},
136169
]
137170
)
138171
}

src/checks/series/step_check.rs

+50-23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{DataCache, Error, Flag};
1+
use crate::{util::Timeseries, DataCache, Error, Flag};
22

33
/// Number of leading values a [`DataCache`] must contain to QC all its
44
/// intended values with step check
@@ -33,11 +33,11 @@ pub fn step_check(data: &[Option<f32>; 2], max: f32) -> Flag {
3333
///
3434
/// - data is invalid
3535
/// - data has `num_leading_points` <= 1
36-
pub fn step_check_cache(cache: &DataCache, max: f32) -> Result<Vec<(String, Vec<Flag>)>, Error> {
36+
pub fn step_check_cache(cache: &DataCache, max: f32) -> Result<Vec<Timeseries<Flag>>, Error> {
3737
let num_series = cache.data.len();
3838
let mut result_vec = Vec::with_capacity(num_series);
3939
let series_len = match cache.data.first() {
40-
Some(ts) => ts.1.len(),
40+
Some(ts) => ts.values.len(),
4141
// if this is none, the cache is empty, so we can just return an empty result vec
4242
None => return Ok(result_vec),
4343
};
@@ -52,17 +52,17 @@ pub fn step_check_cache(cache: &DataCache, max: f32) -> Result<Vec<(String, Vec<
5252
}
5353

5454
for i in 0..num_series {
55-
let trimmed = &cache.data[i].1
55+
let trimmed = &cache.data[i].values
5656
[leading_trim as usize..(series_len - cache.num_trailing_points as usize)];
5757

5858
let windows = trimmed.windows(1 + STEP_LEADING_PER_RUN as usize);
5959

60-
result_vec.push((
61-
cache.data[i].0.clone(),
62-
windows
60+
result_vec.push(Timeseries {
61+
tag: cache.data[i].tag.clone(),
62+
values: windows
6363
.map(|data| step_check(data.try_into().unwrap(), max))
6464
.collect(),
65-
))
65+
})
6666
}
6767

6868
Ok(result_vec)
@@ -78,33 +78,60 @@ mod tests {
7878
assert_eq!(
7979
step_check_cache(
8080
&DataCache::new(
81+
vec![
82+
Timeseries {
83+
tag: "blindern1".to_string(),
84+
values: vec![Some(0.), Some(0.), None]
85+
},
86+
Timeseries {
87+
tag: "blindern2".to_string(),
88+
values: vec![Some(0.), Some(1.), Some(1.)]
89+
},
90+
Timeseries {
91+
tag: "blindern3".to_string(),
92+
values: vec![Some(0.), Some(-1.1), Some(1.)]
93+
},
94+
Timeseries {
95+
tag: "blindern4".to_string(),
96+
values: vec![Some(1.), None, Some(1.)]
97+
},
98+
Timeseries {
99+
tag: "blindern5".to_string(),
100+
values: vec![None, Some(1.), Some(1.)]
101+
},
102+
],
81103
vec![0., 1., 2., 3.],
82104
vec![0., 1., 2., 3.],
83105
vec![0., 0., 0., 0.],
84106
crate::util::Timestamp(0),
85107
RelativeDuration::minutes(10),
86108
1,
87109
1,
88-
vec![
89-
("blindern1".to_string(), vec![Some(0.), Some(0.), None]),
90-
("blindern2".to_string(), vec![Some(0.), Some(1.), Some(1.)]),
91-
(
92-
"blindern3".to_string(),
93-
vec![Some(0.), Some(-1.1), Some(1.)]
94-
),
95-
("blindern4".to_string(), vec![Some(1.), None, Some(1.)]),
96-
("blindern5".to_string(), vec![None, Some(1.), Some(1.)]),
97-
],
98110
),
99111
1.,
100112
)
101113
.unwrap(),
102114
vec![
103-
("blindern1".to_string(), vec![Flag::Pass]),
104-
("blindern2".to_string(), vec![Flag::Pass]),
105-
("blindern3".to_string(), vec![Flag::Fail]),
106-
("blindern4".to_string(), vec![Flag::DataMissing]),
107-
("blindern5".to_string(), vec![Flag::DataMissing]),
115+
Timeseries {
116+
tag: "blindern1".to_string(),
117+
values: vec![Flag::Pass]
118+
},
119+
Timeseries {
120+
tag: "blindern2".to_string(),
121+
values: vec![Flag::Pass]
122+
},
123+
Timeseries {
124+
tag: "blindern3".to_string(),
125+
values: vec![Flag::Fail]
126+
},
127+
Timeseries {
128+
tag: "blindern4".to_string(),
129+
values: vec![Flag::DataMissing]
130+
},
131+
Timeseries {
132+
tag: "blindern5".to_string(),
133+
values: vec![Flag::DataMissing]
134+
},
108135
]
109136
)
110137
}

0 commit comments

Comments
 (0)