docs(ai): AI→行动闭环实施计划完成 — 25 Task / 3 Chunk
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

Chunk 1: 数据层+输出解析(Task 1-11)
Chunk 2: 事件集成+BPMN+行动分发(Task 12-19)
Chunk 3: 闭环对比+前端展示(Task 20-25)
This commit is contained in:
iven
2026-05-01 07:58:44 +08:00
parent 6046ed23c9
commit 2fb0535164

View File

@@ -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<Duration>,
}
/// 根据风险等级和建议类型生成执行决策
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<Uuid>,
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<Uuid>,
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<Vec<serde_json::Value>> {
// 使用 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-altAction 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<MetricChange>,
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