konnektoren_core/analytics/
trend.rs

1use strum_macros::Display;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Display)]
4pub enum Trend {
5    Improving,
6    #[default]
7    Stable,
8    Declining,
9}
10
11impl Trend {
12    const EPSILON: f64 = 0.0001;
13
14    pub fn from_value(value: f64) -> Self {
15        if value > Self::EPSILON {
16            Trend::Improving
17        } else if value < -Self::EPSILON {
18            Trend::Declining
19        } else {
20            Trend::Stable
21        }
22    }
23}
24
25impl PartialOrd for Trend {
26    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
27        match (self, other) {
28            // Improving is greater than everything except itself
29            (Trend::Improving, Trend::Improving) => Some(std::cmp::Ordering::Equal),
30            (Trend::Improving, _) => Some(std::cmp::Ordering::Greater),
31
32            // Stable is less than Improving, greater than Declining
33            (Trend::Stable, Trend::Improving) => Some(std::cmp::Ordering::Less),
34            (Trend::Stable, Trend::Stable) => Some(std::cmp::Ordering::Equal),
35            (Trend::Stable, Trend::Declining) => Some(std::cmp::Ordering::Greater),
36
37            // Declining is less than everything except itself
38            (Trend::Declining, Trend::Declining) => Some(std::cmp::Ordering::Equal),
39            (Trend::Declining, _) => Some(std::cmp::Ordering::Less),
40        }
41    }
42}
43
44impl Ord for Trend {
45    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
46        self.partial_cmp(other).unwrap()
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn test_trend_from_value() {
56        assert_eq!(Trend::from_value(1.5), Trend::Improving);
57        assert_eq!(Trend::from_value(-1.5), Trend::Declining);
58        assert_eq!(Trend::from_value(0.0), Trend::Stable);
59        assert_eq!(Trend::from_value(0.001), Trend::Improving);
60        assert_eq!(Trend::from_value(-0.001), Trend::Declining);
61    }
62
63    #[test]
64    fn test_trend_ordering() {
65        // Test Improving comparisons
66        assert!(Trend::Improving > Trend::Stable);
67        assert!(Trend::Improving > Trend::Declining);
68        assert!(Trend::Improving == Trend::Improving);
69
70        // Test Stable comparisons
71        assert!(Trend::Stable < Trend::Improving);
72        assert!(Trend::Stable == Trend::Stable);
73        assert!(Trend::Stable > Trend::Declining);
74
75        // Test Declining comparisons
76        assert!(Trend::Declining < Trend::Improving);
77        assert!(Trend::Declining < Trend::Stable);
78        assert!(Trend::Declining == Trend::Declining);
79    }
80
81    #[test]
82    fn test_trend_default() {
83        assert_eq!(Trend::default(), Trend::Stable);
84    }
85
86    #[test]
87    fn test_trend_sorting() {
88        let mut trends = vec![
89            Trend::Declining,
90            Trend::Improving,
91            Trend::Stable,
92            Trend::Improving,
93            Trend::Declining,
94        ];
95        trends.sort();
96
97        assert_eq!(
98            trends,
99            vec![
100                Trend::Declining,
101                Trend::Declining,
102                Trend::Stable,
103                Trend::Improving,
104                Trend::Improving,
105            ]
106        );
107    }
108
109    #[test]
110    fn test_trend_clone_and_copy() {
111        let trend = Trend::Improving;
112        let cloned = trend;
113        assert_eq!(trend, cloned);
114
115        let copied = trend;
116        assert_eq!(trend, copied);
117    }
118
119    #[test]
120    fn test_trend_debug_format() {
121        assert_eq!(format!("{:?}", Trend::Improving), "Improving");
122        assert_eq!(format!("{:?}", Trend::Stable), "Stable");
123        assert_eq!(format!("{:?}", Trend::Declining), "Declining");
124    }
125
126    #[test]
127    fn test_trend_hash() {
128        use std::collections::HashSet;
129
130        let mut set = HashSet::new();
131        set.insert(Trend::Improving);
132        set.insert(Trend::Stable);
133        set.insert(Trend::Declining);
134        set.insert(Trend::Improving); // Duplicate
135
136        assert_eq!(set.len(), 3);
137        assert!(set.contains(&Trend::Improving));
138        assert!(set.contains(&Trend::Stable));
139        assert!(set.contains(&Trend::Declining));
140    }
141
142    #[test]
143    fn test_trend_partial_ord() {
144        // Test all possible combinations
145        let trends = [Trend::Improving, Trend::Stable, Trend::Declining];
146
147        for &t1 in &trends {
148            for &t2 in &trends {
149                // Ensure partial_cmp always returns Some value
150                assert!(t1.partial_cmp(&t2).is_some());
151
152                // Test reflexivity
153                if t1 == t2 {
154                    assert_eq!(t1.partial_cmp(&t2), Some(std::cmp::Ordering::Equal));
155                }
156
157                // Test transitivity
158                if t1 > t2 {
159                    assert_eq!(t1.partial_cmp(&t2), Some(std::cmp::Ordering::Greater));
160                } else if t1 < t2 {
161                    assert_eq!(t1.partial_cmp(&t2), Some(std::cmp::Ordering::Less));
162                }
163            }
164        }
165    }
166}