/** * 调试脚本:诊断表单和事件问题 * * 问题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(); 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); });