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:
2026-02-26 13:28:08 +08:00
parent 31b0433195
commit 203846968c
35 changed files with 7353 additions and 22 deletions

View File

@@ -948,6 +948,8 @@ model IitProject {
redcapApiToken String @map("redcap_api_token")
redcapUrl String @map("redcap_url")
lastSyncAt DateTime? @map("last_sync_at")
cronEnabled Boolean @default(false) @map("cron_enabled")
cronExpression String? @map("cron_expression")
status String @default("active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@ -1426,6 +1428,97 @@ model IitQcProjectStats {
@@schema("iit_schema")
}
/// eQuery 表 - AI 自动生成的电子质疑,具有完整生命周期
model IitEquery {
id String @id @default(uuid())
projectId String @map("project_id")
// 来源
recordId String @map("record_id")
eventId String? @map("event_id")
formName String? @map("form_name")
fieldName String? @map("field_name")
qcLogId String? @map("qc_log_id")
reportId String? @map("report_id")
// 质疑内容
queryText String @map("query_text") @db.Text
expectedAction String? @map("expected_action") @db.Text
severity String @default("warning")
category String?
// 状态机: pending → responded → reviewing → closed / reopened
status String @default("pending")
// CRC 回复
assignedTo String? @map("assigned_to")
respondedAt DateTime? @map("responded_at")
responseText String? @map("response_text") @db.Text
responseData Json? @map("response_data") @db.JsonB
// AI 复核
reviewResult String? @map("review_result")
reviewNote String? @map("review_note") @db.Text
reviewedAt DateTime? @map("reviewed_at")
// 关闭
closedAt DateTime? @map("closed_at")
closedBy String? @map("closed_by")
resolution String? @db.Text
// 时间线
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([projectId], map: "idx_iit_equery_project")
@@index([projectId, status], map: "idx_iit_equery_project_status")
@@index([recordId], map: "idx_iit_equery_record")
@@index([assignedTo], map: "idx_iit_equery_assigned")
@@map("equery")
@@schema("iit_schema")
}
/// 重大事件归档表 - SAE、重大方案偏离等长期临床资产
model IitCriticalEvent {
id String @id @default(uuid())
projectId String @map("project_id")
recordId String @map("record_id")
// 事件分类
eventType String @map("event_type")
severity String @default("critical")
// 事件内容
title String
description String @db.Text
detectedAt DateTime @map("detected_at")
detectedBy String @default("ai") @map("detected_by")
// 来源追溯
sourceQcLogId String? @map("source_qc_log_id")
sourceEqueryId String? @map("source_equery_id")
sourceData Json? @map("source_data") @db.JsonB
// 处理状态
status String @default("open")
handledBy String? @map("handled_by")
handledAt DateTime? @map("handled_at")
handlingNote String? @map("handling_note") @db.Text
// 上报追踪
reportedToEc Boolean @default(false) @map("reported_to_ec")
reportedAt DateTime? @map("reported_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([projectId], map: "idx_iit_critical_event_project")
@@index([projectId, eventType], map: "idx_iit_critical_event_type")
@@index([projectId, status], map: "idx_iit_critical_event_status")
@@map("critical_events")
@@schema("iit_schema")
}
model admin_operation_logs {
id Int @id @default(autoincrement())
admin_id String