use serde::Serialize;
use crate::analysis::RunsResult;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
pub enum Verdict {
Pass,
Warn,
Fail,
#[serde(rename = "N/A")]
Na,
}
impl Verdict {
pub fn as_str(&self) -> &'static str {
match self {
Verdict::Pass => "PASS",
Verdict::Warn => "WARN",
Verdict::Fail => "FAIL",
Verdict::Na => "N/A",
}
}
}
impl std::fmt::Display for Verdict {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
pub fn verdict_autocorr(max_abs: f64) -> Verdict {
if max_abs > 0.15 {
Verdict::Fail
} else if max_abs > 0.05 {
Verdict::Warn
} else {
Verdict::Pass
}
}
pub fn verdict_spectral(flatness: f64) -> Verdict {
if flatness < 0.5 {
Verdict::Fail
} else if flatness < 0.75 {
Verdict::Warn
} else {
Verdict::Pass
}
}
pub fn verdict_bias(overall: f64, has_significant: bool) -> Verdict {
if overall > 0.02 {
Verdict::Fail
} else if has_significant {
Verdict::Warn
} else {
Verdict::Pass
}
}
pub fn verdict_distribution(ks_p: f64) -> Verdict {
if ks_p < 0.001 {
Verdict::Fail
} else if ks_p < 0.01 {
Verdict::Warn
} else {
Verdict::Pass
}
}
pub fn verdict_stationarity(f_stat: f64, is_stationary: bool) -> Verdict {
if f_stat > 3.0 {
Verdict::Fail
} else if !is_stationary {
Verdict::Warn
} else {
Verdict::Pass
}
}
pub fn verdict_runs(ru: &RunsResult, _sample_size: usize) -> Verdict {
let longest_ratio = if ru.expected_longest_run > 0.0 {
ru.longest_run as f64 / ru.expected_longest_run
} else {
1.0
};
let runs_dev = if ru.expected_runs > 0.0 {
(ru.total_runs as f64 - ru.expected_runs).abs() / ru.expected_runs
} else {
0.0
};
if longest_ratio > 3.0 || runs_dev > 0.4 {
Verdict::Fail
} else if longest_ratio > 2.0 || runs_dev > 0.2 {
Verdict::Warn
} else {
Verdict::Pass
}
}
pub fn verdict_hurst(h: f64) -> Verdict {
if !h.is_finite() {
return Verdict::Na;
}
if (0.4..=0.6).contains(&h) {
Verdict::Pass
} else if (0.3..=0.7).contains(&h) {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_lyapunov(l: f64) -> Verdict {
if !l.is_finite() {
return Verdict::Na;
}
if l.abs() < 0.1 {
Verdict::Pass
} else if l.abs() < 0.2 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_corrdim(d: f64) -> Verdict {
if !d.is_finite() {
return Verdict::Na;
}
if d > 3.0 {
Verdict::Pass
} else if d > 2.0 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_bientropy(b: f64) -> Verdict {
if !b.is_finite() {
return Verdict::Na;
}
if b > 0.95 {
Verdict::Pass
} else if b > 0.90 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_compression(c: f64) -> Verdict {
if !c.is_finite() {
return Verdict::Na;
}
if c > 0.99 {
Verdict::Pass
} else if c > 0.95 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_sampen(v: f64) -> Verdict {
if !v.is_finite() {
return Verdict::Na;
}
if v > 1.0 {
Verdict::Pass
} else if v >= 0.5 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_dfa(alpha: f64) -> Verdict {
if !alpha.is_finite() {
return Verdict::Na;
}
if alpha > 0.4 && alpha < 0.6 {
Verdict::Pass
} else if (0.3..=0.7).contains(&alpha) {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_rqa_det(det: f64) -> Verdict {
if !det.is_finite() {
return Verdict::Na;
}
if det < 0.1 {
Verdict::Pass
} else if det <= 0.3 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_apen(v: f64) -> Verdict {
if !v.is_finite() {
return Verdict::Na;
}
if v > 1.0 {
Verdict::Pass
} else if v >= 0.5 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_permen(v: f64) -> Verdict {
if !v.is_finite() {
return Verdict::Na;
}
if v > 0.95 {
Verdict::Pass
} else if v >= 0.8 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_anderson_darling(p: f64) -> Verdict {
if !p.is_finite() {
return Verdict::Na;
}
if p > 0.05 {
Verdict::Pass
} else if p >= 0.01 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_ljung_box(p: f64) -> Verdict {
if !p.is_finite() {
return Verdict::Na;
}
if p > 0.05 {
Verdict::Pass
} else if p >= 0.01 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn verdict_cramer_von_mises(p: f64) -> Verdict {
if !p.is_finite() {
return Verdict::Na;
}
if p > 0.05 {
Verdict::Pass
} else if p >= 0.01 {
Verdict::Warn
} else {
Verdict::Fail
}
}
pub fn metric_or_na(value: f64, is_valid: bool) -> String {
if is_valid && value.is_finite() {
format!("{value:.4}")
} else {
"N/A".to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verdict_enum_display() {
assert_eq!(Verdict::Pass.as_str(), "PASS");
assert_eq!(Verdict::Warn.as_str(), "WARN");
assert_eq!(Verdict::Fail.as_str(), "FAIL");
assert_eq!(Verdict::Na.as_str(), "N/A");
assert_eq!(format!("{}", Verdict::Pass), "PASS");
}
#[test]
fn autocorr_thresholds() {
assert_eq!(verdict_autocorr(0.01), Verdict::Pass);
assert_eq!(verdict_autocorr(0.10), Verdict::Warn);
assert_eq!(verdict_autocorr(0.20), Verdict::Fail);
}
#[test]
fn spectral_thresholds() {
assert_eq!(verdict_spectral(0.90), Verdict::Pass);
assert_eq!(verdict_spectral(0.60), Verdict::Warn);
assert_eq!(verdict_spectral(0.30), Verdict::Fail);
}
#[test]
fn hurst_thresholds() {
assert_eq!(verdict_hurst(0.50), Verdict::Pass);
assert_eq!(verdict_hurst(0.35), Verdict::Warn);
assert_eq!(verdict_hurst(0.10), Verdict::Fail);
assert_eq!(verdict_hurst(f64::NAN), Verdict::Na);
}
#[test]
fn lyapunov_thresholds() {
assert_eq!(verdict_lyapunov(0.05), Verdict::Pass);
assert_eq!(verdict_lyapunov(0.15), Verdict::Warn);
assert_eq!(verdict_lyapunov(0.50), Verdict::Fail);
assert_eq!(verdict_lyapunov(f64::NAN), Verdict::Na);
}
#[test]
fn corrdim_thresholds() {
assert_eq!(verdict_corrdim(5.0), Verdict::Pass);
assert_eq!(verdict_corrdim(2.5), Verdict::Warn);
assert_eq!(verdict_corrdim(1.5), Verdict::Fail);
assert_eq!(verdict_corrdim(f64::NAN), Verdict::Na);
}
#[test]
fn bientropy_thresholds() {
assert_eq!(verdict_bientropy(0.98), Verdict::Pass);
assert_eq!(verdict_bientropy(0.92), Verdict::Warn);
assert_eq!(verdict_bientropy(0.80), Verdict::Fail);
assert_eq!(verdict_bientropy(f64::NAN), Verdict::Na);
}
#[test]
fn compression_thresholds() {
assert_eq!(verdict_compression(1.00), Verdict::Pass);
assert_eq!(verdict_compression(0.97), Verdict::Warn);
assert_eq!(verdict_compression(0.90), Verdict::Fail);
assert_eq!(verdict_compression(f64::NAN), Verdict::Na);
}
#[test]
fn metric_display() {
assert_eq!(metric_or_na(0.1234, true), "0.1234");
assert_eq!(metric_or_na(0.1234, false), "N/A");
assert_eq!(metric_or_na(f64::NAN, true), "N/A");
}
}