Files
AIclinicalresearch/backend/debug-form-events.ts

176 lines
5.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 调试脚本:诊断表单和事件问题
*
* 问题1表单数量 - 应该是19个含重复访视但只识别7个
* 问题2Age 为空 - 即使 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('📊 诊断 1REDCap 事件和表单结构');
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('📊 诊断 3age 字段的元数据');
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);
});