402 lines
15 KiB
Markdown
402 lines
15 KiB
Markdown
# 行动收件箱与上下文线程设计规格
|
||
|
||
> 日期: 2026-05-01 | 状态: Draft | 关联: `erp-health`, `erp-ai`, Web 前端, 小程序
|
||
|
||
## 1. 背景与问题
|
||
|
||
### 1.1 问题陈述
|
||
|
||
HMS 平台功能完善(328 路由、25 事件类型、97.5% 测试通过率),但用户体验存在**系统性流程断裂**:
|
||
|
||
- **告警 → 行动断裂**:医生看到告警后,无直接路径跳到查看患者→安排随访→记录处置
|
||
- **数据录入 → 反馈断裂**:患者录入体征后无即时趋势对比和行动建议
|
||
- **AI 建议 → 执行追踪断裂**:审批通过后,后续执行/追踪/结果反馈在 UI 上不可见
|
||
|
||
**根因**:前端交互模型是"功能陈列式"的——每个功能是死胡同,没有出口指向下一步。
|
||
|
||
### 1.2 设计目标
|
||
|
||
建立统一的**行动收件箱 + 上下文线程**机制,让每个"终点"变成"起点":
|
||
|
||
1. 用户登录后第一眼看到待处理事项(按优先级排列)
|
||
2. 每个待办有清晰的时间线展示完整生命周期
|
||
3. 时间线每一步都可直接跳转到对应功能页
|
||
4. 操作按钮根据当前状态动态变化
|
||
5. 架构可扩展——新增行动类型(告警/随访/异常)只需注册聚合器
|
||
|
||
### 1.3 范围
|
||
|
||
**Phase 1(本次)**:AI 建议闭环 — 后端已完整实现(10 次提交),补齐前端体验
|
||
**Future**:告警处理闭环、体征反馈闭环、随访到期提醒
|
||
|
||
---
|
||
|
||
## 2. 设计概览
|
||
|
||
```
|
||
事件源 → 行动收件箱服务 → 前端展示
|
||
├── Web: 顶栏铃铛 → 列表页 → Drawer
|
||
└── 小程序: "待办" Tab → 列表 → 半屏弹窗
|
||
```
|
||
|
||
**核心抽象**:`ActionItem` — 统一的待办数据结构,不同类型共用相同接口
|
||
**核心交互**:`Context Thread` — Drawer/半屏弹窗中的时间线,展示完整处理进度
|
||
|
||
---
|
||
|
||
## 3. 数据模型
|
||
|
||
### 3.1 ActionItem(聚合视图,不建新表)
|
||
|
||
```typescript
|
||
interface ActionItem {
|
||
id: string; // 格式: "{type}:{source_id}"
|
||
type: "ai_suggestion" | "alert" | "followup" | "data_anomaly";
|
||
priority: "urgent" | "high" | "medium" | "low";
|
||
status: "pending" | "in_progress" | "completed" | "dismissed";
|
||
title: string;
|
||
summary: string;
|
||
patient_id: string;
|
||
patient_name: string;
|
||
source_ref: string; // 指向原始实体 ID
|
||
created_at: string;
|
||
updated_at: string;
|
||
}
|
||
```
|
||
|
||
### 3.2 ThreadEvent(上下文线程事件)
|
||
|
||
```typescript
|
||
interface ThreadEvent {
|
||
step: string; // "ai_analysis" | "doctor_approval" | "followup_scheduled" | "followup_completed" | "reanalysis"
|
||
label: string; // 展示文本
|
||
status: "completed" | "in_progress" | "pending";
|
||
detail?: string; // 补充说明
|
||
timestamp?: string; // 完成时间
|
||
operator?: string; // 操作人
|
||
link_to?: string; // 跳转路径
|
||
}
|
||
```
|
||
|
||
### 3.3 ActionDefinition(可用操作)
|
||
|
||
```typescript
|
||
interface ActionDefinition {
|
||
key: string; // "approve" | "reject" | "complete_early" | "acknowledge"
|
||
label: string; // 按钮文本
|
||
variant: "primary" | "danger" | "default";
|
||
confirm_message?: string; // 确认弹窗文案
|
||
api_endpoint: string; // 调用的 API
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 后端 API 设计
|
||
|
||
### 4.1 GET /api/v1/action-inbox
|
||
|
||
获取当前用户的行动收件箱列表。
|
||
|
||
**Query Params**:
|
||
- `status`: `pending` | `in_progress` | `completed` | `dismissed`(可选,默认全部)
|
||
- `type`: `ai_suggestion` | `alert` | `followup` | `data_anomaly`(可选,默认全部)
|
||
- `page` / `page_size`: 分页(默认 1/20)
|
||
|
||
**Response**: `PaginatedResponse<ActionItem>`
|
||
|
||
**实现策略**:聚合查询,不建新表。Phase 1 只查 `ai_suggestion` 表:
|
||
|
||
```sql
|
||
-- 注意:patient_id 在 ai_analysis 表上,不在 ai_suggestion 上,需三表 JOIN
|
||
SELECT s.id, s.suggestion_type, s.risk_level, s.status, s.params,
|
||
s.created_at, s.updated_at,
|
||
p.name as patient_name, a.patient_id,
|
||
a.result_content, a.analysis_type
|
||
FROM ai_suggestion s
|
||
JOIN ai_analysis a ON s.analysis_id = a.id
|
||
JOIN patient p ON a.patient_id = p.id
|
||
WHERE s.tenant_id = $1
|
||
AND s.deleted_at IS NULL
|
||
ORDER BY
|
||
CASE s.risk_level WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END,
|
||
s.created_at DESC
|
||
```
|
||
|
||
### 4.2 GET /api/v1/action-inbox/:source_ref/thread
|
||
|
||
获取某个行动项的上下文线程。
|
||
|
||
**Response**:
|
||
|
||
```typescript
|
||
interface ThreadResponse {
|
||
action_item: ActionItem;
|
||
thread: ThreadEvent[];
|
||
available_actions: ActionDefinition[];
|
||
}
|
||
```
|
||
|
||
**线程拼装逻辑**(以 AI 建议为例):
|
||
|
||
| 步骤 | 数据来源 | 状态判断 |
|
||
|------|---------|---------|
|
||
| AI 分析完成 | `ai_analysis.status = 'completed'` | 始终 completed |
|
||
| 医生审批 | `ai_suggestion.status` | Approved→completed, Pending→in_progress |
|
||
| 执行安排 | `ai_suggestion.workflow_instance_id IS NOT NULL` | 有值→completed |
|
||
| 等待随访 | `follow_up` 关联查询 | 根据随访状态判断 |
|
||
| 再分析对比 | `ai_suggestion.reanalysis_id IS NOT NULL` | 有值→completed |
|
||
|
||
**操作按钮动态生成**:
|
||
|
||
| 建议状态 | 可用操作 |
|
||
|---------|---------|
|
||
| Pending | 批准(→ `POST /ai/suggestions/:id/approve`)、拒绝 |
|
||
| Approved/执行中 | 提前完成随访、标记已知悉 |
|
||
| Executed | 查看前后对比报告 |
|
||
| Expired/Rejected/ParseFailed | 无操作(只读展示) |
|
||
| Executed | 查看前后对比报告 |
|
||
| Expired | 无操作 |
|
||
|
||
### 4.3 代码归属与权限码
|
||
|
||
**代码位置**:
|
||
- `ActionInboxService` → `crates/erp-health/src/service/action_inbox_service.rs`(erp-health crate)
|
||
- `ActionInboxHandler` → `crates/erp-health/src/handler/action_inbox_handler.rs`
|
||
- 路由注册:在 `erp-health` 模块的 `protected_routes()` 中新增
|
||
- 跨 crate 查询:通过 raw SQL 直接查 `ai_suggestion` + `ai_analysis` 表(与现有 `ai_suggestion_loader.rs` 模式一致,erp-health 已有跨 crate 读 ai 表的先例)
|
||
|
||
**权限码**:新增独立权限码,注册在 `erp-health` 模块的 `permissions()` 中:
|
||
- `health.action_inbox.list` — 查看行动收件箱
|
||
- `health.action_inbox.manage` — 执行操作(审批/拒绝/标记已知悉)
|
||
|
||
理由:`action-inbox` 是聚合端点,未来包含告警/随访/异常等多种类型,不应绑定到 `ai.suggestion.*` 权限。用 `health.` 前缀与 erp-health crate 归属一致。
|
||
|
||
**用户范围**:
|
||
- Phase 1(AI 建议):返回当前租户下所有有权限查看的待办(`ai_suggestion` 无 `assigned_to` 字段)
|
||
- Future(告警/随访):告警有 `assigned_to` 关联的负责医生,随访有 `assigned_to` 字段,届时 API 增加 `scope` 参数(`all` | `mine`)
|
||
|
||
---
|
||
|
||
## 5. Web 前端设计
|
||
|
||
### 5.1 入口改造:NotificationPanel 扩展
|
||
|
||
**现有**:`apps/web/src/components/NotificationPanel.tsx`(铃铛 + Popover,SSE 推送消息/告警)
|
||
|
||
**改造**:
|
||
- Popover 中增加"待办"区域,显示最近 3 条 ActionItem
|
||
- 底部增加"查看全部待办"链接,跳转到 `/action-inbox` 页面
|
||
- 角标数字改为:未读消息数 + 待处理 ActionItem 数
|
||
|
||
### 5.2 新增页面:ActionInboxPage
|
||
|
||
**路由**:`/action-inbox`
|
||
|
||
**组件**:`apps/web/src/pages/ActionInboxPage.tsx`
|
||
|
||
**布局**:
|
||
- 顶部:筛选栏(状态 Tab + 类型下拉)
|
||
- 主体:Ant Design List 组件,每项显示:
|
||
- 类型标签(颜色区分)
|
||
- 标题 + 患者姓名
|
||
- 优先级标记
|
||
- 创建时间
|
||
- 点击 → 打开 Drawer
|
||
|
||
### 5.3 新增组件:ActionThreadDrawer
|
||
|
||
**组件**:`apps/web/src/components/ActionThreadDrawer.tsx`
|
||
|
||
**实现**:Ant Design Drawer + Ant Design Steps/Timeline
|
||
|
||
**结构**:
|
||
```
|
||
Drawer (width 480px)
|
||
├── 头部:标题 + 患者摘要卡(年龄/最近体征/关键指标)
|
||
├── 时间线:Ant Design Timeline 组件
|
||
│ ├── 每个节点:状态图标 + 文本 + 时间 + 跳转链接
|
||
│ └── 当前步骤高亮,未来步骤灰色
|
||
├── 关联信息:可点击跳转的快捷链接
|
||
└── 操作区:动态按钮组
|
||
```
|
||
|
||
**状态设计**:
|
||
- **加载中**:Ant Design Skeleton(3 行卡片骨架屏)
|
||
- **数据获取失败**:Ant Design Result + 重试按钮
|
||
- **空列表**:插图 + "暂无待办事项"文案
|
||
- **操作按钮请求中**:按钮 loading 状态 + 禁用
|
||
|
||
### 5.4 实时更新策略
|
||
|
||
**Phase 1(轮询)**:用户手动刷新或切换页面时重新拉取列表。Drawer 打开时,操作按钮点击后主动调用 `GET /action-inbox/:id/thread` 刷新时间线。
|
||
|
||
**Phase 2(SSE 优化)**:复用 `apps/web/src/stores/message.ts` 中的 SSE 连接。后端在建议状态变更时,通过事件总线发布 → 消息服务推送到 SSE 流。前端监听 `suggestion.status_changed` 事件,触发列表/Drawer 刷新。
|
||
|
||
Phase 1 选择轮询的理由:现有 SSE 只推送 `message` 和 `alert` 两种类型,新增 `suggestion` 事件需要后端消息服务改造,且 Phase 1 的使用场景(医生不频繁审批)对实时性要求不高。
|
||
|
||
---
|
||
|
||
## 6. 小程序设计
|
||
|
||
### 6.1 TabBar 改造
|
||
|
||
**当前**:首页 | 健康 | 消息 | 我的(4 Tab)
|
||
**新**:首页 | 健康 | 待办 | 消息 | 我的(5 Tab)
|
||
|
||
- 在"健康"和"消息"之间插入"待办"Tab
|
||
- Tab 图标:`todo-list` 或 `check-circle`
|
||
- 角标显示待处理数量(调用 `GET /action-inbox?status=pending` 获取计数)
|
||
- 修改文件:`apps/miniprogram/src/app.config.ts`
|
||
|
||
### 6.2 新增页面:待办列表
|
||
|
||
**路径**:`apps/miniprogram/src/pages/action-inbox/index.tsx`
|
||
|
||
**布局**:
|
||
- 顶部:状态筛选 Tab(全部 | 待处理 | 进行中 | 已完成)
|
||
- 列表:按优先级排序的待办卡片
|
||
- 每张卡片:类型标签 + 标题 + 患者名 + 时间 + 箭头
|
||
- 点击卡片 → 弹出半屏弹窗
|
||
|
||
### 6.3 新增组件:ActionHalfScreenDialog
|
||
|
||
使用 Taro `half-screen-dialog` 组件或自定义半屏弹窗:
|
||
|
||
```
|
||
半屏弹窗
|
||
├── 头部:拖拽条 + 类型标签 + 标题
|
||
├── 摘要:关键信息(患者/风险等级/时间)
|
||
├── 时间线:简化版(只显示已完成和当前步骤)
|
||
├── 操作按钮
|
||
└── 底部:跳转详情链接
|
||
```
|
||
|
||
### 6.4 Service 层
|
||
|
||
**文件**:`apps/miniprogram/src/services/action-inbox.ts`
|
||
|
||
```typescript
|
||
// 复用现有 listPendingSuggestions()
|
||
// 新增:
|
||
listActionItems(params: { status?: string; type?: string; page: number })
|
||
→ GET /api/v1/action-inbox
|
||
|
||
getActionThread(sourceRef: string)
|
||
→ GET /api/v1/action-inbox/:source_ref/thread
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 数据映射详细规则
|
||
|
||
### 7.1 AI 建议 → ActionItem 映射
|
||
|
||
| ActionItem 字段 | 映射来源 | 规则 |
|
||
|----------------|---------|------|
|
||
| id | `"ai_suggestion:" + ai_suggestion.id` | 类型前缀 + 原 ID |
|
||
| type | 固定 `"ai_suggestion"` | — |
|
||
| priority | `ai_suggestion.risk_level` | High→urgent, Medium→high, Low→medium |
|
||
| status | `ai_suggestion.status` | Pending→pending, Approved→in_progress, Executed→completed, Expired/Rejected/ParseFailed→dismissed |
|
||
| title | `suggestion_type` + `risk_level` 拼接模板 | 如 `suggestion_type=Followup` → "建议安排随访";`suggestion_type=Alert` → "建议关注指标异常";从 `params` JSON 提取 `reason` 字段作为补充(若存在) |
|
||
| summary | `ai_analysis.result_content` | 截取前 100 字符 |
|
||
| patient_id | `ai_analysis.patient_id`(通过 `analysis_id` JOIN) | 三表 JOIN |
|
||
| patient_name | `patient.name` | JOIN 查询 |
|
||
| source_ref | `ai_suggestion.id` | 原始 ID |
|
||
|
||
### 7.2 线程步骤映射
|
||
|
||
| ThreadEvent.step | 触发条件 | 数据来源 |
|
||
|-----------------|---------|---------|
|
||
| `ai_analysis` | 始终存在 | `ai_analysis` 表 |
|
||
| `doctor_approval` | `suggestion.status ∈ {Approved, Rejected}` | `suggestion.updated_at` 作为近似审批时间(无专用 `approved_at` 字段) |
|
||
| `followup_scheduled` | `workflow_instance_id IS NOT NULL` | `workflow_instance` 表。注意:`action_dispatched` 步骤已合并到此处——`ai_action_dispatcher` 是事件消费者,不回写时间戳到 `ai_suggestion`,其效果体现为 `workflow_instance_id` 被设置 |
|
||
| `followup_completed` | `follow_up.status = completed` | `follow_up` 表 |
|
||
| `reanalysis` | `reanalysis_id IS NOT NULL` | `ai_analysis` (reanalysis) 表 |
|
||
|
||
---
|
||
|
||
## 8. 实施分阶段
|
||
|
||
### Phase 1A:后端 API(2-3 天)
|
||
|
||
- [ ] 创建 `ActionInboxService` → `crates/erp-health/src/service/action_inbox_service.rs`
|
||
- [ ] 创建 `ActionInboxHandler` → `crates/erp-health/src/handler/action_inbox_handler.rs`
|
||
- [ ] 在 erp-health `protected_routes()` 中注册 2 个新路由
|
||
- [ ] 实现三表 JOIN 聚合查询(`ai_suggestion` + `ai_analysis` + `patient`)
|
||
- [ ] 实现线程拼装逻辑(合并 `action_dispatched` 到 `followup_scheduled` 步骤)
|
||
- [ ] 实现操作按钮动态生成
|
||
- [ ] 在 erp-health `permissions()` 中注册 `health.action_inbox.list` / `health.action_inbox.manage`
|
||
|
||
### Phase 1B:Web 前端(2-3 天)
|
||
|
||
- [ ] 扩展 `NotificationPanel`,增加待办区域
|
||
- [ ] 创建 `ActionInboxPage` 路由页面
|
||
- [ ] 创建 `ActionThreadDrawer` 组件
|
||
- [ ] SSE 实时更新集成
|
||
- [ ] 路由注册 + 菜单入口
|
||
|
||
### Phase 1C:小程序(2-3 天)
|
||
|
||
- [ ] TabBar 改造(4 Tab → 5 Tab,在"健康"和"消息"间插入"待办")
|
||
- [ ] 创建待办列表页面
|
||
- [ ] 创建半屏弹窗组件
|
||
- [ ] Service 层对接
|
||
|
||
### Phase 1D:验证与收尾(1 天)
|
||
|
||
- [ ] 三端联调测试
|
||
- [ ] 更新 wiki 文档
|
||
- [ ] 提交并推送
|
||
|
||
---
|
||
|
||
## 9. 验证方案
|
||
|
||
### 9.1 后端验证
|
||
|
||
```bash
|
||
# 1. 启动后端
|
||
cd crates/erp-server && cargo run
|
||
|
||
# 2. 创建 AI 分析 → 产生建议
|
||
# 3. 调用 API 验证
|
||
curl -H "Authorization: Bearer $TOKEN" \
|
||
http://localhost:3000/api/v1/action-inbox
|
||
|
||
curl -H "Authorization: Bearer $TOKEN" \
|
||
http://localhost:3000/api/v1/action-inbox/ai_suggestion:{id}/thread
|
||
```
|
||
|
||
### 9.2 Web 验证
|
||
|
||
1. 登录 → 顶栏铃铛应显示待办数量
|
||
2. 点击铃铛 → Popover 显示最近待办
|
||
3. "查看全部" → 进入 ActionInboxPage
|
||
4. 点击待办项 → Drawer 打开,时间线正确
|
||
5. 点击"批准" → 状态变更,时间线更新
|
||
6. SSE 推送 → Drawer 实时更新
|
||
|
||
### 9.3 小程序验证
|
||
|
||
1. 底部 TabBar 显示"待办"Tab + 角标
|
||
2. 进入待办页 → 列表正确
|
||
3. 点击卡片 → 半屏弹窗弹出
|
||
4. 操作后 → 状态更新,列表刷新
|
||
|
||
---
|
||
|
||
## 10. 未来扩展
|
||
|
||
新增行动类型只需:
|
||
|
||
1. 在 `ActionInboxService` 中注册新的聚合查询器
|
||
2. 定义 `type → ActionItem` 的映射规则
|
||
3. 定义线程步骤模板
|
||
4. 前端自动渲染(共用相同组件)
|
||
|
||
**规划中的扩展类型**:
|
||
- `alert` — 告警处理闭环(告警→评估→处置→追踪)
|
||
- `followup` — 随访到期提醒(到期→执行→记录→再分析)
|
||
- `data_anomaly` — 体征异常反馈(异常→趋势对比→建议→操作)
|