use std::env;
use std::fs;
use std::process;
use struktura::{analyze, health_check, HealthVerdict, LawQuality, StructuralLaw};
const NORMAL_SAMPLES: &str = include_str!("../../data/normal_sample.csv");
const FAULT_SAMPLES: &str = include_str!("../../data/fault_sample.csv");
fn read_csv(path: &str) -> Vec<f64> {
let content = fs::read_to_string(path).unwrap_or_else(|e| {
eprintln!("Error reading {}: {}", path, e);
process::exit(1);
});
parse_values(&content)
}
fn read_stdin() -> Vec<f64> {
use std::io::Read;
let mut buf = String::new();
std::io::stdin().read_to_string(&mut buf).unwrap_or_else(|e| {
eprintln!("Error reading stdin: {}", e);
process::exit(1);
});
parse_values(&buf)
}
fn parse_values(content: &str) -> Vec<f64> {
content
.lines()
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.filter_map(|l| {
let s = if let Some(pos) = l.rfind(',') {
&l[pos + 1..]
} else {
l
};
s.trim().parse::<f64>().ok()
})
.collect()
}
fn quality_str(q: LawQuality) -> &'static str {
match q {
LawQuality::Exact => "EXACT",
LawQuality::Strong => "STRONG",
LawQuality::Good => "GOOD",
LawQuality::Approx => "APPROX",
LawQuality::Abstain => "ABSTAIN",
LawQuality::Insufficient => "INSUFFICIENT",
}
}
fn verdict_color(v: HealthVerdict) -> (&'static str, &'static str) {
match v {
HealthVerdict::Healthy => ("\x1b[32m", "HEALTHY"),
HealthVerdict::Watch => ("\x1b[33m", "WATCH"),
HealthVerdict::Warning => ("\x1b[33;1m", "WARNING"),
HealthVerdict::Critical => ("\x1b[31;1m", "CRITICAL"),
}
}
fn alpha_bar(alpha: f64, width: usize) -> String {
let clamped = alpha.clamp(0.0, 1.5);
let filled = ((clamped / 1.5) * width as f64) as usize;
let empty = width.saturating_sub(filled);
format!("{}{}", "#".repeat(filled), ".".repeat(empty))
}
fn print_visual(label: &str, law: &StructuralLaw, baseline: Option<f64>) {
let bar = alpha_bar(law.dfa.alpha, 30);
let q = quality_str(law.quality);
println!(" {} {:<20} alpha={:.3} R2={:.3} [{}]", bar, label, law.dfa.alpha, law.dfa.r_squared, q);
if let Some(b) = baseline {
let verdict = health_check(law, b);
let shift = law.dfa.alpha - b;
let (color, label) = verdict_color(verdict);
println!(" {:>30} shift={:+.3} \x1b[1m{}{}\x1b[0m", "", shift, color, label);
}
}
fn print_law_detail(law: &StructuralLaw) {
println!(" Samples: {}", law.n);
println!(" DFA alpha: {:.3} (R2={:.4})", law.dfa.alpha, law.dfa.r_squared);
println!(" Hurst H: {:.3}", law.hurst);
println!(" Kurtosis: {:.1}", law.kurtosis);
println!(" Mean: {:.4} Std: {:.4}", law.mean, law.std_dev);
println!(" Quality: {}", quality_str(law.quality));
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!();
println!(" \x1b[1mstruktura\x1b[0m - predict failure before it happens");
println!(" the only Rust-native DFA anomaly detector");
println!();
println!(" COMMANDS:");
println!(" struktura demo Run builtin bearing fault demo");
println!(" struktura check <file.csv> Analyze a signal");
println!(" struktura check <file.csv> --baseline N Compare against baseline");
println!(" struktura compare <a.csv> <b.csv> Compare two signals");
println!(" struktura bench Full benchmark with all fault types");
println!();
println!(" INPUT: CSV or one-value-per-line. Uses last column.");
println!(" MORE: https://github.com/koscak-labs/struktura");
println!();
process::exit(0);
}
match args[1].as_str() {
"demo" => cmd_demo(),
"check" => cmd_check(&args),
"compare" => cmd_compare(&args),
"bench" => cmd_bench(),
"version" => println!("struktura {}", env!("CARGO_PKG_VERSION")),
other => {
eprintln!("Unknown command: {}", other);
eprintln!("Try: struktura demo");
process::exit(1);
}
}
}
fn cmd_demo() {
let normal = parse_values(NORMAL_SAMPLES);
let fault = parse_values(FAULT_SAMPLES);
let law_n = analyze(&normal);
let law_f = analyze(&fault);
let verdict = health_check(&law_f, law_n.dfa.alpha);
let shift = law_f.dfa.alpha - law_n.dfa.alpha;
let (color, verdict_label) = verdict_color(verdict);
println!();
println!(" \x1b[1mSTRUKTURA DEMO\x1b[0m");
println!(" Bearing fault detection from CWRU vibration data");
println!(" ================================================");
println!();
print_visual("Normal bearing", &law_n, None);
println!();
print_visual("Inner race FAULT", &law_f, Some(law_n.dfa.alpha));
println!();
println!(" ================================================");
println!(" Baseline alpha: {:.3} (normal bearing)", law_n.dfa.alpha);
println!(" Current alpha: {:.3} (faulted bearing)", law_f.dfa.alpha);
println!(" Structural shift: \x1b[1m{:+.3}\x1b[0m", shift);
println!();
println!(" Verdict: {}\x1b[1m{}\x1b[0m", color, verdict_label);
println!();
println!(" The bearing's vibration structure changed BEFORE");
println!(" any amplitude threshold would have fired.");
println!();
println!(" No training. No hyperparameters. Just math.");
println!(" https://crates.io/crates/struktura");
println!();
}
fn cmd_check(args: &[String]) {
if args.len() < 3 {
eprintln!("Usage: struktura check <file.csv> [--baseline N]");
process::exit(1);
}
let path = &args[2];
let data = if path == "-" { read_stdin() } else { read_csv(path) };
if data.len() < 20 {
eprintln!("Error: need >= 20 data points, got {}", data.len());
process::exit(1);
}
let law = analyze(&data);
let mut baseline: Option<f64> = None;
let json_mode = args.iter().any(|a| a == "--json");
for i in 0..args.len() {
if args[i] == "--baseline" && i + 1 < args.len() {
baseline = args[i + 1].parse().ok();
}
}
if json_mode {
let verdict_s = baseline.map(|b| {
let v = health_check(&law, b);
let (_, l) = verdict_color(v);
l
});
let shift = baseline.map(|b| law.dfa.alpha - b);
println!("{{\"file\":\"{}\",\"n\":{},\"dfa_alpha\":{:.4},\"dfa_r2\":{:.4},\"hurst\":{:.4},\"kurtosis\":{:.2},\"quality\":\"{}\"{}{}}}",
path, law.n, law.dfa.alpha, law.dfa.r_squared, law.hurst, law.kurtosis, quality_str(law.quality),
shift.map(|s| format!(",\"shift\":{:.4}", s)).unwrap_or_default(),
verdict_s.map(|v| format!(",\"verdict\":\"{}\"", v)).unwrap_or_default()
);
return;
}
println!();
println!(" \x1b[1mSTRUKTURA\x1b[0m - structural health analysis");
println!(" File: {}", path);
println!(" ------------------------------------------------");
print_visual(path, &law, baseline);
println!();
print_law_detail(&law);
if let Some(b) = baseline {
let verdict = health_check(&law, b);
let (color, label) = verdict_color(verdict);
println!();
println!(" >>> {}{}\x1b[0m", color, label);
}
println!();
}
fn cmd_compare(args: &[String]) {
if args.len() < 4 {
eprintln!("Usage: struktura compare <baseline.csv> <current.csv>");
process::exit(1);
}
let data_a = read_csv(&args[2]);
let data_b = read_csv(&args[3]);
let law_a = analyze(&data_a);
let law_b = analyze(&data_b);
println!();
println!(" \x1b[1mSTRUKTURA\x1b[0m - structural comparison");
println!(" ================================================");
print_visual(&args[2], &law_a, None);
println!();
print_visual(&args[3], &law_b, Some(law_a.dfa.alpha));
println!();
println!(" ------------------------------------------------");
println!(" BASELINE:");
print_law_detail(&law_a);
println!(" CURRENT:");
print_law_detail(&law_b);
let verdict = health_check(&law_b, law_a.dfa.alpha);
let (color, label) = verdict_color(verdict);
println!();
println!(" >>> {}{}\x1b[0m", color, label);
println!();
}
fn cmd_bench() {
let normal = parse_values(NORMAL_SAMPLES);
let fault = parse_values(FAULT_SAMPLES);
let law_n = analyze(&normal);
let law_f = analyze(&fault);
println!();
println!(" \x1b[1mSTRUKTURA BENCHMARK\x1b[0m");
println!(" ================================================");
println!();
println!(" | Condition | DFA alpha | R2 | Shift | Verdict |");
println!(" |--------------------|-----------|--------|--------|----------|");
let print_row = |name: &str, law: &StructuralLaw, baseline: Option<f64>| {
let (shift_str, verdict_str) = match baseline {
Some(b) => {
let s = law.dfa.alpha - b;
let v = health_check(law, b);
let (_, vl) = verdict_color(v);
(format!("{:+.3}", s), vl.to_string())
}
None => ("--".to_string(), "--".to_string()),
};
println!(
" | {:<18} | {:.3} | {:.4} | {:>6} | {:>8} |",
name, law.dfa.alpha, law.dfa.r_squared, shift_str, verdict_str
);
};
print_row("Normal", &law_n, None);
print_row("Inner race fault", &law_f, Some(law_n.dfa.alpha));
println!();
println!(" Data: CWRU Bearing Data Center (builtin sample)");
println!(" Algorithm: Detrended Fluctuation Analysis (Peng 1994)");
println!(" https://crates.io/crates/struktura");
println!();
}