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%)
1868 lines
64 KiB
Markdown
1868 lines
64 KiB
Markdown
# IIT Manager Agent 瀹屾暣鎶€鏈<E282AC>紑鍙戞柟妗?(V1.1)
|
||
|
||
> **鏂囨。鐗堟湰锛?* V1.1锛堟灦鏋勮瘎瀹′慨姝g増锛?
|
||
> **鍒涘缓鏃ユ湡锛?* 2025-12-31
|
||
> **鏈€鍚庢洿鏂帮細** 2025-12-31
|
||
> **缁存姢鑰咃細** 鏋舵瀯鍥㈤槦
|
||
> **閫傜敤闃舵<E99783>锛?* MVP + Phase 1-4 瀹屾暣寮€鍙?
|
||
> **鏂囨。鐩<E38082>殑锛?* 鍩轰簬鐜版湁绯荤粺鏋舵瀯锛屾彁渚涘彲鐩存帴鎵ц<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>棶棰橈細
|
||
|
||
1. **鉁?鑷村懡椋庨櫓淇<E6AB93><E6B787>**锛氭贩鍚堝悓姝ユā寮忥紙Webhook + 杞<><E69D9E>锛夛紝瑙e喅鍖婚櫌鍐呯綉杩為€氭€ч棶棰?2. **鉁?鍔熻兘琛ュ厖**锛氬巻鍙叉暟鎹<E69A9F>叏閲忔壂鎻忥紝鏀<E7B49D>寔瀛橀噺鏁版嵁璐ㄦ帶
|
||
3. **鉁?鎶€鏈<E282AC>爤鏄庣‘**锛氬墠绔<E5A2A0>噰鐢═aro锛圧eact璇<74>硶锛夛紝鏀<E7B49D>寔灏忕▼搴?H5鍙岀<E98D99>
|
||
|
||
---
|
||
|
||
## 馃搵 鏂囨。瀵艰埅
|
||
|
||
1. [绯荤粺鏋舵瀯璁捐<EFBFBD>](#1-绯荤粺鏋舵瀯璁捐<E79281>)
|
||
2. [鐜版湁鑳藉姏澶嶇敤](#2-鐜版湁鑳藉姏澶嶇敤)
|
||
3. [鏍稿績鎶€鏈<E282AC>疄鐜癩(#3-鏍稿績鎶€鏈<E282AC>疄鐜?
|
||
4. [鏁版嵁搴撹<E690B4>璁<EFBFBD>(#4-鏁版嵁搴撹<E690B4>璁?
|
||
5. [API璁捐<EFBFBD>](#5-api璁捐<E79281>)
|
||
6. [閮ㄧ讲鏋舵瀯](#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. 瀹屽叏澶嶇敤骞冲彴鑳藉姏
|
||
|
||
```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
|
||
// 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閮ㄧ讲锛?
|
||
- 鏃犵姸鎬佸簲鐢<E7B0B2>紙涓嶄緷璧栨湰鍦版枃浠讹級
|
||
- 瀛樺偍鎶借薄灞傦紙Local 鈫?OSS 闆朵唬鐮佸垏鎹<E59E8F>級
|
||
- 寮傛<E5AFAE>浠诲姟锛堥伩鍏?0绉掕秴鏃讹級
|
||
- 鏁版嵁搴撹繛鎺ユ睜锛堥槻姝㈣繛鎺ヨ€楀敖锛?
|
||
---
|
||
|
||
## 2. 鐜版湁鑳藉姏澶嶇敤
|
||
|
||
### 2.1 Dify RAG 闆嗘垚锛堝凡鏈夊熀纭€锛?
|
||
#### 鐜版湁鑳藉姏锛圥KB妯″潡锛?
|
||
```typescript
|
||
// 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 浣跨敤鏂瑰紡
|
||
|
||
```typescript
|
||
// 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 璋冪敤锛堝凡鏈夊伐鍘傦級
|
||
|
||
```typescript
|
||
// 鉁?澶嶇敤鐜版湁 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锛?
|
||
```typescript
|
||
// 鉁?浣跨敤 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鎶借薄灞傦級
|
||
|
||
```typescript
|
||
// 鉁?浣跨敤瀛樺偍鎶借薄灞?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锛?
|
||
```typescript
|
||
// 鉁?浣跨敤骞冲彴鏃ュ織绯荤粺
|
||
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
|
||
<?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鎺ユ敹鍣?
|
||
```typescript
|
||
// 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>紙鏁版嵁鍥炲啓锛?
|
||
```typescript
|
||
// 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>锛?
|
||
```typescript
|
||
// 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 鍔熻兘琛ュ厖锛?
|
||
```typescript
|
||
// 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锛堟牳蹇冧笟鍔★級
|
||
|
||
```typescript
|
||
// 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>俊闆嗘垚
|
||
|
||
```typescript
|
||
// 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
|
||
// 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 鏁版嵁搴撹縼绉?
|
||
```bash
|
||
# 鐢熸垚杩佺Щ鏂囦欢
|
||
npx prisma migrate dev --name add_iit_schema
|
||
|
||
# 鐢熸垚Prisma Client
|
||
npx prisma generate
|
||
```
|
||
|
||
---
|
||
|
||
## 5. API 璁捐<E79281>
|
||
|
||
### 5.1 API 绔<>偣娓呭崟
|
||
|
||
#### 椤圭洰绠$悊
|
||
|
||
| 绔<>偣 | 鏂规硶 | 鍔熻兘 | 浼樺厛绾?|
|
||
|------|------|------|--------|
|
||
| `/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 | 閰嶇疆瀛楁<E7809B>鏄犲皠 | P0 |
|
||
| 馃敟 `/api/v1/iit/projects/:id/scan-all` | POST | **鍏ㄩ噺鎵<E599BA>弿锛圴1.1鏂板<EFBFBD>锛?* | P0 |
|
||
|
||
#### Webhook鎺ユ敹
|
||
|
||
| 绔<>偣 | 鏂规硶 | 鍔熻兘 | 浼樺厛绾?|
|
||
|------|------|------|--------|
|
||
| `/api/v1/iit/webhooks/redcap` | POST | REDCap Webhook | P0 |
|
||
|
||
#### 褰卞瓙鐘舵€佺<E282AC>鐞?
|
||
| 绔<>偣 | 鏂规硶 | 鍔熻兘 | 浼樺厛绾?|
|
||
|------|------|------|--------|
|
||
| `/api/v1/iit/pending-actions` | GET | 鑾峰彇寰呭<E5AFB0>鐞嗗缓璁<E7BC93>垪琛?| P0 |
|
||
| `/api/v1/iit/pending-actions/:id` | GET | 鑾峰彇寤鸿<E5AFA4>璇︽儏 | P0 |
|
||
| `/api/v1/iit/pending-actions/:id/approve` | POST | 纭<><E7BAAD>寤鸿<E5AFA4> | P0 |
|
||
| `/api/v1/iit/pending-actions/:id/reject` | POST | 鎷掔粷寤鸿<E5AFA4> | P1 |
|
||
|
||
#### 浠诲姟绠$悊
|
||
|
||
| 绔<>偣 | 鏂规硶 | 鍔熻兘 | 浼樺厛绾?|
|
||
|------|------|------|--------|
|
||
| `/api/v1/iit/tasks` | GET | 鑾峰彇浠诲姟鍒楄〃 | P1 |
|
||
| `/api/v1/iit/tasks/:id` | GET | 鑾峰彇浠诲姟璇︽儏 | P1 |
|
||
| `/api/v1/iit/tasks/:id/progress` | GET | 鑾峰彇浠诲姟杩涘害 | P1 |
|
||
|
||
### 5.2 API 瀹炵幇绀轰緥
|
||
|
||
```typescript
|
||
// 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 浼樺厛绾ц皟鏁达級
|
||
|
||
**鐩<>爣**锛氭墦閫?REDCap 鈫?Node.js锛堟媺鍙栵級 + 浼佸井鎺ㄩ€?
|
||
**馃敟 浼樺厛绾ц皟鏁寸悊鐢?*锛?- API鎷夊彇鏇村彲鎺э紙涓嶄緷璧栧尰闄㈢綉缁滐級
|
||
- 鑳借В鍐冲巻鍙叉暟鎹<E69A9F>棶棰?- Webhook浣滀负澧炲己锛岃€岄潪鏍稿績渚濊禆
|
||
|
||
**浠诲姟娓呭崟**锛?
|
||
1. **鏁版嵁搴撳垵濮嬪寲**锛圖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
|
||
|
||
2. **浼佷笟寰<E7AC9F>俊娉ㄥ唽**锛圖ay 1, 2灏忔椂锛? - [ ] 娉ㄥ唽浼佷笟寰<E7AC9F>俊寮€鍙戣€呰处鍙? - [ ] 鍒涘缓鑷<E7BC93>缓搴旂敤锛欼IT Manager Agent锛堟祴璇曪級
|
||
- [ ] 鑾峰彇鍑<E5BD87>瘉锛欳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绔<49>偣锛歚POST /api/v1/iit/projects/:id/scan-all`
|
||
- [ ] 娴嬭瘯锛?00鏉″巻鍙叉暟鎹<E69A9F>壂鎻忔垚鍔?
|
||
6. **REDCap EM寮€鍙?*锛圖ay 4, 8灏忔椂锛?*鈫?浣滀负澧炲己**
|
||
- [ ] 鍒涘缓EM鐩<4D>綍缁撴瀯
|
||
- [ ] 缂栧啓 `config.json`锛圗M閰嶇疆鏂囦欢锛? - [ ] 瀹炵幇 `IITManagerConnector.php`
|
||
- [ ] 瀹炵幇 `redcap_save_record` Hook
|
||
- [ ] 瀹炵幇Webhook鎺ㄩ€侊紙甯︾<E794AF>鍚嶏級
|
||
|
||
7. **Node.js Webhook鎺ユ敹鍣?*锛圖ay 4, 8灏忔椂锛? - [ ] 鍒涘缓 `webhookController.ts`
|
||
- [ ] 瀹炵幇绛惧悕楠岃瘉
|
||
- [ ] 瀹炵幇闃查噸鏀炬敾鍑? - [ ] 寮傛<E5AFAE>鎺ㄩ€佸埌璐ㄦ帶闃熷垪
|
||
- [ ] Webhook杩為€氭€ф祴璇曪紙鑷<E7B499>€傚簲鍒囨崲锛?
|
||
8. **浼佸井閫傞厤鍣?*锛圖ay 5, 8灏忔椂锛? - [ ] 鍒涘缓 `WeChatAdapter.ts`
|
||
- [ ] 瀹炵幇Access Token缂撳瓨
|
||
- [ ] 瀹炵幇鍗$墖娑堟伅鎺ㄩ€? - [ ] 娴嬭瘯锛氬彂閫佽川鎺ч<E98EBA>璀﹀崱鐗?
|
||
**楠屾敹鏍囧噯锛圴1.1锛?*锛?- 鉁?**鏍稿績鑳藉姏**锛氳疆璇㈣兘鎷夊彇REDCap鏂版暟鎹<E69A9F>紙寤惰繜<10鍒嗛挓锛?- 鉁?**澧炲己鑳藉姏**锛歐ebhook鑳芥帹閫侊紙濡傛灉缃戠粶閫氾級锛堝欢杩?2绉掞級
|
||
- 鉁?**鍘嗗彶鏁版嵁**锛氬叏閲忔壂鎻忚兘澶勭悊瀛橀噺鏁版嵁
|
||
- 鉁?**浼佸井閫氱煡**锛氳兘鏀跺埌璐ㄦ帶棰勮<E6A3B0>鍗$墖
|
||
- 鉁?**鑷<>€傚簲**锛氱郴缁熻嚜鍔ㄩ€夋嫨鏈€浣冲悓姝ユā寮?
|
||
#### Week 2: AI 鏅鸿兘璐ㄦ帶
|
||
|
||
**鐩<>爣**锛氬疄鐜拌川鎺<E5B79D>gent鐨勫畬鏁撮棴鐜?
|
||
**浠诲姟娓呭崟**锛?
|
||
6. **Protocol鏈嶅姟**锛圖ay 6-7, 16灏忔椂锛? - [ ] 鍒涘缓 `ProtocolService.ts`
|
||
- [ ] 瀹炵幇Protocol PDF涓婁紶鍒癘SS
|
||
- [ ] 璋冪敤Dify鍒涘缓Dataset
|
||
- [ ] 瀹炵幇 `checkProtocolCompliance()` 鏂规硶
|
||
- [ ] 娴嬭瘯锛氫笂浼燩rotocol锛岃兘妫€绱㈠埌鍐呭<E98D90>
|
||
|
||
7. **璐ㄦ帶Agent**锛圖ay 8-9, 16灏忔椂锛? - [ ] 鍒涘缓 `DataQualityAgent.ts`
|
||
- [ ] 瀹炵幇 `checkRecord()` 鏂规硶
|
||
- [ ] 璋冪敤Protocol鏈嶅姟妫€鏌ュ悎瑙勬€? - [ ] 鍒涘缓褰卞瓙寤鸿<E5AFA4>锛坧ending_actions琛<73>級
|
||
- [ ] 鍙戦€佷紒寰<E7B492>€氱煡锛堜弗閲嶈繚鑳岋級
|
||
- [ ] 娴嬭瘯锛氳緭鍏ヨ繚鑳屾暟鎹<E69A9F>紝鐢熸垚姝g‘寤鸿<E5AFA4>
|
||
|
||
8. **PC Workbench鍓嶇<E98D93>楠ㄦ灦**锛圖ay 10-12, 24灏忔椂锛? - [ ] 鍒涘缓鍓嶇<E98D93>璺<EFBFBD>敱锛歚/iit/workbench`
|
||
- [ ] 浠诲姟鍒楄〃椤碉紙鏄剧ず鎵€鏈塒ROPOSED寤鸿<E5AFA4>锛? - [ ] 璇︽儏瀵规瘮椤碉細
|
||
- 宸︿晶锛氬綋鍓嶆暟鎹? - 鍙充晶锛欰I寤鸿<E5AFA4> + 璇佹嵁鐗囨<E99097>
|
||
- [ ] 鎿嶄綔鎸夐挳锛歔鎷掔粷] [纭<><E7BAAD>]
|
||
- [ ] 娴嬭瘯锛氳兘姝g‘鏄剧ず鍜屾搷浣?
|
||
9. **褰卞瓙鐘舵€佹祦杞?*锛圖ay 13, 8灏忔椂锛? - [ ] 瀹炵幇 `PendingActionService.approveAction()`
|
||
- [ ] 璋冪敤REDCap API鍥炲啓鏁版嵁
|
||
- [ ] 鏇存柊鐘舵€侊細PROPOSED 鈫?APPROVED 鈫?EXECUTED
|
||
- [ ] 璁板綍瀹¤<E780B9>鏃ュ織
|
||
- [ ] 娴嬭瘯锛氬畬鏁撮棴鐜<E6A3B4>紙鍙戠幇鈫掔‘璁も啋鍥炲啓锛?
|
||
10. **绔<>埌绔<E59F8C>祴璇?*锛圖ay 14, 8灏忔椂锛? - [ ] 瀹屾暣娴佺▼娴嬭瘯
|
||
- [ ] 鎬ц兘娴嬭瘯锛?00鏉¤<E98F89>褰曪級
|
||
- [ ] 閿欒<E996BF>澶勭悊娴嬭瘯
|
||
- [ ] 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锛?
|
||
**鎶€鏈<E282AC>爤浼樺娍**锛? - 鉁?React Hooks璇<73>硶锛堝洟闃熺啛鎮夛級
|
||
- 鉁?鍙<><E98D99>鐢ㄥ墠绔<E5A2A0>唬鐮佸拰閫昏緫
|
||
- 鉁?涓€娆″紑鍙戯紝澶氱<E6BEB6>杩愯<E69DA9>锛堝皬绋嬪簭 + H5锛? - 鉁?TypeScript鏀<74>寔瀹屽杽
|
||
|
||
12. **浠诲姟椹卞姩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妫€娴嬪噯纭<E599AF>巼<60%锛屽亣闃虫€ц繃澶?
|
||
**搴斿<E690B4>绛栫暐**锛?
|
||
**Plan A锛堜紭鍏堬級**锛?- 涓ユ牸闄愬埗MVP妫€鏌ヨ寖鍥达紙鍙<E7B499><E98D99>鏌?绫荤畝鍗曡<E98D97>鍒欙級
|
||
- 骞撮緞銆佹€у埆銆佸繀濉<E7B980>」 = 瑙勫垯鏄庣‘锛屽噯纭<E599AF>巼楂?- 鍏堥獙璇佹灦鏋勶紝鍚庝紭鍖栧噯纭<E599AF>€?
|
||
**Plan B锛堝<E9949B>閫夛級**锛?- 濡傛灉Dify鏁堟灉涓嶄匠锛屼复鏃剁敤纭<E695A4>紪鐮佽<E990AE>鍒?- MVP閲嶇偣楠岃瘉"褰卞瓙鐘舵€佹満鍒?锛岃€岄潪AI鑳藉姏
|
||
- 瑙勫垯寮曟搸鍦≒hase 2鍐嶄紭鍖?
|
||
**楠岃瘉鏂规硶**锛?- 鐢?0涓<30>湡瀹炵梾渚嬫祴璇?- 鍑嗙‘鐜囩洰鏍囷細>85%
|
||
- 鍋囬槼鎬х巼锛?15%
|
||
|
||
#### 椋庨櫓2锛歊EDCap閮ㄧ讲鍥伴毦
|
||
|
||
**椋庨櫓绛夌骇**锛氫腑
|
||
**褰卞搷**锛氬尰闄㈢殑REDCap鐗堟湰澶<E6B9B0>€?娌℃湁閮ㄧ讲鏉冮檺
|
||
|
||
**搴斿<E690B4>绛栫暐**锛?
|
||
**Plan A锛堟帹鑽愶級**锛?- 鑷<>繁閮ㄧ讲涓€涓<E282AC>祴璇昍EDCap锛圖ocker锛?- 鐢ㄤ簬MVP Demo鍜屽唴閮ㄦ祴璇?- 绛夌<E7BB9B>绾﹀尰闄㈠悗鍐嶅<E98D90>鎺ヤ粬浠<E7B2AC>殑鐢熶骇REDCap
|
||
|
||
**Plan B锛堝<E9949B>閫夛級**锛?- 鍏堣烦杩嘡EDCap锛岀敤Mock鏁版嵁
|
||
- 閲嶇偣灞曠ずWorkbench鍜屼紒寰<E7B492>€氱煡
|
||
- 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`锛氳<EFBFBD>褰曚笂娆″悓姝ユ椂闂达紙澧為噺鎷夊彇锛?- 鉁?`IitUserMapping.miniProgramOpenId`锛氬皬绋嬪簭OpenID锛堝<EFBFBD>绔<EFBFBD>敮鎸侊級
|
||
- 鉁?`IitUserMapping.sessionKey`锛氬井淇<EFBFBD>ession_key锛堢櫥褰曡<EFBFBD>璇侊級
|
||
|
||
### 寮€鍙戜紭鍏堢骇璋冩暣
|
||
|
||
**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浼樺厛绾э級
|
||
|
||
|