feat(ai): 告警洞察生成逻辑 + 事件消费者增强
- engine.rs 新增 generate_anomaly_insights(过滤 info 级别) - copilot_consumer 在风险评分后自动生成 warning/critical 告警洞察
This commit is contained in:
@@ -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 crate::copilot::scoring::{RiskScore, calculate_risk};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
@@ -12,3 +12,84 @@ impl CopilotEngine {
|
|||||||
calculate_risk(matched)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,8 +58,45 @@ async fn process_event(db: &sea_orm::DatabaseConnection, event: &erp_core::event
|
|||||||
},
|
},
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
let _ =
|
// 风险评分计算
|
||||||
crate::service::risk_service::RiskService::compute_risk(db, tenant_id, patient_id).await;
|
if let Ok(risk) =
|
||||||
|
crate::service::risk_service::RiskService::compute_risk(db, tenant_id, patient_id).await
|
||||||
|
{
|
||||||
|
// 异常检测:告警级规则匹配生成洞察
|
||||||
|
let matched_with_severity: Vec<_> = risk
|
||||||
|
.matched_rules
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| (r.rule_id, r.name, r.score, r.severity, r.suggestion))
|
||||||
|
.collect();
|
||||||
|
let anomaly_insights = crate::copilot::engine::generate_anomaly_insights(
|
||||||
|
&patient_id.to_string(),
|
||||||
|
&matched_with_severity,
|
||||||
|
);
|
||||||
|
for insight_data in anomaly_insights {
|
||||||
|
let severity = insight_data["severity"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or("warning")
|
||||||
|
.to_string();
|
||||||
|
let title = insight_data["title"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or("异常告警")
|
||||||
|
.to_string();
|
||||||
|
let _ = crate::service::insight_service::InsightService::create_insight(
|
||||||
|
db,
|
||||||
|
tenant_id,
|
||||||
|
patient_id,
|
||||||
|
"anomaly".into(),
|
||||||
|
"rule".into(),
|
||||||
|
Some(severity),
|
||||||
|
title,
|
||||||
|
insight_data,
|
||||||
|
None,
|
||||||
|
168, // 7 天过期
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
let _ = erp_core::events::mark_event_processed(db, event.id, "copilot_consumer").await;
|
let _ = erp_core::events::mark_event_processed(db, event.id, "copilot_consumer").await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user