From ba0a4f4d2e7d3ae6fb7891b4b3c2fd8eacb3760e Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 12 May 2026 22:14:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E6=AF=8F=E6=97=A5=E9=A3=8E?= =?UTF-8?q?=E9=99=A9=E5=BF=AB=E7=85=A7=E6=89=B9=E9=87=8F=E5=88=B7=E6=96=B0?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - risk_service 新增 refresh_all_patients 方法 - module on_startup 启动每日刷新后台任务 --- crates/erp-ai/src/module.rs | 19 +++++++++++++ crates/erp-ai/src/service/risk_service.rs | 34 ++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/crates/erp-ai/src/module.rs b/crates/erp-ai/src/module.rs index 08a265b..c0cb87c 100644 --- a/crates/erp-ai/src/module.rs +++ b/crates/erp-ai/src/module.rs @@ -312,6 +312,25 @@ impl ErpModule for AiModule { let copilot_handles = crate::event::copilot_consumer::spawn(&ctx.db, &ctx.event_bus); std::mem::forget(copilot_handles); + // 每日凌晨 2:00 批量刷新所有在管患者风险快照 + let refresh_db = ctx.db.clone(); + tokio::spawn(async move { + let mut interval = tokio::time::interval(std::time::Duration::from_secs(86400)); + loop { + interval.tick().await; + match crate::service::risk_service::RiskService::refresh_all_patients(&refresh_db) + .await + { + Ok(count) => { + tracing::info!(patient_count = count, "每日风险快照刷新完成"); + } + Err(e) => { + tracing::warn!(error = %e, "每日风险快照刷新失败"); + } + } + } + }); + tracing::info!( module = "ai", "AI 模块事件处理器已注册(监听 ai.* 事件 + Copilot 事件)" diff --git a/crates/erp-ai/src/service/risk_service.rs b/crates/erp-ai/src/service/risk_service.rs index 804f3e9..ca1d913 100644 --- a/crates/erp-ai/src/service/risk_service.rs +++ b/crates/erp-ai/src/service/risk_service.rs @@ -5,7 +5,7 @@ use crate::entity::copilot_risk_snapshots; use crate::entity::copilot_rules; use crate::provider::registry::ProviderRegistry; use erp_core::error::AppResult; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, FromQueryResult, QueryFilter, Set}; use std::sync::Arc; use uuid::Uuid; @@ -165,4 +165,36 @@ impl RiskService { let _ = db; Ok(serde_json::json!({})) } + + /// 每日批量刷新所有在管患者的风险快照 + /// 通过 raw SQL 查询患者列表(因为 erp-ai 不依赖 erp-health entity) + pub async fn refresh_all_patients(db: &sea_orm::DatabaseConnection) -> AppResult { + #[derive(sea_orm::FromQueryResult)] + struct PatientRow { + id: Uuid, + tenant_id: Uuid, + } + + let patients: Vec = + PatientRow::find_by_statement(sea_orm::Statement::from_sql_and_values( + sea_orm::DatabaseBackend::Postgres, + "SELECT id, tenant_id FROM patients WHERE deleted_at IS NULL", + [], + )) + .all(db) + .await?; + + let total = patients.len() as u64; + for p in &patients { + if let Err(e) = Self::compute_risk(db, p.tenant_id, p.id).await { + tracing::warn!( + patient_id = %p.id, + tenant_id = %p.tenant_id, + error = %e, + "风险评分刷新失败" + ); + } + } + Ok(total) + } }