// 自优化报告生成 — 7 项指标 + 退化检测 // 深度整合步骤 5:收集所有步骤结果 → 生成 WebSocket 推送的报告 use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// 整合完整报告 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConsolidationReport { pub timestamp: String, pub duration_secs: f64, // Step 1: 聚类 pub clusters_found: usize, pub noise_points: usize, pub duplicate_pairs: usize, // Step 2: 修剪 pub pruned: Option, // Step 3: 衰减校准 pub decay_rates: HashMap, pub r_squared_values: HashMap, // Step 4: 质量回溯 pub quality_score: f64, pub low_info_count: usize, pub hallucinations: usize, // Step 5: 自优化仪表盘 pub dashboard: DashboardSnapshot, // 退化检测 pub degradation: Vec, } /// 修剪统计(引用 graph_prune 模块) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PruneStats { pub isolated_nodes_removed: usize, pub low_weight_edges_removed: usize, pub redundant_edges_merged: usize, pub nodes_before: usize, pub nodes_after: usize, pub edges_before: usize, pub edges_after: usize, } /// 仪表盘快照 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DashboardSnapshot { /// 召回有用率 pub recall_useful_rate: f64, /// 召回命中率 pub recall_hit_rate: f64, /// 缺口闭环率 pub gap_closure_rate: f64, /// 修正传播率 pub cascade_propagation_rate: f64, /// 垃圾淘汰速度 (条/天) pub deprecation_rate: f64, /// 蒸馏信息损失率 pub distill_loss_rate: f64, /// 冲突自动裁决率 pub auto_resolve_rate: f64, } /// 退化告警 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DegradationAlert { pub metric: String, pub current_value: f64, pub previous_value: f64, pub severity: String, // warning / critical pub message: String, } /// 报告生成器 pub struct ReportGenerator { previous_metrics: Option, } impl ReportGenerator { pub fn new() -> Self { Self { previous_metrics: None } } /// 生成整合报告 pub fn generate( &self, clusters: usize, noise: usize, duplicate_pairs: usize, pruned: Option, decay_rates: HashMap, r_squared: HashMap, quality_score: f64, low_info: usize, hallucinations: usize, dash: &DashboardSnapshot, ) -> ConsolidationReport { let degradation = self.detect_degradation(dash); let report = ConsolidationReport { timestamp: chrono_now(), duration_secs: 0.0, clusters_found: clusters, noise_points: noise, duplicate_pairs, pruned, decay_rates, r_squared_values: r_squared, quality_score, low_info_count: low_info, hallucinations, dashboard: dash.clone(), degradation, }; eprintln!( "[report] consolidation complete: clusters={}, quality={:.3}, alerts={}", report.clusters_found, report.quality_score, report.degradation.len(), ); report } /// 退化检测 fn detect_degradation(&self, current: &DashboardSnapshot) -> Vec { let mut alerts = Vec::new(); let prev = match &self.previous_metrics { Some(p) => p, None => return alerts, // 首次运行 }; // 检查各项指标的退化 let checks: Vec<(&str, f64, f64, f64, &str)> = vec![ ("召回有用率", current.recall_useful_rate, prev.recall_useful_rate, 0.7, "连续下降 → 检查 not_useful 共性"), ("召回命中率", current.recall_hit_rate, prev.recall_hit_rate, 0.5, "审查 Embedding/Rerank 管线"), ("缺口闭环率", current.gap_closure_rate, prev.gap_closure_rate, 0.0, "14天=0 → 审查缺口分类准确性"), ("蒸馏信息损失率", current.distill_loss_rate, prev.distill_loss_rate, 0.3, "信息损失 > 0.3 → 重蒸馏受影响批次"), ]; for (name, cur, prev_val, threshold, msg) in checks { let drop = prev_val - cur; if drop > 0.1 && cur < threshold { alerts.push(DegradationAlert { metric: name.to_string(), current_value: cur, previous_value: prev_val, severity: if cur < threshold * 0.5 { "critical".into() } else { "warning".into() }, message: msg.to_string(), }); } } // 垃圾淘汰异常检测 let deprec = current.deprecation_rate; if deprec > 20.0 { alerts.push(DegradationAlert { metric: "垃圾淘汰速度".into(), current_value: deprec, previous_value: prev.deprecation_rate, severity: "critical".into(), message: "> 20/天 异常 → 自动暂停遗忘".into(), }); } else if deprec == 0.0 && prev.deprecation_rate > 0.0 { alerts.push(DegradationAlert { metric: "垃圾淘汰速度".into(), current_value: 0.0, previous_value: prev.deprecation_rate, severity: "warning".into(), message: "= 0/天 → 遗忘可能失效".into(), }); } alerts } } /// 生成简易 ISO 8601 时间戳 fn chrono_now() -> String { use std::time::SystemTime; let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap_or_default(); let secs = now.as_secs(); let days = secs / 86400; let remaining = secs % 86400; let hours = remaining / 3600; let minutes = (remaining % 3600) / 60; let seconds = remaining % 60; // 从 Unix epoch 推算日期 (简化版,生产建议用 chrono crate) let (y, m, d) = civil_from_days(days as i64 + 719468); format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", y, m, d, hours, minutes, seconds) } fn civil_from_days(days: i64) -> (i64, u32, u32) { let z = days + 719468; let era = if z >= 0 { z } else { z - 146096 } / 146097; let doe = (z - era * 146097) as u32; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe as i64 + era * 400; let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let m = if mp < 10 { mp + 3 } else { mp - 9 }; let y = if m <= 2 { y + 1 } else { y }; (y, m, d) }