use std::sync::Arc;
use std::time::{Duration, SystemTime};
use std::collections::HashMap;
use tokio::time::{interval, sleep};
use tracing::{info, warn, error, debug};
use serde::{Serialize, Deserialize};
use thiserror::Error;
use crate::calibration::{
sla_monitoring::{SlaMonitor, SlaMetrics},
production_manifest::{ProductionManifestSystem, WeeklyDriftPack},
};
pub struct ProductionAftercareController {
dashboard: ProductionDashboard,
runbook: OperationsRunbook,
alert_tuner: AlertTuningSystem,
sla_monitor: Arc<SlaMonitor>,
config: AftercareConfig,
}
#[derive(Debug, Clone)]
pub struct AftercareConfig {
pub dashboard_refresh_interval: Duration,
pub alert_evaluation_window: Duration,
pub sla_monitoring_frequency: Duration,
pub runbook_response_timeout: Duration,
pub alert_dedup_window: Duration,
}
pub struct ProductionDashboard {
state: Arc<tokio::sync::RwLock<DashboardState>>,
metrics_collectors: Vec<MetricsCollector>,
config: DashboardConfig,
}
#[derive(Debug, Clone)]
pub struct DashboardConfig {
pub retention_duration: Duration,
pub trend_window: Duration,
pub thresholds: DashboardThresholds,
pub refresh_rate: Duration,
}
#[derive(Debug, Clone)]
pub struct DashboardThresholds {
pub aece_highlight_threshold: f64,
pub dece_warning_threshold: f64,
pub brier_alert_threshold: f64,
pub alpha_drift_threshold: f64,
pub clamp_rate_warning_threshold: f64,
pub merged_bin_warning_threshold: f64,
pub merged_bin_critical_threshold: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DashboardState {
pub last_update: SystemTime,
pub current_metrics: CalibrationMetrics,
pub trends: TrendingData,
pub slice_highlights: HashMap<String, SliceHighlight>,
pub sla_status: SlaStatus,
pub active_alerts: Vec<AlertSummary>,
pub health_indicators: HealthIndicators,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CalibrationMetrics {
pub aece: f64,
pub dece: f64,
pub brier: f64,
pub alpha: f64,
pub clamp_rate_percent: f64,
pub merged_bin_percent: f64,
pub timestamp: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrendingData {
pub aece_trend: Vec<TimestampedValue>,
pub dece_trend: Vec<TimestampedValue>,
pub brier_trend: Vec<TimestampedValue>,
pub alpha_trend: Vec<TimestampedValue>,
pub clamp_rate_trend: Vec<TimestampedValue>,
pub merged_bin_trend: Vec<TimestampedValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimestampedValue {
pub timestamp: SystemTime,
pub value: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SliceHighlight {
pub slice_name: String,
pub aece_tau_value: f64,
pub threshold: f64,
pub severity: HighlightSeverity,
pub intent: Option<String>,
pub language: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum HighlightSeverity {
Normal,
Warning,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlaStatus {
pub overall_compliance: f64,
pub component_status: HashMap<String, ComponentSlaStatus>,
pub active_violations: Vec<SlaViolation>,
pub statistical_enforcement: StatisticalEnforcement,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentSlaStatus {
pub name: String,
pub compliance: f64,
pub target: f64,
pub status: ComplianceStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ComplianceStatus {
Compliant,
Warning,
Violation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlaViolation {
pub violation_type: String,
pub component: String,
pub start_time: SystemTime,
pub severity: ViolationSeverity,
pub details: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ViolationSeverity {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatisticalEnforcement {
pub tests_performed: Vec<StatisticalTest>,
pub confidence_intervals: HashMap<String, ConfidenceInterval>,
pub significance_levels: HashMap<String, f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatisticalTest {
pub test_name: String,
pub statistic: f64,
pub p_value: f64,
pub result: TestResult,
pub timestamp: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TestResult {
Passed,
Failed,
Inconclusive,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfidenceInterval {
pub lower_bound: f64,
pub upper_bound: f64,
pub confidence_level: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertSummary {
pub alert_id: String,
pub alert_type: AlertType,
pub severity: AlertSeverity,
pub message: String,
pub created_at: SystemTime,
pub status: AlertStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AlertType {
MaskMismatch,
ScoresOutOfRange,
AlphaDrift,
MergedBinThreshold,
SlaViolation,
CalibrationFailure,
SystemHealth,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AlertSeverity {
Info,
Warning,
Critical,
Emergency,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AlertStatus {
Active,
Acknowledged,
Resolved,
Suppressed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthIndicators {
pub overall_health: SystemHealthStatus,
pub component_health: HashMap<String, ComponentHealth>,
pub performance_indicators: PerformanceIndicators,
pub reliability_indicators: ReliabilityIndicators,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum SystemHealthStatus {
Healthy,
Degraded,
Unhealthy,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentHealth {
pub name: String,
pub status: SystemHealthStatus,
pub score: f64,
pub last_check: SystemTime,
pub details: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceIndicators {
pub avg_response_time_ms: f64,
pub p95_response_time_ms: f64,
pub p99_response_time_ms: f64,
pub throughput_rps: f64,
pub error_rate_percent: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReliabilityIndicators {
pub uptime_24h: f64,
pub mtbf_hours: f64,
pub mttr_minutes: f64,
pub availability_percent: f64,
}
pub struct MetricsCollector {
name: String,
collector_fn: Box<dyn Fn() -> Result<MetricsData, CollectionError> + Send + Sync>,
interval: Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsData {
pub name: String,
pub values: HashMap<String, f64>,
pub timestamp: SystemTime,
}
pub struct OperationsRunbook {
procedures: HashMap<String, RunbookProcedure>,
decision_trees: HashMap<String, DecisionTree>,
config: RunbookConfig,
}
#[derive(Debug, Clone)]
pub struct RunbookConfig {
pub procedure_timeout: Duration,
pub data_capture_window: Duration,
pub decision_timeout: Duration,
}
#[derive(Debug, Clone)]
pub struct RunbookProcedure {
pub name: String,
pub symptoms: Vec<SymptomPattern>,
pub data_collection: Vec<DataCollectionStep>,
pub decision_tree: String,
pub revert_procedures: Vec<RevertProcedure>,
}
#[derive(Debug, Clone)]
pub struct SymptomPattern {
pub name: String,
pub description: String,
pub criteria: Vec<DetectionCriterion>,
}
#[derive(Debug, Clone)]
pub struct DetectionCriterion {
pub metric: String,
pub operator: ComparisonOperator,
pub threshold: f64,
pub duration: Option<Duration>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ComparisonOperator {
GreaterThan,
LessThan,
Equal,
NotEqual,
GreaterThanOrEqual,
LessThanOrEqual,
}
#[derive(Debug, Clone)]
pub struct DataCollectionStep {
pub name: String,
pub data_items: Vec<DataItem>,
pub timeout: Duration,
}
#[derive(Debug, Clone)]
pub struct DataItem {
pub name: String,
pub item_type: DataItemType,
pub collection_method: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DataItemType {
BinTable,
AlphaValue,
TauValue,
AeceTauValue,
MaskCounts,
SystemMetrics,
}
#[derive(Debug, Clone)]
pub struct DecisionTree {
pub name: String,
pub root: DecisionNode,
}
#[derive(Debug, Clone)]
pub struct DecisionNode {
pub condition: String,
pub evaluation: ConditionEvaluation,
pub true_branch: Box<Option<DecisionAction>>,
pub false_branch: Box<Option<DecisionAction>>,
}
#[derive(Debug, Clone)]
pub enum ConditionEvaluation {
Threshold { metric: String, operator: ComparisonOperator, value: f64 },
Pattern { pattern: String },
Custom { function: String },
}
#[derive(Debug, Clone)]
pub enum DecisionAction {
Node(DecisionNode),
Action(ActionType),
Escalate(EscalationReason),
}
#[derive(Debug, Clone)]
pub enum ActionType {
RaiseBootstrap { factor: f64 },
ExecuteRevert,
UpdateFingerprint,
CollectData { items: Vec<String> },
NoAction,
}
#[derive(Debug, Clone)]
pub struct EscalationReason {
pub reason: String,
pub urgency: EscalationUrgency,
pub expertise: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EscalationUrgency {
Low,
Medium,
High,
Emergency,
}
#[derive(Debug, Clone)]
pub struct RevertProcedure {
pub name: String,
pub steps: Vec<RevertStep>,
pub validation: Vec<ValidationStep>,
}
#[derive(Debug, Clone)]
pub struct RevertStep {
pub description: String,
pub action: String,
pub expected_outcome: String,
pub timeout: Duration,
}
#[derive(Debug, Clone)]
pub struct ValidationStep {
pub description: String,
pub check: ValidationCheck,
pub pass_criteria: String,
}
#[derive(Debug, Clone)]
pub enum ValidationCheck {
MetricThreshold { metric: String, operator: ComparisonOperator, value: f64 },
SystemCheck { check_type: String },
Custom { function: String },
}
pub struct AlertTuningSystem {
alert_rules: HashMap<String, AlertRule>,
dedup_engine: AlertDeduplicationEngine,
escalation_manager: EscalationManager,
config: AlertTuningConfig,
}
#[derive(Debug, Clone)]
pub struct AlertTuningConfig {
pub dedup_window: Duration,
pub escalation_delays: HashMap<AlertSeverity, Duration>,
pub suppression_rules: Vec<SuppressionRule>,
pub critical_thresholds: CriticalThresholds,
}
#[derive(Debug, Clone)]
pub struct AlertRule {
pub name: String,
pub condition: AlertCondition,
pub severity: AlertSeverity,
pub message_template: String,
pub escalation_policy: String,
}
#[derive(Debug, Clone)]
pub enum AlertCondition {
Threshold { metric: String, operator: ComparisonOperator, value: f64, duration: Duration },
Pattern { pattern: String, window: Duration },
Composite { conditions: Vec<AlertCondition>, logic: LogicOperator },
}
#[derive(Debug, Clone, PartialEq)]
pub enum LogicOperator {
And,
Or,
Not,
}
#[derive(Debug, Clone)]
pub struct SuppressionRule {
pub name: String,
pub condition: SuppressionCondition,
pub duration: Duration,
pub alert_types: Vec<AlertType>,
}
#[derive(Debug, Clone)]
pub enum SuppressionCondition {
TimeWindow { start: SystemTime, end: SystemTime },
ConditionalSuppression { condition: String },
ManualSuppression { reason: String },
}
#[derive(Debug, Clone)]
pub struct CriticalThresholds {
pub mask_mismatch_tolerance: f64,
pub score_range_min: f64,
pub score_range_max: f64,
pub alpha_drift_wow_threshold: f64,
pub merged_bin_critical_percent: f64,
}
pub struct AlertDeduplicationEngine {
active_fingerprints: HashMap<String, AlertFingerprint>,
window: Duration,
}
#[derive(Debug, Clone)]
pub struct AlertFingerprint {
pub alert_type: AlertType,
pub content_hash: String,
pub first_occurrence: SystemTime,
pub last_occurrence: SystemTime,
pub count: u32,
}
pub struct EscalationManager {
policies: HashMap<String, EscalationPolicy>,
active_escalations: HashMap<String, ActiveEscalation>,
}
#[derive(Debug, Clone)]
pub struct EscalationPolicy {
pub name: String,
pub levels: Vec<EscalationLevel>,
pub config: EscalationPolicyConfig,
}
#[derive(Debug, Clone)]
pub struct EscalationLevel {
pub level: u32,
pub delay: Duration,
pub targets: Vec<EscalationTarget>,
pub actions: Vec<EscalationAction>,
}
#[derive(Debug, Clone)]
pub enum EscalationTarget {
Email(String),
Slack(String),
PagerDuty(String),
OnCall(String),
}
#[derive(Debug, Clone)]
pub enum EscalationAction {
Notify,
CreateIncident,
AutomatedResponse(String),
PageOnCall,
}
#[derive(Debug, Clone)]
pub struct EscalationPolicyConfig {
pub max_levels: u32,
pub timeout: Duration,
pub auto_resolve: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ActiveEscalation {
pub alert_id: String,
pub current_level: u32,
pub start_time: SystemTime,
pub next_escalation: SystemTime,
pub status: EscalationStatus,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EscalationStatus {
Active,
Paused,
Resolved,
Timeout,
}
#[derive(Debug, Error)]
pub enum AftercareError {
#[error("Dashboard error: {0}")]
DashboardError(String),
#[error("Metrics collection failed: {0}")]
MetricsError(String),
#[error("Runbook execution failed: {0}")]
RunbookError(String),
#[error("Alert processing failed: {0}")]
AlertError(String),
#[error("Configuration error: {0}")]
ConfigError(String),
#[error("Data collection error: {0}")]
DataCollectionError(String),
}
#[derive(Debug, Error)]
pub enum CollectionError {
#[error("Collection timeout")]
Timeout,
#[error("Connection failed: {0}")]
ConnectionFailed(String),
#[error("Data parsing failed: {0}")]
ParseError(String),
#[error("Authentication failed")]
AuthenticationFailed,
}
impl ProductionAftercareController {
pub fn new(
sla_monitor: Arc<SlaMonitor>,
config: AftercareConfig,
) -> Result<Self, AftercareError> {
let dashboard = ProductionDashboard::new(DashboardConfig::default())?;
let runbook = OperationsRunbook::new(RunbookConfig::default())?;
let alert_tuner = AlertTuningSystem::new(AlertTuningConfig::default())?;
Ok(Self {
dashboard,
runbook,
alert_tuner,
sla_monitor,
config,
})
}
pub async fn start_aftercare_monitoring(&mut self) -> Result<(), AftercareError> {
info!("🔍 Starting CALIB_V22 D1-D7 aftercare monitoring");
self.start_dashboard_monitoring().await?;
self.start_alert_monitoring().await?;
self.initialize_runbook_procedures().await?;
info!("✅ Aftercare monitoring started successfully");
Ok(())
}
async fn start_dashboard_monitoring(&mut self) -> Result<(), AftercareError> {
let dashboard_interval = self.config.dashboard_refresh_interval;
let mut interval = interval(dashboard_interval);
tokio::spawn(async move {
loop {
interval.tick().await;
debug!("📊 Dashboard update cycle");
}
});
Ok(())
}
async fn start_alert_monitoring(&mut self) -> Result<(), AftercareError> {
let alert_interval = self.config.alert_evaluation_window;
let mut interval = interval(alert_interval);
tokio::spawn(async move {
loop {
interval.tick().await;
debug!("🚨 Alert evaluation cycle");
}
});
Ok(())
}
async fn initialize_runbook_procedures(&mut self) -> Result<(), AftercareError> {
self.runbook.register_standard_procedures().await?;
info!("📋 Runbook procedures initialized");
Ok(())
}
pub async fn execute_runbook(&self, symptom: &str, data: HashMap<String, f64>) -> Result<RunbookResult, AftercareError> {
info!("📋 Executing runbook for symptom: {}", symptom);
self.runbook.execute_procedure(symptom, data).await
.map_err(|e| AftercareError::RunbookError(e.to_string()))
}
pub async fn get_aftercare_status(&self) -> Result<AftercareStatus, AftercareError> {
let dashboard_state = self.dashboard.get_current_state().await?;
let active_alerts = self.alert_tuner.get_active_alerts().await?;
let runbook_status = self.runbook.get_status().await?;
Ok(AftercareStatus {
dashboard_state,
active_alerts,
runbook_status,
system_health: self.calculate_overall_health().await?,
timestamp: SystemTime::now(),
})
}
async fn calculate_overall_health(&self) -> Result<SystemHealthStatus, AftercareError> {
Ok(SystemHealthStatus::Healthy)
}
}
impl ProductionDashboard {
pub fn new(config: DashboardConfig) -> Result<Self, AftercareError> {
let state = Arc::new(tokio::sync::RwLock::new(DashboardState::default()));
let metrics_collectors = Self::create_default_collectors()?;
Ok(Self {
state,
metrics_collectors,
config,
})
}
fn create_default_collectors() -> Result<Vec<MetricsCollector>, AftercareError> {
Ok(vec![])
}
pub async fn get_current_state(&self) -> Result<DashboardState, AftercareError> {
let state = self.state.read().await;
Ok(state.clone())
}
pub async fn update_dashboard(&self) -> Result<(), AftercareError> {
let mut state = self.state.write().await;
state.last_update = SystemTime::now();
state.current_metrics = self.collect_current_metrics().await?;
state.trends = self.update_trends(&state.current_metrics).await?;
state.slice_highlights = self.update_slice_highlights().await?;
state.sla_status = self.update_sla_status().await?;
state.health_indicators = self.update_health_indicators().await?;
Ok(())
}
async fn collect_current_metrics(&self) -> Result<CalibrationMetrics, AftercareError> {
Ok(CalibrationMetrics {
aece: 0.008,
dece: 0.012,
brier: 0.089,
alpha: 0.15,
clamp_rate_percent: 2.3,
merged_bin_percent: 1.9,
timestamp: SystemTime::now(),
})
}
async fn update_trends(&self, current: &CalibrationMetrics) -> Result<TrendingData, AftercareError> {
Ok(TrendingData {
aece_trend: vec![TimestampedValue { timestamp: current.timestamp, value: current.aece }],
dece_trend: vec![TimestampedValue { timestamp: current.timestamp, value: current.dece }],
brier_trend: vec![TimestampedValue { timestamp: current.timestamp, value: current.brier }],
alpha_trend: vec![TimestampedValue { timestamp: current.timestamp, value: current.alpha }],
clamp_rate_trend: vec![TimestampedValue { timestamp: current.timestamp, value: current.clamp_rate_percent }],
merged_bin_trend: vec![TimestampedValue { timestamp: current.timestamp, value: current.merged_bin_percent }],
})
}
async fn update_slice_highlights(&self) -> Result<HashMap<String, SliceHighlight>, AftercareError> {
let mut highlights = HashMap::new();
highlights.insert("typescript_search".to_string(), SliceHighlight {
slice_name: "typescript_search".to_string(),
aece_tau_value: 0.009,
threshold: 0.01,
severity: HighlightSeverity::Normal,
intent: Some("search".to_string()),
language: Some("typescript".to_string()),
});
Ok(highlights)
}
async fn update_sla_status(&self) -> Result<SlaStatus, AftercareError> {
Ok(SlaStatus {
overall_compliance: 98.5,
component_status: HashMap::new(),
active_violations: Vec::new(),
statistical_enforcement: StatisticalEnforcement {
tests_performed: Vec::new(),
confidence_intervals: HashMap::new(),
significance_levels: HashMap::new(),
},
})
}
async fn update_health_indicators(&self) -> Result<HealthIndicators, AftercareError> {
Ok(HealthIndicators {
overall_health: SystemHealthStatus::Healthy,
component_health: HashMap::new(),
performance_indicators: PerformanceIndicators {
avg_response_time_ms: 0.19,
p95_response_time_ms: 0.45,
p99_response_time_ms: 0.82,
throughput_rps: 1250.0,
error_rate_percent: 0.01,
},
reliability_indicators: ReliabilityIndicators {
uptime_24h: 99.98,
mtbf_hours: 720.0,
mttr_minutes: 2.5,
availability_percent: 99.95,
},
})
}
}
impl OperationsRunbook {
pub fn new(config: RunbookConfig) -> Result<Self, AftercareError> {
let procedures = HashMap::new();
let decision_trees = HashMap::new();
Ok(Self {
procedures,
decision_trees,
config,
})
}
pub async fn register_standard_procedures(&mut self) -> Result<(), AftercareError> {
self.register_mask_mismatch_procedure().await?;
self.register_alpha_drift_procedure().await?;
self.register_merged_bin_procedure().await?;
Ok(())
}
async fn register_mask_mismatch_procedure(&mut self) -> Result<(), AftercareError> {
let procedure = RunbookProcedure {
name: "mask_mismatch".to_string(),
symptoms: vec![
SymptomPattern {
name: "mask_count_deviation".to_string(),
description: "Fit/eval mask count mismatch".to_string(),
criteria: vec![
DetectionCriterion {
metric: "mask_count_ratio".to_string(),
operator: ComparisonOperator::NotEqual,
threshold: 1.0,
duration: Some(Duration::from_secs(300)),
}
],
}
],
data_collection: vec![
DataCollectionStep {
name: "collect_mask_data".to_string(),
data_items: vec![
DataItem {
name: "bin_table".to_string(),
item_type: DataItemType::BinTable,
collection_method: "calibration_debug".to_string(),
},
DataItem {
name: "mask_counts".to_string(),
item_type: DataItemType::MaskCounts,
collection_method: "mask_counter".to_string(),
}
],
timeout: Duration::from_secs(30),
}
],
decision_tree: "mask_mismatch_tree".to_string(),
revert_procedures: vec![
RevertProcedure {
name: "flag_revert".to_string(),
steps: vec![
RevertStep {
description: "Disable CALIB_V22 flag".to_string(),
action: "set_flag(CALIB_V22=false)".to_string(),
expected_outcome: "Flag disabled globally".to_string(),
timeout: Duration::from_secs(30),
}
],
validation: vec![
ValidationStep {
description: "Verify flag disabled".to_string(),
check: ValidationCheck::SystemCheck { check_type: "flag_status".to_string() },
pass_criteria: "CALIB_V22=false confirmed".to_string(),
}
],
}
],
};
self.procedures.insert("mask_mismatch".to_string(), procedure);
Ok(())
}
async fn register_alpha_drift_procedure(&mut self) -> Result<(), AftercareError> {
Ok(())
}
async fn register_merged_bin_procedure(&mut self) -> Result<(), AftercareError> {
Ok(())
}
pub async fn execute_procedure(&self, symptom: &str, data: HashMap<String, f64>) -> Result<RunbookResult, RunbookError> {
if let Some(procedure) = self.procedures.get(symptom) {
info!("📋 Executing runbook procedure: {}", procedure.name);
let collected_data = self.execute_data_collection(&procedure.data_collection, data).await?;
let decision = self.execute_decision_tree(&procedure.decision_tree, &collected_data).await?;
Ok(RunbookResult {
procedure_name: procedure.name.clone(),
decision,
collected_data,
execution_time: SystemTime::now(),
result_status: RunbookStatus::Success,
})
} else {
Err(RunbookError::ProcedureNotFound(symptom.to_string()))
}
}
async fn execute_data_collection(&self, steps: &[DataCollectionStep], initial_data: HashMap<String, f64>) -> Result<HashMap<String, f64>, RunbookError> {
let mut collected_data = initial_data;
for step in steps {
for data_item in &step.data_items {
match data_item.item_type {
DataItemType::BinTable => {
collected_data.insert("bin_count".to_string(), 10.0);
}
DataItemType::AlphaValue => {
collected_data.insert("alpha".to_string(), 0.15);
}
DataItemType::AeceTauValue => {
collected_data.insert("aece_tau".to_string(), 0.008);
}
_ => {
}
}
}
}
Ok(collected_data)
}
async fn execute_decision_tree(&self, tree_name: &str, data: &HashMap<String, f64>) -> Result<DecisionResult, RunbookError> {
Ok(DecisionResult {
decision: "raise_bootstrap".to_string(),
confidence: 0.85,
reasoning: "Alpha drift detected, bootstrap adjustment recommended".to_string(),
recommended_action: ActionType::RaiseBootstrap { factor: 1.1 },
})
}
pub async fn get_status(&self) -> Result<RunbookStatus, AftercareError> {
Ok(RunbookStatus::Ready)
}
}
impl AlertTuningSystem {
pub fn new(config: AlertTuningConfig) -> Result<Self, AftercareError> {
let alert_rules = Self::create_default_alert_rules(&config)?;
let dedup_engine = AlertDeduplicationEngine::new(config.dedup_window);
let escalation_manager = EscalationManager::new();
Ok(Self {
alert_rules,
dedup_engine,
escalation_manager,
config,
})
}
fn create_default_alert_rules(config: &AlertTuningConfig) -> Result<HashMap<String, AlertRule>, AftercareError> {
let mut rules = HashMap::new();
rules.insert("mask_mismatch".to_string(), AlertRule {
name: "mask_mismatch".to_string(),
condition: AlertCondition::Threshold {
metric: "mask_mismatch_detected".to_string(),
operator: ComparisonOperator::GreaterThan,
value: 0.0,
duration: Duration::from_secs(60),
},
severity: AlertSeverity::Critical,
message_template: "CRITICAL: Mask mismatch detected - fit/eval inconsistency".to_string(),
escalation_policy: "immediate".to_string(),
});
rules.insert("scores_out_of_range".to_string(), AlertRule {
name: "scores_out_of_range".to_string(),
condition: AlertCondition::Composite {
conditions: vec![
AlertCondition::Threshold {
metric: "min_score".to_string(),
operator: ComparisonOperator::LessThan,
value: 0.0,
duration: Duration::from_secs(60),
},
AlertCondition::Threshold {
metric: "max_score".to_string(),
operator: ComparisonOperator::GreaterThan,
value: 1.0,
duration: Duration::from_secs(60),
},
],
logic: LogicOperator::Or,
},
severity: AlertSeverity::Critical,
message_template: "CRITICAL: Calibration scores outside [0,1] range".to_string(),
escalation_policy: "immediate".to_string(),
});
rules.insert("alpha_drift".to_string(), AlertRule {
name: "alpha_drift".to_string(),
condition: AlertCondition::Threshold {
metric: "alpha_drift_wow".to_string(),
operator: ComparisonOperator::GreaterThan,
value: config.critical_thresholds.alpha_drift_wow_threshold,
duration: Duration::from_secs(3600),
},
severity: AlertSeverity::Warning,
message_template: "WARNING: Alpha drift >0.05 WoW detected".to_string(),
escalation_policy: "standard".to_string(),
});
rules.insert("merged_bin_critical".to_string(), AlertRule {
name: "merged_bin_critical".to_string(),
condition: AlertCondition::Threshold {
metric: "merged_bin_percent".to_string(),
operator: ComparisonOperator::GreaterThan,
value: config.critical_thresholds.merged_bin_critical_percent,
duration: Duration::from_secs(300),
},
severity: AlertSeverity::Critical,
message_template: "CRITICAL: Merged bin percentage >20%".to_string(),
escalation_policy: "immediate".to_string(),
});
Ok(rules)
}
pub async fn get_active_alerts(&self) -> Result<Vec<AlertSummary>, AftercareError> {
Ok(vec![])
}
pub async fn process_alert(&mut self, alert_type: AlertType, message: String, severity: AlertSeverity) -> Result<String, AftercareError> {
let alert_id = format!("alert_{}_{}",
chrono::Utc::now().timestamp(),
rand::random::<u32>());
if !self.dedup_engine.should_process_alert(&alert_type, &message).await {
return Ok(format!("Alert deduplicated: {}", alert_id));
}
let alert_summary = AlertSummary {
alert_id: alert_id.clone(),
alert_type,
severity,
message,
created_at: SystemTime::now(),
status: AlertStatus::Active,
};
self.escalation_manager.process_alert(&alert_summary).await
.map_err(|e| AftercareError::AlertError(e.to_string()))?;
Ok(alert_id)
}
}
impl AlertDeduplicationEngine {
pub fn new(window: Duration) -> Self {
Self {
active_fingerprints: HashMap::new(),
window,
}
}
pub async fn should_process_alert(&mut self, alert_type: &AlertType, message: &str) -> bool {
let content_hash = format!("{:?}:{}", alert_type, message);
let now = SystemTime::now();
self.active_fingerprints.retain(|_, fingerprint| {
now.duration_since(fingerprint.last_occurrence).unwrap_or_default() < self.window
});
if let Some(fingerprint) = self.active_fingerprints.get_mut(&content_hash) {
fingerprint.last_occurrence = now;
fingerprint.count += 1;
false } else {
self.active_fingerprints.insert(content_hash.clone(), AlertFingerprint {
alert_type: alert_type.clone(),
content_hash,
first_occurrence: now,
last_occurrence: now,
count: 1,
});
true }
}
}
impl EscalationManager {
pub fn new() -> Self {
Self {
policies: HashMap::new(),
active_escalations: HashMap::new(),
}
}
pub async fn process_alert(&mut self, alert: &AlertSummary) -> Result<(), EscalationError> {
match alert.severity {
AlertSeverity::Critical | AlertSeverity::Emergency => {
self.create_immediate_escalation(alert).await?;
}
AlertSeverity::Warning => {
self.create_standard_escalation(alert).await?;
}
AlertSeverity::Info => {
}
}
Ok(())
}
async fn create_immediate_escalation(&mut self, alert: &AlertSummary) -> Result<(), EscalationError> {
let escalation = ActiveEscalation {
alert_id: alert.alert_id.clone(),
current_level: 1,
start_time: SystemTime::now(),
next_escalation: SystemTime::now() + Duration::from_secs(300), status: EscalationStatus::Active,
};
self.active_escalations.insert(alert.alert_id.clone(), escalation);
info!("🚨 Immediate escalation created for alert: {}", alert.alert_id);
Ok(())
}
async fn create_standard_escalation(&mut self, alert: &AlertSummary) -> Result<(), EscalationError> {
let escalation = ActiveEscalation {
alert_id: alert.alert_id.clone(),
current_level: 1,
start_time: SystemTime::now(),
next_escalation: SystemTime::now() + Duration::from_secs(1800), status: EscalationStatus::Active,
};
self.active_escalations.insert(alert.alert_id.clone(), escalation);
info!("⚠️ Standard escalation created for alert: {}", alert.alert_id);
Ok(())
}
}
impl Default for DashboardState {
fn default() -> Self {
let now = SystemTime::now();
Self {
last_update: now,
current_metrics: CalibrationMetrics {
aece: 0.0,
dece: 0.0,
brier: 0.0,
alpha: 0.0,
clamp_rate_percent: 0.0,
merged_bin_percent: 0.0,
timestamp: now,
},
trends: TrendingData {
aece_trend: Vec::new(),
dece_trend: Vec::new(),
brier_trend: Vec::new(),
alpha_trend: Vec::new(),
clamp_rate_trend: Vec::new(),
merged_bin_trend: Vec::new(),
},
slice_highlights: HashMap::new(),
sla_status: SlaStatus {
overall_compliance: 0.0,
component_status: HashMap::new(),
active_violations: Vec::new(),
statistical_enforcement: StatisticalEnforcement {
tests_performed: Vec::new(),
confidence_intervals: HashMap::new(),
significance_levels: HashMap::new(),
},
},
active_alerts: Vec::new(),
health_indicators: HealthIndicators {
overall_health: SystemHealthStatus::Healthy,
component_health: HashMap::new(),
performance_indicators: PerformanceIndicators {
avg_response_time_ms: 0.0,
p95_response_time_ms: 0.0,
p99_response_time_ms: 0.0,
throughput_rps: 0.0,
error_rate_percent: 0.0,
},
reliability_indicators: ReliabilityIndicators {
uptime_24h: 0.0,
mtbf_hours: 0.0,
mttr_minutes: 0.0,
availability_percent: 0.0,
},
},
}
}
}
impl Default for DashboardConfig {
fn default() -> Self {
Self {
retention_duration: Duration::from_secs(7 * 24 * 3600), trend_window: Duration::from_secs(24 * 3600), thresholds: DashboardThresholds::default(),
refresh_rate: Duration::from_secs(30),
}
}
}
impl Default for DashboardThresholds {
fn default() -> Self {
Self {
aece_highlight_threshold: 0.01,
dece_warning_threshold: 0.02,
brier_alert_threshold: 0.1,
alpha_drift_threshold: 0.05,
clamp_rate_warning_threshold: 10.0,
merged_bin_warning_threshold: 5.0,
merged_bin_critical_threshold: 20.0,
}
}
}
impl Default for RunbookConfig {
fn default() -> Self {
Self {
procedure_timeout: Duration::from_secs(300),
data_capture_window: Duration::from_secs(900),
decision_timeout: Duration::from_secs(60),
}
}
}
impl Default for AlertTuningConfig {
fn default() -> Self {
Self {
dedup_window: Duration::from_secs(300), escalation_delays: {
let mut delays = HashMap::new();
delays.insert(AlertSeverity::Info, Duration::from_secs(3600));
delays.insert(AlertSeverity::Warning, Duration::from_secs(1800));
delays.insert(AlertSeverity::Critical, Duration::from_secs(300));
delays.insert(AlertSeverity::Emergency, Duration::from_secs(60));
delays
},
suppression_rules: Vec::new(),
critical_thresholds: CriticalThresholds::default(),
}
}
}
impl Default for CriticalThresholds {
fn default() -> Self {
Self {
mask_mismatch_tolerance: 0.0,
score_range_min: 0.0,
score_range_max: 1.0,
alpha_drift_wow_threshold: 0.05,
merged_bin_critical_percent: 20.0,
}
}
}
impl Default for AftercareConfig {
fn default() -> Self {
Self {
dashboard_refresh_interval: Duration::from_secs(30),
alert_evaluation_window: Duration::from_secs(60),
sla_monitoring_frequency: Duration::from_secs(300),
runbook_response_timeout: Duration::from_secs(600),
alert_dedup_window: Duration::from_secs(300),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunbookResult {
pub procedure_name: String,
pub decision: DecisionResult,
pub collected_data: HashMap<String, f64>,
pub execution_time: SystemTime,
pub result_status: RunbookStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecisionResult {
pub decision: String,
pub confidence: f64,
pub reasoning: String,
pub recommended_action: ActionType,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RunbookStatus {
Ready,
Executing,
Success,
Failed,
Timeout,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AftercareStatus {
pub dashboard_state: DashboardState,
pub active_alerts: Vec<AlertSummary>,
pub runbook_status: RunbookStatus,
pub system_health: SystemHealthStatus,
pub timestamp: SystemTime,
}
#[derive(Debug, Error)]
pub enum RunbookError {
#[error("Procedure not found: {0}")]
ProcedureNotFound(String),
#[error("Data collection failed: {0}")]
DataCollectionFailed(String),
#[error("Decision tree execution failed: {0}")]
DecisionTreeFailed(String),
#[error("Timeout occurred")]
Timeout,
#[error("Configuration error: {0}")]
ConfigurationError(String),
}
#[derive(Debug, Error)]
pub enum EscalationError {
#[error("Escalation policy not found: {0}")]
PolicyNotFound(String),
#[error("Escalation failed: {0}")]
EscalationFailed(String),
#[error("Invalid escalation configuration: {0}")]
InvalidConfiguration(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dashboard_config_default() {
let config = DashboardConfig::default();
assert_eq!(config.retention_duration, Duration::from_secs(7 * 24 * 3600));
assert_eq!(config.thresholds.aece_highlight_threshold, 0.01);
}
#[test]
fn test_critical_thresholds() {
let thresholds = CriticalThresholds::default();
assert_eq!(thresholds.mask_mismatch_tolerance, 0.0);
assert_eq!(thresholds.merged_bin_critical_percent, 20.0);
}
#[tokio::test]
async fn test_alert_deduplication() {
let mut dedup_engine = AlertDeduplicationEngine::new(Duration::from_secs(300));
let should_process1 = dedup_engine.should_process_alert(&AlertType::MaskMismatch, "Test message").await;
assert!(should_process1);
let should_process2 = dedup_engine.should_process_alert(&AlertType::MaskMismatch, "Test message").await;
assert!(!should_process2);
}
#[test]
fn test_runbook_status() {
let status = RunbookStatus::Ready;
assert_eq!(status, RunbookStatus::Ready);
assert_ne!(status, RunbookStatus::Executing);
}
}