Files
AIclinicalresearch/backend/test-event-level-qc.ts

172 lines
5.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 测试脚本:验证事件级质控功能
*
* 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<string, { total: number; pass: number; fail: number; warning: number }>();
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<string, number>();
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);
});