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,155 @@
/**
* 调试脚本:诊断字段获取问题
*
* 问题:
* 1. Record 1 有 age=21但质控显示空
* 2. Record 3 应该有年龄但 API 返回无
*/
import { PrismaClient } from '@prisma/client';
import { RedcapAdapter } from './src/modules/iit-manager/adapters/RedcapAdapter.js';
import jsonLogic from 'json-logic-js';
const prisma = new PrismaClient();
async function main() {
console.log('='.repeat(60));
console.log('🔍 字段获取问题诊断');
console.log('='.repeat(60));
// 1. 获取项目配置
const project = await prisma.iitProject.findFirst({
where: { name: { contains: 'test0207' } },
});
if (!project) {
console.log('❌ 未找到项目');
await prisma.$disconnect();
return;
}
// 2. 获取质控规则配置
const skill = await prisma.iitSkill.findFirst({
where: { projectId: project.id, isActive: true },
});
if (!skill) {
console.log('❌ 未找到激活的 Skill');
await prisma.$disconnect();
return;
}
const config = skill.config as any;
const rules = config?.rules || [];
console.log('\n📋 质控规则中的字段配置:');
for (const rule of rules) {
console.log(` ${rule.name}:`);
console.log(` field: "${rule.field}"`);
console.log(` logic: ${JSON.stringify(rule.logic)}`);
}
// 3. 从 REDCap 获取原始数据(检查 Record 1 和 Record 3
const adapter = new RedcapAdapter(project.redcapUrl, project.redcapApiToken);
console.log('\n' + '='.repeat(60));
console.log('📊 检查 Record 1 的原始 REDCap 数据');
console.log('='.repeat(60));
// 获取 Record 1 的原始记录(不合并)
const rawRecords1 = await adapter.exportRecords({ records: ['1'] });
console.log(`\nRecord 1 原始记录数: ${rawRecords1.length} (多事件)`);
for (const r of rawRecords1) {
console.log(` 事件: ${r.redcap_event_name || '(无)'}`);
console.log(` age: ${r.age !== undefined ? r.age : '(字段不存在)'}`);
console.log(` date_of_birth: ${r.date_of_birth || '(空)'}`);
}
// 获取 Record 1 合并后的数据
const merged1 = await adapter.getRecordById('1');
console.log(`\nRecord 1 合并后数据:`);
console.log(` age: ${merged1?.age}`);
console.log(` date_of_birth: ${merged1?.date_of_birth}`);
console.log('\n' + '='.repeat(60));
console.log('📊 检查 Record 3 的原始 REDCap 数据');
console.log('='.repeat(60));
const rawRecords3 = await adapter.exportRecords({ records: ['3'] });
console.log(`\nRecord 3 原始记录数: ${rawRecords3.length} (多事件)`);
for (const r of rawRecords3) {
console.log(` 事件: ${r.redcap_event_name || '(无)'}`);
console.log(` age: ${r.age !== undefined ? r.age : '(字段不存在)'}`);
console.log(` date_of_birth: ${r.date_of_birth || '(空)'}`);
}
const merged3 = await adapter.getRecordById('3');
console.log(`\nRecord 3 合并后数据:`);
console.log(` age: ${merged3?.age}`);
console.log(` date_of_birth: ${merged3?.date_of_birth}`);
// 4. 模拟质控规则执行,看看哪里出问题
console.log('\n' + '='.repeat(60));
console.log('📊 模拟质控规则执行 (Record 1)');
console.log('='.repeat(60));
// 找到年龄规则
const ageRule = rules.find((r: any) => r.name.includes('年龄'));
if (ageRule && merged1) {
console.log(`\n规则: ${ageRule.name}`);
console.log(` field: "${ageRule.field}"`);
console.log(` logic: ${JSON.stringify(ageRule.logic)}`);
// 检查字段值
const fieldValue = merged1[ageRule.field];
console.log(`\n 从数据中获取 "${ageRule.field}" 的值: ${fieldValue} (类型: ${typeof fieldValue})`);
// 尝试执行 JSON Logic
try {
const result = jsonLogic.apply(ageRule.logic, merged1);
console.log(` JSON Logic 执行结果: ${result}`);
} catch (error: any) {
console.log(` JSON Logic 执行失败: ${error.message}`);
}
// 列出数据中所有可能的年龄相关字段
console.log(`\n 数据中的年龄相关字段:`);
for (const [key, value] of Object.entries(merged1)) {
if (key.toLowerCase().includes('age') || key === ageRule.field) {
console.log(` ${key}: ${value}`);
}
}
}
// 5. 检查 SkillRunner 如何获取记录
console.log('\n' + '='.repeat(60));
console.log('📊 检查 SkillRunner 获取记录的方式');
console.log('='.repeat(60));
// 查看 getRecordsToProcess 的实现逻辑
// 它可能使用不同的方法获取数据
const allRecordsMerged = await adapter.getAllRecordsMerged();
console.log(`\ngetAllRecordsMerged 返回 ${allRecordsMerged.length} 条记录`);
const r1FromAll = allRecordsMerged.find(r => r.record_id === '1');
const r3FromAll = allRecordsMerged.find(r => r.record_id === '3');
console.log(`\nRecord 1 from getAllRecordsMerged:`);
console.log(` age: ${r1FromAll?.age}`);
console.log(` date_of_birth: ${r1FromAll?.date_of_birth}`);
console.log(`\nRecord 3 from getAllRecordsMerged:`);
console.log(` age: ${r3FromAll?.age}`);
console.log(` date_of_birth: ${r3FromAll?.date_of_birth}`);
console.log('\n' + '='.repeat(60));
console.log('✅ 诊断完成');
console.log('='.repeat(60));
await prisma.$disconnect();
}
main().catch(async (error) => {
console.error('❌ 脚本出错:', error);
await prisma.$disconnect();
process.exit(1);
});