feat(iit): Implement event-level QC architecture V3.1 with dynamic rule filtering, report deduplication and AI intent enhancement
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
171
backend/test-event-level-qc.ts
Normal file
171
backend/test-event-level-qc.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 测试脚本:验证事件级质控功能
|
||||
*
|
||||
* 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);
|
||||
});
|
||||
Reference in New Issue
Block a user