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:
2026-02-08 21:22:11 +08:00
parent 45c7b32dbb
commit 7a299e8562
51 changed files with 10638 additions and 184 deletions

View 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);
});