anomalyzer-ts 0.1.0

Probabilistic anomaly detection for time-series data
Documentation
//! examples/persistent_basic.rs
//!
//! Demonstrates WAL + snapshot persistence across two simulated "process runs".
//!
//! Run 1: seeds the detector with baseline data and pushes a spike.
//! Run 2: restores history from disk and confirms the spike is still detected.
//!
//! Run with:
//!   cargo run --example persistent_basic --features persist

use anomalyzer_ts::{AnomalyzerConf, PersistentAnomalyzer};
use std::path::Path;

const DATA_DIR: &str = "/tmp/anomalyzer-demo";

fn conf() -> AnomalyzerConf {
    AnomalyzerConf {
        active_size: 1,
        n_seasons: 4,
        methods: vec!["magnitude".to_string(), "highrank".to_string()],
        ..Default::default()
    }
}

/// Simulates the first process run: build up a baseline then push a spike.
fn run_one() {
    println!("── Run 1 (first process) ──────────────────────────");

    let mut detector = PersistentAnomalyzer::open(DATA_DIR, conf())
        .expect("failed to open persistence dir");

    // Warm up with normal baseline values.
    let baseline = [10.0f64, 10.1, 10.2, 10.0, 10.15];
    for v in baseline {
        let prob = detector.push(v).unwrap();
        println!("  baseline {v:>6.2}  →  prob {prob:.3}");
    }

    // Push a spike — should be flagged.
    let prob = detector.push(18.5).unwrap();
    println!("  spike   {:>6.2}  →  prob {prob:.3} {}", 18.5, if prob > 0.7 { "⚠️  ANOMALY" } else { "" });

    // Compact on clean shutdown (optional but speeds up next startup).
    detector.flush().unwrap();

    println!("  WAL entries after flush: {}", detector.pending_wal_entries());
    println!("  history saved to {DATA_DIR}\n");
}

/// Simulates the second process run: history is restored from disk.
fn run_two() {
    println!("── Run 2 (process restart) ────────────────────────");

    let mut detector = PersistentAnomalyzer::open(DATA_DIR, conf())
        .expect("failed to open persistence dir");

    // The detector already knows about the spike — evaluate without pushing.
    let prob = detector.eval();
    println!("  restored state  →  prob {prob:.3} (spike still in window)");

    // Continue with new values; history context is intact.
    let new_values = [10.2f64, 10.0, 10.3];
    println!("  pushing new values after restart:");
    for v in new_values {
        let prob = detector.push(v).unwrap();
        println!("    {v:>6.2}  →  prob {prob:.3}");
    }

    println!("\n  WAL size: {} bytes", detector.wal_size_bytes().unwrap());
}

fn main() {
    // Clean slate for a reproducible demo.
    if Path::new(DATA_DIR).exists() {
        std::fs::remove_dir_all(DATA_DIR).unwrap();
    }

    run_one();
    run_two();
}