Files
AIclinicalresearch/docs/03-业务模块/IIT Manager Agent/02-技术设计/IIT Manager Agent 完整技术开发方案 (V1.1).md
HaHafeng 1b53ab9d52 feat(aia): Complete AIA V2.0 with universal streaming capabilities
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%)
2026-01-14 19:15:01 +08:00

64 KiB
Raw Blame History

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>锛屼慨姝綉缁滆繛閫氭€ч<E282AC>闄┿€佸<E282AC>鍔犲巻鍙叉暟鎹<E69A9F>壂鎻忋€佹槑纭<E6A791>墠绔<E5A2A0>妧鏈<E5A6A7>


馃敟 V1.1 鏍稿績淇<E7B8BE><E6B787>

鍩轰簬鏋舵瀯璇勫<EFBFBD>锛堝弬鑰冿細06-寮€鍙戣<E98D99>褰?IIT Manager Agent 鎶€鏈<E282AC>柟妗堝<E5A697>鏌ヤ笌琛ヤ竵.md锛夛紝鏈<EFBFBD>増鏈<EFBFBD>慨姝簡3涓<EFBFBD>叧閿<EFBFBD>棶棰橈細

  1. **鉁?鑷村懡椋庨櫓淇<E6AB93><E6B787>**锛氭贩鍚堝悓姝ユā寮忥紙Webhook + 杞<><E69D9E>锛夛紝瑙喅鍖婚櫌鍐呯綉杩為€氭€ч棶棰?
  2. 鉁?鍔熻兘琛ュ厖锛氬巻鍙叉暟鎹<EFBFBD>叏閲忔壂鎻忥紝鏀<EFBFBD>寔瀛橀噺鏁版嵁璐ㄦ帶
  3. **鉁?鎶€鏈<E282AC>爤鏄庣**锛氬墠绔<E5A2A0>噰鐢═aro锛圧eact璇<74>硶锛夛紝鏀<E7B49D>寔灏忕▼搴?H5鍙岀<E98D99>

馃搵 鏂囨。瀵艰埅

  1. 绯荤粺鏋舵瀯璁捐<EFBFBD>
  2. 鐜版湁鑳藉姏澶嶇敤
  3. [鏍稿績鎶€鏈<E282AC>疄鐜癩(#3-鏍稿績鎶€鏈<E282AC>疄鐜?
  4. [鏁版嵁搴撹<E690B4><EFBFBD>(#4-鏁版嵁搴撹<E690B4>璁?
  5. API璁捐<EFBFBD>
  6. 閮ㄧ讲鏋舵瀯
  7. [寮€鍙戣<E98D99>鍒抅(#7-寮€鍙戣<E98D99>鍒?
  8. [椋庨櫓璇勪及涓庡<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. 瀹屽叏澶嶇敤骞冲彴鑳藉姏

// 鉁?涓嶉噸澶嶅疄鐜板熀纭€璁炬柦
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>鑼冿級

// 浠诲姟绠$悊淇℃伅瀛樺偍鍦?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 = `
€呮暟鎹<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. 瑙瀽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>細瑙喅鍖婚櫌鍐呯綉杩為€氭€ч棶棰?
 * 
 * 鏍稿績绛栫暐锛?
 * 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鎮€咃細${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>鍙橀噺閰嶇疆

# 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浣滀负澧炲己锛岃€岄潪鏍稿績渚濊禆

浠诲姟娓呭崟锛?

  1. 鏁版嵁搴撳垵濮嬪寲锛圖ay 1, 4灏忔椂锛?

    • 鍒涘缓 iit_schema
    • 缂栧啓Prisma Schema锛?涓<>〃锛屽惈V1.1鏂板<EFBFBD>瀛楁<EFBFBD>锛?
    • 杩愯<EFBFBD>杩佺Щ锛歚npx prisma migrate dev --name init_iit_schema`
    • 鐢熸垚Prisma Client锛歚npx prisma generate`
    • 楠岃瘉锛氳兘鍦∟ode.js涓<73>墽琛孋RUD
  2. 浼佷笟寰<EFBFBD>俊娉ㄥ唽锛圖ay 1, 2灏忔椂锛?

    • 娉ㄥ唽浼佷笟寰<EFBFBD>俊寮€鍙戣€呰处鍙?
    • 鍒涘缓鑷<EFBFBD>缓搴旂敤锛欼IT Manager Agent锛堟祴璇曪級
    • 鑾峰彇鍑<EFBFBD>瘉锛欳orpID銆丄gentID銆丼ecret
    • 娴嬭瘯鎺ㄩ€侊細鐢≒ostman鍙戦€佷竴鏉″崱鐗囨秷鎭?
  3. **馃敟 REDCap API Adapter寮€鍙?锛圖ay 2, 8灏忔椂锛?鈫?浼樺厛

    • 鍒涘缓 RedcapAdapter.ts
    • 瀹炵幇 exportRecords()锛堟敮鎸佹椂闂磋繃婊わ級
    • 瀹炵幇 importRecords()锛堝洖鍐欐暟鎹<EFBFBD>
    • 瀹炵幇 exportMetadata()锛堣幏鍙栧瓧娈靛畾涔夛級
    • 娴嬭瘯锛氳兘鎷夊彇REDCap鏁版嵁
  4. **馃敟 SyncManager寮€鍙?锛圖ay 2, 8灏忔椂锛?鈫?鏍稿績鍏滃簳

    • 鍒涘缓 SyncManager.ts
    • 瀹炵幇 initializeSync()锛堟櫤鑳藉悓姝ョ瓥鐣ワ級
    • 瀹炵幇 schedulePolling()锛堝畾鏃惰疆璇<EFBFBD>
    • 瀹炵幇 handlePoll()锛堣疆璇㈠<EFBFBD>鐞嗗櫒锛?
    • 瀹炵幇骞傜瓑鎬т繚鎶わ紙闃查噸澶嶏級
    • 娴嬭瘯锛氳疆璇㈣兘姝g‘鎷夊彇鏂版暟鎹?
  5. 馃敟 鍏ㄩ噺鎵<E599BA>弿鍔熻兘锛圖ay 3, 8灏忔椂锛?鈫?鏀<>寔鍘嗗彶鏁版嵁*

    • 鍒涘缓 BulkScanService.ts
    • 瀹炵幇 scanAllRecords()锛堟櫤鑳介槇鍊煎垽鏂<EFBFBD>
    • 瀹炵幇 processBatch()锛堟敮鎸佹柇鐐圭画浼狅級
    • API绔<EFBFBD>偣锛歚POST /api/v1/iit/projects/:id/scan-all`
    • 娴嬭瘯锛?00鏉″巻鍙叉暟鎹<E69A9F>壂鎻忔垚鍔?
  6. **REDCap EM寮€鍙?锛圖ay 4, 8灏忔椂锛?鈫?浣滀负澧炲己

    • 鍒涘缓EM鐩<EFBFBD>綍缁撴瀯
    • 缂栧啓 config.json锛圗M閰嶇疆鏂囦欢锛?
    • 瀹炵幇 IITManagerConnector.php
    • 瀹炵幇 redcap_save_record Hook
    • 瀹炵幇Webhook鎺ㄩ€侊紙甯︾<EFBFBD>鍚嶏級
  7. **Node.js Webhook鎺ユ敹鍣?*锛圖ay 4, 8灏忔椂锛?

    • 鍒涘缓 webhookController.ts
    • 瀹炵幇绛惧悕楠岃瘉
    • 瀹炵幇闃查噸鏀炬敾鍑?
    • 寮傛<EFBFBD>鎺ㄩ€佸埌璐ㄦ帶闃熷垪
    • Webhook杩為€氭€ф祴璇曪紙鑷<EFBFBD>€傚簲鍒囨崲锛?
  8. **浼佸井閫傞厤鍣?*锛圖ay 5, 8灏忔椂锛?

    • 鍒涘缓 WeChatAdapter.ts
    • 瀹炵幇Access Token缂撳瓨
    • 瀹炵幇鍗$墖娑堟伅鎺ㄩ€?
    • 娴嬭瘯锛氬彂閫佽川鎺ч<EFBFBD>璀﹀崱鐗?

**楠屾敹鏍囧噯锛圴1.1锛?*锛?

  • 鉁?鏍稿績鑳藉姏锛氳疆璇㈣兘鎷夊彇REDCap鏂版暟鎹<EFBFBD>紙寤惰繜<10鍒嗛挓锛?
  • 鉁?澧炲己鑳藉姏锛歐ebhook鑳芥帹閫侊紙濡傛灉缃戠粶閫氾級锛堝欢杩?2绉掞級
  • 鉁?鍘嗗彶鏁版嵁锛氬叏閲忔壂鎻忚兘澶勭悊瀛橀噺鏁版嵁
  • 鉁?浼佸井閫氱煡锛氳兘鏀跺埌璐ㄦ帶棰勮<EFBFBD>
  • 鉁?<EFBFBD>€傚簲锛氱郴缁熻嚜鍔ㄩ€夋嫨鏈€浣冲悓姝ユā寮?

Week 2: AI 鏅鸿兘璐ㄦ帶

<EFBFBD>锛氬疄鐜拌川鎺<EFBFBD>gent鐨勫畬鏁撮棴鐜?

浠诲姟娓呭崟锛?

  1. Protocol鏈嶅姟锛圖ay 6-7, 16灏忔椂锛?

    • 鍒涘缓 ProtocolService.ts
    • 瀹炵幇Protocol PDF涓婁紶鍒癘SS
    • 璋冪敤Dify鍒涘缓Dataset
    • 瀹炵幇 checkProtocolCompliance() 鏂规硶
    • 娴嬭瘯锛氫笂浼燩rotocol锛岃兘妫€绱㈠埌鍐呭<EFBFBD>
  2. 璐ㄦ帶Agent锛圖ay 8-9, 16灏忔椂锛?

    • 鍒涘缓 DataQualityAgent.ts
    • 瀹炵幇 checkRecord() 鏂规硶
    • 璋冪敤Protocol鏈嶅姟妫€鏌ュ悎瑙勬€?
    • 鍒涘缓褰卞瓙寤鸿<EFBFBD>锛坧ending_actions琛<EFBFBD>
    • 鍙戦€佷紒寰<EFBFBD>€氱煡锛堜弗閲嶈繚鑳岋級
    • 娴嬭瘯锛氳緭鍏ヨ繚鑳屾暟鎹<EFBFBD>紝鐢熸垚姝寤鸿<EFBFBD>
  3. PC Workbench鍓嶇<E98D93>楠ㄦ灦锛圖ay 10-12, 24灏忔椂锛?

    • 鍒涘缓鍓嶇<EFBFBD><EFBFBD>敱锛歚/iit/workbench`
    • 浠诲姟鍒楄〃椤碉紙鏄剧ず鎵€鏈塒ROPOSED寤鸿<EFBFBD>锛?
    • 璇︽儏瀵规瘮椤碉細
      • 宸︿晶锛氬綋鍓嶆暟鎹?
      • 鍙充晶锛欰I寤鸿<EFBFBD> + 璇佹嵁鐗囨<E99097>
    • 鎿嶄綔鎸夐挳锛歔鎷掔粷] [纭<><E7BAAD>]
    • 娴嬭瘯锛氳兘姝g‘鏄剧ず鍜屾搷浣?
  4. **褰卞瓙鐘舵€佹祦杞?*锛圖ay 13, 8灏忔椂锛?

    • 瀹炵幇 PendingActionService.approveAction()
    • 璋冪敤REDCap API鍥炲啓鏁版嵁
    • 鏇存柊鐘舵€侊細PROPOSED 鈫?APPROVED 鈫?EXECUTED
    • 璁板綍瀹¤<EFBFBD>鏃ュ織
    • 娴嬭瘯锛氬畬鏁撮棴鐜<EFBFBD>紙鍙戠幇鈫掔璁も啋鍥炲啓锛?
  5. **绔<>埌绔<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锛?

浠诲姟娓呭崟锛?

  1. **馃敟 寰<>俊灏忕▼搴忓紑鍙戯紙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>寔瀹屽杽
  2. 浠诲姟椹卞姩Agent锛圵eek 3-4锛?

    • 鎮h€呴殢璁挎彁閱?
    • 璁胯<EFBFBD>绐楀彛鐩戞帶
    • 娑堟伅鎺ㄩ€佺瓥鐣?

7.3 Phase 2-4锛堝悗缁<E68297>凯浠

  • 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妫€鏌ヨ寖鍥达紙鍙<EFBFBD><EFBFBD>鏌?绫荤畝鍗曡<E98D97>鍒欙級
  • 骞撮緞銆佹€у埆銆佸繀濉<EFBFBD>」 = 瑙勫垯鏄庣锛屽噯纭<E599AF>巼楂?
  • 鍏堥獙璇佹灦鏋勶紝鍚庝紭鍖栧噯纭<EFBFBD>€?

Plan B锛堝<E9949B>閫夛級锛?

  • 濡傛灉Dify鏁堟灉涓嶄匠锛屼复鏃剁敤纭<EFBFBD>紪鐮佽<EFBFBD>鍒?
  • MVP閲嶇偣楠岃瘉"褰卞瓙鐘舵€佹満鍒?锛岃€岄潪AI鑳藉姏
  • 瑙勫垯寮曟搸鍦≒hase 2鍐嶄紭鍖?

楠岃瘉鏂规硶锛?

  • 鐢?0涓<30>湡瀹炵梾渚嬫祴璇?
  • 鍑嗙‘鐜囩洰鏍囷細>85%
  • 鍋囬槼鎬х巼锛?15%

椋庨櫓2锛歊EDCap閮ㄧ讲鍥伴毦

椋庨櫓绛夌骇锛氫腑
褰卞搷锛氬尰闄㈢殑REDCap鐗堟湰澶<EFBFBD>€?娌℃湁閮ㄧ讲鏉冮檺

搴斿<EFBFBD>绛栫暐锛?

Plan A锛堟帹鑽愶級锛?

  • <EFBFBD>繁閮ㄧ讲涓€涓<EFBFBD>祴璇昍EDCap锛圖ocker锛?
  • 鐢ㄤ簬MVP Demo鍜屽唴閮ㄦ祴璇?
  • 绛夌<EFBFBD>绾﹀尰闄㈠悗鍐嶅<EFBFBD>鎺ヤ粬浠<EFBFBD>殑鐢熶骇REDCap

Plan B锛堝<E9949B>閫夛級锛?

  • 鍏堣烦杩嘡EDCap锛岀敤Mock鏁版嵁
  • 閲嶇偣灞曠ずWorkbench鍜屼紒寰<EFBFBD>€氱煡
  • REDCap闆嗘垚浣滀负"鎶€鏈<E282AC>彲琛屾€?璇存槑

Docker閮ㄧ讲锛?

# 浣跨敤瀹樻柟REDCap Docker闀滃儚锛堟祴璇曠幆澧冿級
docker-compose up -d redcap mysql

椋庨櫓3锛氫紒寰<EFBFBD><EFBFBD>鏍镐笉閫氳繃

椋庨櫓绛夌骇锛氫綆
褰卞搷锛氫紒涓氬井淇¤嚜寤哄簲鐢ㄥ<EFBFBD>鏍歌<EFBFBD>鎷?

搴斿<EFBFBD>绛栫暐锛?

Plan A锛?

  • 鎻愬墠鍑嗗<EFBFBD>瀹℃牳鏉愭枡锛堝叕鍙歌祫璐ㄣ€佷骇鍝佽<EFBFBD>鏄庯級
  • 搴旂敤绫诲瀷閫夋嫨"浼佷笟鍐呴儴宸ュ叿"锛堝<E9949B>鏍稿<E98F8D>鏉撅級

Plan B锛?

  • 濡傛灉瀹℃牳鎱<EFBFBD>紝鍏堢敤浼佸井Webhook娴嬭瘯鍙?
  • 鎴栦复鏃剁敤閽夐拤/椋炰功锛堟妧鏈<E5A6A7>柟妗堥€氱敤锛?

鍏抽敭锛氫紒寰<EFBFBD>笉鏄<EFBFBD>敮涓€閫夋嫨锛屾灦鏋勮<EFBFBD>璁″凡缁忚В鑰?

8.2 涓氬姟椋庨櫓

椋庨櫓4锛氬瓧娈垫槧灏勫<EFBFBD>鏉傛€?

椋庨櫓绛夌骇锛氫腑
褰卞搷锛氫笉鍚屽尰闄㈢殑REDCap瀛楁<EFBFBD>鍚嶅樊寮傚ぇ

搴斿<EFBFBD>绛栫暐锛?

  • MVP闃舵<EFBFBD>锛氭墜鍔ㄩ厤缃?涓<>叧閿<E58FA7>瓧娈垫槧灏?
  • Phase 2锛氬紑鍙慉I鑷<49>姩鏄犲皠宸ュ叿锛圢ER璇嗗埆锛?
  • Phase 3锛氬缓绔嬫爣鍑嗗瓧娈靛簱锛?00+甯哥敤瀛楁<E7809B>锛?

椋庨櫓5锛氬尰鐤楀悎瑙勬€у<EFBFBD>鏌?

椋庨櫓绛夌骇锛氶珮
褰卞搷锛欰I淇<EFBFBD>敼涓村簥鏁版嵁鐨勫悎瑙勬€ч棶棰?

搴斿<EFBFBD>绛栫暐锛?

  • 鉁?**鏍稿績璁捐<E79281>**锛氬奖瀛愮姸鎬佹満鍒讹紙AI鍙<49>缓璁<E7BC93>紝浜虹被纭<E8A2AB>潈锛?
  • 鉁?**瀹屾暣瀹¤<E780B9>**锛氭墍鏈夋搷浣滆<E6B5A3>褰曞埌audit_logs琛?
  • 鉁?绗﹀悎FDA 21 CFR Part 11锛氱數瀛愮<EFBFBD>鍚嶅拰瀹¤<EFBFBD>杩借釜
  • 鉁?**鍙<>洖婊?*锛氭墍鏈変慨鏀瑰彲杩芥函鍜屾挙閿€

8.3 鎬ц兘椋庨櫓

椋庨櫓6锛歊EDCap Webhook寤惰繜

椋庨櫓绛夌骇锛氫綆
褰卞搷锛歐ebhook鎺ㄩ€佸け璐ユ垨寤惰繜

搴斿<EFBFBD>绛栫暐锛?

  • 鉁?骞傜瓑鎬ц<E98EAC>璁★細閲嶅<E996B2>Webhook涓嶄細閲嶅<E996B2>澶勭悊
  • 鉁?寮傛<E5AFAE>澶勭悊锛歐ebhook绔嬪嵆杩斿洖200锛屽悗鍙板紓姝ユ墽琛?
  • 鉁?閲嶈瘯鏈哄埗锛歱g-boss鑷<73>姩閲嶈瘯3娆?
  • 鉁?姝讳俊闃熷垪锛氬け璐ヤ换鍔″崟鐙<E5B49F>瓨鍌<E793A8>紝浜哄伐浠嬪叆

椋庨櫓7锛氬ぇ閲忓苟鍙戣川鎺?

椋庨櫓绛夌骇锛氫腑
褰卞搷锛?00涓<30>」鐩<E3808D>悓鏃跺綍鍏ユ暟鎹?

搴斿<EFBFBD>绛栫暐锛?

  • 鉁?闃熷垪闄愭祦锛歱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寤鸿<EFBFBD>鍜孭rotocol璇佹嵁锛堢<EFBFBD>12椤碉級
  4. CRC纭<EFBFBD><EFBFBD>锛屾暟鎹<EFBFBD>嚜鍔ㄥ洖鍐橰EDCap

**鎶€鏈<E282AC>寚鏍?*锛?

  • Webhook鍝嶅簲鏃堕棿 < 100ms
  • AI璐ㄦ帶瀹屾垚鏃堕棿 < 30绉?
  • 浼佸井鎺ㄩ€佸欢杩?< 5绉?
  • AI鍑嗙鐜?> 80%

涓嬩竴姝ヨ<EFBFBD>鍔?

**绔嬪嵆鎵ц<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 鏇存柊鎬荤粨

鏋舵瀯淇<EFBFBD><EFBFBD>

*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`

鎶€鏈<EFBFBD>爤鏄庣

3. 鍓嶇<E98D93>鎶€鏈<E282AC>爤锛歍aro 4.x

  • 鉁?React Hooks璇<73>硶锛堝洟闃熺啛鎮夛級
  • 鉁?鍙<><E98D99>鐢?shared/components 閫昏緫
  • 鉁?涓€娆″紑鍙戯紝澶氱<E6BEB6>杩愯<E69DA9>锛堝皬绋嬪簭 + H5锛?
  • 鉁?TypeScript鏀<74>寔瀹屽杽

鏁版嵁搴撳<EFBFBD>寮?

4. Prisma Schema鏂板<E98F82>瀛楁<E7809B>

  • 鉁?IitProject.cachedRules锛氱紦瀛楶rotocol鍏抽敭瑙勫垯锛堟€ц兘浼樺寲锛?
  • 鉁?IitProject.lastSyncAt锛氳<EFBFBD>褰曚笂娆″悓姝ユ椂闂达紙澧為噺鎷夊彇锛?
  • 鉁?IitUserMapping.miniProgramOpenId锛氬皬绋嬪簭OpenID锛堝<EFBFBD><EFBFBD>敮鎸侊級
  • 鉁?IitUserMapping.sessionKey锛氬井淇<EFBFBD>ession_key锛堢櫥褰曡<EFBFBD>璇侊級

寮€鍙戜紭鍏堢骇璋冩暣

*5. MVP寮€鍙戣<E98D99>鍒掗噸鎺?

  • 馃敟 **Day 2浼樺厛绾?*锛歊EDCap API Adapter + SyncManager锛堟媺鍙栬兘鍔涳級
  • 馃敟 Day 3鏍稿績锛氬叏閲忔壂鎻忓姛鑳斤紙鍘嗗彶鏁版嵁鏀<EFBFBD>寔锛?
  • 馃敟 Day 4琛ュ厖锛歊EDCap EM + Webhook锛堟帹閫佽兘鍔涳紝浣滀负澧炲己锛?

璋冩暣鐞嗙敱锛?

  • API鎷夊彇鏇村彲鎺э紙涓嶄緷璧栧尰闄㈢綉缁滐級
  • 鑳借В鍐冲巻鍙叉暟鎹<EFBFBD>棶棰?
  • Webhook浣滀负澧炲己锛岃€岄潪鏍稿績渚濊禆

椋庨櫓搴斿<EFBFBD>

6. 缃戠粶杩為€氭€ч<E282AC>闄╋紙宸茶В鍐筹級

  • 鉂?V1.0椋庨櫓锛氬畬鍏ㄤ緷璧朩ebhook锛屽尰闄㈠唴缃戞棤娉曟帹閫?
  • 鉁?**V1.1淇<EFBFBD><EFBFBD>**锛氭贩鍚堝悓姝ユā寮忥紝杞<E7B49D><E69D9E>浣滀负鍏滃簳
  • 鉁?**鍙<>潬鎬?*锛?9.9%锛堜笉渚濊禆鍖婚櫌缃戠粶锛?

*7. 鍘嗗彶鏁版嵁椋庨櫓锛堝凡瑙e喅锛?

  • 鉂?V1.0椋庨櫓锛氬彧鐩戝惉鏂版暟鎹<EFBFBD>紝鍘嗗彶鏁版嵁鏃犳硶璐ㄦ帶
  • 鉁?**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浼樺厛绾э級