Files
AIclinicalresearch/docs/03-业务模块/IIT Manager Agent/04-开发计划/01-数据库设计.md
HaHafeng 5db4a7064c feat(iit): Implement real-time quality control system
Summary:

- Add 4 new database tables: iit_field_metadata, iit_qc_logs, iit_record_summary, iit_qc_project_stats

- Implement pg-boss debounce mechanism in WebhookController

- Refactor QC Worker for dual output: QC logs + record summary

- Enhance HardRuleEngine to support form-based rule filtering

- Create QcService for QC data queries

- Optimize ChatService with new intents: query_enrollment, query_qc_status

- Add admin batch operations: one-click full QC + one-click full summary

- Create IIT Admin management module: project config, QC rules, user mapping

Status: Code complete, pending end-to-end testing
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 21:56:11 +08:00

497 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# IIT Manager Agent 数据库设计
> **版本:** V2.9
> **更新日期:** 2026-02-05
> **关联文档:** [IIT Manager Agent V2.6 综合开发计划](./IIT%20Manager%20Agent%20V2.6%20综合开发计划.md)
>
> **V2.9.1 更新**
> - 扩展 `iit_skills` 表支持 Cron Skill主动提醒
> - 扩展 `iit_conversation_history` 表增加反馈字段
> - 更新 `iit_project_memory` 内容结构(用户画像)
> - **新增 `iit_pii_audit_log` 表**PII 脱敏审计日志(合规必需)
---
## 1. 数据库表总览
| 表名 | 用途 | Phase | 优先级 |
|------|------|-------|--------|
| `iit_skills` | Skill 配置存储 | 1 | P0 |
| `iit_field_mapping` | 字段名映射字典 | 1 | P0 |
| `iit_pii_audit_log` | PII 脱敏审计日志 | 1.5 | P0 |
| `iit_task_run` | SOP 任务执行记录 | 2 | P0 |
| `iit_pending_actions` | 待处理的违规记录 | 2 | P0 |
| `iit_conversation_history` | 对话历史(流水账) | 2 | P1 |
| `iit_project_memory` | 项目级热记忆Markdown | 2 | P1 |
| `iit_weekly_reports` | 周报归档(历史书) | 4 | P1 |
| `iit_agent_trace` | ReAct 推理轨迹 | 5 | P2 |
| `iit_form_templates` | 表单模板(视觉识别) | 6 | 延后 |
---
## 2. Phase 1基础配置表
### 2.1 iit_skills - Skill 配置存储
```prisma
model IitSkill {
id String @id @default(uuid())
projectId String // 绑定项目
skillType String // qc_process | daily_briefing | general_chat | weekly_report | visit_reminder
name String // 技能名称
config Json // 核心配置 JSONSOP 流程图)
isActive Boolean @default(true)
version Int @default(1)
// V2.9 新增:主动触发能力
triggerType String @default("webhook") // 'webhook' | 'cron' | 'event'
cronSchedule String? // Cron 表达式,如 "0 9 * * *" (每天9点)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([projectId, skillType])
@@map("iit_skills")
@@schema("iit_schema")
}
```
**触发类型说明**
| triggerType | 触发方式 | 示例场景 |
|-------------|----------|----------|
| `webhook` | 用户消息触发(默认) | 质控任务、问答查询 |
| `cron` | 定时触发 | 访视提醒、周报生成 |
| `event` | 事件触发(预留) | AE 预警、数据变更通知 |
**Skill 配置示例SOP 流程图)**
```json
{
"name": "肺癌研究入组质控",
"start_node": "baseline_check",
"nodes": {
"baseline_check": {
"type": "hard_rule",
"rules": [
{ "field": "age", "logic": { ">=": [{"var":"age"}, 18] }, "message": "年龄<18岁" },
{ "field": "age", "logic": { "<=": [{"var":"age"}, 75] }, "message": "年龄>75岁" },
{ "field": "ecog", "logic": { "<=": [{"var":"ecog"}, 2] }, "message": "ECOG>2" }
],
"on_pass": "history_check",
"on_fail": "end_with_violation"
},
"history_check": {
"type": "soft_instruction",
"instruction": "检查既往史,排除间质性肺炎、活动性感染、严重心血管疾病。",
"tools": ["read_clinical_data"],
"on_pass": "medication_check",
"on_fail": "end_review_required"
},
"human_review": {
"type": "human_review",
"description": "需要 CRC 人工复核",
"on_approve": "end_success",
"on_reject": "end_rejected"
}
}
}
```
**Cron Skill 配置示例V2.9 新增)**
```json
{
"skillType": "visit_reminder",
"name": "每日访视提醒",
"triggerType": "cron",
"cronSchedule": "0 9 * * *",
"config": {
"start_node": "check_upcoming_visits",
"nodes": {
"check_upcoming_visits": {
"type": "soft_instruction",
"instruction": "查询未来 3 天内到期的访视,生成提醒列表",
"tools": ["read_clinical_data"],
"on_pass": "send_reminder",
"on_fail": "end_no_visits"
},
"send_reminder": {
"type": "soft_instruction",
"instruction": "根据用户画像选择合适的通知方式和语气,发送提醒",
"tools": ["send_message"],
"on_pass": "end_success"
}
}
}
}
```
---
### 2.2 iit_field_mapping - 字段名映射字典
```prisma
model IitFieldMapping {
id String @id @default(uuid())
projectId String // 项目级别映射
aliasName String // LLM 可能传的名称(如 "gender", "性别"
actualName String // REDCap 实际字段名(如 "sex"
createdAt DateTime @default(now())
@@unique([projectId, aliasName])
@@index([projectId])
@@map("iit_field_mapping")
@@schema("iit_schema")
}
```
**初始化示例**
```sql
INSERT INTO iit_field_mapping (project_id, alias_name, actual_name) VALUES
('project-uuid', 'age', 'age_calculated'),
('project-uuid', '年龄', 'age_calculated'),
('project-uuid', 'ecog', 'ecog_score'),
('project-uuid', '既往史', 'medical_history_text'),
('project-uuid', 'history', 'medical_history_text'),
('project-uuid', '性别', 'sex'),
('project-uuid', 'gender', 'sex');
```
---
## 2.5 Phase 1.5隐私安全表P0 合规必需)
### 2.5.1 iit_pii_audit_log - PII 脱敏审计日志
> **重要**:临床数据包含大量患者隐私信息(姓名、身份证、手机号),在调用第三方 LLM 之前**必须脱敏**。
> 此表用于存储脱敏记录,便于事后合规审计。
```prisma
model IitPiiAuditLog {
id String @id @default(uuid())
projectId String
userId String // 操作者
sessionId String // 会话 ID关联 conversation_history
// 脱敏内容(加密存储)
originalHash String // 原始内容的 SHA256 哈希(不存明文)
maskedPayload String @db.Text // 脱敏后发送给 LLM 的内容
maskingMap String @db.Text // 加密存储的映射表 { "[PATIENT_1]": "张三", ... }
// 元数据
piiCount Int // 检测到的 PII 数量
piiTypes String[] // 检测到的 PII 类型 ['name', 'id_card', 'phone']
llmProvider String // 'qwen' | 'deepseek' | 'openai'
createdAt DateTime @default(now())
@@index([projectId, userId])
@@index([sessionId])
@@index([createdAt])
@@map("iit_pii_audit_log")
@@schema("iit_schema")
}
```
**PII 类型说明**
| PII 类型 | 正则模式 | 脱敏示例 |
|----------|----------|----------|
| `name` | 中文姓名2-4字 | 张三 → [PATIENT_1] |
| `id_card` | 身份证号18位 | 420101... → [ID_CARD_1] |
| `phone` | 手机号11位 | 13800138000 → [PHONE_1] |
| `mrn` | 病历号 | MRN123456 → [MRN_1] |
**脱敏流程**
```
用户输入: "张三身份证420101199001011234今天血压偏高"
↓ AnonymizerService.mask()
LLM 收到: "[PATIENT_1](身份证[ID_CARD_1])今天血压偏高"
↓ 同时写入 iit_pii_audit_log
↓ LLM 处理
LLM 返回: "[PATIENT_1] 的血压需要关注..."
↓ AnonymizerService.unmask()
用户看到: "张三 的血压需要关注..."
```
---
## 3. Phase 2SOP 执行与记忆表
### 3.1 iit_task_run - SOP 任务执行记录
```prisma
model IitTaskRun {
id String @id @default(uuid())
projectId String
skillId String // 关联的 Skill
recordId String? // 关联的患者(如有)
triggeredBy String // 触发者 userId
status String // RUNNING | SUSPENDED | COMPLETED | FAILED
currentNode String? // 当前执行到的节点
trace Json? // 执行轨迹
resumeCallback String? // SUSPENDED 时,恢复后的下一步
rejectCallback String? // SUSPENDED 被拒绝时的下一步
suspendedAt DateTime?
resumedAt DateTime?
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([projectId, status])
@@index([recordId])
@@map("iit_task_run")
@@schema("iit_schema")
}
```
---
### 3.2 iit_pending_actions - 待处理的违规记录
```prisma
model IitPendingAction {
id String @id @default(uuid())
projectId String
taskRunId String // 来源任务
recordId String? // 关联患者
actionType String // violation | warning | review_required
field String? // 违规字段
message String // 违规描述
severity String // error | warning | info
resolvedBy String? // 处理人
resolvedAt DateTime?
resolution String? // 处理结论
createdAt DateTime @default(now())
@@index([projectId, actionType])
@@index([recordId])
@@map("iit_pending_actions")
@@schema("iit_schema")
}
```
---
### 3.3 iit_conversation_history - 对话历史(流水账)
> **V2.8 设计**:这是原始对话流水,不直接注入 Prompt只用于生成周报
>
> **V2.9 新增**:增加反馈字段,支持用户点赞/点踩
```prisma
model IitConversationHistory {
id String @id @default(uuid())
projectId String
userId String
recordId String? // 关联的患者(如有)
role String // user | assistant
content String @db.Text
intent String? // 识别出的意图类型
entities Json? // 提取的实体 { record_id, visit, ... }
// V2.9 新增:反馈循环
feedback String? // 'thumbs_up' | 'thumbs_down' | null
feedbackReason String? // 点踩原因:'too_long' | 'inaccurate' | 'unclear'
createdAt DateTime @default(now())
@@index([projectId, userId])
@@index([projectId, recordId])
@@index([createdAt])
@@map("iit_conversation_history")
@@schema("iit_schema")
}
```
---
### 3.4 iit_project_memory - 项目级热记忆
> **V2.8 核心表**:存储 Markdown 格式的热记忆,每次对话都注入 System Prompt
>
> **V2.9 扩展**:增加用户画像结构
```prisma
model IitProjectMemory {
id String @id @default(uuid())
projectId String @unique
content String @db.Text // Markdown 格式的热记忆
lastUpdatedBy String // 'system_daily_job' | 'admin_user_id' | 'profiler_job'
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("iit_project_memory")
@@schema("iit_schema")
}
```
**内容示例V2.9 增强版)**
```markdown
# 用户画像 (User Profiles)
## 张医生 (user_id: zhangyi)
- **角色**: PI
- **偏好**: 简洁汇报,只看结论,不要废话
- **关注点**: AE、入组进度
- **最佳通知时间**: 08:30
- **禁令**: 回复不超过 100 字
- **反馈统计**: 👍 12 / 👎 1
## 王护士 (user_id: wanghushi)
- **角色**: CRC
- **偏好**: 详细步骤,需要解释
- **关注点**: 访视安排、数据录入
- **最佳通知时间**: 09:00
# 当前状态 (Active Context)
- 当前阶段:入组冲刺期
- 重点关注P005 患者依从性差,需每日提醒
- 本周目标:完成 3 例入组
- P003 SAE 已判定为"可能无关"2月5日 PI 决策)
# 常见问题 (FAQ)
- 访视窗口V1 Day 1±3, V2 Day 28±7
# 系统禁令 (Rules)
- 严禁在未授权情况下删除数据
- 写操作必须经过人工确认
```
---
## 4. Phase 4周报归档
### 4.1 iit_weekly_reports - 周报归档(历史书)
> **V2.8 核心表**:存储每周的关键决策、进度、踩坑记录
```prisma
model IitWeeklyReport {
id String @id @default(uuid())
projectId String
weekNumber Int // 第几周(从项目开始计算)
weekStart DateTime // 周起始日期
weekEnd DateTime // 周结束日期
summary String @db.Text // Markdown 格式的周报内容
metrics Json? // 结构化指标 { enrolled: 3, queries: 5, ... }
createdBy String // 'system_scheduler' | 'admin_user_id'
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([projectId, weekNumber])
@@index([projectId])
@@map("iit_weekly_reports")
@@schema("iit_schema")
}
```
**内容示例**
```markdown
## Week 5 (2026-02-03 ~ 2026-02-09)
### 进度
- 本周新入组3 例
- 累计入组15 / 30 例
- 完成率50%
### 关键决策
- 2026-02-05: PI 会议决定放宽入排标准,允许 ECOG 3 分患者入组
- 2026-02-07: 确认 P003 AE 与研究药物"可能无关"
### 踩坑记录
- 曾尝试自动录入化验单,因 OCR 精度不足失败,已回退为人工复核
### 待办
- 清理 1 月份遗留的 Query
- 准备 2 月底期中分析数据
```
---
## 5. Phase 5ReAct 追踪表
### 5.1 iit_agent_trace - ReAct 推理轨迹
> 用于调试,不发送给用户,仅在 Admin 后台查看
```prisma
model IitAgentTrace {
id String @id @default(uuid())
projectId String
userId String
query String @db.Text // 用户原始问题
intentType String? // 识别的意图类型
trace Json // ReAct 的完整思考过程
tokenUsage Int? // 消耗的 Token 数
duration Int? // 执行时长ms
success Boolean
createdAt DateTime @default(now())
@@index([projectId, createdAt])
@@index([userId])
@@map("iit_agent_trace")
@@schema("iit_schema")
}
```
---
## 6. Phase 6视觉能力表延后到 V3.0
### 6.1 iit_form_templates - 表单模板
```prisma
model IitFormTemplate {
id String @id @default(uuid())
projectId String
formName String // REDCap 表单名称
fieldSchema Json // 表单字段结构
keywords String[] // 用于匹配的关键词
createdAt DateTime @default(now())
@@map("iit_form_templates")
@@schema("iit_schema")
}
```
---
## 7. 数据库迁移命令
```bash
# 生成迁移
npx prisma db push
# 生成客户端
npx prisma generate
# 查看表结构
npx prisma studio
```
---
## 8. 索引设计总结
| 表 | 索引 | 用途 |
|----|------|------|
| `iit_skills` | `[projectId, skillType]` (unique) | 按项目查询 Skill |
| `iit_field_mapping` | `[projectId, aliasName]` (unique) | 字段映射查询 |
| `iit_task_run` | `[projectId, status]` | 查询运行中/挂起的任务 |
| `iit_conversation_history` | `[projectId, userId]` | 按用户查询对话 |
| `iit_conversation_history` | `[projectId, recordId]` | 按患者查询对话 |
| `iit_conversation_history` | `[createdAt]` | 按时间范围查询 |
| `iit_weekly_reports` | `[projectId, weekNumber]` (unique) | 按周查询报告 |
| `iit_agent_trace` | `[projectId, createdAt]` | 按时间查询调试日志 |
---
**文档维护人**AI Agent
**最后更新**2026-02-05