avila_telemetry/
time_series.rs1use crate::{Result, TelemetryError};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct TimeSeries {
10 pub values: Vec<f64>,
12 pub timestamps: Option<Vec<DateTime<Utc>>>,
14 pub name: Option<String>,
16}
17
18impl TimeSeries {
19 pub fn new(values: Vec<f64>) -> Self {
21 Self {
22 values,
23 timestamps: None,
24 name: None,
25 }
26 }
27
28 pub fn with_timestamps(values: Vec<f64>, timestamps: Vec<DateTime<Utc>>) -> Result<Self> {
30 if values.len() != timestamps.len() {
31 return Err(TelemetryError::InvalidData(
32 "Values and timestamps must have the same length".to_string(),
33 ));
34 }
35 Ok(Self {
36 values,
37 timestamps: Some(timestamps),
38 name: None,
39 })
40 }
41
42 pub fn with_name(mut self, name: impl Into<String>) -> Self {
44 self.name = Some(name.into());
45 self
46 }
47
48 pub fn len(&self) -> usize {
50 self.values.len()
51 }
52
53 pub fn is_empty(&self) -> bool {
55 self.values.is_empty()
56 }
57
58 pub fn moving_average(&self, window: usize) -> Result<Vec<f64>> {
60 if window == 0 {
61 return Err(TelemetryError::InvalidParameter(
62 "Window size must be greater than 0".to_string(),
63 ));
64 }
65
66 if window > self.values.len() {
67 return Err(TelemetryError::InsufficientData(format!(
68 "Window size {} is larger than data length {}",
69 window,
70 self.values.len()
71 )));
72 }
73
74 let mut result = Vec::with_capacity(self.values.len() - window + 1);
75
76 for i in 0..=(self.values.len() - window) {
77 let sum: f64 = self.values[i..i + window].iter().sum();
78 result.push(sum / window as f64);
79 }
80
81 Ok(result)
82 }
83
84 pub fn exponential_moving_average(&self, alpha: f64) -> Result<Vec<f64>> {
86 if alpha <= 0.0 || alpha > 1.0 {
87 return Err(TelemetryError::InvalidParameter(
88 "Alpha must be between 0 and 1".to_string(),
89 ));
90 }
91
92 if self.is_empty() {
93 return Err(TelemetryError::InsufficientData(
94 "Cannot calculate EMA on empty series".to_string(),
95 ));
96 }
97
98 let mut result = Vec::with_capacity(self.values.len());
99 result.push(self.values[0]);
100
101 for i in 1..self.values.len() {
102 let ema = alpha * self.values[i] + (1.0 - alpha) * result[i - 1];
103 result.push(ema);
104 }
105
106 Ok(result)
107 }
108
109 pub fn diff(&self) -> Vec<f64> {
111 if self.values.len() < 2 {
112 return Vec::new();
113 }
114
115 self.values.windows(2).map(|w| w[1] - w[0]).collect()
116 }
117
118 pub fn pct_change(&self) -> Vec<f64> {
120 if self.values.len() < 2 {
121 return Vec::new();
122 }
123
124 self.values
125 .windows(2)
126 .map(|w| {
127 if w[0] == 0.0 {
128 0.0
129 } else {
130 (w[1] - w[0]) / w[0]
131 }
132 })
133 .collect()
134 }
135
136 pub fn slice(&self, start: usize, end: usize) -> Result<TimeSeries> {
138 if start >= end || end > self.values.len() {
139 return Err(TelemetryError::InvalidParameter(
140 "Invalid slice indices".to_string(),
141 ));
142 }
143
144 let values = self.values[start..end].to_vec();
145 let timestamps = self.timestamps.as_ref().map(|ts| ts[start..end].to_vec());
146
147 Ok(TimeSeries {
148 values,
149 timestamps,
150 name: self.name.clone(),
151 })
152 }
153
154 pub fn statistics(&self) -> Statistics {
156 Statistics::from_values(&self.values)
157 }
158}
159
160#[derive(Debug, Clone)]
162pub struct Statistics {
163 pub mean: f64,
164 pub median: f64,
165 pub std_dev: f64,
166 pub min: f64,
167 pub max: f64,
168 pub count: usize,
169}
170
171impl Statistics {
172 pub fn from_values(values: &[f64]) -> Self {
173 let count = values.len();
174
175 if count == 0 {
176 return Self {
177 mean: 0.0,
178 median: 0.0,
179 std_dev: 0.0,
180 min: 0.0,
181 max: 0.0,
182 count: 0,
183 };
184 }
185
186 let mean = values.iter().sum::<f64>() / count as f64;
187
188 let mut sorted = values.to_vec();
189 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
190
191 let median = if count.is_multiple_of(2) {
192 (sorted[count / 2 - 1] + sorted[count / 2]) / 2.0
193 } else {
194 sorted[count / 2]
195 };
196
197 let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / count as f64;
198
199 let std_dev = variance.sqrt();
200 let min = *sorted.first().unwrap();
201 let max = *sorted.last().unwrap();
202
203 Self {
204 mean,
205 median,
206 std_dev,
207 min,
208 max,
209 count,
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_moving_average() {
220 let ts = TimeSeries::new(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
221 let ma = ts.moving_average(3).unwrap();
222 assert_eq!(ma, vec![2.0, 3.0, 4.0]);
223 }
224
225 #[test]
226 fn test_ema() {
227 let ts = TimeSeries::new(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
228 let ema = ts.exponential_moving_average(0.5).unwrap();
229 assert_eq!(ema.len(), 5);
230 }
231
232 #[test]
233 fn test_statistics() {
234 let ts = TimeSeries::new(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
235 let stats = ts.statistics();
236 assert_eq!(stats.mean, 3.0);
237 assert_eq!(stats.median, 3.0);
238 assert_eq!(stats.min, 1.0);
239 assert_eq!(stats.max, 5.0);
240 }
241}