/** * 测试脚本:验证事件级质控功能 * * V3.1 变更: * - 每个 record + event 作为独立单元质控 * - 规则支持 applicableEvents 和 applicableForms 配置 * - 不再合并事件数据 */ import { PrismaClient } from '@prisma/client'; import { createSkillRunner } from './src/modules/iit-manager/engines/SkillRunner.js'; import { RedcapAdapter } from './src/modules/iit-manager/adapters/RedcapAdapter.js'; const prisma = new PrismaClient(); async function main() { console.log('='.repeat(60)); console.log('📋 V3.1 事件级质控测试'); console.log('='.repeat(60)); // 1. 获取项目配置 const project = await prisma.iitProject.findFirst({ where: { name: { contains: 'test0207' } }, }); if (!project) { console.log('❌ 未找到项目'); await prisma.$disconnect(); return; } console.log(`\n✅ 项目: ${project.name} (${project.id})`); // =============================== // 测试 1:验证事件级数据获取 // =============================== console.log('\n' + '='.repeat(60)); console.log('📊 测试 1:验证事件级数据获取'); console.log('='.repeat(60)); const adapter = new RedcapAdapter(project.redcapUrl, project.redcapApiToken); // 获取 Record 1 的事件级数据(不合并) const record1Events = await adapter.getAllRecordsByEvent({ recordId: '1' }); console.log(`\nRecord 1 共有 ${record1Events.length} 个事件(不合并):`); for (const event of record1Events) { console.log(`\n 📁 事件: ${event.eventLabel} (${event.eventName})`); console.log(` 表单: ${event.forms.join(', ')}`); console.log(` 数据示例: age=${event.data.age ?? '(空)'}, cmss_complete=${event.data.cmss_complete ?? '(空)'}`); } // =============================== // 测试 2:验证事件级质控执行 // =============================== console.log('\n' + '='.repeat(60)); console.log('📊 测试 2:执行事件级质控 (Record 1)'); console.log('='.repeat(60)); const runner = createSkillRunner(project.id); // 只质控 Record 1 const results = await runner.runByTrigger('manual', { recordId: '1' }); console.log(`\n质控完成! 处理了 ${results.length} 个 record+event 组合:`); for (const result of results) { const issueCount = result.allIssues.length; const statusIcon = result.overallStatus === 'PASS' ? '✅' : result.overallStatus === 'WARNING' ? '⚠️' : '🔴'; console.log(`\n ${statusIcon} Record ${result.recordId} - ${result.eventLabel}`); console.log(` 事件: ${result.eventName}`); console.log(` 表单: ${result.forms?.join(', ') || '(无)'}`); console.log(` 状态: ${result.overallStatus}`); console.log(` 问题数: ${issueCount}`); if (issueCount > 0) { console.log(` 问题详情:`); for (const issue of result.allIssues.slice(0, 3)) { console.log(` - ${issue.ruleName}: ${issue.llmMessage || issue.message}`); } if (issueCount > 3) { console.log(` ... 还有 ${issueCount - 3} 个问题`); } } } // =============================== // 测试 3:验证全量质控(所有记录所有事件) // =============================== console.log('\n' + '='.repeat(60)); console.log('📊 测试 3:全量质控统计'); console.log('='.repeat(60)); const allResults = await runner.runByTrigger('manual'); // 按事件类型统计 const eventStats = new Map(); for (const result of allResults) { const eventLabel = result.eventLabel || 'Unknown'; if (!eventStats.has(eventLabel)) { eventStats.set(eventLabel, { total: 0, pass: 0, fail: 0, warning: 0 }); } const stats = eventStats.get(eventLabel)!; stats.total++; if (result.overallStatus === 'PASS') stats.pass++; else if (result.overallStatus === 'FAIL') stats.fail++; else if (result.overallStatus === 'WARNING') stats.warning++; } console.log(`\n质控总数: ${allResults.length} 个 record+event 组合`); console.log(`\n按事件类型统计:`); for (const [event, stats] of eventStats) { console.log(` ${event}: 总${stats.total}, 通过${stats.pass}, 警告${stats.warning}, 失败${stats.fail}`); } // 问题分布 const ruleStats = new Map(); for (const result of allResults) { for (const issue of result.allIssues) { const ruleName = issue.ruleName || 'Unknown'; ruleStats.set(ruleName, (ruleStats.get(ruleName) || 0) + 1); } } console.log(`\n问题分布:`); for (const [rule, count] of ruleStats) { console.log(` ${rule}: ${count} 次`); } // =============================== // 测试 4:验证数据库日志(包含事件信息) // =============================== console.log('\n' + '='.repeat(60)); console.log('📊 测试 4:验证数据库日志'); console.log('='.repeat(60)); const recentLogs = await prisma.iitQcLog.findMany({ where: { projectId: project.id }, orderBy: { createdAt: 'desc' }, take: 5, select: { id: true, recordId: true, eventId: true, qcType: true, formName: true, status: true, createdAt: true, } }); console.log(`\n最近 ${recentLogs.length} 条质控日志:`); for (const log of recentLogs) { console.log(` [${log.recordId}] 事件: ${log.eventId || '(无)'}`); console.log(` 类型: ${log.qcType}, 表单: ${log.formName || '(无)'}, 状态: ${log.status}`); } console.log('\n' + '='.repeat(60)); console.log('✅ V3.1 事件级质控测试完成'); console.log('='.repeat(60)); await prisma.$disconnect(); } main().catch(async (error) => { console.error('❌ 脚本出错:', error); await prisma.$disconnect(); process.exit(1); });