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>
This commit is contained in:
2026-02-07 21:56:11 +08:00
parent 0c590854b5
commit 5db4a7064c
74 changed files with 13383 additions and 2129 deletions

View File

@@ -0,0 +1,200 @@
# **IIT Manager Agent V2.9 补充:业务规则与数据治理细则**
**文档性质:** 业务逻辑落地方案
**评审结论:** 4 点建议逻辑清晰,完全可行。本方案在原有基础上进行了 Postgres 原生特性的优化。
## **1\. 质控规则来源:自动化与人工的完美结合**
**评价**:非常合理。这是降低实施成本的关键。如果全部靠人工配,运营会累死;如果全部靠自动,医学逻辑会缺失。
**落地优化建议**
我们在 iit\_skills 表的 JSON 配置中,引入 **source** 字段来标记规则来源,方便管理。
// qc\_skill.json (最终生成的配置文件)
{
"hard\_rules": \[
// \=== 自动生成区 (Auto-Generated) \===
{
"field": "age",
"logic": { "\>=": \[{ "var": "age" }, 18\] },
"message": "年龄必须 \>= 18",
"source": "meta\_validation\_min", // 标记来源
"level": "warning"
},
{
"field": "informed\_consent\_date",
"logic": { "\!=": \[{ "var": "informed\_consent\_date" }, ""\] },
"message": "知情同意日期必填",
"source": "meta\_required",
"level": "error"
},
// \=== 人工配置区 (Manual) \===
{
"field": "visit\_date",
"logic": { "\<=": \[{ "var": "visit\_date" }, { "var": "today" }\] },
"message": "访视日期不能是未来",
"source": "manual\_config",
"level": "error"
}
\]
}
**实施流程**
1. **Sync**: RedcapAdapter 拉取 Metadata。
2. **Generate**: 代码遍历 Metadata生成一个 draft\_rules 数组。
3. **Merge**: 将 draft\_rules 与数据库里已有的 manual\_rules 合并。
4. **Confirm**: 在管理端展示,人工确认后保存。
## **2\. 质控数据存储:利用 JSONB 简化分层**
**评价**:分层思路是对的,但**建议简化物理表结构**。
如果每个字段的错误都存一行记录iit\_qc\_results当项目有 1000 个病人 x 400 个变量时,这张表会瞬间爆炸(千万级行数),查询变慢。
**Postgres 优化方案**
利用 Postgres 强大的 **JSONB** 能力12合并保留3
### **优化后的 Schema**
**(1) \+ (2) 合并为iit\_qc\_logs (记录级 \+ 字段级详情)**
model IitQcLog {
id String @id @default(uuid())
projectId String
recordId String
eventId String
// 核心结果
status String // 'PASS' | 'FAIL' | 'WARNING'
// 字段级详情,直接存 JSONB
// 格式: \[{ field: "age", error: "范围越界", level: "RED" }, { ... }\]
// 优势: Postgres 支持对 JSONB 内部字段建立索引,查询速度一样快,但表行数少 100 倍
issues Json
ruleVersion String // 对应问题4的解决方案
createdAt DateTime @default(now())
@@index(\[projectId, recordId\])
@@map("iit\_qc\_logs")
}
**(3) 保留iit\_qc\_project\_stats (每日汇总)**
* 用于 Dashboard 快速展示,避免每次都 COUNT(\*) 几百万行日志。
## **3\. 主动干预分级:防打扰机制**
**评价**:非常棒的\*\*“抗疲劳设计”\*\*。如果不分级PI 一天收 50 条推送,第二天就会把机器人拉黑。
**落地实现**
在 WechatService 中实现一个 **Notification Filter**
// NotificationFilter.ts
async function handleAlert(projectId, logs) {
// 1\. 红色:立即发送
const redIssues \= logs.filter(i \=\> i.level \=== 'RED');
if (redIssues.length \> 0\) {
await sendWechat("🚨 紧急报警: 发现 SAE 或严重违规...");
await sendSms("..."); // 可选
}
// 2\. 黄色:存入每日摘要队列 (Redis/DB),不立即发
const yellowIssues \= logs.filter(i \=\> i.level \=== 'YELLOW');
if (yellowIssues.length \> 0\) {
await addToDailyDigest(projectId, yellowIssues);
}
// 3\. 绿色:只记录日志,完全不发
}
## **4\. 潜在问题与解决方案:技术细节补全**
你提出的解决方案都很到位,我从技术落地角度做一点补充。
### **(1) 规则版本管理**
* **你的方案**:增加 rule\_version。✅
* **补充**iit\_skills 表本身应该有一个 version 字段(自增 Int。每次生成 IitQcLog 时,把这个 version 抄进去。这样以后回溯时,就知道当时是按哪套法律判的案。
### **(2) 性能问题**
* **你的方案**Webhook 只查单条,全量用异步。✅
* **补充**:这就是我们 V2.9 架构的天然优势。
* **实时**WebhookController \-\> SopEngine (单条)。
* **全量**pg-boss 定时任务 \-\> BatchQcJob (批量)。
### **(3) 重复质控 (防抖)**
* **你的方案**幂等性检查5分钟。✅
* **技术落地**:利用 pg-boss 的 **Debounce** 功能。
// WebhookController.ts
// 如果 5 分钟内来了同一个 record\_id 的 Webhook只执行最后一次
await boss.send('qc-job', { recordId }, {
singletonKey: \`qc-${projectId}-${recordId}\`, // 唯一键
singletonSeconds: 300 // 300秒防抖窗口
});
这样根本不需要写复杂的 Redis 锁pg-boss 帮你搞定。
### **(4) 字段映射变更**
* **你的方案**:重新同步。✅
* **补充**:增加一个 **"变更检测"**。
* 每次 Webhook 数据来了,检查一下 payload 里的字段名是否在我们的 iit\_field\_mapping 里。如果不认识,说明 REDCap 改了Agent 自动触发一次 Metadata Sync 任务。
## **5\. 异步架构落地场景 (Asynchronous Implementation)**
为了保证高并发下的系统稳定性,本方案在以下 4 个关键环节强制使用异步处理(基于 pg-boss
### **5.1 核心质控执行 (Core Execution)**
* **场景**CRC 在 REDCap 点击保存,触发 DET Webhook。
* **机制**
* **同步层 (Node.js)**:接收 HTTP 请求 ![][image1] 校验签名 ![][image1] 写入 pg-boss 队列 ![][image1] 立即返回 200 OK (耗时 \< 10ms)。
* **异步层 (Worker)**:后台 Worker 从队列获取 record\_id ![][image1] 拉取数据 ![][image1] 执行 Engine A/B ![][image1] 写入日志。
* **价值**:确保 EDC 前端操作零卡顿,彻底解耦录入与质控。
### **5.2 批量全量回溯 (Batch Processing)**
* **场景**:规则变更后(如入排标准修改),需要重新检查历史数据;或每日定时巡检。
* **机制**
* **调度器**:创建 BatchQcJob。
* **分片执行**:将 1000 条记录拆分为 20 个子任务(每批 50 条),并行推入队列。
* **流控**Worker 控制并发数,避免瞬间打爆 REDCap API。
### **5.3 "黄色"报警汇总 (Delayed Notifications)**
* **场景**:发现非紧急的逻辑矛盾或缺失值(黄色/绿色级别)。
* **机制**
* **写入**:质控 Worker 发现问题 ![][image1] 写入 iit\_qc\_logs ![][image1] 标记为 pending\_digest。
* **发送**:每日下午 17:00 定时任务触发 ![][image1] 聚合当日所有黄色问题 ![][image1] 生成一份简报发送给 PI。
* **价值**:防消息轰炸,提升用户体验。
### **5.4 规则自动同步 (Metadata Sync)**
* **场景**:项目初始化或 REDCap 字段变更。
* **机制**
* **触发**:用户点击"同步字段"按钮。
* **执行**:前端收到"任务已提交" ![][image1] 后台 Worker 调用 exportMetadata (耗时较长) ![][image1] 解析并更新 iit\_field\_mapping ![][image1] 更新任务状态。
* **价值**:防止长连接超时。
## **6\. 总结**
这 4 个建议是**完全成熟的生产级方案**。
* 它们解决了 **"规则从哪来"** (1)。
* 解决了 **"数据怎么存"** (2)。
* 解决了 **"怎么发通知"** (3)。
* 解决了 **"异常怎么办"** (4)。
配合之前的 **V2.9 极简架构**Engine \+ Tools \+ Skill以及本补充文档中的 **异步处理规范**,这套系统已经具备了极高的商业交付价值。建议直接纳入 PRD 开发。
[image1]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAXCAYAAADpwXTaAAAAX0lEQVR4XmNgGAWjYCQAeXn50+hiZAOgYU/QxcgGcnJy2kA8HV2cbAB03SwgDkIXB0lIkomnAfF1oBHM1DBsERCfRzGMHCCPy5ukAmgETEAXJwvIUzFpMMpTM9GOcAAAmV0cRTlI2MMAAAAASUVORK5CYII=>