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

@@ -833,7 +833,8 @@ model IitProject {
id String @id @default(uuid())
name String
description String?
difyDatasetId String? @unique @map("dify_dataset_id")
difyDatasetId String? @unique @map("dify_dataset_id") // 已废弃,使用 knowledgeBaseId
knowledgeBaseId String? @map("knowledge_base_id") // 关联 ekb_schema.knowledge_bases
protocolFileKey String? @map("protocol_file_key")
cachedRules Json? @map("cached_rules")
fieldMappings Json @map("field_mappings")
@@ -851,6 +852,7 @@ model IitProject {
userMappings IitUserMapping[]
@@index([status, deletedAt])
@@index([knowledgeBaseId], map: "idx_iit_project_kb")
@@map("projects")
@@schema("iit_schema")
}
@@ -950,6 +952,336 @@ model IitAuditLog {
@@schema("iit_schema")
}
// ============================================================
// IIT Manager Agent 新增表 (V2.9.1)
// Phase 1-6 完整数据库设计
// ============================================================
/// Skill 配置存储表 - 存储质控规则、SOP流程图
/// 支持 webhook/cron/event 三种触发方式
model IitSkill {
id String @id @default(uuid())
projectId String @map("project_id")
skillType String @map("skill_type") // qc_process | daily_briefing | general_chat | weekly_report | visit_reminder
name String // 技能名称
description String? // 技能描述
config Json @db.JsonB // 核心配置 JSONSOP 流程图)
isActive Boolean @default(true) @map("is_active")
version Int @default(1)
// V2.9 新增:主动触发能力
triggerType String @default("webhook") @map("trigger_type") // 'webhook' | 'cron' | 'event'
cronSchedule String? @map("cron_schedule") // Cron 表达式,如 "0 9 * * *"
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([projectId, skillType], map: "unique_iit_skill_project_type")
@@index([projectId], map: "idx_iit_skill_project")
@@index([isActive], map: "idx_iit_skill_active")
@@map("skills")
@@schema("iit_schema")
}
/// 字段名映射字典表 - 解决 LLM 生成的字段名与 REDCap 实际字段名不一致的问题
model IitFieldMapping {
id String @id @default(uuid())
projectId String @map("project_id")
aliasName String @map("alias_name") // LLM 可能传的名称(如 "gender", "性别"
actualName String @map("actual_name") // REDCap 实际字段名(如 "sex"
fieldType String? @map("field_type") // 字段类型text, number, date, radio, checkbox
fieldLabel String? @map("field_label") // 字段显示标签
validation Json? @db.JsonB // 验证规则 { min, max, pattern, choices }
createdAt DateTime @default(now()) @map("created_at")
@@unique([projectId, aliasName], map: "unique_iit_field_mapping")
@@index([projectId], map: "idx_iit_field_mapping_project")
@@map("field_mapping")
@@schema("iit_schema")
}
/// 对话历史表(流水账)- 存储原始对话,用于生成周报
/// V2.9 新增反馈字段支持用户点赞/点踩
model IitConversationHistory {
id String @id @default(uuid())
projectId String @map("project_id")
userId String @map("user_id")
recordId String? @map("record_id") // 关联的患者(如有)
role String // user | assistant
content String @db.Text
intent String? // 识别出的意图类型
entities Json? @db.JsonB // 提取的实体 { record_id, visit, ... }
// V2.9 新增:反馈循环
feedback String? @map("feedback") // 'thumbs_up' | 'thumbs_down' | null
feedbackReason String? @map("feedback_reason") // 点踩原因:'too_long' | 'inaccurate' | 'unclear'
// 向量嵌入(用于语义搜索)
embedding Unsupported("vector(1536)")?
createdAt DateTime @default(now()) @map("created_at")
@@index([projectId, userId], map: "idx_iit_conv_project_user")
@@index([projectId, recordId], map: "idx_iit_conv_project_record")
@@index([createdAt], map: "idx_iit_conv_created")
@@map("conversation_history")
@@schema("iit_schema")
}
/// 项目级热记忆表 - 存储 Markdown 格式的热记忆
/// 每次对话都注入 System Prompt包含用户画像、当前状态、系统禁令
model IitProjectMemory {
id String @id @default(uuid())
projectId String @unique @map("project_id")
content String @db.Text // Markdown 格式的热记忆
lastUpdatedBy String @map("last_updated_by") // 'system_daily_job' | 'admin_user_id' | 'profiler_job'
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("project_memory")
@@schema("iit_schema")
}
/// 周报归档表(历史书)- 存储每周的关键决策、进度、踩坑记录
model IitWeeklyReport {
id String @id @default(uuid())
projectId String @map("project_id")
weekNumber Int @map("week_number") // 第几周(从项目开始计算)
weekStart DateTime @map("week_start") // 周起始日期
weekEnd DateTime @map("week_end") // 周结束日期
summary String @db.Text // Markdown 格式的周报内容
metrics Json? @db.JsonB // 结构化指标 { enrolled, queries, ... }
createdBy String @map("created_by") // 'system_scheduler' | 'admin_user_id'
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([projectId, weekNumber], map: "unique_iit_weekly_report")
@@index([projectId], map: "idx_iit_weekly_report_project")
@@map("weekly_reports")
@@schema("iit_schema")
}
/// ReAct 推理轨迹表 - 用于调试,记录 AI 的思考过程
model IitAgentTrace {
id String @id @default(uuid())
projectId String @map("project_id")
userId String @map("user_id")
query String @db.Text // 用户原始问题
intentType String? @map("intent_type") // 识别的意图类型
trace Json @db.JsonB // ReAct 的完整思考过程
tokenUsage Int? @map("token_usage") // 消耗的 Token 数
duration Int? // 执行时长ms
success Boolean @default(true)
errorMsg String? @map("error_msg")
createdAt DateTime @default(now()) @map("created_at")
@@index([projectId, createdAt], map: "idx_iit_trace_project_time")
@@index([userId], map: "idx_iit_trace_user")
@@map("agent_trace")
@@schema("iit_schema")
}
/// PII 脱敏审计日志表 - 记录所有发送给 LLM 的脱敏信息(合规必需)
/// 暂时创建表结构,功能延后到 Phase 1.5 实现
model IitPiiAuditLog {
id String @id @default(uuid())
projectId String @map("project_id")
userId String @map("user_id") // 操作者
sessionId String @map("session_id") // 会话 ID
// 脱敏内容(加密存储)
originalHash String @map("original_hash") // 原始内容的 SHA256 哈希
maskedPayload String @db.Text @map("masked_payload") // 脱敏后发送给 LLM 的内容
maskingMap String @db.Text @map("masking_map") // 加密存储的映射表
// 元数据
piiCount Int @map("pii_count") // 检测到的 PII 数量
piiTypes String[] @map("pii_types") // 检测到的 PII 类型
llmProvider String @map("llm_provider") // 'qwen' | 'deepseek' | 'openai'
createdAt DateTime @default(now()) @map("created_at")
@@index([projectId, userId], map: "idx_iit_pii_project_user")
@@index([sessionId], map: "idx_iit_pii_session")
@@index([createdAt], map: "idx_iit_pii_created")
@@map("pii_audit_log")
@@schema("iit_schema")
}
/// 表单模板表Phase 6 视觉能力)- 预留表结构
model IitFormTemplate {
id String @id @default(uuid())
projectId String @map("project_id")
formName String @map("form_name") // REDCap 表单名称
fieldSchema Json @db.JsonB @map("field_schema") // 表单字段结构
keywords String[] // 用于匹配的关键词
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([projectId, formName], map: "unique_iit_form_template")
@@index([projectId], map: "idx_iit_form_template_project")
@@map("form_templates")
@@schema("iit_schema")
}
// ============================================================
// IIT Manager Agent - 实时质控系统表 (V2.9.1)
// 参考文档: docs/03-业务模块/IIT Manager Agent/04-开发计划/06-实时质控系统开发计划.md
// ============================================================
/// REDCap 字段元数据表 - 从 REDCap 同步的字段信息,用于自动生成质控规则
/// 注意:与 IitFieldMapping别名映射表不同此表存储 REDCap 原始元数据
model IitFieldMetadata {
id String @id @default(uuid())
projectId String @map("project_id")
// REDCap 字段信息
fieldName String @map("field_name") // REDCap 字段名
fieldLabel String @map("field_label") // 字段标签(中文)
fieldType String @map("field_type") // text, radio, checkbox, dropdown, etc.
formName String @map("form_name") // ⭐ 所属表单名,用于单表质控规则过滤
sectionHeader String? @map("section_header") // 所属区段
// 验证规则(从 REDCap 元数据提取)
validation String? @map("validation") // text_validation_type
validationMin String? @map("validation_min") // 最小值
validationMax String? @map("validation_max") // 最大值
choices String? @map("choices") // 选项,如 "1, 男 | 2, 女"
required Boolean @default(false) // 是否必填
branching String? @map("branching") // 分支逻辑
// LLM 友好名称(可选)
alias String?
// 规则来源标记
ruleSource String? @map("rule_source") // 'auto' | 'manual'
// 时间戳
syncedAt DateTime @default(now()) @map("synced_at")
@@unique([projectId, fieldName], map: "unique_iit_field_metadata")
@@index([projectId], map: "idx_iit_field_metadata_project")
@@index([projectId, formName], map: "idx_iit_field_metadata_form")
@@map("field_metadata")
@@schema("iit_schema")
}
/// 质控日志表 - 仅新增,不覆盖,保留完整审计轨迹
/// 设计原则:每次质控都新增记录,用于审计轨迹和趋势分析
model IitQcLog {
id String @id @default(uuid())
projectId String @map("project_id")
recordId String @map("record_id")
eventId String? @map("event_id")
// 质控类型
qcType String @map("qc_type") // 'form' | 'holistic'
formName String? @map("form_name") // 单表质控时记录表单名
// 核心结果
status String // 'PASS' | 'FAIL' | 'WARNING'
// 字段级详情 (JSONB)
// 格式: [{ field: "age", rule: "range_check", level: "RED", message: "..." }, ...]
issues Json @default("[]") @db.JsonB
// 规则统计
rulesEvaluated Int @default(0) @map("rules_evaluated") // 实际评估的规则数
rulesSkipped Int @default(0) @map("rules_skipped") // 逻辑守卫跳过的规则数
rulesPassed Int @default(0) @map("rules_passed")
rulesFailed Int @default(0) @map("rules_failed")
// 规则版本(用于历史追溯)
ruleVersion String @map("rule_version")
// 入排标准检查(全案质控时填充)
inclusionPassed Boolean? @map("inclusion_passed")
exclusionPassed Boolean? @map("exclusion_passed")
// 审计信息
triggeredBy String @map("triggered_by") // 'webhook' | 'manual' | 'batch'
createdAt DateTime @default(now()) @map("created_at")
// 索引 - 支持历史查询和趋势分析
@@index([projectId, recordId, createdAt], map: "idx_iit_qc_log_record_time")
@@index([projectId, status, createdAt], map: "idx_iit_qc_log_status_time")
@@index([projectId, qcType, createdAt], map: "idx_iit_qc_log_type_time")
@@map("qc_logs")
@@schema("iit_schema")
}
/// 录入汇总表 - 记录入组和录入进度
/// 设计原则:使用 upsert每个记录只有一条汇总
model IitRecordSummary {
id String @id @default(uuid())
projectId String @map("project_id")
recordId String @map("record_id")
// 入组信息
enrolledAt DateTime? @map("enrolled_at") // 首次录入时间 = 入组时间
enrolledBy String? @map("enrolled_by") // 入组录入人REDCap username
// 最新录入信息
lastUpdatedAt DateTime @map("last_updated_at")
lastUpdatedBy String? @map("last_updated_by")
lastFormName String? @map("last_form_name") // 最后更新的表单
// 表单完成状态 (JSONB)
// 格式: { "demographics": 2, "baseline": 1, "visit1": 0 }
// 0=未开始, 1=进行中, 2=完成
formStatus Json @default("{}") @db.JsonB @map("form_status")
// 数据完整度
totalForms Int @default(0) @map("total_forms")
completedForms Int @default(0) @map("completed_forms")
completionRate Float @default(0) @map("completion_rate") // 0-100%
// 最新质控状态(冗余存储,查询更快)
latestQcStatus String? @map("latest_qc_status") // 'PASS' | 'FAIL' | 'WARNING'
latestQcAt DateTime? @map("latest_qc_at")
// 更新次数(用于趋势分析)
updateCount Int @default(0) @map("update_count")
// 时间戳
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 唯一约束 - 每个记录只有一条汇总
@@unique([projectId, recordId], map: "unique_iit_record_summary")
@@index([projectId, enrolledAt], map: "idx_iit_record_summary_enrolled")
@@index([projectId, latestQcStatus], map: "idx_iit_record_summary_qc_status")
@@index([projectId, completionRate], map: "idx_iit_record_summary_completion")
@@map("record_summary")
@@schema("iit_schema")
}
/// 项目级汇总表 - 用于 Dashboard 快速展示
/// 设计原则:使用 upsert每个项目只有一条汇总
model IitQcProjectStats {
id String @id @default(uuid())
projectId String @unique @map("project_id")
// 汇总统计
totalRecords Int @default(0) @map("total_records")
passedRecords Int @default(0) @map("passed_records")
failedRecords Int @default(0) @map("failed_records")
warningRecords Int @default(0) @map("warning_records")
// 入排标准统计
inclusionMet Int @default(0) @map("inclusion_met")
exclusionMet Int @default(0) @map("exclusion_met")
// 录入进度统计
avgCompletionRate Float @default(0) @map("avg_completion_rate")
// 更新时间
updatedAt DateTime @updatedAt @map("updated_at")
@@map("qc_project_stats")
@@schema("iit_schema")
}
model admin_operation_logs {
id Int @id @default(autoincrement())
admin_id String