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:
@@ -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 // 核心配置 JSON(SOP 流程图)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user