diff --git a/docs/superpowers/plans/2026-05-01-ai-action-loop-plan.md b/docs/superpowers/plans/2026-05-01-ai-action-loop-plan.md index 1f5daed..34ced52 100644 --- a/docs/superpowers/plans/2026-05-01-ai-action-loop-plan.md +++ b/docs/superpowers/plans/2026-05-01-ai-action-loop-plan.md @@ -1025,3 +1025,688 @@ git push --- Chunk 1 完成。下一步进入 Chunk 2(事件集成 + BPMN 流程定义 + 行动分发)。 + +--- + +## Chunk 2: 事件集成 + BPMN 流程 + 行动分发(Phase 2) + +### Task 12: AI 行动分发服务 + +**Files:** +- Create: `crates/erp-health/src/service/ai_action_dispatcher.rs` + +- [ ] **Step 1: 编写分发器测试(TDD RED)** + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn route_low_risk_to_auto_execute() { + let decision = dispatch_decision(&RiskLevel::Low, &SuggestionType::Alert); + assert_eq!(decision.execution_mode, ExecutionMode::AutoExecute); + assert_eq!(decision.response_timeout, None); + } + + #[test] + fn route_medium_risk_to_doctor_review() { + let decision = dispatch_decision(&RiskLevel::Medium, &SuggestionType::Followup); + assert_eq!(decision.execution_mode, ExecutionMode::DoctorReview); + assert_eq!(decision.response_timeout, Some(Duration::from_secs(86400))); // 24h + } + + #[test] + fn route_high_risk_to_urgent_confirm() { + let decision = dispatch_decision(&RiskLevel::High, &SuggestionType::Alert); + assert_eq!(decision.execution_mode, ExecutionMode::UrgentConfirm); + assert_eq!(decision.response_timeout, Some(Duration::from_secs(14400))); // 4h + } +} +``` + +- [ ] **Step 2: 运行测试确认失败** + +- [ ] **Step 3: 实现行动分发器** + +```rust +// crates/erp-health/src/service/ai_action_dispatcher.rs +use std::time::Duration; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; +use sea_orm::DatabaseConnection; +use erp_core::error::AppResult; +use erp_core::events::EventBus; + +/// 执行模式 +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ExecutionMode { + AutoExecute, + DoctorReview, + UrgentConfirm, +} + +/// 分发决策 +#[derive(Debug, Clone)] +pub struct DispatchDecision { + pub execution_mode: ExecutionMode, + pub response_timeout: Option, +} + +/// 根据风险等级和建议类型生成执行决策 +pub fn dispatch_decision(risk_level: &str, suggestion_type: &str) -> DispatchDecision { + match risk_level { + "low" => DispatchDecision { + execution_mode: ExecutionMode::AutoExecute, + response_timeout: None, + }, + "medium" => DispatchDecision { + execution_mode: ExecutionMode::DoctorReview, + response_timeout: Some(Duration::from_secs(86400)), // 24h + }, + "high" => DispatchDecision { + execution_mode: ExecutionMode::UrgentConfirm, + response_timeout: Some(Duration::from_secs(14400)), // 4h + }, + _ => DispatchDecision { + execution_mode: ExecutionMode::DoctorReview, + response_timeout: Some(Duration::from_secs(86400)), + }, + } +} + +/// 处理 AI 建议事件:根据风险等级分发到不同执行路径 +pub async fn handle_ai_suggestions( + db: &DatabaseConnection, + event_bus: &EventBus, + tenant_id: Uuid, + analysis_id: Uuid, + patient_id: Uuid, + doctor_id: Option, + suggestions: &[serde_json::Value], + risk_level: &str, +) -> AppResult<()> { + for suggestion in suggestions { + let suggestion_type = suggestion["type"].as_str().unwrap_or("alert"); + let decision = dispatch_decision(risk_level, suggestion_type); + + match decision.execution_mode { + ExecutionMode::AutoExecute => { + execute_action(db, event_bus, tenant_id, patient_id, suggestion_type, suggestion).await?; + } + ExecutionMode::DoctorReview | ExecutionMode::UrgentConfirm => { + create_pending_action( + db, event_bus, tenant_id, patient_id, doctor_id, + suggestion_type, suggestion, risk_level, &decision, + ).await?; + } + } + } + Ok(()) +} + +async fn execute_action( + db: &DatabaseConnection, + event_bus: &EventBus, + tenant_id: Uuid, + patient_id: Uuid, + action_type: &str, + params: &serde_json::Value, +) -> AppResult<()> { + match action_type { + "alert" => { + // 直接发送预警通知 + let event = erp_core::events::DomainEvent::new( + "health.ai_alert.sent", + tenant_id, + serde_json::json!({ + "patient_id": patient_id, + "alert_type": "ai_risk_warning", + "severity": params.get("severity").and_then(|v| v.as_str()).unwrap_or("warning"), + "message": params.get("message").and_then(|v| v.as_str()).unwrap_or(""), + "source": "ai_analysis", + }), + ); + event_bus.publish(event, db).await?; + } + "followup" | "appointment" => { + // 低风险的随访/预约也可以自动创建,但仍通知医生 + let event = erp_core::events::DomainEvent::new( + "health.ai_action.auto_executed", + tenant_id, + serde_json::json!({ + "patient_id": patient_id, + "action_type": action_type, + "params": params, + }), + ); + event_bus.publish(event, db).await?; + } + _ => {} + } + Ok(()) +} + +async fn create_pending_action( + db: &DatabaseConnection, + event_bus: &EventBus, + tenant_id: Uuid, + patient_id: Uuid, + doctor_id: Option, + action_type: &str, + params: &serde_json::Value, + risk_level: &str, + decision: &DispatchDecision, +) -> AppResult<()> { + let event = erp_core::events::DomainEvent::new( + "health.ai_action.pending_approval", + tenant_id, + serde_json::json!({ + "patient_id": patient_id, + "doctor_id": doctor_id, + "action_type": action_type, + "risk_level": risk_level, + "timeout_seconds": decision.response_timeout.map(|d| d.as_secs()), + "params": params, + }), + ); + event_bus.publish(event, db).await?; + Ok(()) +} +``` + +- [ ] **Step 4: 运行测试确认通过** + +- [ ] **Step 5: 提交** + +```bash +git add crates/erp-health/src/service/ai_action_dispatcher.rs +git commit -m "feat(health): AI 行动分发器 — 风险分级路由到自动执行/医生审批/紧急确认" +``` + +--- + +### Task 13: erp-health 事件消费者 — 订阅 ai.analysis.completed + +**Files:** +- Modify: `crates/erp-health/src/event.rs` + +- [ ] **Step 1: 在 register_handlers_with_state 中新增消费者** + +在 `crates/erp-health/src/event.rs` 的 `register_handlers_with_state` 函数中,在已有的 `ai.analysis.completed` 通知消费者之后,新增行动分发消费者: + +```rust +// 在现有的 ai_analysis_notifier 消费者之后 + +// AI→行动闭环消费者 +let action_db = state.db.clone(); +let action_event_bus = state.event_bus.clone(); +let (mut ai_action_rx, ai_action_handle) = event_bus.subscribe_filtered("ai.analysis.".to_string()); + +tokio::spawn(async move { + while let Some(event) = ai_action_rx.recv().await { + if event.event_type != "ai.analysis.completed" { continue; } + + let consumer_id = "ai_action_dispatcher"; + if let Ok(true) = erp_core::events::is_event_processed(&action_db, event.id, consumer_id).await { + continue; + } + + let tenant_id = event.tenant_id; + let payload = &event.payload; + let analysis_id = payload.get("analysis_id").and_then(|v| v.as_str()).unwrap_or(""); + let patient_id = payload.get("patient_id").and_then(|v| v.as_str()).unwrap_or(""); + let doctor_id = payload.get("doctor_id").and_then(|v| v.as_str()); + let risk_level = payload.get("risk_level").and_then(|v| v.as_str()).unwrap_or("medium"); + let suggestion_count = payload.get("suggestion_count").and_then(|v| v.as_u64()).unwrap_or(0); + + // 只有有建议时才触发行动分发 + if suggestion_count > 0 { + // 从 ai_suggestion 表加载建议列表 + if let Ok(suggestions) = crate::service::ai_suggestion_loader::load_by_analysis( + &action_db, tenant_id, analysis_id + ).await { + let _ = crate::service::ai_action_dispatcher::handle_ai_suggestions( + &action_db, + &action_event_bus, + tenant_id, + analysis_id.parse().unwrap_or_default(), + patient_id.parse().unwrap_or_default(), + doctor_id.and_then(|s| s.parse().ok()), + &suggestions, + risk_level, + ).await; + } + } + + let _ = erp_core::events::mark_event_processed(&action_db, event.id, consumer_id).await; + } +}); +``` + +- [ ] **Step 2: 创建建议加载辅助函数** + +在 `crates/erp-health/src/service/` 中新建 `ai_suggestion_loader.rs`,从 erp-ai 的 `ai_suggestion` 表读取建议列表: + +```rust +// 通过 raw SQL 跨 crate 读取 ai_suggestion 表 +pub async fn load_by_analysis( + db: &DatabaseConnection, + tenant_id: Uuid, + analysis_id: &str, +) -> AppResult> { + // 使用 sea_orm raw query 从 ai_suggestion 表查询 + // ... +} +``` + +- [ ] **Step 3: 运行 `cargo check -p erp-health` 验证** + +- [ ] **Step 4: 提交** + +```bash +git add crates/erp-health/src/event.rs crates/erp-health/src/service/ai_suggestion_loader.rs +git commit -m "feat(health): 订阅 ai.analysis.completed — 行动分发事件消费者" +``` + +--- + +### Task 14: BPMN 流程定义 — 审计前置条件 + +**前置条件:** Spec 要求 Phase 2 启动前审计 erp-workflow BPMN 功能覆盖度。 + +- [ ] **Step 1: 审计 erp-workflow 支持的能力** + +检查 `crates/erp-workflow/src/engine/` 和 `dto.rs`: +- `ExclusiveGateway` — 条件分支:确认支持 +- `UserTask` — 医生审批节点:确认支持 +- `ServiceTask` — HTTP 调用节点:确认支持 +- 定时器边界事件:检查 `timeout.rs` 支持情况 +- 信号事件:检查是否支持 + +- [ ] **Step 2: 评估审计结果** + +如果所有能力已支持 → 继续 Task 15-17 +如果关键能力缺失 → 回退到 Task 15-alt(Action Registry 模式,不依赖 BPMN) + +--- + +### Task 15: BPMN 流程定义 — AI 随访流程 + +**Files:** +- Create: `crates/erp-server/src/seed/ai_followup_workflow.rs`(或通过 API seed) +- Reference: `crates/erp-workflow/src/service/definition_service.rs` + +- [ ] **Step 1: 定义随访流程 JSON** + +通过 `DefinitionService::create()` API 创建流程定义。节点设计: + +``` +StartEvent → ExclusiveGateway(风险分级) + → [low] → ServiceTask(自动创建随访) → EndEvent + → [medium] → UserTask(医生审批) → [approve] → ServiceTask(创建随访) → EndEvent + → [reject] → EndEvent + → [high] → UserTask(紧急确认, 4h超时) → [confirm] → ServiceTask(创建随访) → EndEvent + → [reject] → EndEvent + → [timeout] → ServiceTask(升级通知) → EndEvent +``` + +- [ ] **Step 2: 通过 seed 或 migration 插入流程定义** + +在 `erp-server/src/main.rs` 启动时检查是否存在 `ai_followup_workflow` 流程定义,不存在则创建。 + +- [ ] **Step 3: 提交** + +```bash +git add crates/erp-server/src/seed/ai_followup_workflow.rs crates/erp-server/src/main.rs +git commit -m "feat(workflow): AI 随访流程 BPMN 定义 — 风险分级 + 医生审批 + 自动创建" +``` + +--- + +### Task 16: BPMN 流程定义 — AI 预约流程 + +**Files:** +- Create: `crates/erp-server/src/seed/ai_appointment_workflow.rs` + +- [ ] **Step 1: 定义预约流程 JSON** + +``` +StartEvent → ExclusiveGateway(风险分级) + → [low] → ServiceTask(推荐预约时段) → EndEvent + → [medium] → UserTask(医生确认时段) → ServiceTask(创建预约) → EndEvent + → [high] → UserTask(紧急预约, 4h超时) → ServiceTask(创建预约) → EndEvent +``` + +- [ ] **Step 2: Seed 到数据库** + +- [ ] **Step 3: 提交** + +```bash +git add crates/erp-server/src/seed/ai_appointment_workflow.rs crates/erp-server/src/main.rs +git commit -m "feat(workflow): AI 预约流程 BPMN 定义 — 智能时段推荐" +``` + +--- + +### Task 17: BPMN 流程定义 — AI 预警流程 + +**Files:** +- Create: `crates/erp-server/src/seed/ai_alert_workflow.rs` + +- [ ] **Step 1: 定义预警流程 JSON** + +``` +StartEvent → ExclusiveGateway(风险分级) + → [low] → ServiceTask(双通道发送通知) → EndEvent + → [medium] → ServiceTask(推送给医生) → UserTask(24h响应) → EndEvent + → [timeout] → ServiceTask(自动提醒) → EndEvent + → [high] → ServiceTask(即时推送+仪表盘标红) → UserTask(4h确认) → EndEvent + → [timeout] → ServiceTask(升级上级) → EndEvent +``` + +- [ ] **Step 2: Seed 到数据库** + +- [ ] **Step 3: 提交** + +```bash +git add crates/erp-server/src/seed/ai_alert_workflow.rs crates/erp-server/src/main.rs +git commit -m "feat(workflow): AI 预警流程 BPMN 定义 — 分级通知+超时升级" +``` + +--- + +### Task 18: 行动分发 → 工作流启动集成 + +**Files:** +- Modify: `crates/erp-health/src/service/ai_action_dispatcher.rs` + +- [ ] **Step 1: 在分发器中集成工作流启动** + +在 `create_pending_action` 和需要审批的路径中,调用 `InstanceService::start()` 启动对应的 BPMN 流程实例: + +```rust +// 根据 suggestion_type 选择流程定义 key +let workflow_key = match action_type { + "followup" => "ai_followup_workflow", + "appointment" => "ai_appointment_workflow", + "alert" => "ai_alert_workflow", + _ => return Ok(()), +}; + +// 通过 event 触发工作流启动(解耦,不直接依赖 erp-workflow) +let event = erp_core::events::DomainEvent::new( + "workflow.ai_action.start_requested", + tenant_id, + serde_json::json!({ + "workflow_key": workflow_key, + "patient_id": patient_id, + "doctor_id": doctor_id, + "risk_level": risk_level, + "action_type": action_type, + "params": params, + }), +); +event_bus.publish(event, db).await?; +``` + +- [ ] **Step 2: 在 erp-workflow 事件处理器中消费启动请求** + +在 `crates/erp-workflow/src/module.rs` 的事件注册中,新增 `workflow.ai_action.start_requested` 消费者,调用 `InstanceService::start()`。 + +- [ ] **Step 3: 运行 `cargo check` 全 workspace 验证** + +- [ ] **Step 4: 提交** + +```bash +git add crates/erp-health/src/service/ai_action_dispatcher.rs crates/erp-workflow/src/module.rs +git commit -m "feat(health+workflow): 行动分发→工作流启动集成 — 事件驱动 BPMN 实例化" +``` + +--- + +### Task 19: Chunk 2 端到端验证 + +- [ ] **Step 1: 启动后端服务** + +```bash +cd crates/erp-server && cargo run +``` + +- [ ] **Step 2: 触发 AI 分析** + +POST `/api/v1/ai/analyze/trends` → 确认结构化建议已创建 + +- [ ] **Step 3: 验证事件链** + +检查日志确认: +- `ai.analysis.completed` 事件已发布 +- `health.ai_action.pending_approval` 或 `health.ai_alert.sent` 事件已触发 +- BPMN 流程实例已创建 + +- [ ] **Step 4: 验证医生审批** + +通过 Task API 完成医生审批任务,确认随访/预约已创建 + +- [ ] **Step 5: 推送** + +```bash +git push +``` + +--- + +Chunk 2 完成。下一步进入 Chunk 3(闭环对比 + 前端展示)。 + +--- + +## Chunk 3: 闭环对比 + 前端展示(Phase 3-4) + +### Task 20: 随访完成 → 再分析触发 + +**Files:** +- Modify: `crates/erp-health/src/event.rs` + +- [ ] **Step 1: 扩展 follow_up.completed 事件消费** + +在现有的 `follow_up.completed` 事件处理中,检查该随访是否由 AI 建议触发(通过 `follow_up_task` 的 `content_template` 字段包含 `ai_suggestion_id` 判断): + +```rust +// 在 follow_up.completed 消费者中添加 +if let Some(ai_suggestion_id) = extract_ai_suggestion_id(&task) { + // 发布再分析请求 + let event = erp_core::events::DomainEvent::new( + "ai.reanalysis.requested", + tenant_id, + serde_json::json!({ + "original_suggestion_id": ai_suggestion_id, + "patient_id": task.patient_id, + "followup_id": task.id, + "trigger": "loop_closure", + }), + ); + event_bus.publish(event, db).await?; +} +``` + +- [ ] **Step 2: 在 erp-ai 中消费再分析请求** + +在 `crates/erp-ai/src/handler/mod.rs` 或独立的再分析服务中,订阅 `ai.reanalysis.requested` 事件: +1. 加载原始建议的 `baseline_snapshot` +2. 提取随访期间的新数据 +3. 执行趋势分析(带 baseline) +4. 生成对比报告 + +- [ ] **Step 3: 提交** + +```bash +git add crates/erp-health/src/event.rs crates/erp-ai/src/service/reanalysis.rs +git commit -m "feat(health+ai): 随访完成→再分析触发 — 闭环核心链路" +``` + +--- + +### Task 21: 前后对比报告生成 + +**Files:** +- Create: `crates/erp-ai/src/service/comparison.rs` + +- [ ] **Step 1: 实现对比报告生成** + +```rust +// crates/erp-ai/src/service/comparison.rs + +pub struct ComparisonReport { + pub baseline: serde_json::Value, + pub current: serde_json::Value, + pub changes: Vec, + pub overall_trend: TrendDirection, +} + +pub struct MetricChange { + pub metric: String, + pub baseline_value: f64, + pub current_value: f64, + pub change_percent: f64, + pub trend: TrendDirection, +} + +pub enum TrendDirection { Improving, Stable, Worsening } + +pub fn generate_comparison( + baseline: &serde_json::Value, + current: &serde_json::Value, +) -> ComparisonReport { + // 对比 baseline_summary 和当前指标 + // 计算每个指标的变化百分比和趋势方向 + // 综合判断整体趋势 +} +``` + +- [ ] **Step 2: 在再分析流程中调用对比生成** + +再分析完成后,调用 `generate_comparison()` 生成对比报告,存储到 `ai_analysis.result_metadata` 中,关联到原始建议的 `reanalysis_id`。 + +- [ ] **Step 3: 新增对比报告 API 端点** + +`GET /ai/suggestions/{id}/comparison` — 返回前后对比报告 + +- [ ] **Step 4: 提交** + +```bash +git add crates/erp-ai/src/service/comparison.rs +git commit -m "feat(ai): 前后对比报告生成 — 闭环效果评估" +``` + +--- + +### Task 22: Web 前端 — AI 建议面板 + +**Files:** +- Modify: `apps/web/src/pages/health/AiAnalysisList.tsx` + +- [ ] **Step 1: 在分析详情展开行中增加建议面板** + +在 `AiAnalysisList.tsx` 的展开行中,当分析有结构化建议时: +- 显示风险等级标签(低/中/高,不同颜色) +- 显示建议列表(类型图标 + 原因 + 状态标签) +- 显示执行状态(待审批/已批准/已执行/已拒绝) + +- [ ] **Step 2: 添加建议审批操作按钮** + +对中/高风险建议,显示「批准」/「拒绝」按钮,调用 `POST /api/v1/ai/suggestions/{id}/approve` + +- [ ] **Step 3: 提交** + +```bash +git add apps/web/src/pages/health/AiAnalysisList.tsx +git commit -m "feat(web): AI 分析详情增加建议面板 — 风险等级+建议列表+审批操作" +``` + +--- + +### Task 23: Web 前端 — 医生 AI 待办区域 + +**Files:** +- Modify: `apps/web/src/pages/health/PatientDetail.tsx`(或新建 Dashboard widget) + +- [ ] **Step 1: 在患者详情页添加 AI 待办区域** + +显示该患者待审批的 AI 建议,按风险等级排序,显示倒计时(中/高风险有超时) + +- [ ] **Step 2: 添加前后对比报告展示** + +当建议有闭环对比报告时,显示前后指标变化趋势图 + +- [ ] **Step 3: 提交** + +```bash +git add apps/web/src/pages/health/PatientDetail.tsx +git commit -m "feat(web): 患者 AI 待办区域 + 前后对比趋势图" +``` + +--- + +### Task 24: 小程序 — AI 建议卡片 + 预警通知 + +**Files:** +- Modify: `apps/miniprogram/src/pages/health/index.tsx` +- Modify: `apps/miniprogram/src/services/ai-analysis.ts` + +- [ ] **Step 1: 健康页增加 AI 建议卡片** + +在健康页面顶部显示最新的 AI 建议摘要卡片: +- 风险等级颜色标签 +- 建议文字摘要 +- 点击跳转详情页 + +- [ ] **Step 2: 消息页增加风险预警通知类型** + +在消息页面中识别 AI 预警通知,用醒目样式显示 + +- [ ] **Step 3: 提交** + +```bash +git add apps/miniprogram/src/pages/health/index.tsx apps/miniprogram/src/services/ai-analysis.ts +git commit -m "feat(miniprogram): AI 建议卡片 + 风险预警通知" +``` + +--- + +### Task 25: 全流程端到端验证 + +- [ ] **Step 1: 启动全栈服务** + +```bash +cd crates/erp-server && cargo run +cd apps/web && pnpm dev +``` + +- [ ] **Step 2: 完整闭环测试** + +1. 发起 AI 趋势分析 → 确认结构化建议生成 +2. 医生在 Web 端审批建议 → 确认随访计划已创建 +3. 患者小程序查看 AI 建议卡片 → 确认显示正常 +4. 模拟随访完成 → 确认再分析触发 +5. 查看前后对比报告 → 确认趋势对比正确 + +- [ ] **Step 3: 运行全量测试** + +```bash +cargo check +cargo test --workspace +pnpm --filter web build +``` + +- [ ] **Step 4: 最终推送** + +```bash +git push +``` + +--- + +**计划完成。** 共 25 个 Task,分 3 个 Chunk: +- Chunk 1 (Task 1-11): 数据层 + 输出解析 — 可独立执行 +- Chunk 2 (Task 12-19): 事件集成 + BPMN + 行动分发 — 依赖 Chunk 1 +- Chunk 3 (Task 20-25): 闭环对比 + 前端展示 — 依赖 Chunk 2