use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, VecDeque};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Andon {
pub alerts: VecDeque<AndonAlert>,
pub rules: Vec<AndonRule>,
pub handlers: Vec<AndonHandler>,
pub stats: AndonStats,
pub max_history: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AndonAlert {
pub id: String,
pub severity: AndonSeverity,
pub trigger: String,
pub message: String,
pub status: AlertStatus,
pub triggered_at: String,
pub resolved_at: Option<String>,
pub root_cause: Option<String>,
pub actions: Vec<String>,
pub context: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum AndonSeverity {
Minor,
Major,
Critical,
Emergency,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum AlertStatus {
Active,
Investigating,
Suppressed,
Resolved,
Acknowledged,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AndonRule {
pub id: String,
pub condition: String,
pub severity: AndonSeverity,
pub auto_stop: bool,
pub notify_channels: Vec<String>,
pub enabled: bool,
pub trigger_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AndonHandler {
pub id: String,
pub handles: String,
pub action: String,
pub response_time_ms: u32,
pub automatic: bool,
pub requires_approval: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AndonStats {
pub total_alerts: u32,
pub active_alerts: u32,
pub alerts_by_severity: BTreeMap<String, u32>,
pub avg_resolution_time_ms: u32,
pub top_triggers: Vec<(String, u32)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GembaWalk {
pub walks: Vec<GembaWalkSession>,
pub observations: Vec<GembaObservation>,
pub problem_areas: Vec<ProblemArea>,
pub actions: Vec<ImprovementAction>,
pub stats: GembaStats,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GembaWalkSession {
pub id: String,
pub date: String,
pub duration_minutes: u32,
pub area: String,
pub observers: Vec<String>,
pub observations: Vec<String>,
pub problems_found: u32,
pub improvements_suggested: u32,
pub summary: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GembaObservation {
pub id: String,
pub description: String,
pub location: String,
pub frequency: ObservationFrequency,
pub impact: ObservationImpact,
pub is_problem: bool,
pub related_improvement: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ObservationFrequency {
Rare,
Occasional,
Frequent,
Constant,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum ObservationImpact {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProblemArea {
pub id: String,
pub location: String,
pub description: String,
pub root_cause: Option<String>,
pub severity: ProblemSeverity,
pub attempts: Vec<String>,
pub status: ProblemStatus,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum ProblemSeverity {
Minor,
Moderate,
Major,
Critical,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ProblemStatus {
Identified,
InProgress,
Awaiting,
Resolved,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImprovementAction {
pub id: String,
pub improvement: String,
pub owner: String,
pub due_date: Option<String>,
pub status: ActionStatus,
pub progress: u32,
pub blockers: Vec<String>,
pub expected_benefit: String,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ActionStatus {
NotStarted,
InProgress,
Blocked,
Completed,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GembaStats {
pub total_walks: u32,
pub total_observations: u32,
pub problems_identified: u32,
pub problems_resolved: u32,
pub improvements_implemented: u32,
pub avg_problems_per_walk: f32,
pub effectiveness: f32,
}
impl Andon {
pub fn new(max_history: usize) -> Self {
Self {
alerts: VecDeque::new(),
rules: Vec::new(),
handlers: Vec::new(),
stats: AndonStats::default(),
max_history,
}
}
pub fn add_rule(&mut self, rule: AndonRule) {
self.rules.push(rule);
}
pub fn trigger_alert(&mut self, alert: AndonAlert) {
self.stats.total_alerts += 1;
self.stats.active_alerts += 1;
let severity_str = format!("{:?}", alert.severity);
*self
.stats
.alerts_by_severity
.entry(severity_str)
.or_insert(0) += 1;
self.alerts.push_back(alert.clone());
while self.alerts.len() > self.max_history {
self.alerts.pop_front();
}
if alert.severity == AndonSeverity::Critical || alert.severity == AndonSeverity::Emergency {
self.invoke_handlers(&alert);
}
}
pub fn resolve_alert(&mut self, alert_id: &str, root_cause: String) {
for alert in self.alerts.iter_mut().rev() {
if alert.id == alert_id {
alert.status = AlertStatus::Resolved;
alert.root_cause = Some(root_cause);
alert.resolved_at = Some(chrono::Utc::now().to_rfc3339());
self.stats.active_alerts = self.stats.active_alerts.saturating_sub(1);
break;
}
}
}
fn invoke_handlers(&self, alert: &AndonAlert) {
for handler in &self.handlers {
if handler.automatic && handler.handles == alert.trigger {
}
}
}
pub fn critical_alerts(&self) -> Vec<&AndonAlert> {
self.alerts
.iter()
.filter(|a| a.severity >= AndonSeverity::Critical && a.status == AlertStatus::Active)
.collect()
}
pub fn should_stop(&self) -> bool {
!self.critical_alerts().is_empty()
&& self
.rules
.iter()
.any(|r| r.enabled && r.auto_stop && r.severity == AndonSeverity::Emergency)
}
}
impl GembaWalk {
pub fn new() -> Self {
Self {
walks: Vec::new(),
observations: Vec::new(),
problem_areas: Vec::new(),
actions: Vec::new(),
stats: GembaStats::default(),
}
}
pub fn conduct_walk(&mut self, session: GembaWalkSession) {
for obs_desc in &session.observations {
self.observations.push(GembaObservation {
id: format!("obs-{}", self.observations.len()),
description: obs_desc.clone(),
location: session.area.clone(),
frequency: ObservationFrequency::Occasional,
impact: ObservationImpact::Medium,
is_problem: false,
related_improvement: None,
});
}
self.stats.total_walks += 1;
self.stats.total_observations += self.observations.len() as u32;
self.stats.problems_identified += session.problems_found;
self.walks.push(session);
self.recalculate_stats();
}
pub fn add_problem(&mut self, problem: ProblemArea) {
self.problem_areas.push(problem);
}
pub fn add_improvement(&mut self, action: ImprovementAction) {
self.actions.push(action);
}
pub fn resolve_problem(&mut self, problem_id: &str) {
for problem in &mut self.problem_areas {
if problem.id == problem_id {
problem.status = ProblemStatus::Resolved;
self.stats.problems_resolved += 1;
break;
}
}
}
pub fn complete_improvement(&mut self, action_id: &str) {
for action in &mut self.actions {
if action.id == action_id {
action.status = ActionStatus::Completed;
action.progress = 100;
self.stats.improvements_implemented += 1;
break;
}
}
}
pub fn unresolved_problems(&self) -> Vec<&ProblemArea> {
self.problem_areas
.iter()
.filter(|p| p.status != ProblemStatus::Resolved)
.collect()
}
pub fn blocked_improvements(&self) -> Vec<&ImprovementAction> {
self.actions
.iter()
.filter(|a| a.status == ActionStatus::Blocked)
.collect()
}
fn recalculate_stats(&mut self) {
if self.stats.total_walks > 0 {
self.stats.avg_problems_per_walk =
self.stats.problems_identified as f32 / self.stats.total_walks as f32;
}
if self.stats.problems_identified > 0 {
self.stats.effectiveness = (self.stats.problems_resolved as f32
/ self.stats.problems_identified as f32)
* 100.0;
}
}
}
impl Default for GembaWalk {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_andon_alert_trigger() {
let mut andon = Andon::new(100);
let alert = AndonAlert {
id: "alert-1".to_string(),
severity: AndonSeverity::Critical,
trigger: "config-invalid".to_string(),
message: "Invalid configuration detected".to_string(),
status: AlertStatus::Active,
triggered_at: chrono::Utc::now().to_rfc3339(),
resolved_at: None,
root_cause: None,
actions: vec![],
context: BTreeMap::new(),
};
andon.trigger_alert(alert.clone());
assert_eq!(andon.stats.total_alerts, 1);
assert_eq!(andon.stats.active_alerts, 1);
}
#[test]
fn test_andon_critical_alerts() {
let mut andon = Andon::new(100);
let alert = AndonAlert {
id: "alert-1".to_string(),
severity: AndonSeverity::Critical,
trigger: "test".to_string(),
message: "Test".to_string(),
status: AlertStatus::Active,
triggered_at: chrono::Utc::now().to_rfc3339(),
resolved_at: None,
root_cause: None,
actions: vec![],
context: BTreeMap::new(),
};
andon.trigger_alert(alert);
assert_eq!(andon.critical_alerts().len(), 1);
}
#[test]
fn test_gemba_walk_conduction() {
let mut gemba = GembaWalk::new();
let session = GembaWalkSession {
id: "walk-1".to_string(),
date: chrono::Utc::now().to_rfc3339(),
duration_minutes: 30,
area: "config-system".to_string(),
observers: vec!["engineer-1".to_string()],
observations: vec!["Redundant checks".to_string()],
problems_found: 1,
improvements_suggested: 2,
summary: "Found opportunity to optimize".to_string(),
};
gemba.conduct_walk(session);
assert_eq!(gemba.stats.total_walks, 1);
assert_eq!(gemba.stats.problems_identified, 1);
}
}