feat(ai): 告警洞察生成逻辑 + 事件消费者增强

- engine.rs 新增 generate_anomaly_insights(过滤 info 级别)
- copilot_consumer 在风险评分后自动生成 warning/critical 告警洞察
This commit is contained in:
iven
2026-05-12 22:34:11 +08:00
parent a87425e551
commit a48ad6ed33
2 changed files with 121 additions and 3 deletions

View File

@@ -1,4 +1,4 @@
use crate::copilot::rules::{RuleData, evaluate_rules};
use crate::copilot::rules::{MatchedRuleData, RuleData, evaluate_rules};
use crate::copilot::scoring::{RiskScore, calculate_risk};
use serde_json::Value;
@@ -12,3 +12,84 @@ impl CopilotEngine {
calculate_risk(matched)
}
}
/// 根据规则匹配结果生成异常洞察
/// 仅 warning 和 critical 级别生成告警洞察info 级别仅在档案内展示
pub fn generate_anomaly_insights(
patient_id: &str,
matched: &[MatchedRuleData],
) -> Vec<serde_json::Value> {
matched
.iter()
.filter(|(_, _, _, severity, _)| severity == "warning" || severity == "critical")
.map(|(rule_id, name, score, severity, suggestion)| {
serde_json::json!({
"patient_id": patient_id,
"insight_type": "anomaly",
"source": "rule",
"severity": severity,
"title": name,
"content": {
"rule_id": rule_id.to_string(),
"score": score,
"suggestion": suggestion,
},
})
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_anomaly_insight_critical() {
let matched = vec![(
uuid::Uuid::now_v7(),
"高钾血症风险".into(),
4,
"critical".into(),
Some("立即通知主治医生".into()),
)];
let insights = generate_anomaly_insights("patient-123", &matched);
assert_eq!(insights.len(), 1);
assert_eq!(insights[0]["severity"], "critical");
assert_eq!(insights[0]["insight_type"], "anomaly");
}
#[test]
fn test_generate_anomaly_insight_filters_info() {
let matched = vec![(
uuid::Uuid::now_v7(),
"体重轻微波动".into(),
1,
"info".into(),
None,
)];
let insights = generate_anomaly_insights("patient-123", &matched);
assert!(insights.is_empty());
}
#[test]
fn test_generate_anomaly_insight_warning_and_critical() {
let matched = vec![
(
uuid::Uuid::now_v7(),
"eGFR下降".into(),
3,
"warning".into(),
Some("建议调整".into()),
),
(
uuid::Uuid::now_v7(),
"透析质量危急".into(),
5,
"critical".into(),
Some("紧急评估".into()),
),
];
let insights = generate_anomaly_insights("patient-123", &matched);
assert_eq!(insights.len(), 2);
}
}