#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::sync::Arc;
use chronix::prelude::*;
use chronix::{AnomalyConfig, Chronix, fields, tags};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let config = ChronixConfig::builder().data_dir(dir.path()).build()?;
let db = Arc::new(Chronix::open(config)?);
let base_ts = 1_700_000_000_000_000_000_i64;
let interval_ns = 10_000_000_000_i64;
let key = SeriesKey::new(
"temperature",
tags! { "sensor" => "tank-1", "zone" => "east" },
)?;
let n = 200;
let mut points = Vec::with_capacity(n + 5);
for i in 0..n {
let t = i as f64;
let value = 22.0 + (t * 0.3).sin() * 1.5 + (t * 0.7).cos() * 0.8;
points.push(Point::new(
key.clone(),
fields! { "value" => value },
base_ts + (i as i64) * interval_ns,
)?);
}
let anomaly_offsets = [n, n + 1, n + 2];
let anomaly_values = [55.0, -10.0, 80.0]; for (j, &val) in anomaly_values.iter().enumerate() {
points.push(Point::new(
key.clone(),
fields! { "value" => val },
base_ts + anomaly_offsets[j] as i64 * interval_ns,
)?);
}
for k in 0..2 {
let i = n + 3 + k;
points.push(Point::new(
key.clone(),
fields! { "value" => 22.5 },
base_ts + (i as i64) * interval_ns,
)?);
}
assert!(
db.insert_batch(&points)?.is_complete(),
"insert was partial"
);
let total = points.len();
println!("✅ Seeded {total} points ({n} normal + 3 spikes + 2 recovery)\n");
let start = base_ts;
let end = base_ts + (total as i64) * interval_ns;
let tag_pairs: &[(&str, &str)] = &[("sensor", "tank-1"), ("zone", "east")];
let methods: &[(&str, f64, Option<usize>)] = &[
("zscore", 2.5, None),
("modified_zscore", 2.5, None),
("iqr", 1.5, None),
("dynamic_threshold", 3.0, Some(30)),
("moving_average", 3.0, Some(20)),
("forecast_residual", 3.0, None),
];
for (i, &(method, threshold, window)) in methods.iter().enumerate() {
println!(
"─── {}. {} (threshold={threshold}{}) ───",
i + 1,
method,
window.map_or(String::new(), |w| format!(", window={w}"))
);
let scores = db.detect_anomalies(
"temperature",
"value",
tag_pairs,
start,
end,
Some(AnomalyConfig {
method: Some(method.into()),
threshold,
window_size: window,
}),
)?;
let anomalies: Vec<_> = scores.iter().filter(|s| s.is_anomaly).collect();
println!(" Scored points : {}", scores.len());
println!(" Anomalies : {}", anomalies.len());
for a in &anomalies {
println!(
" ⚠️ ts={} value={:>7.2} score={:.3} method={:?} | {}",
a.timestamp, a.value, a.score, a.method, a.details
);
}
println!();
}
db.close()?;
println!("✅ Done");
Ok(())
}