Files
AIclinicalresearch/backend/src/modules/admin/iit-projects/iitQcCockpitRoutes.ts
HaHafeng 71d32d11ee feat(iit): V3.2 data consistency + project isolation + admin config redesign + Chinese labels
Summary:
- Refactor timeline API to read from qc_field_status (SSOT) instead of qc_logs
- Add field-issues paginated API with severity/dimension/recordId filters
- Add LEFT JOIN field_metadata + qc_event_status for Chinese display names
- Implement per-project ChatOrchestrator cache and SessionMemory isolation
- Redesign admin IIT config tabs (REDCap -> Fields -> KB -> Rules -> Members)
- Add AI-powered QC rule generation (D3 programmatic + D1/D5/D6 LLM-based)
- Add clickable warning/critical detail Modal in ReportsPage
- Auto-dispatch eQuery after batch QC via DailyQcOrchestrator
- Update module status documentation to v3.2

Backend changes:
- iitQcCockpitController: rewrite getTimeline from qc_field_status, add getFieldIssues
- iitQcCockpitRoutes: add field-issues route
- ChatOrchestrator: per-projectId cached instances
- SessionMemory: keyed by userId::projectId
- WechatCallbackController: resolve projectId from iitUserMapping
- iitRuleSuggestionService: dimension-based suggest + generateD3Rules
- iitBatchController: call DailyQcOrchestrator after batch QC

Frontend changes:
- AiStreamPage: adapt to new timeline structure with dimension tags
- ReportsPage: clickable stats cards with issue detail Modal
- IitProjectDetailPage: reorder tabs, add AI rule generation UI
- iitProjectApi: add TimelineIssue, FieldIssueItem types and APIs

Status: TypeScript compilation verified, no new lint errors
Made-with: Cursor
2026-03-02 14:29:59 +08:00

366 lines
13 KiB
TypeScript
Raw 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.
/**
* IIT 质控驾驶舱路由
*
* API:
* - GET /api/v1/admin/iit-projects/:projectId/qc-cockpit 获取驾驶舱数据
* - GET /api/v1/admin/iit-projects/:projectId/qc-cockpit/records/:recordId 获取记录详情
* - GET /api/v1/admin/iit-projects/:projectId/qc-cockpit/report 获取质控报告
* - POST /api/v1/admin/iit-projects/:projectId/qc-cockpit/report/refresh 刷新质控报告
*/
import { FastifyInstance } from 'fastify';
import { iitQcCockpitController } from './iitQcCockpitController.js';
export async function iitQcCockpitRoutes(fastify: FastifyInstance) {
// 获取质控驾驶舱数据
fastify.get('/:projectId/qc-cockpit', {
schema: {
description: '获取质控驾驶舱数据(统计 + 热力图)',
tags: ['IIT Admin - 质控驾驶舱'],
params: {
type: 'object',
properties: {
projectId: { type: 'string', description: 'IIT 项目 ID' },
},
required: ['projectId'],
},
response: {
200: {
type: 'object',
properties: {
success: { type: 'boolean' },
data: {
type: 'object',
properties: {
stats: {
type: 'object',
properties: {
qualityScore: { type: 'number' },
totalRecords: { type: 'number' },
passedRecords: { type: 'number' },
failedRecords: { type: 'number' },
warningRecords: { type: 'number' },
pendingRecords: { type: 'number' },
criticalCount: { type: 'number' },
queryCount: { type: 'number' },
deviationCount: { type: 'number' },
passRate: { type: 'number' },
topIssues: {
type: 'array',
items: {
type: 'object',
properties: {
issue: { type: 'string' },
count: { type: 'number' },
severity: { type: 'string' },
},
},
},
},
},
heatmap: {
type: 'object',
properties: {
columns: { type: 'array', items: { type: 'string' } },
rows: {
type: 'array',
items: {
type: 'object',
properties: {
recordId: { type: 'string' },
status: { type: 'string' },
cells: {
type: 'array',
items: {
type: 'object',
properties: {
formName: { type: 'string' },
status: { type: 'string' },
issueCount: { type: 'number' },
recordId: { type: 'string' },
},
},
},
},
},
},
},
},
lastUpdatedAt: { type: 'string' },
},
},
},
},
},
},
}, iitQcCockpitController.getCockpitData.bind(iitQcCockpitController));
// 获取记录质控详情
fastify.get('/:projectId/qc-cockpit/records/:recordId', {
schema: {
description: '获取单条记录的质控详情(含 LLM Trace',
tags: ['IIT Admin - 质控驾驶舱'],
params: {
type: 'object',
properties: {
projectId: { type: 'string', description: 'IIT 项目 ID' },
recordId: { type: 'string', description: '记录 ID' },
},
required: ['projectId', 'recordId'],
},
querystring: {
type: 'object',
properties: {
formName: { type: 'string', description: '表单名称' },
},
},
response: {
200: {
type: 'object',
properties: {
success: { type: 'boolean' },
data: {
type: 'object',
properties: {
recordId: { type: 'string' },
formName: { type: 'string' },
status: { type: 'string' },
data: { type: 'object', additionalProperties: true },
fieldMetadata: { type: 'object', additionalProperties: true },
issues: {
type: 'array',
items: {
type: 'object',
properties: {
field: { type: 'string' },
ruleName: { type: 'string' },
message: { type: 'string' },
severity: { type: 'string' },
actualValue: {},
expectedValue: { type: 'string' },
confidence: { type: 'string' },
},
},
},
llmTrace: {
type: 'object',
properties: {
promptSent: { type: 'string' },
responseReceived: { type: 'string' },
model: { type: 'string' },
latencyMs: { type: 'number' },
},
},
entryTime: { type: 'string' },
},
},
},
},
},
},
}, iitQcCockpitController.getRecordDetail.bind(iitQcCockpitController));
// 获取质控报告
fastify.get('/:projectId/qc-cockpit/report', {
schema: {
description: '获取质控报告(支持 JSON 和 XML 格式)',
tags: ['IIT Admin - 质控驾驶舱'],
params: {
type: 'object',
properties: {
projectId: { type: 'string', description: 'IIT 项目 ID' },
},
required: ['projectId'],
},
querystring: {
type: 'object',
properties: {
format: {
type: 'string',
enum: ['json', 'xml'],
default: 'json',
description: '响应格式json默认或 xmlLLM 友好格式)',
},
},
},
response: {
200: {
type: 'object',
properties: {
success: { type: 'boolean' },
data: {
type: 'object',
properties: {
projectId: { type: 'string' },
reportType: { type: 'string' },
generatedAt: { type: 'string' },
expiresAt: { type: 'string' },
summary: {
type: 'object',
properties: {
totalRecords: { type: 'number' },
completedRecords: { type: 'number' },
criticalIssues: { type: 'number' },
warningIssues: { type: 'number' },
pendingQueries: { type: 'number' },
passRate: { type: 'number' },
lastQcTime: { type: 'string' },
},
},
criticalIssues: { type: 'array' },
warningIssues: { type: 'array' },
formStats: { type: 'array' },
llmFriendlyXml: { type: 'string' },
},
},
},
},
},
},
}, iitQcCockpitController.getReport.bind(iitQcCockpitController));
// 刷新质控报告
fastify.post('/:projectId/qc-cockpit/report/refresh', {
schema: {
description: '强制刷新质控报告(忽略缓存)',
tags: ['IIT Admin - 质控驾驶舱'],
params: {
type: 'object',
properties: {
projectId: { type: 'string', description: 'IIT 项目 ID' },
},
required: ['projectId'],
},
response: {
200: {
type: 'object',
properties: {
success: { type: 'boolean' },
data: { type: 'object' },
},
},
},
},
}, iitQcCockpitController.refreshReport.bind(iitQcCockpitController));
// V3.1: D1-D7 维度统计
fastify.get('/:projectId/qc-cockpit/dimensions', {
schema: {
description: 'D1-D7 各维度详细统计',
tags: ['IIT Admin - 质控驾驶舱'],
params: { type: 'object', properties: { projectId: { type: 'string' } }, required: ['projectId'] },
},
}, iitQcCockpitController.getDimensions.bind(iitQcCockpitController));
// V3.1: 按受试者缺失率
fastify.get('/:projectId/qc-cockpit/completeness', {
schema: {
description: '按受试者返回缺失率',
tags: ['IIT Admin - 质控驾驶舱'],
params: { type: 'object', properties: { projectId: { type: 'string' } }, required: ['projectId'] },
},
}, iitQcCockpitController.getCompleteness.bind(iitQcCockpitController));
// V3.1: 字段级质控结果(分页)
fastify.get('/:projectId/qc-cockpit/field-status', {
schema: {
description: '字段级质控结果(分页,支持 recordId/eventId/status 筛选)',
tags: ['IIT Admin - 质控驾驶舱'],
params: { type: 'object', properties: { projectId: { type: 'string' } }, required: ['projectId'] },
querystring: {
type: 'object',
properties: {
recordId: { type: 'string' },
eventId: { type: 'string' },
status: { type: 'string', enum: ['PASS', 'FAIL', 'WARNING'] },
page: { type: 'string', default: '1' },
pageSize: { type: 'string', default: '50' },
},
},
},
}, iitQcCockpitController.getFieldStatus.bind(iitQcCockpitController));
// AI 工作时间线
fastify.get('/:projectId/qc-cockpit/timeline', iitQcCockpitController.getTimeline.bind(iitQcCockpitController));
// 重大事件列表
fastify.get('/:projectId/qc-cockpit/critical-events', iitQcCockpitController.getCriticalEvents.bind(iitQcCockpitController));
// 质控趋势近N天通过率折线
fastify.get('/:projectId/qc-cockpit/trend', iitQcCockpitController.getTrend.bind(iitQcCockpitController));
// V3.1: D6 方案偏离列表
fastify.get('/:projectId/qc-cockpit/deviations', iitQcCockpitController.getDeviations.bind(iitQcCockpitController));
// 字段级问题分页查询(支持按维度/严重程度筛选)
fastify.get('/:projectId/qc-cockpit/field-issues', {
schema: {
description: '从 qc_field_status 分页查询所有问题字段',
tags: ['IIT Admin - QC 驾驶舱'],
params: { type: 'object', properties: { projectId: { type: 'string' } }, required: ['projectId'] },
querystring: {
type: 'object',
properties: {
page: { type: 'string' },
pageSize: { type: 'string' },
severity: { type: 'string' },
dimension: { type: 'string' },
recordId: { type: 'string' },
},
},
},
}, iitQcCockpitController.getFieldIssues.bind(iitQcCockpitController));
// ============================================================
// GCP 业务报表路由
// ============================================================
fastify.get('/:projectId/qc-cockpit/report/eligibility', {
schema: {
description: 'D1 筛选入选表 — 入排合规性评估',
tags: ['IIT Admin - GCP 报表'],
params: { type: 'object', properties: { projectId: { type: 'string' } }, required: ['projectId'] },
},
}, iitQcCockpitController.getEligibilityReport.bind(iitQcCockpitController));
fastify.get('/:projectId/qc-cockpit/report/completeness', {
schema: {
description: 'D2 数据完整性总览 — L1 项目 + L2 受试者 + L3 事件级统计',
tags: ['IIT Admin - GCP 报表'],
params: { type: 'object', properties: { projectId: { type: 'string' } }, required: ['projectId'] },
},
}, iitQcCockpitController.getCompletenessReport.bind(iitQcCockpitController));
fastify.get('/:projectId/qc-cockpit/report/completeness/fields', {
schema: {
description: 'D2 字段级懒加载 — 按 recordId + eventId 返回缺失字段清单',
tags: ['IIT Admin - GCP 报表'],
params: { type: 'object', properties: { projectId: { type: 'string' } }, required: ['projectId'] },
querystring: {
type: 'object',
properties: {
recordId: { type: 'string' },
eventId: { type: 'string' },
},
required: ['recordId', 'eventId'],
},
},
}, iitQcCockpitController.getCompletenessFields.bind(iitQcCockpitController));
fastify.get('/:projectId/qc-cockpit/report/equery-log', {
schema: {
description: 'D3/D4 eQuery 全生命周期跟踪 — 统计 + 分组 + 全量明细',
tags: ['IIT Admin - GCP 报表'],
params: { type: 'object', properties: { projectId: { type: 'string' } }, required: ['projectId'] },
},
}, iitQcCockpitController.getEqueryLogReport.bind(iitQcCockpitController));
fastify.get('/:projectId/qc-cockpit/report/deviations', {
schema: {
description: 'D6 方案偏离报表 — 结构化超窗数据 + 汇总统计',
tags: ['IIT Admin - GCP 报表'],
params: { type: 'object', properties: { projectId: { type: 'string' } }, required: ['projectId'] },
},
}, iitQcCockpitController.getDeviationReport.bind(iitQcCockpitController));
}