entrenar/dashboard/
trend.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub enum Trend {
8 Rising,
10 Falling,
12 Stable,
14}
15
16impl Trend {
17 pub fn from_values(values: &[f64]) -> Self {
21 if values.len() < 2 {
22 return Self::Stable;
23 }
24
25 let n = values.len() as f64;
27 let sum_x: f64 = (0..values.len()).map(|i| i as f64).sum();
28 let sum_y: f64 = values.iter().sum();
29 let sum_xy: f64 = values.iter().enumerate().map(|(i, &y)| i as f64 * y).sum();
30 let sum_x2: f64 = (0..values.len()).map(|i| (i as f64).powi(2)).sum();
31
32 let denominator = n * sum_x2 - sum_x.powi(2);
33 if denominator.abs() < f64::EPSILON {
34 return Self::Stable;
35 }
36
37 let slope = (n * sum_xy - sum_x * sum_y) / denominator;
38
39 let mean = sum_y / n;
41 if mean.abs() < f64::EPSILON {
42 return Self::Stable;
43 }
44
45 let relative_slope = slope / mean;
46
47 const THRESHOLD: f64 = 0.05;
49
50 if relative_slope > THRESHOLD {
51 Self::Rising
52 } else if relative_slope < -THRESHOLD {
53 Self::Falling
54 } else {
55 Self::Stable
56 }
57 }
58
59 pub fn emoji(&self) -> &'static str {
61 match self {
62 Self::Rising => "↑",
63 Self::Falling => "↓",
64 Self::Stable => "→",
65 }
66 }
67}
68
69impl std::fmt::Display for Trend {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 match self {
72 Self::Rising => write!(f, "rising"),
73 Self::Falling => write!(f, "falling"),
74 Self::Stable => write!(f, "stable"),
75 }
76 }
77}