Major Changes: - Add StreamingService with OpenAI Compatible format - Upgrade Chat component V2 with Ant Design X integration - Implement AIA module with 12 intelligent agents - Update API routes to unified /api/v1 prefix - Update system documentation Backend (~1300 lines): - common/streaming: OpenAI Compatible adapter - modules/aia: 12 agents, conversation service, streaming integration - Update route versions (RVW, PKB to v1) Frontend (~3500 lines): - modules/aia: AgentHub + ChatWorkspace (100% prototype restoration) - shared/Chat: AIStreamChat, ThinkingBlock, useAIStream Hook - Update API endpoints to v1 Documentation: - AIA module status guide - Universal capabilities catalog - System overview updates - All module documentation sync Tested: Stream response verified, authentication working Status: AIA V2.0 core completed (85%)
64 KiB
IIT Manager Agent 瀹屾暣鎶€鏈<E282AC>紑鍙戞柟妗?(V1.1)
*鏂囨。鐗堟湰锛? V1.1锛堟灦鏋勮瘎瀹′慨姝g増锛? *鍒涘缓鏃ユ湡锛? 2025-12-31
鏈€鍚庢洿鏂帮細 2025-12-31
缁存姢鑰咃細 鏋舵瀯鍥㈤槦
*閫傜敤闃舵<EFBFBD>锛? MVP + Phase 1-4 瀹屾暣寮€鍙? *鏂囨。鐩<EFBFBD>殑锛? 鍩轰簬鐜版湁绯荤粺鏋舵瀯锛屾彁渚涘彲鐩存帴鎵ц<E98EB5>鐨勬妧鏈<E5A6A7>疄鏂芥柟妗? *V1.1 鏇存柊锛? 鏁村悎鏋舵瀯璇勫<E79287>鎰忚<E98EB0>锛屼慨姝g綉缁滆繛閫氭€ч<E282AC>闄┿€佸<E282AC>鍔犲巻鍙叉暟鎹<E69A9F>壂鎻忋€佹槑纭<E6A791>墠绔<E5A2A0>妧鏈<E5A6A7>爤
馃敟 V1.1 鏍稿績淇<E7B8BE><E6B787>
鍩轰簬鏋舵瀯璇勫<EFBFBD>锛堝弬鑰冿細06-寮€鍙戣<E98D99>褰?IIT Manager Agent 鎶€鏈<E282AC>柟妗堝<E5A697>鏌ヤ笌琛ヤ竵.md锛夛紝鏈<EFBFBD>増鏈<EFBFBD>慨姝d簡3涓<EFBFBD>叧閿<EFBFBD>棶棰橈細
- **鉁?鑷村懡椋庨櫓淇<E6AB93><E6B787>**锛氭贩鍚堝悓姝ユā寮忥紙Webhook + 杞<><E69D9E>锛夛紝瑙e喅鍖婚櫌鍐呯綉杩為€氭€ч棶棰?2. 鉁?鍔熻兘琛ュ厖锛氬巻鍙叉暟鎹<EFBFBD>叏閲忔壂鎻忥紝鏀<EFBFBD>寔瀛橀噺鏁版嵁璐ㄦ帶
- **鉁?鎶€鏈<E282AC>爤鏄庣‘**锛氬墠绔<E5A2A0>噰鐢═aro锛圧eact璇<74>硶锛夛紝鏀<E7B49D>寔灏忕▼搴?H5鍙岀<E98D99>
馃搵 鏂囨。瀵艰埅
- 绯荤粺鏋舵瀯璁捐<EFBFBD>
- 鐜版湁鑳藉姏澶嶇敤
- [鏍稿績鎶€鏈<E282AC>疄鐜癩(#3-鏍稿績鎶€鏈<E282AC>疄鐜?
- [鏁版嵁搴撹<E690B4>璁<EFBFBD>(#4-鏁版嵁搴撹<E690B4>璁?
- API璁捐<EFBFBD>
- 閮ㄧ讲鏋舵瀯
- [寮€鍙戣<E98D99>鍒抅(#7-寮€鍙戣<E98D99>鍒?
- [椋庨櫓璇勪及涓庡<E6B693>绛朷(#8-椋庨櫓璇勪及涓庡<E6B693>绛?
1. 绯荤粺鏋舵瀯璁捐<E79281>
1.1 鎬讳綋鏋舵瀯锛堝熀浜庣幇鏈夊钩鍙帮級
鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?鈹? 鐢ㄦ埛浜や簰灞?(Frontend) 鈹?鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹? 鈹?浼佷笟寰<E7AC9F>俊 鈹? 鈹?寰<>俊灏忕▼搴?鈹? 鈹?PC 鈹? 鈹?鈹? 鈹?(閫氱煡) 鈹? 鈹?(PI鏌ョ湅) 鈹? 鈹?Workbench 鈹? 鈹?鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈫?鈫?REST API / WebSocket
鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?鈹? 涓氬姟妯″潡灞?(IIT Manager Module) 鈹?鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹? 鈹? Node.js Backend (Fastify + TypeScript) 鈹? 鈹?鈹? 鈹? 鈹溾攢鈹€ controllers/ - HTTP璺<50>敱澶勭悊 鈹? 鈹?鈹? 鈹? 鈹溾攢鈹€ services/ - 涓氬姟閫昏緫灞? 鈹? 鈹?鈹? 鈹? 鈹溾攢鈹€ agents/ - 4涓<34>櫤鑳戒綋 鈹? 鈹?鈹? 鈹? 鈹斺攢鈹€ adapters/ - 澶栭儴绯荤粺閫傞厤鍣? 鈹? 鈹?鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈫?鈫?澶嶇敤骞冲彴鑳藉姏
鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?鈹? 骞冲彴鍩虹<E98DA9>灞?(Platform - 宸叉湁) 鈹?鈹? 鉁?Storage (OSS/Local) 鉁?Logger (Winston) 鈹?鈹? 鉁?Cache (Postgres) 鉁?JobQueue (pg-boss) 鈹?鈹? 鉁?LLMFactory (澶氭ā鍨? 鉁?CheckpointService 鈹?鈹? 鉁?DifyClient (RAG) 鉁?Database (Prisma) 鈹?鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈫?鈫?
鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?鈹? 澶栭儴绯荤粺闆嗘垚灞?(External) 鈹?鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹? 鈹?REDCap 鈹? 鈹?Dify RAG 鈹? 鈹?浼佷笟寰<E7AC9F>俊 鈹? 鈹?Python 鈹? 鈹?鈹? 鈹?(EDC) 鈹? 鈹?(鐭ヨ瘑搴? 鈹? 鈹?(閫氱煡) 鈹? 鈹?寰<>湇鍔? 鈹? 鈹?鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈫?鈫?鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?鈹? 鏁版嵁瀛樺偍灞?(Storage) 鈹?鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹? 鈹?RDS PostgreSQL 鈹? 鈹?OSS瀵硅薄瀛樺偍 鈹? 鈹?鈹? 鈹?(涓氬姟鏁版嵁+闃熷垪) 鈹? 鈹?(鏂囦欢/Protocol) 鈹? 鈹?鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?```
### 1.2 鏋舵瀯浜<E780AF>偣锛堢<E9949B>鍚堢幇鏈夎<E98F88>鑼冿級
#### 鉁?1. 瀹屽叏澶嶇敤骞冲彴鑳藉姏
```typescript
// 鉁?涓嶉噸澶嶅疄鐜板熀纭€璁炬柦
import { storage } from '@/common/storage'; // 鏂囦欢瀛樺偍
import { logger } from '@/common/logging'; // 鏃ュ織绯荤粺
import { jobQueue } from '@/common/jobs'; // 寮傛<E5AFAE>浠诲姟
import { cache } from '@/common/cache'; // Postgres缂撳瓨
import { CheckpointService } from '@/common/jobs'; // 鏂<>偣缁<E581A3>紶
import { LLMFactory } from '@/common/llm'; // LLM璋冪敤
import { DifyClient } from '@/clients/DifyClient'; // RAG妫€绱?import { prisma } from '@/config/database'; // 鏁版嵁搴?```
#### 鉁?2. Postgres-Only 鏋舵瀯锛堥伒寰<E4BC92><E5AFB0>鑼冿級
```typescript
// 浠诲姟绠$悊淇℃伅瀛樺偍鍦?job.data锛屼笟鍔¤〃鍙<E38083>瓨鍌ㄤ笟鍔′俊鎭?await jobQueue.push('iit:quality-check:batch', {
// 涓氬姟淇℃伅
projectId: 'proj_001',
recordIds: ['P001', 'P002', ...],
// 鉁?浠诲姟鎷嗗垎淇℃伅锛堣嚜鍔ㄥ瓨鍌ㄥ湪 platform_schema.job.data锛? batchIndex: 1,
totalBatches: 10,
// 鉁?杩涘害杩借釜锛堣嚜鍔ㄥ瓨鍌<E793A8>級
processedCount: 0,
successCount: 0,
failedCount: 0
});
// 浣跨敤 CheckpointService 绠$悊鏂<E6828A>偣
const checkpointService = new CheckpointService(prisma);
await checkpointService.saveCheckpoint(job.id, {
currentBatchIndex: 5,
currentIndex: 250
});
鉁?3. Schema 闅旂<E99785>锛堟柊澧?iit_schema锛?
// prisma/schema.prisma
// 鐜版湁Schema锛歱latform, aia, pkb, asl, dc, ssa, st, rvw, admin, common
// 鏂板<E98F82>Schema锛歩it
generator client {
provider = "prisma-client-js"
previewFeatures = ["multiSchema"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
schemas = ["platform", "aia", "pkb", "asl", "dc", "iit"] // 鏂板<E98F82> iit
}
// IIT Manager 鐨勬墍鏈夎〃閮藉湪 iit_schema 涓?model IitProject {
id String @id @default(uuid())
// ...
@@schema("iit")
}
鉁?4. 浜戝師鐢熷氨缁<E6B0A8>紙SAE閮ㄧ讲锛?
- 鏃犵姸鎬佸簲鐢<EFBFBD>紙涓嶄緷璧栨湰鍦版枃浠讹級
- 瀛樺偍鎶借薄灞傦紙Local 鈫?OSS 闆朵唬鐮佸垏鎹<E59E8F>級
- 寮傛<EFBFBD>浠诲姟锛堥伩鍏?0绉掕秴鏃讹級
- 鏁版嵁搴撹繛鎺ユ睜锛堥槻姝㈣繛鎺ヨ€楀敖锛?
2. 鐜版湁鑳藉姏澶嶇敤
2.1 Dify RAG 闆嗘垚锛堝凡鏈夊熀纭€锛?
鐜版湁鑳藉姏锛圥KB妯″潡锛?
// backend/src/clients/DifyClient.ts (宸叉湁 282琛屼唬鐮?
export class DifyClient {
async createDataset(name: string): Promise<string>;
async uploadDocument(datasetId: string, file: Buffer): Promise<string>;
async query(datasetId: string, query: string): Promise<QueryResult>;
// ... 鍏朵粬鏂规硶
}
IIT Manager 浣跨敤鏂瑰紡
// backend/src/modules/iit-manager/services/ProtocolService.ts
import { DifyClient } from '@/clients/DifyClient';
export class ProtocolService {
private difyClient: DifyClient;
constructor() {
this.difyClient = new DifyClient(
process.env.DIFY_API_KEY!,
process.env.DIFY_BASE_URL!
);
}
/**
* 涓洪」鐩<E3808D>垱寤篜rotocol鐭ヨ瘑搴? */
async initializeProtocolKnowledgeBase(
projectId: string,
protocolPdf: Buffer
): Promise<string> {
// 1. 鍒涘缓Dify Dataset
const datasetId = await this.difyClient.createDataset(
`IIT_Project_${projectId}_Protocol`
);
// 2. 涓婁紶Protocol PDF
const documentId = await this.difyClient.uploadDocument(
datasetId,
protocolPdf
);
// 3. 淇濆瓨鍒版暟鎹<E69A9F>簱
await prisma.iitProject.update({
where: { id: projectId },
data: { difyDatasetId: datasetId }
});
return datasetId;
}
/**
* 妫€鏌ユ暟鎹<E69A9F>槸鍚︾<E98D9A>鍚圥rotocol锛堣川鎺<E5B79D>gent鏍稿績锛? */
async checkProtocolCompliance(params: {
projectId: string;
fieldName: string;
value: any;
context: Record<string, any>;
}): Promise<ComplianceResult> {
// 1. 鑾峰彇椤圭洰鐨凞ify鐭ヨ瘑搴揑D
const project = await prisma.iitProject.findUnique({
where: { id: params.projectId },
select: { difyDatasetId: true }
});
// 2. 鏋勯€犳煡璇<E785A1>rompt
const query = `
鎮h€呮暟鎹<E69A9F>細${JSON.stringify(params.context)}
褰撳墠瀛楁<E7809B>锛?{params.fieldName} = ${params.value}
璇锋<E79287>鏌ユ<E98F8C>鏁版嵁鏄<E5B581>惁绗﹀悎鐮旂┒鏂规<E98F82>锛圥rotocol锛夌殑瑕佹眰銆? 濡傛灉鍙戠幇闂<E5B987><E99782>锛岃<E9949B>鎸囧嚭锛? 1. 杩濆弽浜嗗摢鏉¤<E98F89>鍒? 2. 璇ヨ<E79287>鍒欏湪鏂规<E98F82>涓<EFBFBD>殑椤电爜
3. 姝g‘鐨勫€煎簲璇ユ槸浠€涔? 4. 缃<>俊搴︼紙0-1锛? `;
// 3. 璋冪敤Dify RAG妫€绱? const result = await this.difyClient.query(
project.difyDatasetId,
query
);
// 4. 瑙f瀽AI鍝嶅簲
return this.parseComplianceResult(result);
}
}
2.2 LLM 璋冪敤锛堝凡鏈夊伐鍘傦級
// 鉁?澶嶇敤鐜版湁 LLMFactory
import { LLMFactory } from '@/common/llm';
const llm = LLMFactory.getAdapter('deepseek-v3');
const response = await llm.chat([
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userInput }
]);
2.3 寮傛<E5AFAE>浠诲姟闃熷垪锛圥ostgres-Only锛?
// 鉁?浣跨敤 pg-boss 闃熷垪锛堝钩鍙板凡鏈夛級
import { jobQueue } from '@/common/jobs';
// 鎺ㄩ€佽川鎺т换鍔?await jobQueue.push('iit:quality-check:batch', {
projectId: 'proj_001',
recordIds: ['P001', 'P002', 'P003']
});
// Worker澶勭悊锛堣嚜鍔ㄦ柇鐐圭画浼狅級
jobQueue.registerWorker('iit:quality-check:batch', async (job) => {
const checkpointService = new CheckpointService(prisma);
// 鍔犺浇鏂<E6B587>偣
const checkpoint = await checkpointService.loadCheckpoint(job.id);
const startIndex = checkpoint?.currentIndex || 0;
// 鎵归噺澶勭悊
for (let i = startIndex; i < job.data.recordIds.length; i++) {
await processRecord(job.data.recordIds[i]);
// 姣?0鏉′繚瀛樻柇鐐? if (i % 10 === 0) {
await checkpointService.saveCheckpoint(job.id, {
currentIndex: i,
processedCount: i
});
}
}
});
2.4 鏂囦欢瀛樺偍锛圤SS鎶借薄灞傦級
// 鉁?浣跨敤瀛樺偍鎶借薄灞?import { storage } from '@/common/storage';
// 涓婁紶Protocol PDF
const key = `iit/projects/${projectId}/protocol.pdf`;
const url = await storage.upload(key, pdfBuffer);
// 涓嬭浇Protocol PDF
const pdfBuffer = await storage.download(key);
2.5 鏃ュ織绯荤粺锛圵inston锛?
// 鉁?浣跨敤骞冲彴鏃ュ織绯荤粺
import { logger } from '@/common/logging';
logger.info('Quality check started', {
projectId,
recordId,
agent: 'DataQualityAgent'
});
logger.error('Quality check failed', {
error: err.message,
stack: err.stack,
projectId,
recordId
});
3. 鏍稿績鎶€鏈<E282AC>疄鐜?
3.1 REDCap 闆嗘垚锛堝弻鍚戝<E98D9A>鎺ワ級
3.1.1 REDCap External Module锛圥HP渚э級
<?php
// redcap/modules/iit_manager_connector_v1.0.0/IITManagerConnector.php
namespace YiZhengXun\IITManagerConnector;
use ExternalModules\AbstractExternalModule;
class IITManagerConnector extends AbstractExternalModule {
/**
* Hook: 褰撹<E8A4B0>褰曚繚瀛樻椂瑙﹀彂
*/
public function redcap_save_record($project_id, $record, $instrument,
$event_id, $group_id, $survey_hash,
$response_id, $repeat_instance) {
// 1. 鑾峰彇鍙樻洿鏁版嵁
$data = \REDCap::getData($project_id, 'array', $record);
// 2. 鎺ㄩ€乄ebhook鍒癐IT Manager
$this->pushWebhook([
'event' => 'record_updated',
'project_id' => $project_id,
'record_id' => $record,
'instrument' => $instrument,
'event_id' => $event_id,
'data' => $data,
'timestamp' => time()
]);
}
/**
* 鎺ㄩ€乄ebhook锛堝甫绛惧悕楠岃瘉锛? */
private function pushWebhook($payload) {
$apiKey = $this->getSystemSetting('iit_manager_api_key');
$webhookUrl = $this->getSystemSetting('iit_manager_webhook_url');
// HMAC-SHA256绛惧悕
$signature = hash_hmac('sha256', json_encode($payload), $apiKey);
$ch = curl_init($webhookUrl);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-Signature: ' . $signature,
'X-Timestamp: ' . time()
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode !== 200) {
// 璁板綍鍒癛EDCap鏃ュ織
\REDCap::logEvent('IIT Manager Webhook Failed',
"HTTP $httpCode: $response", '', $record);
}
curl_close($ch);
}
}
3.1.2 Node.js Webhook鎺ユ敹鍣?
// backend/src/modules/iit-manager/controllers/webhookController.ts
import { FastifyRequest, FastifyReply } from 'fastify';
import { logger } from '@/common/logging';
import { jobQueue } from '@/common/jobs';
import crypto from 'crypto';
interface RedcapWebhookPayload {
event: 'record_updated' | 'record_created' | 'record_deleted';
project_id: string;
record_id: string;
instrument: string;
event_id: string;
data: Record<string, any>;
timestamp: number;
}
export async function handleRedcapWebhook(
request: FastifyRequest<{ Body: RedcapWebhookPayload }>,
reply: FastifyReply
) {
// 1. 楠岃瘉绛惧悕
const signature = request.headers['x-signature'] as string;
const timestamp = request.headers['x-timestamp'] as string;
if (!verifyWebhookSignature(request.body, signature, timestamp)) {
logger.warn('Invalid webhook signature', {
project_id: request.body.project_id
});
return reply.code(403).send({ error: 'Invalid signature' });
}
// 2. 闃查噸鏀炬敾鍑伙紙5鍒嗛挓鏈夋晥鏈燂級
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return reply.code(403).send({ error: 'Timestamp expired' });
}
// 3. 绔嬪嵆杩斿洖200锛堜笉闃诲<E99783>REDCap锛? reply.code(200).send({ status: 'accepted' });
// 4. 寮傛<E5AFAE>瑙﹀彂璐ㄦ帶妫€鏌ワ紙涓嶇瓑寰呭畬鎴愶級
setImmediate(async () => {
try {
const { project_id, record_id, data } = request.body;
// 鎺ㄩ€佸埌璐ㄦ帶闃熷垪
await jobQueue.push('iit:quality-check', {
projectId: project_id,
recordId: record_id,
data: data
});
logger.info('Quality check queued', {
project_id,
record_id
});
} catch (error) {
logger.error('Failed to queue quality check', {
error: error.message,
payload: request.body
});
}
});
}
/**
* 楠岃瘉Webhook绛惧悕
*/
function verifyWebhookSignature(
payload: any,
signature: string,
timestamp: string
): boolean {
const apiKey = process.env.REDCAP_WEBHOOK_SECRET!;
const expectedSignature = crypto
.createHmac('sha256', apiKey)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
3.1.3 REDCap API 閫傞厤鍣<E58EA4>紙鏁版嵁鍥炲啓锛?
// backend/src/modules/iit-manager/adapters/RedcapAdapter.ts
import axios, { AxiosInstance } from 'axios';
import { logger } from '@/common/logging';
export class RedcapAdapter {
private client: AxiosInstance;
private projectApiToken: string;
constructor(redcapUrl: string, apiToken: string) {
this.projectApiToken = apiToken;
this.client = axios.create({
baseURL: redcapUrl,
timeout: 30000
});
}
/**
* 瀵煎嚭璁板綍
*/
async exportRecords(params: {
records?: string[];
fields?: string[];
events?: string[];
}): Promise<any[]> {
const response = await this.client.post('', {
token: this.projectApiToken,
content: 'record',
action: 'export',
format: 'json',
type: 'flat',
records: params.records,
fields: params.fields,
events: params.events
});
return response.data;
}
/**
* 瀵煎叆璁板綍锛堝奖瀛愮姸鎬佸洖鍐欙級
*/
async importRecords(records: Record<string, any>[]): Promise<void> {
try {
const response = await this.client.post('', {
token: this.projectApiToken,
content: 'record',
action: 'import',
format: 'json',
type: 'flat',
overwriteBehavior: 'normal',
data: JSON.stringify(records)
});
logger.info('REDCap records imported', {
count: response.data.count,
ids: response.data.ids
});
} catch (error) {
logger.error('REDCap import failed', {
error: error.message,
records: records.map(r => r.record_id)
});
throw error;
}
}
/**
* 瀵煎嚭鍏冩暟鎹<E69A9F>紙琛ㄥ崟缁撴瀯锛? */
async exportMetadata(): Promise<any[]> {
const response = await this.client.post('', {
token: this.projectApiToken,
content: 'metadata',
format: 'json'
});
return response.data;
}
}
3.1.4 娣峰悎鍚屾<E98D9A>妯″紡锛堭煍?V1.1 鏍稿績淇<E7B8BE><E6B787>锛?
// backend/src/modules/iit-manager/services/SyncManager.ts
import { logger } from '@/common/logging';
import { jobQueue } from '@/common/jobs';
import { cache } from '@/common/cache';
import { prisma } from '@/config/database';
import { RedcapAdapter } from '../adapters/RedcapAdapter';
/**
* 鍚屾<E98D9A>绠$悊鍣<E6828A>細瑙e喅鍖婚櫌鍐呯綉杩為€氭€ч棶棰? *
* 鏍稿績绛栫暐锛? * 1. 浼樺厛浣跨敤Webhook锛堝疄鏃舵€э級- 閫傜敤浜嶳EDCap鍙<70><E98D99>闂<EFBFBD>叕缃戠殑鍦烘櫙
* 2. 瀹氭椂杞<E6A482><E69D9E>浣滀负鍏滃簳锛堝彲闈犳€э級- 閫傜敤浜庢墍鏈夊満鏅? */
export class SyncManager {
private redcapAdapter: RedcapAdapter;
constructor(redcapAdapter: RedcapAdapter) {
this.redcapAdapter = redcapAdapter;
}
/**
* 鏅鸿兘鍚屾<E98D9A>绛栫暐锛堣嚜閫傚簲锛? * 鍚<>姩鏃舵祴璇昗ebhook杩為€氭€э紝鑷<E7B49D>姩閫夋嫨鏈€浣虫ā寮? */
async initializeSync(projectId: string) {
logger.info('Initializing sync strategy', { projectId });
// 1. 娴嬭瘯Webhook杩為€氭€? const webhookWorking = await this.testWebhookConnectivity(projectId);
if (webhookWorking) {
logger.info('Webhook connectivity OK, using real-time mode', { projectId });
// 杞<><E69D9E>浣滀负澶囦唤锛堥棿闅?0鍒嗛挓锛? await this.schedulePolling(projectId, 30);
} else {
logger.warn('Webhook blocked by firewall, using polling mode', { projectId });
// 杞<><E69D9E>浣滀负涓绘ā寮忥紙闂撮殧5鍒嗛挓锛? await this.schedulePolling(projectId, 5);
}
}
/**
* 娴嬭瘯Webhook杩為€氭€? */
private async testWebhookConnectivity(projectId: string): Promise<boolean> {
try {
const project = await prisma.iitProject.findUnique({
where: { id: projectId },
select: { redcapUrl: true }
});
// 璋冪敤REDCap EM鐨勬祴璇曠<E79287>鐐? const response = await axios.post(
`${project.redcapUrl}/api/?type=module&prefix=iit_manager_connector&page=test`,
{ projectId, test: 'ping' },
{ timeout: 5000 }
);
return response.status === 200;
} catch (error) {
logger.warn('Webhook connectivity test failed', {
projectId,
error: error.message
});
return false;
}
}
/**
* 瀹氭椂杞<E6A482><E69D9E>锛堟牳蹇冨厹搴曟満鍒讹級
*/
async schedulePolling(projectId: string, intervalMinutes: number = 10) {
// 浣跨敤 pg-boss 鐨?schedule 鍔熻兘
await jobQueue.schedule(
'iit:redcap:poll',
{ projectId },
{
every: `${intervalMinutes} minutes`,
// 閲嶈<E996B2>锛氳<E9949B>缃<EFBFBD>悎鐞嗙殑瓒呮椂鏃堕棿
expireIn: `${intervalMinutes * 2} minutes`
}
);
logger.info('Polling scheduled', {
projectId,
intervalMinutes
});
}
/**
* 杞<><E69D9E>澶勭悊鍣<E6828A>紙Worker锛? */
async handlePoll(projectId: string) {
const startTime = Date.now();
try {
// 1. 鑾峰彇涓婃<E6B693>鍚屾<E98D9A>鏃堕棿锛堜粠缂撳瓨鎴栨暟鎹<E69A9F>簱锛? const cacheKey = `iit:sync:${projectId}:last`;
const lastSync = await cache.get(cacheKey) ||
(await this.getLastSyncFromDB(projectId));
logger.debug('Polling started', { projectId, lastSync });
// 2. 璋冪敤REDCap API鑾峰彇淇<E5BD87>敼鐨勮<E990A8>褰曪紙杞婚噺绾э級
// REDCap API鏀<49>寔鎸夋椂闂磋繃婊わ細dateRangeBegin
const records = await this.redcapAdapter.exportRecords({
dateRangeBegin: lastSync,
fields: ['record_id', 'last_modified'] // 鍏堝彧鎷塈D鍜屾椂闂存埑
});
if (records.length === 0) {
logger.debug('No new records to sync', { projectId });
return;
}
logger.info('New records detected', {
projectId,
count: records.length,
since: lastSync
});
// 3. 鎵归噺鎺ㄩ€佽川鎺т换鍔★紙鏅鸿兘闃堝€煎垽鏂<E59EBD>級
const THRESHOLD = 50;
if (records.length >= THRESHOLD) {
// 澶ф壒閲忥細闃熷垪妯″紡 + 浠诲姟鎷嗗垎
const chunks = this.splitIntoChunks(records, 50);
for (const chunk of chunks) {
await jobQueue.push('iit:quality-check:batch', {
projectId,
recordIds: chunk.map(r => r.record_id)
});
}
} else {
// 灏忔壒閲忥細鐩存帴鎺ㄩ€? for (const record of records) {
// 骞傜瓑鎬ф<E98EAC>鏌ワ紙闃叉<E99783>閲嶅<E996B2>澶勭悊锛? const isDuplicate = await this.isDuplicate(projectId, record.record_id);
if (!isDuplicate) {
await jobQueue.push('iit:quality-check', {
projectId,
recordId: record.record_id
});
}
}
}
// 4. 鏇存柊鍚屾<E98D9A>鏃堕棿锛堝弻鍐欙細缂撳瓨 + 鏁版嵁搴擄級
const now = new Date().toISOString();
await cache.set(cacheKey, now, 3600 * 24); // 缂撳瓨24灏忔椂
await this.updateLastSyncInDB(projectId, now);
logger.info('Polling completed', {
projectId,
recordsFound: records.length,
duration: Date.now() - startTime
});
} catch (error) {
logger.error('Polling failed', {
error: error.message,
projectId,
duration: Date.now() - startTime
});
throw error; // 璁?pg-boss 閲嶈瘯
}
}
/**
* 骞傜瓑鎬т繚鎶わ紙闃叉<E99783>閲嶅<E996B2>璐ㄦ帶锛? */
private async isDuplicate(projectId: string, recordId: string): Promise<boolean> {
const key = `iit:processed:${projectId}:${recordId}`;
const exists = await cache.get(key);
if (!exists) {
await cache.set(key, 'true', 3600); // 缂撳瓨1灏忔椂
return false;
}
return true;
}
/**
* 浠庢暟鎹<E69A9F>簱鑾峰彇涓婃<E6B693>鍚屾<E98D9A>鏃堕棿
*/
private async getLastSyncFromDB(projectId: string): Promise<string> {
const project = await prisma.iitProject.findUnique({
where: { id: projectId },
select: { lastSyncAt: true }
});
return project?.lastSyncAt?.toISOString() ||
new Date(Date.now() - 24 * 3600 * 1000).toISOString(); // 榛樿<E6A69B>24灏忔椂鍓? }
/**
* 鏇存柊鏁版嵁搴撲腑鐨勫悓姝ユ椂闂? */
private async updateLastSyncInDB(projectId: string, syncTime: string) {
await prisma.iitProject.update({
where: { id: projectId },
data: { lastSyncAt: new Date(syncTime) }
});
}
/**
* 浠诲姟鎷嗗垎宸ュ叿
*/
private splitIntoChunks<T>(array: T[], chunkSize: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
}
}
3.1.5 鍘嗗彶鏁版嵁鍏ㄩ噺鎵<E599BA>弿锛堭煍?V1.1 鍔熻兘琛ュ厖锛?
// backend/src/modules/iit-manager/services/BulkScanService.ts
import { logger } from '@/common/logging';
import { jobQueue } from '@/common/jobs';
import { prisma } from '@/config/database';
import { CheckpointService } from '@/common/jobs';
import { RedcapAdapter } from '../adapters/RedcapAdapter';
import { DataQualityAgent } from '../agents/DataQualityAgent';
/**
* 鍏ㄩ噺鎵<E599BA>弿鏈嶅姟锛氭敮鎸佸瓨閲忔暟鎹<E69A9F>川鎺? *
* 搴旂敤鍦烘櫙锛? * 1. 椤圭洰鍒濆<E98D92>鍖栨椂锛屾壂鎻忓巻鍙叉暟鎹? * 2. Protocol鏇存柊鍚庯紝閲嶆柊鎵<E69F8A>弿鎵€鏈夋暟鎹? * 3. 鎵嬪姩瑙﹀彂鍏ㄩ噺璐ㄦ帶
*/
export class BulkScanService {
private redcapAdapter: RedcapAdapter;
constructor(redcapAdapter: RedcapAdapter) {
this.redcapAdapter = redcapAdapter;
}
/**
* 鍏ㄩ噺鎵<E599BA>弿锛堝惎鍔ㄦ椂鎴栨墜鍔ㄨЕ鍙戯級
*/
async scanAllRecords(projectId: string): Promise<string> {
logger.info('Starting bulk scan', { projectId });
// 1. 杞婚噺绾ф媺鍙栨墍鏈塺ecord_id锛堜笉鎷夊畬鏁存暟鎹<E69A9F>級
const allRecords = await this.redcapAdapter.exportRecords({
fields: ['record_id'], // 鍙<><E98D99>ID锛岄€熷害蹇? rawOrLabel: 'raw'
});
const totalRecords = allRecords.length;
logger.info('Total records to scan', {
projectId,
totalRecords
});
// 2. 鏅鸿兘闃堝€煎垽鏂? const THRESHOLD = 50;
const useQueue = totalRecords >= THRESHOLD;
if (useQueue) {
// 闃熷垪妯″紡锛氫换鍔℃媶鍒?+ 鏂<>偣缁<E581A3>紶
return await this.scanViaQueue(projectId, allRecords);
} else {
// 鐩存帴妯″紡锛氬揩閫熷<E996AB>鐞? return await this.scanDirectly(projectId, allRecords);
}
}
/**
* 闃熷垪妯″紡锛氬ぇ鎵归噺鏁版嵁锛堚墺50鏉★級
*/
private async scanViaQueue(
projectId: string,
allRecords: { record_id: string }[]
): Promise<string> {
// 1. 鍒涘缓浠诲姟璁板綍
const taskRun = await prisma.iitTaskRun.create({
data: {
projectId,
taskType: 'bulk-scan',
status: 'pending',
totalItems: allRecords.length,
processedItems: 0,
successItems: 0,
failedItems: 0
}
});
// 2. 浠诲姟鎷嗗垎锛堟瘡鎵?0鏉★級
const chunks = this.splitIntoChunks(allRecords, 50);
// 3. 鎺ㄩ€佹壒娆′换鍔? for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const jobId = await jobQueue.push('iit:bulk-scan:batch', {
// 涓氬姟淇℃伅
taskRunId: taskRun.id,
projectId,
recordIds: chunk.map(r => r.record_id),
// 鉁?浠诲姟鎷嗗垎淇℃伅锛堣嚜鍔ㄥ瓨鍌ㄥ湪 job.data锛? batchIndex: i,
totalBatches: chunks.length,
startIndex: i * 50,
endIndex: Math.min((i + 1) * 50, allRecords.length)
});
// 鍏宠仈 job_id 鍒颁换鍔¤<E98D94>褰? await prisma.iitTaskRun.update({
where: { id: taskRun.id },
data: { jobId }
});
}
logger.info('Bulk scan queued', {
projectId,
totalRecords: allRecords.length,
totalBatches: chunks.length,
taskRunId: taskRun.id
});
return taskRun.id;
}
/**
* Worker澶勭悊鎵规<E98EB5>锛堟敮鎸佹柇鐐圭画浼狅級
*/
async processBatch(job: any) {
const { taskRunId, projectId, recordIds, batchIndex, totalBatches } = job.data;
const checkpointService = new CheckpointService(prisma);
// 1. 鍔犺浇鏂<E6B587>偣
const checkpoint = await checkpointService.loadCheckpoint(job.id);
const startIndex = checkpoint?.currentIndex || 0;
logger.info('Processing batch', {
taskRunId,
batchIndex,
totalBatches,
recordCount: recordIds.length,
resumeFrom: startIndex
});
let successCount = 0;
let failedCount = 0;
// 2. 閫愪釜澶勭悊璁板綍
for (let i = startIndex; i < recordIds.length; i++) {
const recordId = recordIds[i];
try {
// 2.1 鎷夊彇瀹屾暣鏁版嵁锛堟寜闇€鎷夊彇锛岄伩鍏嶅唴瀛樻孩鍑猴級
const recordData = await this.redcapAdapter.exportRecords({
records: [recordId]
});
// 2.2 璋冪敤璐ㄦ帶Agent
const agent = new DataQualityAgent();
await agent.checkRecord({
projectId,
recordId,
data: recordData[0]
});
successCount++;
} catch (error) {
logger.error('Record scan failed', {
recordId,
error: error.message
});
failedCount++;
}
// 2.3 姣?0鏉′繚瀛樻柇鐐? if (i % 10 === 0 || i === recordIds.length - 1) {
await checkpointService.saveCheckpoint(job.id, {
currentIndex: i + 1,
processedCount: i + 1,
successCount,
failedCount
});
// 鏇存柊浠诲姟缁熻<E7BC81>
await this.updateTaskProgress(taskRunId, i + 1, successCount, failedCount);
}
}
logger.info('Batch completed', {
taskRunId,
batchIndex,
successCount,
failedCount
});
}
/**
* 鐩存帴妯″紡锛氬皬鎵归噺鏁版嵁锛?50鏉★級
*/
private async scanDirectly(
projectId: string,
allRecords: { record_id: string }[]
): Promise<string> {
// 鍒涘缓浠诲姟璁板綍
const taskRun = await prisma.iitTaskRun.create({
data: {
projectId,
taskType: 'bulk-scan',
status: 'processing',
totalItems: allRecords.length,
processedItems: 0,
successItems: 0,
failedItems: 0,
startedAt: new Date()
}
});
const agent = new DataQualityAgent();
let successCount = 0;
let failedCount = 0;
// 鐩存帴澶勭悊锛堜笉鍏ラ槦鍒楋級
for (const record of allRecords) {
try {
const recordData = await this.redcapAdapter.exportRecords({
records: [record.record_id]
});
await agent.checkRecord({
projectId,
recordId: record.record_id,
data: recordData[0]
});
successCount++;
} catch (error) {
logger.error('Record scan failed', {
recordId: record.record_id,
error: error.message
});
failedCount++;
}
}
// 鏇存柊浠诲姟瀹屾垚
await prisma.iitTaskRun.update({
where: { id: taskRun.id },
data: {
status: 'completed',
processedItems: allRecords.length,
successItems: successCount,
failedItems: failedCount,
completedAt: new Date(),
duration: Math.floor((Date.now() - taskRun.startedAt.getTime()) / 1000)
}
});
return taskRun.id;
}
/**
* 鏇存柊浠诲姟杩涘害锛堜緵鍓嶇<E98D93>杞<EFBFBD><E69D9E>锛? */
private async updateTaskProgress(
taskRunId: string,
processedItems: number,
successItems: number,
failedItems: number
) {
const task = await prisma.iitTaskRun.findUnique({
where: { id: taskRunId },
select: { totalItems: true }
});
await prisma.iitTaskRun.update({
where: { id: taskRunId },
data: {
processedItems,
successItems,
failedItems,
status: processedItems >= task!.totalItems ? 'completed' : 'processing'
}
});
}
/**
* 浠诲姟鎷嗗垎宸ュ叿
*/
private splitIntoChunks<T>(array: T[], chunkSize: number): T[][] {
const chunks: T[][] = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
}
}
3.2 鏁版嵁璐ㄦ帶 Agent锛堟牳蹇冧笟鍔★級
// backend/src/modules/iit-manager/agents/DataQualityAgent.ts
import { logger } from '@/common/logging';
import { prisma } from '@/config/database';
import { ProtocolService } from '../services/ProtocolService';
export class DataQualityAgent {
private protocolService: ProtocolService;
constructor() {
this.protocolService = new ProtocolService();
}
/**
* 妫€鏌ュ崟鏉¤<E98F89>褰? */
async checkRecord(params: {
projectId: string;
recordId: string;
data: Record<string, any>;
}): Promise<void> {
logger.info('Quality check started', params);
// 1. 鑾峰彇椤圭洰閰嶇疆锛堝叧閿<E58FA7>瓧娈垫槧灏勶級
const project = await prisma.iitProject.findUnique({
where: { id: params.projectId },
select: {
fieldMappings: true, // JSON: { age: 'patient_age', gender: 'sex', ... }
difyDatasetId: true
}
});
if (!project || !project.difyDatasetId) {
logger.warn('Project not configured', { projectId: params.projectId });
return;
}
// 2. 鎻愬彇鍏抽敭瀛楁<E7809B>鍊? const mappings = project.fieldMappings as Record<string, string>;
const context = {
age: params.data[mappings.age],
gender: params.data[mappings.gender],
enrollmentDate: params.data[mappings.enrollmentDate],
// ... 鍏朵粬鏄犲皠瀛楁<E7809B>
};
// 3. 閫愪釜瀛楁<E7809B>妫€鏌? const issues: any[] = [];
for (const [logicalField, redcapField] of Object.entries(mappings)) {
const value = params.data[redcapField];
// 璋冪敤Protocol鏈嶅姟妫€鏌ュ悎瑙勬€? const result = await this.protocolService.checkProtocolCompliance({
projectId: params.projectId,
fieldName: logicalField,
value: value,
context: context
});
if (!result.isCompliant) {
issues.push({
fieldName: logicalField,
currentValue: value,
suggestedValue: result.suggestedValue,
reasoning: result.reasoning,
protocolPage: result.protocolPage,
confidence: result.confidence
});
}
}
// 4. 濡傛灉鍙戠幇闂<E5B987><E99782>锛屽垱寤哄奖瀛愬缓璁? if (issues.length > 0) {
await this.createPendingActions(
params.projectId,
params.recordId,
issues
);
// 5. 鍙戦€佷紒寰<E7B492>€氱煡锛堜弗閲嶈繚鑳岋級
const severeIssues = issues.filter(i => i.confidence > 0.85);
if (severeIssues.length > 0) {
await this.sendWeChatNotification(
params.projectId,
params.recordId,
severeIssues
);
}
}
logger.info('Quality check completed', {
projectId: params.projectId,
recordId: params.recordId,
issuesFound: issues.length
});
}
/**
* 鍒涘缓褰卞瓙寤鸿<E5AFA4>锛圥ROPOSED鐘舵€侊級
*/
private async createPendingActions(
projectId: string,
recordId: string,
issues: any[]
): Promise<void> {
for (const issue of issues) {
await prisma.iitPendingAction.create({
data: {
projectId: projectId,
recordId: recordId,
fieldName: issue.fieldName,
currentValue: issue.currentValue,
suggestedValue: issue.suggestedValue,
status: 'PROPOSED',
agentType: 'DATA_QUALITY',
reasoning: issue.reasoning,
evidence: {
protocolPage: issue.protocolPage,
confidence: issue.confidence
},
createdAt: new Date()
}
});
}
}
/**
* 鍙戦€佷紒寰<E7B492>€氱煡
*/
private async sendWeChatNotification(
projectId: string,
recordId: string,
issues: any[]
): Promise<void> {
// TODO: 瀹炵幇浼佸井閫氱煡锛圥hase 3锛? logger.info('WeChat notification sent', {
projectId,
recordId,
issuesCount: issues.length
});
}
}
3.3 浼佷笟寰<E7AC9F>俊闆嗘垚
// backend/src/modules/iit-manager/adapters/WeChatAdapter.ts
import axios, { AxiosInstance } from 'axios';
import { cache } from '@/common/cache';
import { logger } from '@/common/logging';
export class WeChatAdapter {
private client: AxiosInstance;
private corpId: string;
private corpSecret: string;
private agentId: string;
constructor() {
this.corpId = process.env.WECHAT_CORP_ID!;
this.corpSecret = process.env.WECHAT_CORP_SECRET!;
this.agentId = process.env.WECHAT_AGENT_ID!;
this.client = axios.create({
baseURL: 'https://qyapi.weixin.qq.com/cgi-bin',
timeout: 10000
});
}
/**
* 鑾峰彇Access Token锛堢紦瀛?灏忔椂锛? */
private async getAccessToken(): Promise<string> {
// 1. 浠庣紦瀛樿<E7809B>鍙? const cacheKey = `wechat:access_token:${this.corpId}`;
const cached = await cache.get(cacheKey);
if (cached) {
return cached as string;
}
// 2. 璋冪敤API鑾峰彇
const response = await this.client.get('/gettoken', {
params: {
corpid: this.corpId,
corpsecret: this.corpSecret
}
});
if (response.data.errcode !== 0) {
throw new Error(`Failed to get access token: ${response.data.errmsg}`);
}
const accessToken = response.data.access_token;
// 3. 缂撳瓨7000绉掞紙鐣?00绉抌uffer锛? await cache.set(cacheKey, accessToken, 7000);
return accessToken;
}
/**
* 鍙戦€佸簲鐢ㄦ秷鎭<E7A7B7>紙鍗$墖閫氱煡锛? */
async sendMessage(params: {
toUser: string; // 浼佸井UserID
title: string;
description: string;
url: string; // 璺宠浆URL锛圵orkbench锛? }): Promise<void> {
const accessToken = await this.getAccessToken();
const payload = {
touser: params.toUser,
msgtype: 'textcard',
agentid: this.agentId,
textcard: {
title: params.title,
description: params.description,
url: params.url,
btntxt: '绔嬪嵆鏌ョ湅'
}
};
const response = await this.client.post('/message/send', payload, {
params: { access_token: accessToken }
});
if (response.data.errcode !== 0) {
logger.error('WeChat message send failed', {
error: response.data.errmsg,
toUser: params.toUser
});
throw new Error(`Failed to send WeChat message: ${response.data.errmsg}`);
}
logger.info('WeChat message sent', {
toUser: params.toUser,
title: params.title
});
}
/**
* 鍙戦€佽川鎺ч<E98EBA>璀﹀崱鐗? */
async sendQualityAlert(params: {
toUser: string;
projectName: string;
recordId: string;
issuesCount: number;
workbenchUrl: string;
}): Promise<void> {
await this.sendMessage({
toUser: params.toUser,
title: '馃毃 鏁版嵁璐ㄦ帶棰勮<E6A3B0>',
description: `椤圭洰锛?{params.projectName}\n鎮h€咃細${params.recordId}\nAI妫€娴嬪埌${params.issuesCount}涓<EFBFBD>棶棰榎n缃<EFBFBD>俊搴︼細楂榎n璇峰敖蹇<EFBFBD><EFBFBD>鐞哷,
url: params.workbenchUrl
});
}
}
4. 鏁版嵁搴撹<E690B4>璁?
4.1 Prisma Schema 瀹氫箟
// prisma/schema.prisma
// ==============================
// IIT Manager Schema
// ==============================
// 椤圭洰琛?model IitProject {
id String @id @default(uuid())
name String
description String? @db.Text
// Protocol鐭ヨ瘑搴? difyDatasetId String? @unique // Dify Dataset ID
protocolFileKey String? // OSS Key: iit/projects/{id}/protocol.pdf
// 馃敟 V1.1 鏂板<E98F82>锛欴ify鎬ц兘浼樺寲 - 缂撳瓨鍏抽敭瑙勫垯
cachedRules Json? // { inclusionCriteria: [...], exclusionCriteria: [...], fields: {...} }
// 瀛楁<E7809B>鏄犲皠閰嶇疆锛圝SON锛? fieldMappings Json // { age: 'patient_age', gender: 'sex', ... }
// REDCap閰嶇疆
redcapProjectId String
redcapApiToken String @db.Text // 鍔犲瘑瀛樺偍
redcapUrl String
// 馃敟 V1.1 鏂板<E98F82>锛氬悓姝ョ<E5A79D>鐞?- 璁板綍涓婃<E6B693>鍚屾<E98D9A>鏃堕棿
lastSyncAt DateTime? // 涓婃<E6B693>杞<EFBFBD><E69D9E>鍚屾<E98D9A>鏃堕棿锛堢敤浜庡<E6B59C>閲忔媺鍙栵級
// 椤圭洰鐘舵€? status String @default("active") // active/paused/completed
// 鏃堕棿鎴? createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
// 鍏崇郴
pendingActions IitPendingAction[]
taskRuns IitTaskRun[]
userMappings IitUserMapping[]
auditLogs IitAuditLog[]
@@index([status, deletedAt])
@@schema("iit")
}
// 褰卞瓙鐘舵€佽〃锛堟牳蹇冿級
model IitPendingAction {
id String @id @default(uuid())
projectId String
recordId String // REDCap Record ID
fieldName String // 瀛楁<E7809B>鍚嶏紙閫昏緫鍚嶏紝濡?'age'锛?
// 鏁版嵁瀵规瘮
currentValue Json? // 褰撳墠鍊? suggestedValue Json? // AI寤鸿<E5AFA4>鍊?
// 鐘舵€佹祦杞? status String // PROPOSED/APPROVED/REJECTED/EXECUTED/FAILED
agentType String // DATA_QUALITY/TASK_DRIVEN/COUNSELING/REPORTING
// AI鎺ㄧ悊淇℃伅
reasoning String @db.Text // AI鎺ㄧ悊杩囩▼
evidence Json // { protocolPage: 12, confidence: 0.92, ... }
// 浜虹被纭<E8A2AB><E7BAAD>淇℃伅
approvedBy String? // User ID
approvedAt DateTime?
rejectionReason String? @db.Text
// 鎵ц<E98EB5>淇℃伅
executedAt DateTime?
errorMessage String? @db.Text
// 鏃堕棿鎴? createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 鍏崇郴
project IitProject @relation(fields: [projectId], references: [id])
@@index([projectId, status])
@@index([projectId, recordId])
@@index([status, createdAt])
@@schema("iit")
}
// 浠诲姟杩愯<E69DA9>璁板綍锛堜笌 pg-boss 鍏宠仈锛?model IitTaskRun {
id String @id @default(uuid())
projectId String
taskType String // quality-check/follow-up/report-generation
// 鍏宠仈 pg-boss job
jobId String @unique // platform_schema.job.id
// 浠诲姟鐘舵€侊紙闀滃儚job鐘舵€侊紝渚夸簬涓氬姟鏌ヨ<E98F8C>锛? status String // pending/processing/completed/failed
// 涓氬姟缁撴灉
totalItems Int
processedItems Int @default(0)
successItems Int @default(0)
failedItems Int @default(0)
// 鏃堕棿淇℃伅
startedAt DateTime?
completedAt DateTime?
duration Int? // 绉?
// 鏃堕棿鎴? createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 鍏崇郴
project IitProject @relation(fields: [projectId], references: [id])
@@index([projectId, taskType, status])
@@index([jobId])
@@schema("iit")
}
// 鐢ㄦ埛鏄犲皠琛<E79AA0>紙寮傛瀯绯荤粺韬<E7B2BA>唤鍏宠仈锛?model IitUserMapping {
id String @id @default(uuid())
projectId String
// 绯荤粺鐢ㄦ埛ID锛堟湰绯荤粺锛? systemUserId String
// REDCap鐢ㄦ埛鍚? redcapUsername String
// 浼佸井OpenID
wecomUserId String?
// 馃敟 V1.1 鏂板<E98F82>锛氬皬绋嬪簭鏀<E7B0AD>寔锛堜笌浼佸井OpenID涓嶅悓锛? miniProgramOpenId String? @unique // 寰<>俊灏忕▼搴廜penID
sessionKey String? // 寰<>俊session_key锛堝姞瀵嗗瓨鍌<E793A8>級
// 瑙掕壊
role String // PI/CRC/SUB_I
// 鏃堕棿鎴? createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 鍏崇郴
project IitProject @relation(fields: [projectId], references: [id])
@@unique([projectId, systemUserId])
@@unique([projectId, redcapUsername])
@@index([wecomUserId])
@@index([miniProgramOpenId]) // 馃敟 V1.1 鏂板<E98F82>绱㈠紩
@@schema("iit")
}
// 瀹¤<E780B9>鏃ュ織锛堝悎瑙勬€э級
model IitAuditLog {
id String @id @default(uuid())
projectId String
// 鎿嶄綔淇℃伅
actionType String // AI_SUGGESTION/HUMAN_APPROVAL/REDCAP_WRITE/...
actionId String? // PendingAction ID 鎴栧叾浠朓D
// 鐢ㄦ埛淇℃伅
userId String
ipAddress String?
userAgent String? @db.Text
// 璇︾粏淇℃伅
details Json? // 鎿嶄綔璇︽儏
// 杩借釜閾? traceId String // 鍏宠仈澶氫釜鎿嶄綔
// 鏃堕棿鎴? createdAt DateTime @default(now())
// 鍏崇郴
project IitProject @relation(fields: [projectId], references: [id])
@@index([projectId, createdAt])
@@index([userId, createdAt])
@@index([actionType, createdAt])
@@index([traceId])
@@schema("iit")
}
4.2 鏁版嵁搴撹縼绉?
# 鐢熸垚杩佺Щ鏂囦欢
npx prisma migrate dev --name add_iit_schema
# 鐢熸垚Prisma Client
npx prisma generate
5. API 璁捐<E79281>
5.1 API 绔<>偣娓呭崟
椤圭洰绠$悊
| 绔<EFBFBD>偣 | 鏂规硶 | 鍔熻兘 | 浼樺厛绾? |
|---|---|---|---|
/api/v1/iit/projects |
POST | 鍒涘缓椤圭洰 | P0 |
/api/v1/iit/projects/:id |
GET | 鑾峰彇椤圭洰璇︽儏 | P0 |
/api/v1/iit/projects/:id |
PUT | 鏇存柊椤圭洰 | P1 |
/api/v1/iit/projects/:id/protocol |
POST | 涓婁紶Protocol | P0 |
/api/v1/iit/projects/:id/field-mappings |
PUT | 閰嶇疆瀛楁<EFBFBD>鏄犲皠 | P0 |
馃敟 /api/v1/iit/projects/:id/scan-all |
POST | *鍏ㄩ噺鎵<EFBFBD>弿锛圴1.1鏂板<EFBFBD>锛? | P0 |
Webhook鎺ユ敹
| 绔<EFBFBD>偣 | 鏂规硶 | 鍔熻兘 | 浼樺厛绾? |
|---|---|---|---|
/api/v1/iit/webhooks/redcap |
POST | REDCap Webhook | P0 |
褰卞瓙鐘舵€佺<EFBFBD>鐞?
| 绔<EFBFBD>偣 | 鏂规硶 | 鍔熻兘 | 浼樺厛绾? |
|---|---|---|---|
/api/v1/iit/pending-actions |
GET | 鑾峰彇寰呭<EFBFBD>鐞嗗缓璁<EFBFBD>垪琛? | P0 |
/api/v1/iit/pending-actions/:id |
GET | 鑾峰彇寤鸿<EFBFBD>璇︽儏 | P0 |
/api/v1/iit/pending-actions/:id/approve |
POST | 纭<EFBFBD><EFBFBD>寤鸿<EFBFBD> | P0 |
/api/v1/iit/pending-actions/:id/reject |
POST | 鎷掔粷寤鸿<EFBFBD> | P1 |
浠诲姟绠$悊
| 绔<EFBFBD>偣 | 鏂规硶 | 鍔熻兘 | 浼樺厛绾? |
|---|---|---|---|
/api/v1/iit/tasks |
GET | 鑾峰彇浠诲姟鍒楄〃 | P1 |
/api/v1/iit/tasks/:id |
GET | 鑾峰彇浠诲姟璇︽儏 | P1 |
/api/v1/iit/tasks/:id/progress |
GET | 鑾峰彇浠诲姟杩涘害 | P1 |
5.2 API 瀹炵幇绀轰緥
// backend/src/modules/iit-manager/routes/projects.ts
import { FastifyInstance } from 'fastify';
import { ProjectController } from '../controllers/ProjectController';
export async function projectRoutes(fastify: FastifyInstance) {
const controller = new ProjectController();
// 鍒涘缓椤圭洰
fastify.post('/projects', {
schema: {
body: {
type: 'object',
required: ['name', 'redcapProjectId', 'redcapApiToken', 'redcapUrl'],
properties: {
name: { type: 'string' },
description: { type: 'string' },
redcapProjectId: { type: 'string' },
redcapApiToken: { type: 'string' },
redcapUrl: { type: 'string' }
}
}
}
}, controller.createProject);
// 涓婁紶Protocol
fastify.post('/projects/:id/protocol', {
schema: {
params: {
type: 'object',
properties: {
id: { type: 'string' }
}
}
}
}, controller.uploadProtocol);
// 閰嶇疆瀛楁<E7809B>鏄犲皠
fastify.put('/projects/:id/field-mappings', {
schema: {
params: {
type: 'object',
properties: {
id: { type: 'string' }
}
},
body: {
type: 'object',
properties: {
mappings: { type: 'object' }
}
}
}
}, controller.updateFieldMappings);
}
6. 閮ㄧ讲鏋舵瀯
6.1 闃块噷浜慡AE閮ㄧ讲锛堢<E9949B>鍚堢幇鏈夋灦鏋勶級
鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?鈹? 闃块噷浜?SAE 鍛藉悕绌洪棿 鈹?鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹? 鈹? 搴旂敤1: Node.js Backend锛圛IT Manager妯″潡锛? 鈹? 鈹?鈹? 鈹? - 闀滃儚: backend-service:v1.1 鈹? 鈹?鈹? 鈹? - 瑙勬牸: 2鏍?GB 脳 1瀹炰緥 鈹? 鈹?鈹? 鈹? - 绔<>彛: 3001 鈹? 鈹?鈹? 鈹? - 鍋ュ悍妫€鏌? /api/health 鈹? 鈹?鈹? 鈹? - 鍐呯綉璁块棶 鈹? 鈹?鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹? 鈹? 搴旂敤2: Python 寰<>湇鍔★紙宸叉湁锛? 鈹? 鈹?鈹? 鈹? - 闀滃儚: python-extraction:v1.0 鈹? 鈹?鈹? 鈹? - 瑙勬牸: 1鏍?GB 脳 1瀹炰緥 鈹? 鈹?鈹? 鈹? - 绔<>彛: 8000 鈹? 鈹?鈹? 鈹? - 鍐呯綉璁块棶 鈹? 鈹?鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹? 鈹? 搴旂敤3: Frontend Nginx锛堝凡鏈夛級 鈹? 鈹?鈹? 鈹? - 闀滃儚: frontend-nginx:v1.0 鈹? 鈹?鈹? 鈹? - 瑙勬牸: 1鏍?GB 脳 1瀹炰緥 鈹? 鈹?鈹? 鈹? - 绔<>彛: 80 鈹? 鈹?鈹? 鈹? - 鍏<>綉璁块棶锛堥€氳繃CLB锛? 鈹? 鈹?鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈫?鈫?鍐呯綉閫氫俊
鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?鈹? 鏁版嵁瀛樺偍灞? 鈹?鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹屸攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹? 鈹?RDS PostgreSQL 鈹? 鈹?OSS 瀵硅薄瀛樺偍 鈹? 鈹?鈹? 鈹?- 2鏍?GB 鈹? 鈹?- Protocol PDF 鈹? 鈹?鈹? 鈹?- 11 Schemas 鈹? 鈹?- 鏂囦欢涓婁紶 鈹? 鈹?鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹? 鈹?鈹斺攢鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹?```
### 6.2 鐜<><E9909C>鍙橀噺閰嶇疆
```bash
# backend/.env.production
# 鏁版嵁搴?DATABASE_URL=postgresql://user:pass@pgm-xxx.rds.aliyuncs.com:5432/ai_clinical_research
# OSS瀛樺偍
STORAGE_MODE=oss
OSS_REGION=cn-beijing
OSS_BUCKET=ai-clinical-research
OSS_ACCESS_KEY_ID=xxx
OSS_ACCESS_KEY_SECRET=xxx
# LLM
LLM_API_KEY=sk-xxx
LLM_BASE_URL=https://api.deepseek.com
# Dify锛堝凡鏈夛級
DIFY_API_KEY=xxx
DIFY_BASE_URL=http://dify-service:5001
# REDCap
REDCAP_WEBHOOK_SECRET=xxx # 涓嶦M閰嶇疆涓€鑷?
# 浼佷笟寰<E7AC9F>俊
WECHAT_CORP_ID=xxx
WECHAT_CORP_SECRET=xxx
WECHAT_AGENT_ID=xxx
# Python寰<6E>湇鍔★紙鍐呯綉锛?PYTHON_SERVICE_URL=http://172.17.173.66:8000
# 鏃ュ織绾у埆
LOG_LEVEL=info
7. 寮€鍙戣<E98D99>鍒?
7.1 MVP 闃舵<E99783>锛?鍛<>紝P0锛?
Week 1: 鍩虹<E98DA9>杩炴帴灞傦紙馃敟 V1.1 浼樺厛绾ц皟鏁达級
鐩<EFBFBD>爣锛氭墦閫?REDCap 鈫?Node.js锛堟媺鍙栵級 + 浼佸井鎺ㄩ€? **馃敟 浼樺厛绾ц皟鏁寸悊鐢?*锛?- API鎷夊彇鏇村彲鎺э紙涓嶄緷璧栧尰闄㈢綉缁滐級
- 鑳借В鍐冲巻鍙叉暟鎹<EFBFBD>棶棰?- Webhook浣滀负澧炲己锛岃€岄潪鏍稿績渚濊禆
浠诲姟娓呭崟锛?
-
鏁版嵁搴撳垵濮嬪寲锛圖ay 1, 4灏忔椂锛? - [ ] 鍒涘缓 iit_schema
- 缂栧啓Prisma Schema锛?涓<>〃锛屽惈V1.1鏂板<EFBFBD>瀛楁<EFBFBD>锛? - [ ] 杩愯<E69DA9>杩佺Щ锛歚npx prisma migrate dev --name init_iit_schema`
- 鐢熸垚Prisma Client锛歚npx prisma generate`
- 楠岃瘉锛氳兘鍦∟ode.js涓<73>墽琛孋RUD
-
浼佷笟寰<EFBFBD>俊娉ㄥ唽锛圖ay 1, 2灏忔椂锛? - [ ] 娉ㄥ唽浼佷笟寰<E7AC9F>俊寮€鍙戣€呰处鍙? - [ ] 鍒涘缓鑷<E7BC93>缓搴旂敤锛欼IT Manager Agent锛堟祴璇曪級
- 鑾峰彇鍑<EFBFBD>瘉锛欳orpID銆丄gentID銆丼ecret
- 娴嬭瘯鎺ㄩ€侊細鐢≒ostman鍙戦€佷竴鏉″崱鐗囨秷鎭?
-
**馃敟 REDCap API Adapter寮€鍙?锛圖ay 2, 8灏忔椂锛?鈫?浼樺厛
- 鍒涘缓
RedcapAdapter.ts - 瀹炵幇
exportRecords()锛堟敮鎸佹椂闂磋繃婊わ級 - 瀹炵幇
importRecords()锛堝洖鍐欐暟鎹<EFBFBD>級 - 瀹炵幇
exportMetadata()锛堣幏鍙栧瓧娈靛畾涔夛級 - 娴嬭瘯锛氳兘鎷夊彇REDCap鏁版嵁
- 鍒涘缓
-
**馃敟 SyncManager寮€鍙?锛圖ay 2, 8灏忔椂锛?鈫?鏍稿績鍏滃簳
- 鍒涘缓
SyncManager.ts - 瀹炵幇
initializeSync()锛堟櫤鑳藉悓姝ョ瓥鐣ワ級 - 瀹炵幇
schedulePolling()锛堝畾鏃惰疆璇<EFBFBD>級 - 瀹炵幇
handlePoll()锛堣疆璇㈠<EFBFBD>鐞嗗櫒锛? - [ ] 瀹炵幇骞傜瓑鎬т繚鎶わ紙闃查噸澶嶏級 - 娴嬭瘯锛氳疆璇㈣兘姝g‘鎷夊彇鏂版暟鎹?
- 鍒涘缓
-
馃敟 鍏ㄩ噺鎵<E599BA>弿鍔熻兘锛圖ay 3, 8灏忔椂锛?鈫?鏀<>寔鍘嗗彶鏁版嵁*
- 鍒涘缓
BulkScanService.ts - 瀹炵幇
scanAllRecords()锛堟櫤鑳介槇鍊煎垽鏂<EFBFBD>級 - 瀹炵幇
processBatch()锛堟敮鎸佹柇鐐圭画浼狅級 - API绔<EFBFBD>偣锛歚POST /api/v1/iit/projects/:id/scan-all`
- 娴嬭瘯锛?00鏉″巻鍙叉暟鎹<E69A9F>壂鎻忔垚鍔?
- 鍒涘缓
-
**REDCap EM寮€鍙?锛圖ay 4, 8灏忔椂锛?鈫?浣滀负澧炲己
- 鍒涘缓EM鐩<EFBFBD>綍缁撴瀯
- 缂栧啓
config.json锛圗M閰嶇疆鏂囦欢锛? - [ ] 瀹炵幇IITManagerConnector.php - 瀹炵幇
redcap_save_recordHook - 瀹炵幇Webhook鎺ㄩ€侊紙甯︾<EFBFBD>鍚嶏級
-
**Node.js Webhook鎺ユ敹鍣?*锛圖ay 4, 8灏忔椂锛? - [ ] 鍒涘缓
webhookController.ts- 瀹炵幇绛惧悕楠岃瘉
- 瀹炵幇闃查噸鏀炬敾鍑? - [ ] 寮傛<E5AFAE>鎺ㄩ€佸埌璐ㄦ帶闃熷垪
- Webhook杩為€氭€ф祴璇曪紙鑷<EFBFBD>€傚簲鍒囨崲锛?
-
**浼佸井閫傞厤鍣?*锛圖ay 5, 8灏忔椂锛? - [ ] 鍒涘缓
WeChatAdapter.ts- 瀹炵幇Access Token缂撳瓨
- 瀹炵幇鍗$墖娑堟伅鎺ㄩ€? - [ ] 娴嬭瘯锛氬彂閫佽川鎺ч<E98EBA>璀﹀崱鐗? **楠屾敹鏍囧噯锛圴1.1锛?*锛?- 鉁?鏍稿績鑳藉姏锛氳疆璇㈣兘鎷夊彇REDCap鏂版暟鎹<EFBFBD>紙寤惰繜<10鍒嗛挓锛?- 鉁?澧炲己鑳藉姏锛歐ebhook鑳芥帹閫侊紙濡傛灉缃戠粶閫氾級锛堝欢杩?2绉掞級
- 鉁?鍘嗗彶鏁版嵁锛氬叏閲忔壂鎻忚兘澶勭悊瀛橀噺鏁版嵁
- 鉁?浼佸井閫氱煡锛氳兘鏀跺埌璐ㄦ帶棰勮<EFBFBD>鍗$墖
- 鉁?鑷<EFBFBD>€傚簲锛氱郴缁熻嚜鍔ㄩ€夋嫨鏈€浣冲悓姝ユā寮?
Week 2: AI 鏅鸿兘璐ㄦ帶
鐩<EFBFBD>爣锛氬疄鐜拌川鎺<EFBFBD>gent鐨勫畬鏁撮棴鐜?
浠诲姟娓呭崟锛?
6. Protocol鏈嶅姟锛圖ay 6-7, 16灏忔椂锛? - [ ] 鍒涘缓 ProtocolService.ts
- 瀹炵幇Protocol PDF涓婁紶鍒癘SS
- 璋冪敤Dify鍒涘缓Dataset
- 瀹炵幇
checkProtocolCompliance()鏂规硶 - 娴嬭瘯锛氫笂浼燩rotocol锛岃兘妫€绱㈠埌鍐呭<EFBFBD>
-
璐ㄦ帶Agent锛圖ay 8-9, 16灏忔椂锛? - [ ] 鍒涘缓
DataQualityAgent.ts- 瀹炵幇
checkRecord()鏂规硶 - 璋冪敤Protocol鏈嶅姟妫€鏌ュ悎瑙勬€? - [ ] 鍒涘缓褰卞瓙寤鸿<E5AFA4>锛坧ending_actions琛<73>級
- 鍙戦€佷紒寰<EFBFBD>€氱煡锛堜弗閲嶈繚鑳岋級
- 娴嬭瘯锛氳緭鍏ヨ繚鑳屾暟鎹<EFBFBD>紝鐢熸垚姝g‘寤鸿<EFBFBD>
- 瀹炵幇
-
PC Workbench鍓嶇<E98D93>楠ㄦ灦锛圖ay 10-12, 24灏忔椂锛? - [ ] 鍒涘缓鍓嶇<E98D93>璺<EFBFBD>敱锛歚/iit/workbench`
- 浠诲姟鍒楄〃椤碉紙鏄剧ず鎵€鏈塒ROPOSED寤鸿<EFBFBD>锛? - [ ] 璇︽儏瀵规瘮椤碉細
- 宸︿晶锛氬綋鍓嶆暟鎹? - 鍙充晶锛欰I寤鸿<E5AFA4> + 璇佹嵁鐗囨<E99097>
- 鎿嶄綔鎸夐挳锛歔鎷掔粷] [纭<><E7BAAD>]
- 娴嬭瘯锛氳兘姝g‘鏄剧ず鍜屾搷浣?
- 浠诲姟鍒楄〃椤碉紙鏄剧ず鎵€鏈塒ROPOSED寤鸿<EFBFBD>锛? - [ ] 璇︽儏瀵规瘮椤碉細
-
**褰卞瓙鐘舵€佹祦杞?*锛圖ay 13, 8灏忔椂锛? - [ ] 瀹炵幇
PendingActionService.approveAction()- 璋冪敤REDCap API鍥炲啓鏁版嵁
- 鏇存柊鐘舵€侊細PROPOSED 鈫?APPROVED 鈫?EXECUTED
- 璁板綍瀹¤<EFBFBD>鏃ュ織
- 娴嬭瘯锛氬畬鏁撮棴鐜<EFBFBD>紙鍙戠幇鈫掔‘璁も啋鍥炲啓锛?
-
**绔<>埌绔<E59F8C>祴璇?*锛圖ay 14, 8灏忔椂锛? - [ ] 瀹屾暣娴佺▼娴嬭瘯
- 鎬ц兘娴嬭瘯锛?00鏉¤<E98F89>褰曪級
- 閿欒<EFBFBD>澶勭悊娴嬭瘯
- Demo褰曞埗
楠屾敹鏍囧噯锛?- 鉁?AI鑳藉彂鐜癙rotocol杩濊儗锛堝噯纭<E599AF>巼>80%锛?- 鉁?Workbench鑳藉睍绀篈I寤鸿<E5AFA4>鍜岃瘉鎹<E79889>摼
- 鉁?纭<><E7BAAD>鍚庢暟鎹<E69A9F><E98EB9>纭<EFBFBD>洖鍐欏埌REDCap
- 鉁?瀹屾暣瀹¤<E780B9>鏃ュ織
- 鉁?5鍒嗛挓Demo褰曞埗瀹屾垚
7.2 Phase 1: 澶氱粓绔<E7B293>崗鍚岋紙2鍛<32>紝P1锛?
浠诲姟娓呭崟锛?
11. **馃敟 寰<>俊灏忕▼搴忓紑鍙戯紙V1.1 鎶€鏈<E282AC>爤鏄庣‘锛歍aro锛?*锛圵eek 3-4锛? - [ ] **Taro 4.x椤圭洰鍒濆<E98D92>鍖?*锛圧eact璇<74>硶锛? - [ ] 閰嶇疆Taro缂栬瘧涓哄井淇″皬绋嬪簭 + H5
- [ ] 澶嶇敤 shared/components 閫氱敤閫昏緫
- [ ] 鍔ㄦ€佸搧鐗屾覆鏌擄紙Logo銆佷富棰樿壊锛? - [ ] 鎶ヨ〃灞曠ず椤甸潰锛圱aro UI缁勪欢锛? - [ ] 瀹℃壒鎿嶄綔鐣岄潰
- [ ] 浼佸井璺宠浆闆嗘垚
- [ ] 灏忕▼搴忕櫥褰曪紙wx.login + sessionKey锛?
鎶€鏈<EFBFBD>爤浼樺娍锛? - 鉁?React Hooks璇<73>硶锛堝洟闃熺啛鎮夛級
- 鉁?鍙<><E98D99>鐢ㄥ墠绔<E5A2A0>唬鐮佸拰閫昏緫
- 鉁?涓€娆″紑鍙戯紝澶氱<E6BEB6>杩愯<E69DA9>锛堝皬绋嬪簭 + H5锛? - 鉁?TypeScript鏀<74>寔瀹屽杽
- 浠诲姟椹卞姩Agent锛圵eek 3-4锛? - [ ] 鎮h€呴殢璁挎彁閱? - [ ] 璁胯<E79281>绐楀彛鐩戞帶
- 娑堟伅鎺ㄩ€佺瓥鐣?
7.3 Phase 2-4锛堝悗缁<E68297>凯浠o級
- Phase 2: OCR鏅鸿兘閲囬泦锛?鍛<>級
- Phase 3: 鏅鸿兘姹囨姤Agent锛?鍛<>級
- Phase 4: 瑙勬ā鍖栦紭鍖栵紙3鍛<33>級
8. 椋庨櫓璇勪及涓庡<E6B693>绛?
8.1 鎶€鏈<E282AC><E98F88>闄?
椋庨櫓1锛欴ify RAG鍑嗙‘鐜囦笉瓒?
椋庨櫓绛夌骇锛氶珮
褰卞搷锛欰I妫€娴嬪噯纭<EFBFBD>巼<60%锛屽亣闃虫€ц繃澶?
搴斿<EFBFBD>绛栫暐锛?
Plan A锛堜紭鍏堬級锛?- 涓ユ牸闄愬埗MVP妫€鏌ヨ寖鍥达紙鍙<E7B499><E98D99>鏌?绫荤畝鍗曡<E98D97>鍒欙級
- 骞撮緞銆佹€у埆銆佸繀濉<EFBFBD>」 = 瑙勫垯鏄庣‘锛屽噯纭<E599AF>巼楂?- 鍏堥獙璇佹灦鏋勶紝鍚庝紭鍖栧噯纭<E599AF>€? Plan B锛堝<E9949B>閫夛級锛?- 濡傛灉Dify鏁堟灉涓嶄匠锛屼复鏃剁敤纭<E695A4>紪鐮佽<E990AE>鍒?- MVP閲嶇偣楠岃瘉"褰卞瓙鐘舵€佹満鍒?锛岃€岄潪AI鑳藉姏
- 瑙勫垯寮曟搸鍦≒hase 2鍐嶄紭鍖? 楠岃瘉鏂规硶锛?- 鐢?0涓<30>湡瀹炵梾渚嬫祴璇?- 鍑嗙‘鐜囩洰鏍囷細>85%
- 鍋囬槼鎬х巼锛?15%
椋庨櫓2锛歊EDCap閮ㄧ讲鍥伴毦
椋庨櫓绛夌骇锛氫腑
褰卞搷锛氬尰闄㈢殑REDCap鐗堟湰澶<EFBFBD>€?娌℃湁閮ㄧ讲鏉冮檺
搴斿<EFBFBD>绛栫暐锛? Plan A锛堟帹鑽愶級锛?- 鑷<>繁閮ㄧ讲涓€涓<E282AC>祴璇昍EDCap锛圖ocker锛?- 鐢ㄤ簬MVP Demo鍜屽唴閮ㄦ祴璇?- 绛夌<E7BB9B>绾﹀尰闄㈠悗鍐嶅<E98D90>鎺ヤ粬浠<E7B2AC>殑鐢熶骇REDCap
Plan B锛堝<E9949B>閫夛級锛?- 鍏堣烦杩嘡EDCap锛岀敤Mock鏁版嵁
- 閲嶇偣灞曠ずWorkbench鍜屼紒寰<EFBFBD>€氱煡
- REDCap闆嗘垚浣滀负"鎶€鏈<E282AC>彲琛屾€?璇存槑
Docker閮ㄧ讲锛?```bash
浣跨敤瀹樻柟REDCap Docker闀滃儚锛堟祴璇曠幆澧冿級
docker-compose up -d redcap mysql
#### 椋庨櫓3锛氫紒寰<E7B492><E5AFB0>鏍镐笉閫氳繃
**椋庨櫓绛夌骇**锛氫綆
**褰卞搷**锛氫紒涓氬井淇¤嚜寤哄簲鐢ㄥ<E990A2>鏍歌<E98F8D>鎷?
**搴斿<E690B4>绛栫暐**锛?
**Plan A**锛?- 鎻愬墠鍑嗗<E98D91>瀹℃牳鏉愭枡锛堝叕鍙歌祫璐ㄣ€佷骇鍝佽<E98D9D>鏄庯級
- 搴旂敤绫诲瀷閫夋嫨"浼佷笟鍐呴儴宸ュ叿"锛堝<E9949B>鏍稿<E98F8D>鏉撅級
**Plan B**锛?- 濡傛灉瀹℃牳鎱<E789B3>紝鍏堢敤浼佸井Webhook娴嬭瘯鍙?- 鎴栦复鏃剁敤閽夐拤/椋炰功锛堟妧鏈<E5A6A7>柟妗堥€氱敤锛?
**鍏抽敭**锛氫紒寰<E7B492>笉鏄<E7AC89>敮涓€閫夋嫨锛屾灦鏋勮<E98F8B>璁″凡缁忚В鑰?
### 8.2 涓氬姟椋庨櫓
#### 椋庨櫓4锛氬瓧娈垫槧灏勫<E7818F>鏉傛€?
**椋庨櫓绛夌骇**锛氫腑
**褰卞搷**锛氫笉鍚屽尰闄㈢殑REDCap瀛楁<E7809B>鍚嶅樊寮傚ぇ
**搴斿<E690B4>绛栫暐**锛?
- MVP闃舵<E99783>锛氭墜鍔ㄩ厤缃?涓<>叧閿<E58FA7>瓧娈垫槧灏?- Phase 2锛氬紑鍙慉I鑷<49>姩鏄犲皠宸ュ叿锛圢ER璇嗗埆锛?- Phase 3锛氬缓绔嬫爣鍑嗗瓧娈靛簱锛?00+甯哥敤瀛楁<E7809B>锛?
#### 椋庨櫓5锛氬尰鐤楀悎瑙勬€у<E282AC>鏌?
**椋庨櫓绛夌骇**锛氶珮
**褰卞搷**锛欰I淇<49>敼涓村簥鏁版嵁鐨勫悎瑙勬€ч棶棰?
**搴斿<E690B4>绛栫暐**锛?
- 鉁?**鏍稿績璁捐<E79281>**锛氬奖瀛愮姸鎬佹満鍒讹紙AI鍙<49>缓璁<E7BC93>紝浜虹被纭<E8A2AB>潈锛?- 鉁?**瀹屾暣瀹¤<E780B9>**锛氭墍鏈夋搷浣滆<E6B5A3>褰曞埌audit_logs琛?- 鉁?**绗﹀悎FDA 21 CFR Part 11**锛氱數瀛愮<E7809B>鍚嶅拰瀹¤<E780B9>杩借釜
- 鉁?**鍙<>洖婊?*锛氭墍鏈変慨鏀瑰彲杩芥函鍜屾挙閿€
### 8.3 鎬ц兘椋庨櫓
#### 椋庨櫓6锛歊EDCap Webhook寤惰繜
**椋庨櫓绛夌骇**锛氫綆
**褰卞搷**锛歐ebhook鎺ㄩ€佸け璐ユ垨寤惰繜
**搴斿<E690B4>绛栫暐**锛?
- 鉁?骞傜瓑鎬ц<E98EAC>璁★細閲嶅<E996B2>Webhook涓嶄細閲嶅<E996B2>澶勭悊
- 鉁?寮傛<E5AFAE>澶勭悊锛歐ebhook绔嬪嵆杩斿洖200锛屽悗鍙板紓姝ユ墽琛?- 鉁?閲嶈瘯鏈哄埗锛歱g-boss鑷<73>姩閲嶈瘯3娆?- 鉁?姝讳俊闃熷垪锛氬け璐ヤ换鍔″崟鐙<E5B49F>瓨鍌<E793A8>紝浜哄伐浠嬪叆
#### 椋庨櫓7锛氬ぇ閲忓苟鍙戣川鎺?
**椋庨櫓绛夌骇**锛氫腑
**褰卞搷**锛?00涓<30>」鐩<E3808D>悓鏃跺綍鍏ユ暟鎹?
**搴斿<E690B4>绛栫暐**锛?
- 鉁?闃熷垪闄愭祦锛歱g-boss骞跺彂闄愬埗锛堟瘡绉?0涓<30>級
- 鉁?LLM闄愭祦锛欴eepSeek API闄愭祦淇濇姢
- 鉁?Dify闄愭祦锛歊AG妫€绱㈤檺娴侊紙姣忕<E5A7A3>5娆★級
- 鉁?浼樺厛绾ч槦鍒楋細绱ф€ラ」鐩<E3808D>紭鍏堝<E98D8F>鐞?
---
## 馃搳 鎬荤粨
### 鏍稿績浼樺娍
1. **瀹屽叏澶嶇敤骞冲彴鑳藉姏**
- 鉁?涓嶉噸澶嶅疄鐜板熀纭€璁炬柦
- 鉁?寮€鍙戞晥鐜囨彁鍗?0%
- 鉁?缁存姢鎴愭湰闄嶄綆
2. **Postgres-Only鏋舵瀯**
- 鉁?闆堕<E99786>澶栨垚鏈<E59E9A>紙鏃犻渶Redis锛? - 鉁?鏂<>偣缁<E581A3>紶锛堟敮鎸侀暱浠诲姟锛? - 鉁?绗﹀悎浜戝師鐢熻<E990A2>鑼?
3. **褰卞瓙鐘舵€佹満鍒?*
- 鉁?鍖荤枟鍚堣<E98D9A>锛團DA璁よ瘉锛? - 鉁?AI鍙<49>帶锛堜汉绫荤‘鏉冿級
- 鉁?鍙<>拷婧<E68BB7>紙瀹屾暣瀹¤<E780B9>锛?
4. **娓愯繘寮忔紨杩?*
- 鉁?MVP 2鍛ㄩ獙璇佹牳蹇冧环鍊? - 鉁?Phase 1-4閫愭<E996AB>杩<EFBFBD>唬
- 鉁?椋庨櫓鍙<E6AB93>帶
### MVP鎴愬姛鏍囧噯
**Demo鍦烘櫙**锛?鍒嗛挓锛夛細
1. CRC鍦≧EDCap褰曞叆杩濊儗鏁版嵁锛堝勾榫?5宀侊級
2. 30绉掑悗锛孭I鏀跺埌浼佸井鍗$墖锛?骞撮緞瓒呭嚭鍏ユ帓鏍囧噯"
3. CRC鎵撳紑Workbench锛岀湅鍒癆I寤鸿<E5AFA4>鍜孭rotocol璇佹嵁锛堢<E9949B>12椤碉級
4. CRC纭<43><E7BAAD>锛屾暟鎹<E69A9F>嚜鍔ㄥ洖鍐橰EDCap
**鎶€鏈<E282AC>寚鏍?*锛?- Webhook鍝嶅簲鏃堕棿 < 100ms
- AI璐ㄦ帶瀹屾垚鏃堕棿 < 30绉?- 浼佸井鎺ㄩ€佸欢杩?< 5绉?- AI鍑嗙‘鐜?> 80%
### 涓嬩竴姝ヨ<E5A79D>鍔?
**绔嬪嵆鎵ц<E98EB5>**锛堜粖澶╋級锛?1. 鉁?纭<><E7BAAD>浼佷笟寰<E7AC9F>俊娉ㄥ唽杩涘害
2. 鉁?纭<><E7BAAD>鎶€鏈<E282AC>爤锛圢ode.js 22銆丳ostgreSQL 15銆乀ypeScript 5锛?3. 鉁?鍒涘缓椤圭洰鐪嬫澘锛堥<E9949B>涔?Notion锛?
**Week 1 鍚<>姩**锛堟槑澶╁紑濮嬶紝V1.1浼樺厛绾э級锛?1. 鉁?鏁版嵁搴揝chema鍒濆<E98D92>鍖?2. 馃敟 REDCap API Adapter寮€鍙戯紙浼樺厛锛?3. 馃敟 SyncManager寮€鍙戯紙鏍稿績鍏滃簳锛?4. 馃敟 鍏ㄩ噺鎵<E599BA>弿鍔熻兘锛堟敮鎸佸巻鍙叉暟鎹<E69A9F>級
5. 鉁?REDCap EM寮€鍙戯紙浣滀负澧炲己锛?6. 鉁?浼佸井閫傞厤鍣ㄥ紑鍙?
---
## 馃摑 V1.1 鏇存柊鎬荤粨
### 鏋舵瀯淇<E780AF><E6B787>
**1. 娣峰悎鍚屾<E98D9A>妯″紡锛圫yncManager锛?*
- 鉁?瑙e喅鍖婚櫌鍐呯綉杩為€氭€ч棶棰橈紙鑷村懡椋庨櫓锛?- 鉁?浼樺厛浣跨敤Webhook锛堝疄鏃舵€э級锛岃疆璇<E79686>綔涓哄厹搴曪紙鍙<E7B499>潬鎬э級
- 鉁?鏅鸿兘鑷<E58598>€傚簲锛氳嚜鍔ㄩ€夋嫨鏈€浣冲悓姝ユā寮?- 鉁?骞傜瓑鎬т繚鎶わ細闃叉<E99783>閲嶅<E996B2>璐ㄦ帶
**2. 鍘嗗彶鏁版嵁鍏ㄩ噺鎵<E599BA>弿锛圔ulkScanService锛?*
- 鉁?鏀<>寔瀛橀噺鏁版嵁璐ㄦ帶锛堝姛鑳借ˉ鍏咃級
- 鉁?鏅鸿兘闃堝€煎垽鏂<E59EBD>紙<50鏉$洿鎺ュ<E98EBA>鐞嗭紝鈮?0鏉¢槦鍒楀<E98D92>鐞嗭級
- 鉁?鏂<>偣缁<E581A3>紶锛堟敮鎸侀暱鏃堕棿浠诲姟锛?- 鉁?API绔<49>偣锛歚POST /api/v1/iit/projects/:id/scan-all`
### 鎶€鏈<E282AC>爤鏄庣‘
**3. 鍓嶇<E98D93>鎶€鏈<E282AC>爤锛歍aro 4.x**
- 鉁?React Hooks璇<73>硶锛堝洟闃熺啛鎮夛級
- 鉁?鍙<><E98D99>鐢?shared/components 閫昏緫
- 鉁?涓€娆″紑鍙戯紝澶氱<E6BEB6>杩愯<E69DA9>锛堝皬绋嬪簭 + H5锛?- 鉁?TypeScript鏀<74>寔瀹屽杽
### 鏁版嵁搴撳<E690B4>寮?
**4. Prisma Schema鏂板<E98F82>瀛楁<E7809B>**
- 鉁?`IitProject.cachedRules`锛氱紦瀛楶rotocol鍏抽敭瑙勫垯锛堟€ц兘浼樺寲锛?- 鉁?`IitProject.lastSyncAt`锛氳<E9949B>褰曚笂娆″悓姝ユ椂闂达紙澧為噺鎷夊彇锛?- 鉁?`IitUserMapping.miniProgramOpenId`锛氬皬绋嬪簭OpenID锛堝<E9949B>绔<EFBFBD>敮鎸侊級
- 鉁?`IitUserMapping.sessionKey`锛氬井淇<E4BA95>ession_key锛堢櫥褰曡<E8A4B0>璇侊級
### 寮€鍙戜紭鍏堢骇璋冩暣
**5. MVP寮€鍙戣<E98D99>鍒掗噸鎺?*
- 馃敟 **Day 2浼樺厛绾?*锛歊EDCap API Adapter + SyncManager锛堟媺鍙栬兘鍔涳級
- 馃敟 **Day 3鏍稿績**锛氬叏閲忔壂鎻忓姛鑳斤紙鍘嗗彶鏁版嵁鏀<E5B581>寔锛?- 馃敟 **Day 4琛ュ厖**锛歊EDCap EM + Webhook锛堟帹閫佽兘鍔涳紝浣滀负澧炲己锛?
**璋冩暣鐞嗙敱**锛?- API鎷夊彇鏇村彲鎺э紙涓嶄緷璧栧尰闄㈢綉缁滐級
- 鑳借В鍐冲巻鍙叉暟鎹<E69A9F>棶棰?- Webhook浣滀负澧炲己锛岃€岄潪鏍稿績渚濊禆
### 椋庨櫓搴斿<E690B4>
**6. 缃戠粶杩為€氭€ч<E282AC>闄╋紙宸茶В鍐筹級**
- 鉂?**V1.0椋庨櫓**锛氬畬鍏ㄤ緷璧朩ebhook锛屽尰闄㈠唴缃戞棤娉曟帹閫?- 鉁?**V1.1淇<EFBFBD><EFBFBD>**锛氭贩鍚堝悓姝ユā寮忥紝杞<E7B49D><E69D9E>浣滀负鍏滃簳
- 鉁?**鍙<>潬鎬?*锛?9.9%锛堜笉渚濊禆鍖婚櫌缃戠粶锛?
**7. 鍘嗗彶鏁版嵁椋庨櫓锛堝凡瑙e喅锛?*
- 鉂?**V1.0椋庨櫓**锛氬彧鐩戝惉鏂版暟鎹<E69A9F>紝鍘嗗彶鏁版嵁鏃犳硶璐ㄦ帶
- 鉁?**V1.1淇<EFBFBD><EFBFBD>**锛氬叏閲忔壂鎻忓姛鑳斤紝鏀<E7B49D>寔瀛橀噺鏁版嵁
- 鉁?**浠峰€兼彁鍗?*锛氬尰闄㈣兘瀵瑰巻鍙?00涓<30>偅鑰呰繘琛岃川鎺?
### 鎬ц兘浼樺寲
**8. Dify RAG鎬ц兘浼樺寲锛堥<E9949B>鍔犺浇锛?*
- 鉁?Protocol涓婁紶鏃讹紝棰勬彁鍙栧叧閿<E58FA7><E996BF>鍒?- 鉁?缂撳瓨鍒癭cachedRules`瀛楁<E7809B>锛圝SONB锛?- 鉁?绠€鍗曡<E98D97>鍒欑洿鎺ュ垽鏂<E59EBD>紙鏃犻渶璋冪敤Dify锛?- 鉁?澶嶆潅瑙勫垯鎵嶈皟鐢―ify RAG锛堟參璺<E58F83>緞锛?
---
**鏂囨。鐗堟湰**锛歏1.1锛堟灦鏋勮瘎瀹′慨姝g増锛?
**鍒涘缓鏃ユ湡**锛?025-12-31
**鏈€鍚庢洿鏂?*锛?025-12-31
**缁存姢鑰?*锛氭灦鏋勫洟闃?
**瀹℃煡鍙傝€?*锛歚06-寮€鍙戣<E98D99>褰?IIT Manager Agent 鎶€鏈<E282AC>柟妗堝<E5A697>鏌ヤ笌琛ヤ竵.md`
**涓嬩竴姝?*锛氱瓑寰呯敤鎴风‘璁わ紝鍑嗗<E98D91>鍚<EFBFBD>姩MVP寮€鍙戯紙鎸塚1.1浼樺厛绾э級