use chrono::{Duration, Utc};
use xbp_analytics::{
empirical_cdf, histogram, iqr, mad, mean, median, percentile, standard_deviation,
AnalyticsError, DataQuality, DataQualityWarning, EvidenceCoverage, FlowMetrics, FlowSamples,
TimeWindow,
};
#[test]
fn computes_descriptive_statistics() {
let values = [1.0, 2.0, 3.0, 4.0, 100.0];
assert_eq!(mean(&values).unwrap(), 22.0);
assert_eq!(median(&values).unwrap(), 3.0);
assert_eq!(percentile(&values, 0.8).unwrap(), 4.0);
assert!(standard_deviation(&values).unwrap() > 38.0);
assert_eq!(iqr(&values).unwrap(), 2.0);
assert_eq!(mad(&values).unwrap(), 1.0);
}
#[test]
fn builds_histogram_and_empirical_cdf() {
let values = [0.0, 1.0, 2.0, 3.0];
assert_eq!(histogram(&values, 2).unwrap(), vec![2, 2]);
assert_eq!(empirical_cdf(&values, 2.0).unwrap(), 0.75);
}
#[test]
fn empty_samples_return_an_explicit_error() {
assert_eq!(mean(&[]).unwrap_err(), AnalyticsError::EmptySample);
}
#[test]
fn flow_metrics_keep_cycle_time_distinct_from_active_time() {
let now = Utc::now();
let metrics = FlowMetrics::from_samples(
TimeWindow::new(now - Duration::days(14), now),
14,
10,
3,
FlowSamples {
lead_times_hours: &[48.0],
cycle_times_hours: &[24.0],
active_times_hours: &[8.0],
blocked_times_hours: &[4.0],
review_wait_times_hours: &[6.0],
deployment_wait_times_hours: &[2.0],
},
)
.unwrap();
assert_eq!(metrics.throughput_per_day, 1.0);
assert_eq!(metrics.arrival_rate_per_day, 10.0 / 14.0);
assert_eq!(metrics.lead_time_hours.mean.unwrap(), 48.0);
assert_eq!(metrics.cycle_time_hours.mean.unwrap(), 24.0);
assert_eq!(metrics.active_time_hours.mean.unwrap(), 8.0);
assert_eq!(metrics.flow_efficiency.unwrap(), 8.0 / 24.0);
}
#[test]
fn data_quality_keeps_missing_evidence_explicit() {
let quality = DataQuality::new(
EvidenceCoverage::new(10, 8, 7),
Some(18),
7.0,
vec!["started_at".into()],
vec![DataQualityWarning::PartialStateHistory],
);
assert_eq!(quality.coverage.ratio, 0.7);
assert_eq!(quality.missing_fields, vec!["started_at"]);
}
#[test]
fn evidence_coverage_rejects_inconsistent_denominators() {
assert!(EvidenceCoverage::try_new(1, 2, 1).is_err());
assert!(EvidenceCoverage::try_new(2, 1, 2).is_err());
}