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