/** * CRA 智能质控引擎 - 默认规则配置 Seed * * 五大规则体系: * 1. 变量质控 (VARIABLE_QC) - 硬规则 * 2. 入排标准 (INCLUSION_EXCLUSION) - LLM 检查 * 3. 方案偏离 (PROTOCOL_DEVIATION) - 混合规则 * 4. AE 监测 (AE_MONITORING) - LLM 检查 * 5. 伦理合规 (ETHICS_COMPLIANCE) - 硬规则 */ import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); /** * 默认 Skills 配置 */ const DEFAULT_SKILLS = [ // ============================================================ // 1. 变量质控 - 硬规则 (Level: normal, 实时触发) // ============================================================ { skillType: 'variable_qc', name: '变量质控', description: '针对每个变量的数据校验,包括空值检查、数值范围、格式验证', ruleType: 'HARD_RULE', level: 'normal', priority: 100, triggerType: 'webhook', requiredTags: ['#demographics', '#lab'], config: { engine: 'HardRuleEngine', rules: [ { id: 'VQ-001', name: '年龄范围检查', field: 'age', formName: 'demographics', logic: { and: [ { '>=': [{ var: 'age' }, 18] }, { '<=': [{ var: 'age' }, 100] }, ], }, message: '年龄超出合理范围 (18-100岁)', severity: 'error', category: 'lab_values', }, { id: 'VQ-002', name: 'BMI 范围检查', field: 'bmi', formName: 'demographics', logic: { and: [ { '>': [{ var: 'bmi' }, 10] }, { '<': [{ var: 'bmi' }, 60] }, ], }, message: 'BMI 值异常 (正常范围 10-60)', severity: 'warning', category: 'lab_values', }, { id: 'VQ-003', name: '体重范围检查', field: 'weight', formName: 'demographics', logic: { and: [ { '>': [{ var: 'weight' }, 20] }, { '<': [{ var: 'weight' }, 300] }, ], }, message: '体重值异常 (正常范围 20-300kg)', severity: 'warning', category: 'lab_values', }, { id: 'VQ-004', name: '身高范围检查', field: 'height', formName: 'demographics', logic: { and: [ { '>': [{ var: 'height' }, 50] }, { '<': [{ var: 'height' }, 250] }, ], }, message: '身高值异常 (正常范围 50-250cm)', severity: 'warning', category: 'lab_values', }, ], }, }, // ============================================================ // 2. 入排标准 - LLM 检查 (Level: normal, 定时触发) // ============================================================ { skillType: 'inclusion_exclusion', name: '入排标准核查', description: '判断受试者是否符合研究方案的入组标准和排除标准', ruleType: 'LLM_CHECK', level: 'normal', priority: 200, triggerType: 'cron', cronSchedule: '0 2 * * *', // 每日凌晨 2 点 requiredTags: ['#demographics', '#medical_history', '#lab'], config: { engine: 'SoftRuleEngine', model: 'deepseek-v3', systemPrompt: '你是一个专业的临床研究监查员,负责核查受试者是否符合入组标准。', checks: [ { id: 'IE-001', name: '年龄入组标准', desc: '检查受试者年龄是否符合研究方案规定的入组年龄范围', promptTemplate: `请根据以下受试者数据,判断其年龄是否符合入组标准。 受试者年龄: {{age}} 研究方案通常要求受试者年龄在 18-75 岁之间。请判断此受试者是否符合年龄入组标准。`, requiredTags: ['#demographics'], category: 'inclusion', severity: 'critical', }, { id: 'IE-002', name: '确诊时间入组标准', desc: '检查受试者确诊时间是否在研究方案规定的时间窗口内', promptTemplate: `请根据以下受试者数据,判断其确诊时间是否符合入组标准。 确诊日期: {{diagnosis_date}} 入组日期: {{enrollment_date}} 研究方案通常要求确诊时间在入组前一定时间内(如 3 个月、6 个月等)。请判断此受试者是否符合确诊时间入组标准。`, requiredTags: ['#demographics', '#medical_history'], category: 'inclusion', severity: 'critical', }, ], }, }, // ============================================================ // 3. 方案偏离 - 混合规则 (Level: normal, 定时触发) // ============================================================ { skillType: 'protocol_deviation', name: '方案偏离检测', description: '检测访视超窗、漏做检查、违反用药规定等方案偏离情况', ruleType: 'HYBRID', level: 'normal', priority: 300, triggerType: 'cron', cronSchedule: '0 3 * * *', // 每日凌晨 3 点 requiredTags: ['#visits', '#medications'], config: { engine: 'HybridEngine', hardRules: [ { id: 'PD-001', name: '访视间隔检查', field: ['visit_1_date', 'visit_2_date'], logic: { '<=': [ { '-': [{ var: 'visit_2_date' }, { var: 'visit_1_date' }] }, 31, // 最大间隔 28+3 天 ], }, message: '访视超窗:访视 2 与访视 1 间隔超过 31 天', severity: 'warning', category: 'logic_check', }, ], softChecks: [ { id: 'PD-002', name: '禁用药物检查', desc: '检查受试者是否使用了研究方案禁止的药物', promptTemplate: '请检查受试者的用药记录,判断是否存在使用研究方案禁止药物的情况。', requiredTags: ['#medications'], category: 'protocol_deviation', severity: 'warning', }, ], }, }, // ============================================================ // 4. AE 监测 - LLM 检查 (Level: normal, 定时触发) // ============================================================ { skillType: 'ae_monitoring', name: 'AE 事件监测', description: '检测未报告的不良事件,包括实验室异常与 AE 记录的一致性检查', ruleType: 'LLM_CHECK', level: 'normal', priority: 250, triggerType: 'cron', cronSchedule: '0 4 * * *', // 每日凌晨 4 点 requiredTags: ['#lab', '#ae'], config: { engine: 'SoftRuleEngine', model: 'deepseek-v3', systemPrompt: '你是一个专业的药物安全监查员,负责核查不良事件报告的完整性和准确性。', checks: [ { id: 'AE-001', name: 'Lab 异常与 AE 一致性', desc: '检查实验室检查异常值是否已在 AE 表中报告', promptTemplate: `请对比以下实验室检查数据和不良事件记录,判断是否存在未报告的实验室异常。 重点关注: 1. Grade 3 及以上的实验室异常是否已记录为 AE 2. 持续异常是否已报告 3. 与基线相比显著变化的指标 请给出判断结果,并列出可能遗漏报告的异常。`, requiredTags: ['#lab', '#ae'], category: 'ae_detection', severity: 'critical', }, { id: 'AE-002', name: 'SAE 报告时效性', desc: '检查严重不良事件是否在规定时间内报告', promptTemplate: `请检查以下 SAE 记录,判断报告时效是否符合法规要求。 通常要求: - 致死/危及生命的 SAE: 24 小时内报告 - 其他 SAE: 15 天内报告 请判断各 SAE 的报告时效是否合规。`, requiredTags: ['#ae'], category: 'ae_detection', severity: 'critical', }, ], }, }, // ============================================================ // 5. 伦理合规 - 硬规则 (Level: blocking, 实时触发) // ============================================================ { skillType: 'ethics_compliance', name: '伦理合规检查', description: '检查知情同意书签署时间、隐私保护等伦理合规问题', ruleType: 'HARD_RULE', level: 'blocking', // 阻断性检查,失败则跳过后续 AI 检查 priority: 10, // 最高优先级 triggerType: 'webhook', requiredTags: ['#consent', '#demographics'], config: { engine: 'HardRuleEngine', rules: [ { id: 'EC-001', name: '知情同意书签署时间', field: ['icf_date', 'first_visit_date'], logic: { '<=': [{ var: 'icf_date' }, { var: 'first_visit_date' }], }, message: '伦理违规:知情同意书签署日期晚于首次访视日期', severity: 'error', category: 'ethics', }, { id: 'EC-002', name: '知情同意书必填检查', field: 'icf_date', logic: { '!!': [{ var: 'icf_date' }], }, message: '伦理违规:缺少知情同意书签署日期', severity: 'error', category: 'ethics', }, { id: 'EC-003', name: '受试者 ID 必填检查', field: 'record_id', logic: { '!!': [{ var: 'record_id' }], }, message: '数据完整性:缺少受试者 ID', severity: 'error', category: 'ethics', }, ], }, }, ]; /** * 为项目创建默认 Skills * * @param projectId 项目 ID */ export async function seedDefaultSkills(projectId: string): Promise { console.log(`[Seed] Creating default skills for project: ${projectId}`); for (const skillData of DEFAULT_SKILLS) { try { // 使用 upsert 避免重复创建 await prisma.iitSkill.upsert({ where: { projectId_skillType: { projectId, skillType: skillData.skillType, }, }, update: { name: skillData.name, description: skillData.description, ruleType: skillData.ruleType, level: skillData.level, priority: skillData.priority, triggerType: skillData.triggerType, cronSchedule: skillData.cronSchedule, requiredTags: skillData.requiredTags, config: skillData.config, isActive: true, updatedAt: new Date(), }, create: { projectId, skillType: skillData.skillType, name: skillData.name, description: skillData.description, ruleType: skillData.ruleType, level: skillData.level, priority: skillData.priority, triggerType: skillData.triggerType, cronSchedule: skillData.cronSchedule, requiredTags: skillData.requiredTags, config: skillData.config, isActive: true, }, }); console.log(`[Seed] Created/Updated skill: ${skillData.name}`); } catch (error: any) { console.error(`[Seed] Failed to create skill: ${skillData.name}`, error.message); } } console.log(`[Seed] Completed creating default skills for project: ${projectId}`); } /** * 主函数 - 可以直接运行 */ async function main() { // 获取命令行参数 const projectId = process.argv[2]; if (!projectId) { console.error('Usage: npx ts-node prisma/seeds/cra-qc-skills.seed.ts '); console.error('Example: npx ts-node prisma/seeds/cra-qc-skills.seed.ts test0102-pd-study'); process.exit(1); } // 检查项目是否存在 const project = await prisma.iitProject.findUnique({ where: { id: projectId }, }); if (!project) { console.error(`Project not found: ${projectId}`); process.exit(1); } await seedDefaultSkills(projectId); } // 如果直接运行此文件 main() .catch(console.error) .finally(() => prisma.$disconnect());