1use crate::drift::detector::DriftLevel;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub enum DriftAction {
17 #[default]
21 NotifyOnly,
22 ReduceConfidence,
26 ResetModel,
30 ResetPreprocessor,
34 ReplaceWithBaseline,
37 IncreaseAdaptationRate,
41}
42
43impl DriftAction {
44 pub const fn as_str(&self) -> &'static str {
50 match self {
51 DriftAction::NotifyOnly => "notify_only",
52 DriftAction::ReduceConfidence => "reduce_confidence",
53 DriftAction::ResetModel => "reset_model",
54 DriftAction::ResetPreprocessor => "reset_preprocessor",
55 DriftAction::ReplaceWithBaseline => "replace_with_baseline",
56 DriftAction::IncreaseAdaptationRate => "increase_adaptation_rate",
57 }
58 }
59
60 pub const fn is_destructive(self) -> bool {
65 matches!(
66 self,
67 DriftAction::ResetModel
68 | DriftAction::ResetPreprocessor
69 | DriftAction::ReplaceWithBaseline
70 | DriftAction::IncreaseAdaptationRate
71 )
72 }
73}
74
75#[derive(Debug, Clone)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81pub struct DriftEvent {
82 pub sample_index: u64,
84 pub level: DriftLevel,
86 pub action: DriftAction,
88 pub detector_value: f64,
91}
92
93impl DriftEvent {
94 pub const fn new(
96 sample_index: u64,
97 level: DriftLevel,
98 action: DriftAction,
99 detector_value: f64,
100 ) -> Self {
101 Self {
102 sample_index,
103 level,
104 action,
105 detector_value,
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn action_as_str() {
116 assert_eq!(DriftAction::NotifyOnly.as_str(), "notify_only");
117 assert_eq!(DriftAction::ReduceConfidence.as_str(), "reduce_confidence");
118 assert_eq!(DriftAction::ResetModel.as_str(), "reset_model");
119 assert_eq!(
120 DriftAction::ResetPreprocessor.as_str(),
121 "reset_preprocessor"
122 );
123 assert_eq!(
124 DriftAction::ReplaceWithBaseline.as_str(),
125 "replace_with_baseline"
126 );
127 assert_eq!(
128 DriftAction::IncreaseAdaptationRate.as_str(),
129 "increase_adaptation_rate"
130 );
131 }
132
133 #[test]
134 fn action_is_destructive() {
135 assert!(!DriftAction::NotifyOnly.is_destructive());
136 assert!(!DriftAction::ReduceConfidence.is_destructive());
137 assert!(DriftAction::ResetModel.is_destructive());
138 assert!(DriftAction::ResetPreprocessor.is_destructive());
139 assert!(DriftAction::ReplaceWithBaseline.is_destructive());
140 assert!(DriftAction::IncreaseAdaptationRate.is_destructive());
141 }
142
143 #[test]
144 fn action_default_is_notify_only() {
145 assert_eq!(DriftAction::default(), DriftAction::NotifyOnly);
146 }
147
148 #[test]
149 fn drift_event_construction() {
150 let event = DriftEvent::new(42, DriftLevel::Drift, DriftAction::ResetModel, 1.23);
151 assert_eq!(event.sample_index, 42);
152 assert_eq!(event.level, DriftLevel::Drift);
153 assert_eq!(event.action, DriftAction::ResetModel);
154 assert!((event.detector_value - 1.23).abs() < 1e-12);
155 }
156
157 #[cfg(feature = "serde")]
158 #[test]
159 fn action_serde_roundtrip() {
160 for action in [
161 DriftAction::NotifyOnly,
162 DriftAction::ReduceConfidence,
163 DriftAction::ResetModel,
164 DriftAction::ResetPreprocessor,
165 DriftAction::ReplaceWithBaseline,
166 DriftAction::IncreaseAdaptationRate,
167 ] {
168 let json = serde_json::to_string(&action).unwrap();
169 let restored: DriftAction = serde_json::from_str(&json).unwrap();
170 assert_eq!(restored, action);
171 }
172 }
173
174 #[cfg(feature = "serde")]
175 #[test]
176 fn drift_event_serde_roundtrip() {
177 let event = DriftEvent::new(10, DriftLevel::Warning, DriftAction::ReduceConfidence, 1.5);
178 let json = serde_json::to_string(&event).unwrap();
179 let restored: DriftEvent = serde_json::from_str(&json).unwrap();
180 assert_eq!(restored.sample_index, 10);
181 assert_eq!(restored.level, DriftLevel::Warning);
182 assert_eq!(restored.action, DriftAction::ReduceConfidence);
183 assert!((restored.detector_value - 1.5).abs() < 1e-12);
184 }
185}