feat(ai): Phase 2B 洞察→推送→反馈闭环 — 风险评分+通知+建议反馈

- 风险评分引擎 load_patient_data 实装(体征+化验异常)
- refresh_all_patients 高风险自动创建洞察+事件推送
- erp-message 订阅 copilot.insight.created 推送医护通知
- 每日 cron 增加洞察过期清理+建议过期清理
- POST /ai/suggestions/{id}/feedback 建议反馈端点
- SuggestionFeedbackService 反馈服务层
- 小程序健康页建议卡片增加采纳/忽略/咨询医生按钮
This commit is contained in:
iven
2026-05-19 01:19:09 +08:00
parent 2660f1afff
commit 9576e80175
10 changed files with 504 additions and 32 deletions

View File

@@ -1,6 +1,6 @@
use axum::Router;
use axum::routing::{delete, get, put};
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
use sea_orm::{ColumnTrait, EntityTrait, FromQueryResult, QueryFilter};
use std::sync::Arc;
use tokio::sync::Semaphore;
use uuid::Uuid;
@@ -1003,6 +1003,69 @@ async fn handle_workflow_event(
"医生在线状态变更"
);
}
// AI Copilot 洞察生成 → 通知主管医生
"copilot.insight.created" => {
let patient_id = event
.payload
.get("patient_id")
.and_then(|v| v.as_str())
.and_then(|s| uuid::Uuid::parse_str(s).ok());
let severity = event
.payload
.get("severity")
.and_then(|v| v.as_str())
.unwrap_or("warning");
let title = event
.payload
.get("title")
.and_then(|v| v.as_str())
.unwrap_or("AI 健康洞察");
if let Some(pid) = patient_id {
// 查询患者的责任医生(通过 follow_up_task 的 assigned_to
#[derive(sea_orm::FromQueryResult)]
struct DoctorRow {
assigned_to: uuid::Uuid,
}
let doctor: Option<DoctorRow> = DoctorRow::find_by_statement(
sea_orm::Statement::from_sql_and_values(
sea_orm::DatabaseBackend::Postgres,
"SELECT assigned_to FROM follow_up_task WHERE tenant_id = $1 AND patient_id = $2 AND assigned_to IS NOT NULL AND deleted_at IS NULL AND status IN ('pending', 'in_progress') ORDER BY created_at DESC LIMIT 1",
[event.tenant_id.into(), pid.into()],
),
)
.one(db)
.await
.unwrap_or(None);
if let Some(doc) = doctor {
let priority = match severity {
"critical" => "urgent",
"warning" | "high" => "important",
_ => "normal",
};
if should_skip_for_dnd(event.tenant_id, doc.assigned_to, priority, db).await {
return Ok(());
}
let _ = crate::service::message_service::MessageService::send_system(
event.tenant_id,
doc.assigned_to,
format!("AI 健康洞察:{}", title),
format!(
"AI 系统检测到患者存在「{}」级别的健康风险,请及时关注。洞察内容:{}",
severity, title
),
priority,
Some("ai_insight".to_string()),
Some(event.id),
db,
event_bus,
)
.await
.map_err(|e| e.to_string())?;
}
}
}
// 关怀计划激活 — 温暖通知患者
"care_plan.activated" => {
let patient_id = event