anomalyzer-ts 0.1.0

Probabilistic anomaly detection for time-series data
Documentation
// src/async_anomalyzer.rs
#![cfg(feature = "async")]

use std::sync::Arc;
use tokio::sync::Mutex;
use crate::{Anomalyzer, AnomalyzerConf};

/// Async wrapper around [`Anomalyzer`].
///
/// CPU-bound work is offloaded to [`tokio::task::spawn_blocking`] so it
/// never blocks the async executor.
///
/// # Example
/// ```rust,no_run
/// # #[cfg(feature = "async")]
/// # #[tokio::main]
/// # async fn main() {
/// use anomalyzer_ts::{AnomalyzerConf, async_anomalyzer::AsyncAnomalyzer};
///
/// let conf = AnomalyzerConf {
///     active_size: 1,
///     n_seasons: 4,
///     methods: vec!["magnitude".into(), "highrank".into()],
///     ..Default::default()
/// };
///
/// let detector = AsyncAnomalyzer::new(conf, Some(vec![10.0, 10.1, 10.2, 10.0]))
///     .await
///     .unwrap();
///
/// let prob = detector.push(15.0).await;
/// println!("Anomaly probability: {prob:.3}");
/// # }
/// ```
pub struct AsyncAnomalyzer {
    inner: Arc<Mutex<Anomalyzer>>,
}

impl AsyncAnomalyzer {
    pub async fn new(
        conf: AnomalyzerConf,
        initial_data: Option<Vec<f64>>,
    ) -> Result<Self, String> {
        let inner = tokio::task::spawn_blocking(move || {
            Anomalyzer::new(conf, initial_data)
        })
        .await
        .map_err(|e| format!("task join error: {e}"))??;

        Ok(Self {
            inner: Arc::new(Mutex::new(inner)),
        })
    }

    /// Push a new value and return its anomaly probability.
    pub async fn push(&self, value: f64) -> f64 {
        let inner = Arc::clone(&self.inner);
        tokio::task::spawn_blocking(move || {
            // `blocking_lock` is correct here — we're already on a blocking thread
            let mut guard = inner.blocking_lock();
            guard.push(value)
        })
        .await
        .unwrap_or(0.0)
    }

    /// Re-evaluate the current windows without pushing a new value.
    pub async fn eval(&self) -> f64 {
        let inner = Arc::clone(&self.inner);
        tokio::task::spawn_blocking(move || {
            let guard = inner.blocking_lock();
            guard.eval()
        })
        .await
        .unwrap_or(0.0)
    }
}