use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AndonSeverity {
Info,
Warning,
Critical,
}
impl std::fmt::Display for AndonSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Info => write!(f, "INFO"),
Self::Warning => write!(f, "WARNING"),
Self::Critical => write!(f, "CRITICAL"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AndonEvent {
HighRejectionRate {
rate: f32,
threshold: f32,
},
QualityDrift {
current: f32,
baseline: f32,
},
DiversityCollapse {
score: f32,
minimum: f32,
},
GenerationFailure {
message: String,
},
}
impl AndonEvent {
#[must_use]
pub fn severity(&self) -> AndonSeverity {
match self {
Self::HighRejectionRate { rate, threshold } => {
if *rate > threshold + 0.02 {
AndonSeverity::Critical
} else {
AndonSeverity::Warning
}
}
Self::QualityDrift { current, baseline } => {
if *current < baseline * 0.8 {
AndonSeverity::Critical
} else {
AndonSeverity::Warning
}
}
Self::DiversityCollapse { .. } => AndonSeverity::Warning,
Self::GenerationFailure { .. } => AndonSeverity::Critical,
}
}
#[must_use]
pub fn should_halt(&self) -> bool {
self.severity() == AndonSeverity::Critical
}
}
impl std::fmt::Display for AndonEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::HighRejectionRate { rate, threshold } => {
write!(
f,
"ANDON: High rejection rate {:.1}% > {:.1}%",
rate * 100.0,
threshold * 100.0
)
}
Self::QualityDrift { current, baseline } => {
write!(
f,
"ANDON: Quality drift {current:.3} < baseline {baseline:.3}"
)
}
Self::DiversityCollapse { score, minimum } => {
write!(
f,
"ANDON: Diversity collapse {score:.3} < minimum {minimum:.3}"
)
}
Self::GenerationFailure { message } => {
write!(f, "ANDON: Generation failed - {message}")
}
}
}
}
pub trait AndonHandler: Send + Sync {
fn on_event(&self, event: &AndonEvent);
fn should_halt(&self, event: &AndonEvent) -> bool;
fn on_high_rejection(&self, rate: f32, threshold: f32) {
let event = AndonEvent::HighRejectionRate { rate, threshold };
self.on_event(&event);
}
fn on_quality_drift(&self, current: f32, baseline: f32) {
let event = AndonEvent::QualityDrift { current, baseline };
self.on_event(&event);
}
fn on_diversity_collapse(&self, score: f32, minimum: f32) {
let event = AndonEvent::DiversityCollapse { score, minimum };
self.on_event(&event);
}
}
#[derive(Debug, Clone, Default)]
pub struct DefaultAndon {
halted: Arc<AtomicBool>,
}
impl DefaultAndon {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn is_halted(&self) -> bool {
self.halted.load(Ordering::SeqCst)
}
pub fn reset(&self) {
self.halted.store(false, Ordering::SeqCst);
}
}
impl AndonHandler for DefaultAndon {
fn on_event(&self, event: &AndonEvent) {
eprintln!("[ANDON {}] {}", event.severity(), event);
if self.should_halt(event) {
self.halted.store(true, Ordering::SeqCst);
}
}
fn should_halt(&self, event: &AndonEvent) -> bool {
event.should_halt()
}
}
#[derive(Debug, Default)]
pub struct TestAndon {
events: std::sync::Mutex<Vec<AndonEvent>>,
halted: AtomicBool,
}
impl TestAndon {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn events(&self) -> Vec<AndonEvent> {
self.events
.lock()
.expect("TestAndon mutex poisoned")
.clone()
}
#[must_use]
pub fn was_halted(&self) -> bool {
self.halted.load(Ordering::SeqCst)
}
pub fn clear(&self) {
self.events
.lock()
.expect("TestAndon mutex poisoned")
.clear();
self.halted.store(false, Ordering::SeqCst);
}
#[must_use]
pub fn count_high_rejection(&self) -> usize {
self.events()
.iter()
.filter(|e| matches!(e, AndonEvent::HighRejectionRate { .. }))
.count()
}
#[must_use]
pub fn count_quality_drift(&self) -> usize {
self.events()
.iter()
.filter(|e| matches!(e, AndonEvent::QualityDrift { .. }))
.count()
}
}
impl AndonHandler for TestAndon {
fn on_event(&self, event: &AndonEvent) {
self.events
.lock()
.expect("TestAndon mutex poisoned")
.push(event.clone());
if self.should_halt(event) {
self.halted.store(true, Ordering::SeqCst);
}
}
fn should_halt(&self, event: &AndonEvent) -> bool {
event.should_halt()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AndonConfig {
pub enabled: bool,
pub rejection_threshold: f32,
pub quality_baseline: Option<f32>,
pub diversity_minimum: f32,
}
impl Default for AndonConfig {
fn default() -> Self {
Self {
enabled: true,
rejection_threshold: 0.90, quality_baseline: None, diversity_minimum: 0.1, }
}
}
impl AndonConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
#[must_use]
pub fn with_rejection_threshold(mut self, threshold: f32) -> Self {
self.rejection_threshold = threshold.clamp(0.0, 1.0);
self
}
#[must_use]
pub fn with_quality_baseline(mut self, baseline: f32) -> Self {
self.quality_baseline = Some(baseline.clamp(0.0, 1.0));
self
}
#[must_use]
pub fn with_diversity_minimum(mut self, minimum: f32) -> Self {
self.diversity_minimum = minimum.clamp(0.0, 1.0);
self
}
#[must_use]
pub fn exceeds_rejection_threshold(&self, rate: f32) -> bool {
self.enabled && rate > self.rejection_threshold
}
#[must_use]
pub fn has_quality_drift(&self, current: f32) -> bool {
if !self.enabled {
return false;
}
match self.quality_baseline {
Some(baseline) => current < baseline * 0.9, None => false,
}
}
#[must_use]
pub fn has_diversity_collapse(&self, score: f32) -> bool {
self.enabled && score < self.diversity_minimum
}
}
#[cfg(test)]
#[path = "andon_tests.rs"]
mod tests;