feat(iit): Complete CRA Agent V3.0 P0 milestone - autonomous QC pipeline
P0-1: Variable list sync from REDCap metadata P0-2: QC rule configuration with JSON Logic + AI suggestion P0-3: Scheduled QC + report generation + eQuery closed loop P0-4: Unified dashboard + AI stream timeline + critical events Backend: - Add IitEquery, IitCriticalEvent Prisma models + migration - Add cronEnabled/cronExpression to IitProject - Implement eQuery service/controller/routes (CRUD + respond/review/close) - Implement DailyQcOrchestrator (report -> eQuery -> critical events -> notify) - Add AI rule suggestion service - Register daily QC cron worker and eQuery auto-review worker - Extend QC cockpit with timeline, trend, critical events APIs - Fix timeline issues field compat (object vs array format) Frontend: - Create IIT business module with 6 pages (Dashboard, AI Stream, eQuery, Reports, Variable List + project config pages) - Migrate IIT config from admin panel to business module - Implement health score, risk heatmap, trend chart, critical event alerts - Register IIT module in App router and top navigation Testing: - Add E2E API test script covering 7 modules (46 assertions, all passing) Tested: E2E API tests 46/46 passed, backend and frontend verified Made-with: Cursor
This commit is contained in:
@@ -286,6 +286,64 @@ export async function syncMetadata(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段元数据列表
|
||||
*/
|
||||
export async function listFieldMetadata(
|
||||
request: FastifyRequest<{
|
||||
Params: ProjectIdParams;
|
||||
Querystring: { formName?: string; search?: string };
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { formName, search } = request.query as { formName?: string; search?: string };
|
||||
|
||||
const where: any = { projectId: id };
|
||||
if (formName) {
|
||||
where.formName = formName;
|
||||
}
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ fieldName: { contains: search, mode: 'insensitive' } },
|
||||
{ fieldLabel: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
const [fields, total] = await Promise.all([
|
||||
prisma.iitFieldMetadata.findMany({
|
||||
where,
|
||||
orderBy: [{ formName: 'asc' }, { fieldName: 'asc' }],
|
||||
}),
|
||||
prisma.iitFieldMetadata.count({ where }),
|
||||
]);
|
||||
|
||||
const forms = await prisma.iitFieldMetadata.findMany({
|
||||
where: { projectId: id },
|
||||
select: { formName: true },
|
||||
distinct: ['formName'],
|
||||
orderBy: { formName: 'asc' },
|
||||
});
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
data: {
|
||||
fields,
|
||||
total,
|
||||
forms: forms.map(f => f.formName),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
logger.error('获取字段元数据失败', { error: message });
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
error: message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联知识库
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user