use crate::drift::detector::DriftLevel;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DriftAction {
#[default]
NotifyOnly,
ReduceConfidence,
ResetModel,
ResetPreprocessor,
ReplaceWithBaseline,
IncreaseAdaptationRate,
}
impl DriftAction {
pub const fn as_str(&self) -> &'static str {
match self {
DriftAction::NotifyOnly => "notify_only",
DriftAction::ReduceConfidence => "reduce_confidence",
DriftAction::ResetModel => "reset_model",
DriftAction::ResetPreprocessor => "reset_preprocessor",
DriftAction::ReplaceWithBaseline => "replace_with_baseline",
DriftAction::IncreaseAdaptationRate => "increase_adaptation_rate",
}
}
pub const fn is_destructive(self) -> bool {
matches!(
self,
DriftAction::ResetModel
| DriftAction::ResetPreprocessor
| DriftAction::ReplaceWithBaseline
| DriftAction::IncreaseAdaptationRate
)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DriftEvent {
pub sample_index: u64,
pub level: DriftLevel,
pub action: DriftAction,
pub detector_value: f64,
}
impl DriftEvent {
pub const fn new(
sample_index: u64,
level: DriftLevel,
action: DriftAction,
detector_value: f64,
) -> Self {
Self {
sample_index,
level,
action,
detector_value,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn action_as_str() {
assert_eq!(DriftAction::NotifyOnly.as_str(), "notify_only");
assert_eq!(DriftAction::ReduceConfidence.as_str(), "reduce_confidence");
assert_eq!(DriftAction::ResetModel.as_str(), "reset_model");
assert_eq!(
DriftAction::ResetPreprocessor.as_str(),
"reset_preprocessor"
);
assert_eq!(
DriftAction::ReplaceWithBaseline.as_str(),
"replace_with_baseline"
);
assert_eq!(
DriftAction::IncreaseAdaptationRate.as_str(),
"increase_adaptation_rate"
);
}
#[test]
fn action_is_destructive() {
assert!(!DriftAction::NotifyOnly.is_destructive());
assert!(!DriftAction::ReduceConfidence.is_destructive());
assert!(DriftAction::ResetModel.is_destructive());
assert!(DriftAction::ResetPreprocessor.is_destructive());
assert!(DriftAction::ReplaceWithBaseline.is_destructive());
assert!(DriftAction::IncreaseAdaptationRate.is_destructive());
}
#[test]
fn action_default_is_notify_only() {
assert_eq!(DriftAction::default(), DriftAction::NotifyOnly);
}
#[test]
fn drift_event_construction() {
let event = DriftEvent::new(42, DriftLevel::Drift, DriftAction::ResetModel, 1.23);
assert_eq!(event.sample_index, 42);
assert_eq!(event.level, DriftLevel::Drift);
assert_eq!(event.action, DriftAction::ResetModel);
assert!((event.detector_value - 1.23).abs() < 1e-12);
}
#[cfg(feature = "serde")]
#[test]
fn action_serde_roundtrip() {
for action in [
DriftAction::NotifyOnly,
DriftAction::ReduceConfidence,
DriftAction::ResetModel,
DriftAction::ResetPreprocessor,
DriftAction::ReplaceWithBaseline,
DriftAction::IncreaseAdaptationRate,
] {
let json = serde_json::to_string(&action).unwrap();
let restored: DriftAction = serde_json::from_str(&json).unwrap();
assert_eq!(restored, action);
}
}
#[cfg(feature = "serde")]
#[test]
fn drift_event_serde_roundtrip() {
let event = DriftEvent::new(10, DriftLevel::Warning, DriftAction::ReduceConfidence, 1.5);
let json = serde_json::to_string(&event).unwrap();
let restored: DriftEvent = serde_json::from_str(&json).unwrap();
assert_eq!(restored.sample_index, 10);
assert_eq!(restored.level, DriftLevel::Warning);
assert_eq!(restored.action, DriftAction::ReduceConfidence);
assert!((restored.detector_value - 1.5).abs() < 1e-12);
}
}