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:
175
backend/debug-form-events.ts
Normal file
175
backend/debug-form-events.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 调试脚本:诊断表单和事件问题
|
||||
*
|
||||
* 问题1:表单数量 - 应该是19个(含重复访视),但只识别7个
|
||||
* 问题2:Age 为空 - 即使 Complete 状态,某些记录 age 仍为空
|
||||
*/
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { RedcapAdapter } from './src/modules/iit-manager/adapters/RedcapAdapter.js';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('='.repeat(60));
|
||||
console.log('🔍 表单和事件诊断');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
const project = await prisma.iitProject.findFirst({
|
||||
where: { name: { contains: 'test0207' } },
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
console.log('❌ 未找到项目');
|
||||
await prisma.$disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
const adapter = new RedcapAdapter(project.redcapUrl, project.redcapApiToken);
|
||||
|
||||
// ===============================
|
||||
// 诊断 1:获取事件和表单映射
|
||||
// ===============================
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('📊 诊断 1:REDCap 事件和表单结构');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// 获取事件列表
|
||||
try {
|
||||
const formData = new (await import('form-data')).default();
|
||||
formData.append('token', project.redcapApiToken);
|
||||
formData.append('content', 'event');
|
||||
formData.append('format', 'json');
|
||||
|
||||
const axios = (await import('axios')).default;
|
||||
const eventsResponse = await axios.post(`${project.redcapUrl}/api/`, formData, {
|
||||
headers: formData.getHeaders()
|
||||
});
|
||||
|
||||
const events = eventsResponse.data;
|
||||
console.log(`\n📋 事件列表 (共 ${events.length} 个):`);
|
||||
for (const event of events) {
|
||||
console.log(` ${event.unique_event_name}: ${event.event_name}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(' 获取事件列表失败:', error.message);
|
||||
}
|
||||
|
||||
// 获取表单-事件映射
|
||||
try {
|
||||
const formData = new (await import('form-data')).default();
|
||||
formData.append('token', project.redcapApiToken);
|
||||
formData.append('content', 'formEventMapping');
|
||||
formData.append('format', 'json');
|
||||
|
||||
const axios = (await import('axios')).default;
|
||||
const mappingResponse = await axios.post(`${project.redcapUrl}/api/`, formData, {
|
||||
headers: formData.getHeaders()
|
||||
});
|
||||
|
||||
const mapping = mappingResponse.data;
|
||||
console.log(`\n📋 表单-事件映射 (共 ${mapping.length} 个):`);
|
||||
|
||||
// 按事件分组
|
||||
const eventForms = new Map<string, string[]>();
|
||||
for (const m of mapping) {
|
||||
if (!eventForms.has(m.unique_event_name)) {
|
||||
eventForms.set(m.unique_event_name, []);
|
||||
}
|
||||
eventForms.get(m.unique_event_name)!.push(m.form);
|
||||
}
|
||||
|
||||
for (const [event, forms] of eventForms) {
|
||||
console.log(`\n 📁 ${event}:`);
|
||||
for (const form of forms) {
|
||||
console.log(` - ${form}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 统计唯一表单
|
||||
const uniqueForms = [...new Set(mapping.map((m: any) => m.form))];
|
||||
console.log(`\n📊 统计:`);
|
||||
console.log(` 唯一表单数: ${uniqueForms.length}`);
|
||||
console.log(` 表单-事件映射总数: ${mapping.length}`);
|
||||
console.log(` 这就是为什么显示19个而不是7个!`);
|
||||
|
||||
} catch (error: any) {
|
||||
console.log(' 获取表单-事件映射失败:', error.message);
|
||||
}
|
||||
|
||||
// ===============================
|
||||
// 诊断 2:检查 Record 4, 5, 14 的 age 问题
|
||||
// ===============================
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('📊 诊断 2:检查 age 为空的记录');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
const problemRecords = ['4', '5', '14'];
|
||||
|
||||
for (const recId of problemRecords) {
|
||||
console.log(`\n === Record ${recId} ===`);
|
||||
|
||||
// 获取原始数据(所有事件)
|
||||
const rawRecords = await adapter.exportRecords({ records: [recId] });
|
||||
console.log(` 原始记录数: ${rawRecords.length} (多事件)`);
|
||||
|
||||
for (const r of rawRecords) {
|
||||
const event = r.redcap_event_name || '(无事件)';
|
||||
const age = r.age !== undefined && r.age !== '' ? r.age : '(空)';
|
||||
const dob = r.date_of_birth || '(空)';
|
||||
const formComplete = r.basic_demography_form_complete;
|
||||
|
||||
console.log(` 事件: ${event}`);
|
||||
console.log(` age: ${age}, dob: ${dob}, form_complete: ${formComplete}`);
|
||||
}
|
||||
|
||||
// 获取合并后数据
|
||||
const merged = await adapter.getRecordById(recId);
|
||||
console.log(` 合并后: age=${merged?.age || '(空)'}, dob=${merged?.date_of_birth || '(空)'}`);
|
||||
}
|
||||
|
||||
// ===============================
|
||||
// 诊断 3:检查 age 字段的元数据
|
||||
// ===============================
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('📊 诊断 3:age 字段的元数据');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
const calcFields = await adapter.getCalculatedFields();
|
||||
const ageField = calcFields.find(f => f.fieldName === 'age');
|
||||
|
||||
if (ageField) {
|
||||
console.log(`\n 字段名: ${ageField.fieldName}`);
|
||||
console.log(` 标签: ${ageField.fieldLabel}`);
|
||||
console.log(` 表单: ${ageField.formName}`);
|
||||
console.log(` 计算公式: ${ageField.calculation}`);
|
||||
} else {
|
||||
console.log('\n ⚠️ 未找到 age 计算字段!');
|
||||
|
||||
// 检查是否有其他年龄相关字段
|
||||
const metadata = await adapter.exportMetadata();
|
||||
const ageRelated = metadata.filter((f: any) =>
|
||||
f.field_name.toLowerCase().includes('age') ||
|
||||
f.field_label?.toLowerCase().includes('年龄')
|
||||
);
|
||||
|
||||
console.log(`\n 年龄相关字段:`);
|
||||
for (const f of ageRelated) {
|
||||
console.log(` ${f.field_name} (${f.field_type}): ${f.field_label}`);
|
||||
if (f.field_type === 'calc') {
|
||||
console.log(` 公式: ${f.select_choices_or_calculations}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user