From 44dcfbd5cb7aa3312c72e669b464984c0af01f4c Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 12 May 2026 22:00:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20Copilot=20=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E6=B6=88=E8=B4=B9=E8=80=85=EF=BC=88=E8=AE=A2=E9=98=85=20health?= =?UTF-8?q?=20=E4=BA=8B=E4=BB=B6=E8=A7=A6=E5=8F=91=E9=A3=8E=E9=99=A9?= =?UTF-8?q?=E8=AF=84=E5=88=86=E5=88=B7=E6=96=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/erp-ai/src/event/copilot_consumer.rs | 85 +++++++++++++++++++++ crates/erp-ai/src/event/mod.rs | 1 + crates/erp-ai/src/lib.rs | 1 + crates/erp-ai/src/module.rs | 9 ++- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 crates/erp-ai/src/event/copilot_consumer.rs create mode 100644 crates/erp-ai/src/event/mod.rs diff --git a/crates/erp-ai/src/event/copilot_consumer.rs b/crates/erp-ai/src/event/copilot_consumer.rs new file mode 100644 index 0000000..8f750e4 --- /dev/null +++ b/crates/erp-ai/src/event/copilot_consumer.rs @@ -0,0 +1,85 @@ +//! Copilot 事件消费者 — 订阅 health 模块事件,触发风险评分刷新 + +/// Copilot 关注的事件前缀 +pub fn copilot_event_prefixes() -> Vec { + vec![ + "daily_monitoring.".to_string(), + "lab_report.".to_string(), + "follow_up.".to_string(), + "patient.".to_string(), + ] +} + +/// 判断事件是否应触发风险评分刷新 +pub fn should_trigger_risk_refresh(event_type: &str) -> bool { + matches!( + event_type, + "daily_monitoring.created" + | "lab_report.reviewed" + | "follow_up.completed" + | "follow_up.overdue" + | "patient.created" + ) +} + +/// 启动 Copilot 事件消费者 +pub fn spawn( + db: &sea_orm::DatabaseConnection, + event_bus: &erp_core::events::EventBus, +) -> Vec { + let mut handles = Vec::new(); + for prefix in copilot_event_prefixes() { + let (mut rx, handle) = event_bus.subscribe_filtered(prefix); + handles.push(handle); + let db = db.clone(); + tokio::spawn(async move { + while let Some(event) = rx.recv().await { + if should_trigger_risk_refresh(&event.event_type) { + process_event(&db, &event).await; + } + } + }); + } + handles +} + +async fn process_event(db: &sea_orm::DatabaseConnection, event: &erp_core::events::DomainEvent) { + if erp_core::events::is_event_processed(db, event.id, "copilot_consumer") + .await + .unwrap_or(false) + { + return; + } + let tenant_id = event.tenant_id; + let patient_id = match event.payload.get("patient_id").and_then(|v| v.as_str()) { + Some(id) => match uuid::Uuid::parse_str(id) { + Ok(uid) => uid, + Err(_) => return, + }, + None => return, + }; + let _ = + crate::service::risk_service::RiskService::compute_risk(db, tenant_id, patient_id).await; + let _ = erp_core::events::mark_event_processed(db, event.id, "copilot_consumer").await; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_event_prefixes_include_health_events() { + let prefixes = copilot_event_prefixes(); + assert!(prefixes.contains(&"daily_monitoring.".to_string())); + assert!(prefixes.contains(&"lab_report.".to_string())); + assert!(prefixes.contains(&"follow_up.".to_string())); + assert!(prefixes.contains(&"patient.".to_string())); + } + + #[test] + fn test_should_trigger_risk_refresh_for_vital_signs() { + assert!(should_trigger_risk_refresh("daily_monitoring.created")); + assert!(should_trigger_risk_refresh("lab_report.reviewed")); + assert!(!should_trigger_risk_refresh("patient.updated")); + } +} diff --git a/crates/erp-ai/src/event/mod.rs b/crates/erp-ai/src/event/mod.rs new file mode 100644 index 0000000..e96eaee --- /dev/null +++ b/crates/erp-ai/src/event/mod.rs @@ -0,0 +1 @@ +pub mod copilot_consumer; diff --git a/crates/erp-ai/src/lib.rs b/crates/erp-ai/src/lib.rs index a4b460f..3dc3728 100644 --- a/crates/erp-ai/src/lib.rs +++ b/crates/erp-ai/src/lib.rs @@ -3,6 +3,7 @@ pub mod copilot; pub mod dto; pub mod entity; pub mod error; +pub mod event; pub mod handler; pub mod knowledge; pub mod module; diff --git a/crates/erp-ai/src/module.rs b/crates/erp-ai/src/module.rs index d741efe..08a265b 100644 --- a/crates/erp-ai/src/module.rs +++ b/crates/erp-ai/src/module.rs @@ -308,7 +308,14 @@ impl ErpModule for AiModule { } }); - tracing::info!(module = "ai", "AI 模块事件处理器已注册(监听 ai.* 事件)"); + // Copilot 事件消费者 — 订阅 health 事件触发风险评分刷新 + let copilot_handles = crate::event::copilot_consumer::spawn(&ctx.db, &ctx.event_bus); + std::mem::forget(copilot_handles); + + tracing::info!( + module = "ai", + "AI 模块事件处理器已注册(监听 ai.* 事件 + Copilot 事件)" + ); Ok(()) } }