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%)
1090 lines
30 KiB
Markdown
1090 lines
30 KiB
Markdown
# REDCap蟇ケ謗・謚譛ッ譁ケ譯井ク主ョ樊命謖<E591BD>漉
|
||
|
||
**迚域悽<E59F9F>?* V1.0
|
||
**蛻帛サコ譌・譛滂シ?* 2026-01-02
|
||
**騾ら畑髦カ谿オ<E8B0BF>?* IIT Manager Agent MVP - Day 2
|
||
**譁<>。」諤ァ雍ィ<E99B8D>?* 譬ク蠢<EFBDB8>橿譛ッ蝓コ遏?
|
||
**驥崎ヲ∫ィ句コヲ<EFBDBA>?* 箝絶ュ絶ュ絶ュ絶ュ?
|
||
|
||
---
|
||
|
||
## <20>搭 譁<>。」逶ョ譬<EFBDAE>
|
||
|
||
譛ャ譁<EFBFBD>。」譏ッIIT Manager Agent荳山EDCap髮<70><E9ABAE>逧?*譚<>ィ∵橿譛ッ謖<EFBDAF><E8AC96>?*<2A>梧<EFBFBD>遑ョ<E98191><EFBDAE>
|
||
1. 笨?REDCap蟇ケ謗・譁ケ蠑冗噪謚譛ッ騾牙梛
|
||
2. 笨?Data Entry Trigger (DET) 逧<>ェ瑚ッ∽ク朱<EFBDB8>鄂ョ
|
||
3. 笨?REST API逧<49>スソ逕ィ譁ケ豕?
|
||
4. 笨?螳樊慮雍ィ謗ァ逧<EFBDA7>ョ梧紛譫カ譫?
|
||
5. 笨?Day 2逧<32>シ蜿大ョ樊命豁・鬪?
|
||
|
||
---
|
||
|
||
## <20>識 譬ク蠢<EFBDB8>サ楢ョコ<EFBDAE><EFBDBA>xecutive Summary<72>?
|
||
|
||
### 笨?**謚譛ッ騾牙梛<E78999>咼ET<45>亥ョ樊慮隗ヲ蜿托シ<E68998> + REST API<50>域焚謐ョ隸サ蜀呻シ<E591BB>**
|
||
|
||
**荳埼㊦逕ィ<E98095><EFBDA8>** External Module (EM)
|
||
**蜴溷屏<E6BAB7>?* EM髴隕 ̄HP蠑蜿代∽セオ蜈・REDCap貅千<E8B285>√∫サエ謚、謌先悽鬮倥∽ク埼ょ粋螟夜Κ邉サ扈滄寔謌<E5AF94>
|
||
|
||
**驥<>畑譁ケ譯茨シ?*
|
||
|
||
| 扈<>サカ | 謚譛?| 逕ィ騾?| 螳樊慮諤?|
|
||
|------|-----|------|--------|
|
||
| **Data Entry Trigger** | REDCap蜴溽函蜉溯<E89C89> | 謨ー謐ョ菫晏ュ俶慮螳樊慮騾夂衍 | 0遘貞サカ霑?|
|
||
| **REST API (Export)** | HTTP + JSON | 諡牙叙謨ー謐ョ縲∝<E7B8B2>謨ー謐ョ | 謖蛾怙隹<E68099>畑 |
|
||
| **REST API (Import)** | HTTP + JSON | 蝗槫<E89D97>雍ィ謗ァ諢剰ァ<E589B0>シ<EFBFBD>hase 2<>?| 謖蛾怙隹<E68099>畑 |
|
||
| **霓ョ隸「譛コ蛻カ** | pg-boss螳壽慮莉サ蜉。 | 陦・蜈<EFBDA5>ET驕玲シ乗焚謐ョ | 30蛻<30>帖 |
|
||
|
||
---
|
||
|
||
## <20>剥 謚譛ッ隹<EFBDAF><E99AB9>皮サ捺<EFBDBB>?
|
||
|
||
### 隹<><E99AB9><EFBFBD>1<EFBFBD>啌EDCap謠蝉セ帷噪蟇ケ謗・譁ケ蠑?
|
||
|
||
**隹<><E99AB9>疲擂貅撰シ?*
|
||
- REDCap 15.8.0貅千<EFBFBD><EFBFBD>
|
||
- External Module Framework螳俶婿譁<E5A9BF>。」
|
||
- REDCap莠梧ャ。蠑蜿第キア蠎ヲ謖<EFBDA6><E8AC96>?
|
||
|
||
**蜿醍鴫逧<E9B4AB>ッケ謗・譁ケ蠑擾シ<E693BE>**
|
||
|
||
| 譁ケ蠑<EFBDB9> | 謚譛ッ譬<EFBDAF> | 騾ら畑蝨コ譎ッ | 謌台サャ譏ッ蜷ヲ騾ら畑 |
|
||
|------|--------|---------|-------------|
|
||
| **External Module** | PHP + Hook | REDCap蜀<70>Κ蜉溯<E89C89>謇ゥ螻輔ゞI螳壼宛 | 笶?荳埼ら畑 |
|
||
| **REST API** | HTTP + JSON | 螟夜Κ邉サ扈滄寔謌舌∵焚謐ョ蜷梧ュ?| 笨?**騾ら畑** |
|
||
| **Data Entry Trigger** | Webhook | 螳樊慮騾夂衍螟夜Κ邉サ扈<EFBDBB> | 笨?**騾ら畑** |
|
||
|
||
### 隹<><E99AB9><EFBFBD>2<EFBFBD>咼ata Entry Trigger (DET) 鬪瑚ッ<E7919A>
|
||
|
||
**貅千<E8B285>∽ス咲スョ<EFBDBD>?*
|
||
```
|
||
/var/www/html/redcap/redcap_v15.8.0/Classes/DataEntry.php
|
||
/var/www/html/redcap/redcap_v15.8.0/ProjectSetup/index.php
|
||
/var/www/html/redcap/redcap_v15.8.0/ControlCenter/modules_settings.php
|
||
```
|
||
|
||
**蜈ウ髞ョ莉」遐<EFBDA3>シ?*
|
||
```php
|
||
// DataEntry.php 譬ク蠢<EFBDB8>ョ樒鴫
|
||
global $data_entry_trigger_url, $data_entry_trigger_enabled;
|
||
if (!$data_entry_trigger_enabled || $data_entry_trigger_url == '') {
|
||
return;
|
||
}
|
||
// 蜿鷹?TTP POST蛻ー驟咲スョ逧ФRL
|
||
$full_url = $pre_url . $data_entry_trigger_url;
|
||
```
|
||
|
||
**鬪瑚ッ∫サ楢ョコ<EFBDAE>?* 笨?DET譏ッREDCap蜴溽函蜉溯<E89C89><E6BAAF>檎悄螳槫ュ伜惠荳泌ケソ豕帑スソ逕ィ
|
||
|
||
---
|
||
|
||
## <20>女<EFBFBD>?螳梧紛譫カ譫<EFBDB6>ョセ隶。
|
||
|
||
### 譫カ譫<EFBDB6><E8ADAB>?
|
||
|
||
```
|
||
笏娯楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
笏? 1. CRC 蝨?REDCap 荳ュ菫晏ュ倩ョー蠖? 笏?
|
||
笏? - 蠖募<E8A096>謔」閠<EFBDA3>焚謐? 笏?
|
||
笏? - 轤ケ蜃サ"Save"謖蛾聴 笏?
|
||
笏披楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏ャ笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
竊?遶句叉隗ヲ蜿托シ<E68998>EDCap DET<45>?遘貞サカ霑滂シ<E6BB82>
|
||
笏娯楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
笏? 2. REDCap Data Entry Trigger (DET) 笏?
|
||
笏? POST 竊?https://iit.xunzhengyixue.com/ 笏?
|
||
笏? api/v1/iit/webhooks/redcap 笏?
|
||
笏? Payload: 笏?
|
||
笏? - project_id: 16 笏?
|
||
笏? - record: 101 笏?
|
||
笏? - instrument: demographics 笏?
|
||
笏? - redcap_event_name: baseline_arm_1 笏?
|
||
笏披楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏ャ笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
竊?1遘貞<E98198>
|
||
笏娯楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
笏? 3. Node.js Webhook謗・謾カ蝎? 笏?
|
||
笏? - 鬪瑚ッ∬ッキ豎よ擂貅撰シ亥庄騾会シ<E4BC9A> 笏?
|
||
笏? - 遶句叉霑泌屓200 OK<4F>?100ms<6D>御ク埼仆蝪朿EDCap<61>? 笏?
|
||
笏? - 蠑よュ・隹<EFBDA5>畑雍ィ謗ァ豬∫ィ具シ<E585B7>etImmediate<74>? 笏?
|
||
笏披楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏ャ笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
竊?蠑よュ・螟<EFBDA5>炊
|
||
笏娯楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
笏? 4. REDCap REST API - exportRecords 笏?
|
||
笏? - 菴ソ逕ィAPI Token隶、隸<EFBDA4> 笏?
|
||
笏? - 諡牙叙謖<E58F99>ョ嗷ecord_id逧<64>ョ梧紛謨ー謐? 笏?
|
||
笏? - 蛹<>性謇譛牙ュ玲ョオ蛟シ縲∝<E7B8B2>謨ー謐ョ 笏?
|
||
笏披楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏ャ笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
竊?
|
||
笏娯楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
笏? 5. 謗ィ騾∝芦雍ィ謗ァ髦溷<E9ABA6> 笏?
|
||
笏? - jobQueue.send('iit:quality-check', record) 笏?
|
||
笏? - 蠑よュ・螟<EFBDA5>炊<EFBFBD>御ク榊スア蜩榊ョ樊慮蜩榊コ<E6A68A> 笏?
|
||
笏披楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏ャ笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
竊?逕アQualityCheckAgent螟<74>炊<EFBFBD><E7828A>ay 6<>?
|
||
笏娯楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
笏? 6. AI雍ィ謗ァ蛻<EFBDA7>梵 笏?
|
||
笏? - 隗<><E99A97>蠑墓梼鬪瑚ッ<E7919A> 笏?
|
||
笏? - AI謗ィ逅<EFBDA8>」豬句シょク? 笏?
|
||
笏? - 逕滓<E98095>雍ィ謗ァ謚・蜻<EFBDA5> 笏?
|
||
笏披楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏ャ笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
竊?3-5遘貞<E98198>
|
||
笏娯楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
笏? 7. 螟壽ク<E5A3BD>驕灘渚鬥? 笏?
|
||
笏? A. 莨∽ク壼セョ菫。謗ィ騾∫サ僂RC<52>亥ョ樊慮騾夂衍<E5A482>? 笏?
|
||
笏? B. REDCap API importQueries<65>亥<EFBFBD>蝗櫁エィ逍托シ<E68998> 笏?
|
||
笏? C. IIT Manager蜑咲ォッ譏セ遉コ<E98189>亥セ<E4BAA5>萱莠矩。ケ<EFBDA1><EFBDB9> 笏?
|
||
笏披楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
|
||
笏娯楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
笏? 陦・蜈<EFBDA5>シ壼ョ壽慮霓ョ隸「譛コ蛻カ<E89BBB>域ッ?0蛻<30>帖<EFBFBD>? 笏?
|
||
笏? - 髦イ豁「DET驕玲シ乗焚謐ョ 笏?
|
||
笏? - 菴ソ逕ィdateRangeBegin蠅樣㍼諡牙叙 笏?
|
||
笏? - 謗ィ騾∝芦逶ク蜷檎噪雍ィ謗ァ髦溷<E9ABA6>? 笏?
|
||
笏披楳笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏笏?
|
||
```
|
||
|
||
### 螳樊慮諤ァ蟇ケ豈?
|
||
|
||
| 譁ケ譯<EFBDB9> | 蟒カ霑滓慮髣エ | 謌台サャ逧<EFBDAC>画叫 |
|
||
|------|---------|-----------|
|
||
| **郤ッ霓ョ隸「<E99AB8>域ッ?蛻<>帖<EFBFBD>?* | 譛鬮?蛻<>帖 | 笶?荳肴サ。雜ウ螳樊慮諤ァ隕∵ア?|
|
||
| **郤ッ霓ョ隸「<E99AB8>域ッ?蛻<>帖<EFBFBD>?* | 譛鬮?蛻<>帖 | 笞<><E7AC9E><EFBFBD> 襍<>コ先オェ雍ケ縲∝サカ霑滉サ埼ォ?|
|
||
| **DET + API** | 0遘定ァヲ蜿?+ 3-5遘貞、<E8B29E><EFBDA4>?| 笨?**譛莨俶婿譯?* |
|
||
| **DET + 霓ョ隸「陦・蜈<EFBDA5>** | 螳樊慮 + 蜿ッ髱<EFBDAF> | 笨?**譛扈域楔譫?* |
|
||
|
||
**扈楢ョコ<EFBDAE>?* CRC轤ケ蜃サ"菫晏ュ<E6998F>" 竊?5遘貞<E98198>謾カ蛻ー莨∽ク壼セョ菫。騾夂衍 笨?
|
||
|
||
---
|
||
|
||
## <20>逃 謚譛ッ螳樒鴫隸ヲ隗?
|
||
|
||
### 1. Data Entry Trigger (DET) 驟咲スョ
|
||
|
||
#### 豁・鬪、1<EFBDA4>壼惠Control Center蜷ッ逕ィDET
|
||
|
||
```
|
||
1. 逋サ蠖紐EDCap<61>喇ttp://localhost:8080/
|
||
2. 菴ソ逕ィ邂。逅<EFBDA1>遭雍ヲ謌キ<E8AC8C><EFBDB7>dmin / Admin123!<21>?
|
||
3. 霑帛<E99C91><E5B89B>咾ontrol Center 竊?"Additional Customizations"
|
||
4. 謇セ蛻ー<E89BBB>?Data Entry Trigger" 騾蛾。ケ
|
||
5. 隶セ鄂ョ荳コ<E88DB3><EFBDBA>"Enabled"
|
||
6. 菫晏ュ倬<EFBDAD>鄂ョ
|
||
```
|
||
|
||
**謨ー謐ョ蠎灘ュ玲ョオ<EFBDAE><EFBDB5>** `redcap_config.data_entry_trigger_enabled = 1`
|
||
|
||
#### 豁・鬪、2<EFBDA4>壼惠鬘ケ逶ョ荳ュ驟咲スョWebhook URL
|
||
|
||
```
|
||
1. 霑帛<E99C91>豬玖ッ暮。ケ逶ョ<E980B6>嗾est0102 (PID 16)
|
||
2. 蟾ヲ萓ァ闖懷黒 竊?Project Setup
|
||
3. 謇セ蛻ー<E89BBB>?Additional Customizations"
|
||
4. 螻募シ<EFBDBC>?Data Entry Trigger URL"
|
||
5. 蝪ォ蜈・Webhook URL<52>?
|
||
- 蠑蜿醍識蠅<E8AD98>シ喇ttp://localhost:3001/api/v1/iit/webhooks/redcap
|
||
- 豬玖ッ慕識蠅<E8AD98>シ喇ttps://backend-dev.xunzhengyixue.com/api/v1/iit/webhooks/redcap
|
||
- 逕滉コァ邇ッ蠅<EFBDAF>シ喇ttps://iit.xunzhengyixue.com/api/v1/iit/webhooks/redcap
|
||
6. 菫晏ュ<E6998F>
|
||
```
|
||
|
||
**謨ー謐ョ蠎灘ュ玲ョオ<EFBDAE><EFBDB5>** `redcap_projects.data_entry_trigger_url`
|
||
|
||
#### 豁・鬪、3<EFBDA4>夐ェ瑚ッ.ET驟咲スョ
|
||
|
||
**菴ソ逕ィRequestBin/ngrok鬪瑚ッ<E7919A>シ?*
|
||
|
||
```bash
|
||
# 菴ソ逕ィngrok蛻帛サコ荳エ譌カ蜈ャ鄂繕RL<52>亥シ蜿第オ玖ッ慕畑<E68595>?
|
||
ngrok http 3001
|
||
|
||
# 蟆<>grok URL驟咲スョ蛻ーREDCap DET
|
||
# 萓句ヲゑシ喇ttps://1234-abc-def.ngrok.io/api/v1/iit/webhooks/redcap
|
||
|
||
# 蝨ィREDCap荳ュ菫晏ュ倅ク譚。隶ー蠖?
|
||
# 譽譟・ngrok謗ァ蛻カ蜿ー譏ッ蜷ヲ謾カ蛻ーPOST隸キ豎<EFBDB7>
|
||
```
|
||
|
||
#### DET逧ПOST Payload譬シ蠑<EFBDBC>
|
||
|
||
**REDCap蜿鷹∫噪隸キ豎ゑシ?*
|
||
|
||
```http
|
||
POST /api/v1/iit/webhooks/redcap HTTP/1.1
|
||
Host: iit.xunzhengyixue.com
|
||
Content-Type: application/x-www-form-urlencoded
|
||
User-Agent: REDCap/15.8.0
|
||
|
||
project_id=16
|
||
&record=101
|
||
&instrument=demographics
|
||
&redcap_event_name=baseline_arm_1
|
||
&demographics_complete=2
|
||
&redcap_url=http://localhost:8080/
|
||
```
|
||
|
||
**蟄玲ョオ隸エ譏趣シ?*
|
||
|
||
| 蟄玲ョオ | 邀サ蝙<EFBDBB> | 隸エ譏<EFBDB4> | 遉コ萓<EFBDBA> |
|
||
|------|-----|------|------|
|
||
| `project_id` | string | REDCap鬘ケ逶ョID | "16" |
|
||
| `record` | string | 隶ー蠖肘D | "101" |
|
||
| `instrument` | string | 陦ィ蜊募錐遘ー | "demographics" |
|
||
| `redcap_event_name` | string | 莠倶サカ蜷咲ァー<EFBDA7>育コオ蜷醍<E89CB7>皮ゥカ<EFBDA9><EFBDB6> | "baseline_arm_1" |
|
||
| `[instrument]_complete` | string | 陦ィ蜊募ョ梧<EFBDAE>迥カ諤<EFBDB6>シ<EFBFBD>0/1/2<>?| "2" |
|
||
| `redcap_url` | string | REDCap螳樔セ偽RL | "http://localhost:8080/" |
|
||
|
||
---
|
||
|
||
### 2. REDCap REST API 菴ソ逕ィ
|
||
|
||
#### API遶ッ轤ケ
|
||
|
||
```
|
||
http://localhost:8080/api/
|
||
```
|
||
|
||
#### API隶、隸<EFBDA4>シ啜oken
|
||
|
||
**逕滓<E98095>豁・鬪、<E9ACAA>?*
|
||
```
|
||
1. 逋サ蠖紐EDCap
|
||
2. 霑帛<E99C91>鬘ケ逶ョ<E980B6>嗾est0102 (PID 16)
|
||
3. 蟾ヲ萓ァ闖懷黒 竊?"API"
|
||
4. 轤ケ蜃サ "Generate API Token"
|
||
5. 螟榊宛Token<65>?2菴榊ュ礼ャヲ荳イ<E88DB3>?
|
||
6. 菫晏ュ伜芦邇ッ蠅<EFBDAF>序驥擾シ<E693BE>
|
||
REDCAP_API_TOKEN_TEST=YOUR_TOKEN_HERE
|
||
```
|
||
|
||
**Token蟄伜お<E4BC9C>?*
|
||
- 笨?邇ッ蠅<EFBDAF>序驥擾シ域耳闕撰シ<E692B0>
|
||
- 笨?謨ー謐ョ蠎灘刈蟇<E58888>ュ玲ョオ<EFBDAE><EFBDB5>itProject.redcapApiToken<65>?
|
||
- 笶?荳崎ヲ∵署莠、蛻ーGit
|
||
|
||
#### API譁ケ豕<EFBDB9>1<EFBFBD>壼ッシ蜃コ隶ー蠖?(exportRecords)
|
||
|
||
**逕ィ騾費シ<E8B2BB>** 諡牙叙謨ー謐ョ<E8AC90>域髪謖∝「樣㍼蜷梧ュ・<EFBDAD><EFBDA5>
|
||
|
||
**隸キ豎ら、コ萓具シ<E585B7>url<72>会シ<E4BC9A>**
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8080/api/ \
|
||
-F "token=YOUR_API_TOKEN" \
|
||
-F "content=record" \
|
||
-F "format=json" \
|
||
-F "type=flat" \
|
||
-F "records[0]=101" \
|
||
-F "records[1]=102" \
|
||
-F "dateRangeBegin=2026-01-01 00:00:00"
|
||
```
|
||
|
||
**蜿よ焚隸エ譏趣シ?*
|
||
|
||
| 蜿よ焚 | 蠢<>。ォ | 隸エ譏<EFBDB4> | 遉コ萓<EFBDBA> |
|
||
|------|-----|------|------|
|
||
| `token` | 笨?| API Token | "ABC123..." |
|
||
| `content` | 笨?| 蝗コ螳壼?| "record" |
|
||
| `format` | 笨?| 霑泌屓譬シ蠑<EFBDBC> | "json" |
|
||
| `type` | 笨?| 謨ー謐ョ扈捺桷 | "flat" |
|
||
| `records` | 笶?| 謖<>ョ夊ョー蠖肘D | ["101", "102"] |
|
||
| `fields` | 笶?| 謖<>ョ壼ュ玲ョオ | ["age", "gender"] |
|
||
| `dateRangeBegin` | 笶?| 譌カ髣エ霑<EFBDB4>サ、<EFBDBB>亥「樣㍼蜷梧ュ・蜈ウ髞ョ<E9AB9E><EFBDAE> | "2026-01-01 00:00:00" |
|
||
| `dateRangeEnd` | 笶?| 扈捺據譌カ髣エ | "2026-01-31 23:59:59" |
|
||
|
||
**蜩榊コ皮、コ萓具シ?*
|
||
|
||
```json
|
||
[
|
||
{
|
||
"record_id": "101",
|
||
"redcap_event_name": "baseline_arm_1",
|
||
"first_name": "蠑?,
|
||
"last_name": "荳?,
|
||
"age": "30",
|
||
"gender": "1",
|
||
"demographics_complete": "2"
|
||
}
|
||
]
|
||
```
|
||
|
||
#### API譁ケ豕<EFBDB9>2<EFBFBD>壼ッシ蜃コ蜈<EFBDBA>焚謐ョ (exportMetadata)
|
||
|
||
**逕ィ騾費シ<E8B2BB>** 闔キ蜿門ュ玲ョオ螳壻ケ峨∬。ィ蜊慕サ捺<EFBDBB>?
|
||
|
||
**隸キ豎ら、コ萓具シ?*
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8080/api/ \
|
||
-F "token=YOUR_API_TOKEN" \
|
||
-F "content=metadata" \
|
||
-F "format=json"
|
||
```
|
||
|
||
**蜩榊コ皮、コ萓具シ?*
|
||
|
||
```json
|
||
[
|
||
{
|
||
"field_name": "age",
|
||
"form_name": "demographics",
|
||
"section_header": "",
|
||
"field_type": "text",
|
||
"field_label": "Age",
|
||
"select_choices_or_calculations": "",
|
||
"field_note": "",
|
||
"text_validation_type_or_show_slider_number": "integer",
|
||
"text_validation_min": "0",
|
||
"text_validation_max": "120",
|
||
"identifier": "",
|
||
"branching_logic": "",
|
||
"required_field": "y",
|
||
"custom_alignment": "",
|
||
"question_number": "",
|
||
"matrix_group_name": "",
|
||
"matrix_ranking": "",
|
||
"field_annotation": ""
|
||
}
|
||
]
|
||
```
|
||
|
||
#### API譁ケ豕<EFBDB9>3<EFBFBD>壼ッシ蜈・隶ー蠖?(importRecords) - Phase 2
|
||
|
||
**逕ィ騾費シ<E8B2BB>** 蝗槫<E89D97>謨ー謐ョ<E8AC90>亥ヲりエィ謗ァ諢剰ァ√、I蟒コ隶ョ<E99AB6>?
|
||
|
||
**隸キ豎ら、コ萓具シ?*
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8080/api/ \
|
||
-F "token=YOUR_API_TOKEN" \
|
||
-F "content=record" \
|
||
-F "format=json" \
|
||
-F "type=flat" \
|
||
-F "overwriteBehavior=normal" \
|
||
-F 'data=[{"record_id":"101","ai_review":"謨ー謐ョ蠑ょクク"}]'
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Node.js螳樒鴫莉」遐<EFBDA3>
|
||
|
||
#### 3.1 RedcapAdapter<65><72>PI騾る<E9A8BE>蝎ィ<E89D8E><EFBDA8>
|
||
|
||
**譁<>サカ<EFBDBB>?* `backend/src/modules/iit-manager/adapters/RedcapAdapter.ts`
|
||
|
||
```typescript
|
||
import axios from 'axios';
|
||
import FormData from 'form-data';
|
||
import { logger } from '@/common/logging';
|
||
|
||
export interface RedcapExportOptions {
|
||
records?: string[];
|
||
fields?: string[];
|
||
dateRangeBegin?: Date;
|
||
dateRangeEnd?: Date;
|
||
}
|
||
|
||
export class RedcapAdapter {
|
||
private baseUrl: string;
|
||
private apiToken: string;
|
||
private timeout: number;
|
||
|
||
constructor(baseUrl: string, apiToken: string, timeout = 30000) {
|
||
this.baseUrl = baseUrl.replace(/\/$/, ''); // 遘サ髯、譛ォ蟆セ譁懈擒
|
||
this.apiToken = apiToken;
|
||
this.timeout = timeout;
|
||
}
|
||
|
||
/**
|
||
* 蟇シ蜃コ隶ー蠖包シ域髪謖∝「樣㍼蜷梧ュ・<EFBDAD><EFBDA5>
|
||
*/
|
||
async exportRecords(options: RedcapExportOptions = {}): Promise<any[]> {
|
||
const formData = new FormData();
|
||
formData.append('token', this.apiToken);
|
||
formData.append('content', 'record');
|
||
formData.append('format', 'json');
|
||
formData.append('type', 'flat');
|
||
|
||
// 謖<>ョ夊ョー蠖肘D
|
||
if (options.records && options.records.length > 0) {
|
||
options.records.forEach((recordId, index) => {
|
||
formData.append(`records[${index}]`, recordId);
|
||
});
|
||
}
|
||
|
||
// 謖<>ョ壼ュ玲ョオ
|
||
if (options.fields && options.fields.length > 0) {
|
||
options.fields.forEach((field, index) => {
|
||
formData.append(`fields[${index}]`, field);
|
||
});
|
||
}
|
||
|
||
// 譌カ髣エ霑<EFBDB4>サ、<EFBDBB>亥「樣㍼蜷梧ュ・蜈ウ髞ョ<E9AB9E><EFBDAE>
|
||
if (options.dateRangeBegin) {
|
||
const dateStr = this.formatRedcapDate(options.dateRangeBegin);
|
||
formData.append('dateRangeBegin', dateStr);
|
||
}
|
||
|
||
if (options.dateRangeEnd) {
|
||
const dateStr = this.formatRedcapDate(options.dateRangeEnd);
|
||
formData.append('dateRangeEnd', dateStr);
|
||
}
|
||
|
||
try {
|
||
const response = await axios.post(
|
||
`${this.baseUrl}/api/`,
|
||
formData,
|
||
{
|
||
headers: formData.getHeaders(),
|
||
timeout: this.timeout
|
||
}
|
||
);
|
||
|
||
if (!Array.isArray(response.data)) {
|
||
throw new Error('Invalid response format');
|
||
}
|
||
|
||
logger.info(`REDCap API: Exported ${response.data.length} records`);
|
||
return response.data;
|
||
|
||
} catch (error) {
|
||
logger.error('REDCap API exportRecords failed:', error);
|
||
throw new Error(`REDCap API error: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 蟇シ蜃コ蜈<EFBDBA>焚謐ョ<E8AC90>亥ュ玲ョオ螳壻ケ会シ?
|
||
*/
|
||
async exportMetadata(): Promise<any[]> {
|
||
const formData = new FormData();
|
||
formData.append('token', this.apiToken);
|
||
formData.append('content', 'metadata');
|
||
formData.append('format', 'json');
|
||
|
||
try {
|
||
const response = await axios.post(
|
||
`${this.baseUrl}/api/`,
|
||
formData,
|
||
{
|
||
headers: formData.getHeaders(),
|
||
timeout: this.timeout
|
||
}
|
||
);
|
||
|
||
logger.info(`REDCap API: Exported ${response.data.length} metadata fields`);
|
||
return response.data;
|
||
|
||
} catch (error) {
|
||
logger.error('REDCap API exportMetadata failed:', error);
|
||
throw new Error(`REDCap API error: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 蟇シ蜈・隶ー蠖包シ亥屓蜀呎焚謐ョ<E8AC90><EFBDAE>- Phase 2
|
||
*/
|
||
async importRecords(records: any[]): Promise<void> {
|
||
const formData = new FormData();
|
||
formData.append('token', this.apiToken);
|
||
formData.append('content', 'record');
|
||
formData.append('format', 'json');
|
||
formData.append('type', 'flat');
|
||
formData.append('overwriteBehavior', 'normal');
|
||
formData.append('data', JSON.stringify(records));
|
||
|
||
try {
|
||
await axios.post(
|
||
`${this.baseUrl}/api/`,
|
||
formData,
|
||
{
|
||
headers: formData.getHeaders(),
|
||
timeout: this.timeout
|
||
}
|
||
);
|
||
|
||
logger.info(`REDCap API: Imported ${records.length} records`);
|
||
|
||
} catch (error) {
|
||
logger.error('REDCap API importRecords failed:', error);
|
||
throw new Error(`REDCap API error: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 譬シ蠑丞喧譌・譛滉クコREDCap譬シ蠑擾シ唳YYY-MM-DD HH:MM:SS
|
||
*/
|
||
private formatRedcapDate(date: Date): string {
|
||
return date
|
||
.toISOString()
|
||
.replace('T', ' ')
|
||
.substring(0, 19);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 3.2 WebhookController<65><72>ebhook謗・謾カ蝎ィ<E89D8E><EFBDA8>
|
||
|
||
**譁<>サカ<EFBDBB>?* `backend/src/modules/iit-manager/controllers/webhookController.ts`
|
||
|
||
```typescript
|
||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||
import { prisma } from '@/config/database';
|
||
import { logger } from '@/common/logging';
|
||
import { jobQueue } from '@/common/jobs';
|
||
import { RedcapAdapter } from '../adapters/RedcapAdapter';
|
||
|
||
export class WebhookController {
|
||
/**
|
||
* 謗・謾カREDCap Data Entry Trigger Webhook
|
||
*
|
||
* 諤ァ閭ス隕∵アゑシ壼桃蠎疲慮髣?< 100ms<6D>井ク埼仆蝪朿EDCap<61>?
|
||
*/
|
||
async handleRedcapWebhook(
|
||
request: FastifyRequest,
|
||
reply: FastifyReply
|
||
): Promise<void> {
|
||
const startTime = Date.now();
|
||
|
||
// 隗」譫晋ebhook Payload
|
||
const payload = request.body as Record<string, any>;
|
||
const {
|
||
project_id,
|
||
record,
|
||
instrument,
|
||
redcap_event_name,
|
||
redcap_url
|
||
} = payload;
|
||
|
||
// 蝓コ譛ャ鬪瑚ッ<E7919A>
|
||
if (!project_id || !record) {
|
||
reply.code(400).send({
|
||
error: 'Missing required fields: project_id, record'
|
||
});
|
||
return;
|
||
}
|
||
|
||
logger.info('REDCap DET webhook received:', {
|
||
project_id,
|
||
record,
|
||
instrument,
|
||
event: redcap_event_name
|
||
});
|
||
|
||
// 遶句叉霑泌屓200<30>井ク埼仆蝪朿EDCap<61>?
|
||
const responseTime = Date.now() - startTime;
|
||
reply.code(200).send({
|
||
status: 'received',
|
||
timestamp: new Date().toISOString(),
|
||
response_time_ms: responseTime
|
||
});
|
||
|
||
// 蠑よュ・螟<EFBDA5>炊<EFBFBD>井ク埼仆蝪曰TTP蜩榊コ費シ?
|
||
setImmediate(async () => {
|
||
try {
|
||
await this.processWebhook({
|
||
project_id,
|
||
record,
|
||
instrument,
|
||
redcap_event_name,
|
||
redcap_url
|
||
});
|
||
} catch (error) {
|
||
logger.error('Webhook processing failed:', error);
|
||
// 荳肴鴨蜃コ髞呵ッッ<EFBDAF>悟屏荳コ蟾イ扈剰ソ泌屓200莠?
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 蠑よュ・螟<EFBDA5>炊Webhook
|
||
*/
|
||
private async processWebhook(payload: {
|
||
project_id: string;
|
||
record: string;
|
||
instrument: string;
|
||
redcap_event_name?: string;
|
||
redcap_url?: string;
|
||
}): Promise<void> {
|
||
const { project_id, record, instrument } = payload;
|
||
|
||
// 1. 譟・謇セ鬘ケ逶ョ驟咲スョ
|
||
const project = await prisma.iitProject.findFirst({
|
||
where: { redcapProjectId: project_id }
|
||
});
|
||
|
||
if (!project) {
|
||
logger.warn(`Unknown REDCap project_id: ${project_id}`);
|
||
return;
|
||
}
|
||
|
||
// 2. 蟷らュ画ァ譽譟・<E8AD9F>磯亟豁「驥榊、榊、<E6A68A>炊<EFBFBD>?
|
||
const isDuplicate = await this.checkDuplicate(
|
||
project.id,
|
||
record,
|
||
instrument
|
||
);
|
||
|
||
if (isDuplicate) {
|
||
logger.info(`Duplicate webhook ignored: ${record}`);
|
||
return;
|
||
}
|
||
|
||
// 3. 隹<>畑REDCap API諡牙叙螳梧紛謨ー謐ョ
|
||
const adapter = new RedcapAdapter(
|
||
project.redcapBaseUrl,
|
||
project.redcapApiToken
|
||
);
|
||
|
||
const records = await adapter.exportRecords({
|
||
records: [record] // 蜿ェ諡牙叙霑吩ク譚。隶ー蠖?
|
||
});
|
||
|
||
if (records.length === 0) {
|
||
logger.warn(`No data found for record: ${record}`);
|
||
return;
|
||
}
|
||
|
||
// 4. 謗ィ騾∝芦雍ィ謗ァ髦溷<E9ABA6>
|
||
await jobQueue.send('iit:quality-check', {
|
||
projectId: project.id,
|
||
record: records[0],
|
||
trigger: 'webhook',
|
||
instrument,
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
|
||
logger.info(`Webhook processed successfully: project=${project_id}, record=${record}`);
|
||
|
||
// 5. 隶ー蠖募ョ。隶。譌・蠢<EFBDA5>
|
||
await prisma.iitAuditLog.create({
|
||
data: {
|
||
projectId: project.id,
|
||
action: 'WEBHOOK_RECEIVED',
|
||
targetType: 'RECORD',
|
||
targetId: record,
|
||
details: {
|
||
instrument,
|
||
trigger: 'redcap_det'
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 蟷らュ画ァ譽譟・<E8AD9F>磯亟豁「5蛻<35>帖蜀<E5B896>㍾螟榊、<E6A68A>炊逶ク蜷瑚ョー蠖包シ<E58C85>
|
||
*/
|
||
private async checkDuplicate(
|
||
projectId: string,
|
||
recordId: string,
|
||
instrument: string
|
||
): Promise<boolean> {
|
||
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
||
|
||
const recentLog = await prisma.iitAuditLog.findFirst({
|
||
where: {
|
||
projectId,
|
||
action: 'WEBHOOK_RECEIVED',
|
||
targetId: recordId,
|
||
createdAt: { gte: fiveMinutesAgo },
|
||
details: {
|
||
path: ['instrument'],
|
||
equals: instrument
|
||
}
|
||
}
|
||
});
|
||
|
||
return recentLog !== null;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 3.3 SyncManager<65>郁スョ隸「陦・蜈<EFBDA5>シ<EFBFBD>
|
||
|
||
**譁<>サカ<EFBDBB>?* `backend/src/modules/iit-manager/services/SyncManager.ts`
|
||
|
||
```typescript
|
||
import { prisma } from '@/config/database';
|
||
import { logger } from '@/common/logging';
|
||
import { jobQueue } from '@/common/jobs';
|
||
import { RedcapAdapter } from '../adapters/RedcapAdapter';
|
||
|
||
export class SyncManager {
|
||
/**
|
||
* 蛻晏ァ句喧蜷梧ュ・<EFBDAD>域ウィ蜀悟ョ壽慮莉サ蜉。<E89C89>?
|
||
*/
|
||
async initializeSync(projectId: string, interval: string = '*/30 * * * *'): Promise<void> {
|
||
// 豕ィ蜀悟ョ壽慮莉サ蜉。<E89C89>域ッ<E59F9F>30蛻<30>帖<EFBFBD>?
|
||
await jobQueue.schedule(
|
||
'iit:redcap:poll',
|
||
interval,
|
||
{ projectId }
|
||
);
|
||
|
||
logger.info(`Polling sync initialized for project ${projectId}, interval: ${interval}`);
|
||
}
|
||
|
||
/**
|
||
* 螟<>炊霓ョ隸「<E99AB8>域級蜿門「樣㍼謨ー謐ョ<E8AC90><EFBDAE>
|
||
*/
|
||
async handlePoll(projectId: string): Promise<void> {
|
||
logger.info(`Starting poll sync for project ${projectId}`);
|
||
|
||
try {
|
||
// 1. 闔キ蜿夜。ケ逶ョ驟咲スョ
|
||
const project = await prisma.iitProject.findUnique({
|
||
where: { id: projectId }
|
||
});
|
||
|
||
if (!project) {
|
||
throw new Error(`Project not found: ${projectId}`);
|
||
}
|
||
|
||
// 2. 闔キ蜿紋ク頑ャ。蜷梧ュ・譌カ髣エ
|
||
const lastSyncAt = project.lastSyncAt || new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
||
|
||
logger.info(`Last sync: ${lastSyncAt.toISOString()}`);
|
||
|
||
// 3. 諡牙叙蠅樣㍼謨ー謐ョ
|
||
const adapter = new RedcapAdapter(
|
||
project.redcapBaseUrl,
|
||
project.redcapApiToken
|
||
);
|
||
|
||
const records = await adapter.exportRecords({
|
||
dateRangeBegin: lastSyncAt
|
||
});
|
||
|
||
logger.info(`Pulled ${records.length} records from REDCap`);
|
||
|
||
// 4. 謗ィ騾∝芦雍ィ謗ァ髦溷<E9ABA6>
|
||
for (const record of records) {
|
||
await jobQueue.send('iit:quality-check', {
|
||
projectId,
|
||
record,
|
||
trigger: 'polling',
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
}
|
||
|
||
// 5. 譖エ譁ー蜷梧ュ・譌カ髣エ
|
||
await prisma.iitProject.update({
|
||
where: { id: projectId },
|
||
data: { lastSyncAt: new Date() }
|
||
});
|
||
|
||
logger.info(`Poll sync completed for project ${projectId}`);
|
||
|
||
} catch (error) {
|
||
logger.error(`Poll sync failed for project ${projectId}:`, error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 蛛懈ュ「蜷梧ュ・<EFBDAD>亥叙豸亥ョ壽慮莉サ蜉。<E89C89><EFBDA1>
|
||
*/
|
||
async stopSync(projectId: string): Promise<void> {
|
||
// pg-boss荳肴髪謖∫峩謗・蜿匁カ<E58C81>chedule<6C>碁怙隕∝惠Worker荳ュ譽譟・鬘ケ逶ョ迥カ諤?
|
||
logger.info(`Sync stopped for project ${projectId}`);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 3.4 霍ッ逕ア驟咲スョ
|
||
|
||
**譁<>サカ<EFBDBB>?* `backend/src/modules/iit-manager/routes/index.ts`
|
||
|
||
```typescript
|
||
import { FastifyInstance } from 'fastify';
|
||
import { WebhookController } from '../controllers/webhookController';
|
||
import { SyncManager } from '../services/SyncManager';
|
||
|
||
export async function iitManagerRoutes(fastify: FastifyInstance) {
|
||
const webhookController = new WebhookController();
|
||
const syncManager = new SyncManager();
|
||
|
||
// Webhook遶ッ轤ケ<E8BDA4>域磁謾カREDCap DET<45>?
|
||
fastify.post('/webhooks/redcap', async (request, reply) => {
|
||
return webhookController.handleRedcapWebhook(request, reply);
|
||
});
|
||
|
||
// 謇句勘隗ヲ蜿大酔豁・<E8B181>域オ玖ッ慕畑<E68595>?
|
||
fastify.post('/projects/:id/sync', async (request, reply) => {
|
||
const { id } = request.params as { id: string };
|
||
await syncManager.handlePoll(id);
|
||
return { status: 'synced' };
|
||
});
|
||
}
|
||
```
|
||
|
||
#### 3.5 Worker豕ィ蜀<EFBDA8>
|
||
|
||
**譁<>サカ<EFBDBB>?* `backend/src/modules/iit-manager/index.ts`
|
||
|
||
```typescript
|
||
import { jobQueue } from '@/common/jobs';
|
||
import { SyncManager } from './services/SyncManager';
|
||
import { logger } from '@/common/logging';
|
||
|
||
export async function initializeIITManager() {
|
||
const syncManager = new SyncManager();
|
||
|
||
// 豕ィ蜀瑚スョ隸「Worker
|
||
await jobQueue.work('iit:redcap:poll', async (job) => {
|
||
const { projectId } = job.data;
|
||
await syncManager.handlePoll(projectId);
|
||
});
|
||
|
||
logger.info('IIT Manager initialized: Poll worker registered');
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## <20>ァェ 豬玖ッ暮ェ瑚ッ<E7919A>
|
||
|
||
### 豬玖ッ<E78E96>1<EFBFBD>夐ェ瑚ッ.ET驟咲スョ
|
||
|
||
**豁・鬪、<E9ACAA>?*
|
||
1. 驟咲スョngrok<6F>啻ngrok http 3001`
|
||
2. 蟆<>grok URL驟咲スョ蛻ーREDCap DET
|
||
3. 蝨ィREDCap荳ュ菫晏ュ倅ク譚。隶ー蠖?
|
||
4. 譽譟・ngrok謗ァ蛻カ蜿ー譏ッ蜷ヲ謾カ蛻ーPOST隸キ豎<EFBDB7>
|
||
|
||
**鬚<>悄扈捺棡<E68DBA>?*
|
||
- 笨?ngrok謾カ蛻ーPOST隸キ豎<EFBDB7>
|
||
- 笨?Payload蛹<64>性project_id縲〉ecord遲牙ュ玲ョ?
|
||
|
||
### 豬玖ッ<E78E96>2<EFBFBD>夐ェ瑚ッ、PI Token
|
||
|
||
**豬玖ッ戊<EFBDAF>譛ャ<E8AD9B>?* `test-redcap-api.ts`
|
||
|
||
```typescript
|
||
import { RedcapAdapter } from './adapters/RedcapAdapter';
|
||
|
||
async function testRedcapAPI() {
|
||
const adapter = new RedcapAdapter(
|
||
'http://localhost:8080',
|
||
process.env.REDCAP_API_TOKEN_TEST!
|
||
);
|
||
|
||
// 豬玖ッ募ッシ蜃コ隶ー蠖<EFBDB0>
|
||
const records = await adapter.exportRecords();
|
||
console.log('Records:', records);
|
||
|
||
// 豬玖ッ募ッシ蜃コ蜈<EFBDBA>焚謐?
|
||
const metadata = await adapter.exportMetadata();
|
||
console.log('Metadata fields:', metadata.length);
|
||
|
||
console.log('笨?REDCap API test passed!');
|
||
}
|
||
|
||
testRedcapAPI();
|
||
```
|
||
|
||
### 豬玖ッ<E78E96>3<EFBFBD>夂ォッ蛻ー遶ッ髮<EFBDAF><E9ABAE>豬玖ッ<E78E96>
|
||
|
||
**豬玖ッ戊<EFBDAF>譛ャ<E8AD9B>?* `test-redcap-integration.ts`
|
||
|
||
```typescript
|
||
async function testIntegration() {
|
||
// 1. 蛻帛サコ豬玖ッ暮。ケ逶ョ
|
||
const project = await prisma.iitProject.create({
|
||
data: {
|
||
name: 'test0102',
|
||
redcapBaseUrl: 'http://localhost:8080',
|
||
redcapApiToken: process.env.REDCAP_API_TOKEN_TEST!,
|
||
redcapProjectId: '16',
|
||
status: 'ACTIVE'
|
||
}
|
||
});
|
||
|
||
// 2. 蛻晏ァ句喧蜷梧ュ?
|
||
const syncManager = new SyncManager();
|
||
await syncManager.initializeSync(project.id);
|
||
|
||
// 3. 謇句勘隗ヲ蜿台ク谺。霓ョ隸?
|
||
await syncManager.handlePoll(project.id);
|
||
|
||
// 4. 讓。諡欷ebhook
|
||
const webhookController = new WebhookController();
|
||
await webhookController.handleRedcapWebhook(
|
||
{
|
||
body: {
|
||
project_id: '16',
|
||
record: '101',
|
||
instrument: 'demographics'
|
||
}
|
||
} as any,
|
||
{} as any
|
||
);
|
||
|
||
console.log('笨?Integration test passed!');
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## <20>搭 Day 2螳樊命貂<E591BD>黒
|
||
|
||
### 髦カ谿オ1<EFBDB5>夂識蠅<E8AD98>㊥螟<E38AA5>シ<EFBFBD>30蛻<30>帖<EFBFBD>?
|
||
|
||
- [ ] 蝨ィREDCap Control Center蜷ッ逕ィDET蜉溯<E89C89>
|
||
- [ ] 蝨ィtest0102鬘ケ逶ョ逕滓<E98095>API Token
|
||
- [ ] 驟咲スョ邇ッ蠅<EFBDAF>序驥擾シ?
|
||
```env
|
||
REDCAP_BASE_URL=http://localhost:8080
|
||
REDCAP_API_TOKEN_TEST=YOUR_TOKEN_HERE
|
||
```
|
||
- [ ] 菴ソ逕ィcurl豬玖ッ柊PI譏ッ蜷ヲ蜿ッ逕ィ
|
||
|
||
### 髦カ谿オ2<EFBDB5>壼シ蜿然edcapAdapter<65>?.5蟆乗慮<EFBFBD>?
|
||
|
||
- [ ] 蛻帛サコ譁<EFBDBA>サカ<EFBDBB>啻adapters/RedcapAdapter.ts`
|
||
- [ ] 螳樒鴫 `exportRecords()` 譁ケ豕<EFBDB9>
|
||
- [ ] 螳樒鴫 `exportMetadata()` 譁ケ豕<EFBDB9>
|
||
- [ ] 螳樒鴫 `formatRedcapDate()` 蟾・蜈キ譁ケ豕<EFBDB9>
|
||
- [ ] 郛門<E9839B>蜊募<E89C8A>豬玖ッ包シ啻RedcapAdapter.test.ts`
|
||
|
||
### 髦カ谿オ3<EFBDB5>壼シ蜿糎ebhookController<65>?蟆乗慮<E4B997>?
|
||
|
||
- [ ] 蛻帛サコ譁<EFBDBA>サカ<EFBDBB>啻controllers/webhookController.ts`
|
||
- [ ] 螳樒鴫 `handleRedcapWebhook()` 譁ケ豕<EFBDB9>
|
||
- [ ] 螳樒鴫 `processWebhook()` 遘∵怏譁ケ豕<EFBDB9>
|
||
- [ ] 螳樒鴫 `checkDuplicate()` 蟷らュ画ァ譽譟?
|
||
- [ ] 驟咲スョ霍ッ逕ア<E98095>啻POST /api/v1/iit/webhooks/redcap`
|
||
|
||
### 髦カ谿オ4<EFBDB5>壼シ蜿全yncManager<65>?.5蟆乗慮<EFBFBD>?
|
||
|
||
- [ ] 蛻帛サコ譁<EFBDBA>サカ<EFBDBB>啻services/SyncManager.ts`
|
||
- [ ] 螳樒鴫 `initializeSync()` 譁ケ豕<EFBDB9>
|
||
- [ ] 螳樒鴫 `handlePoll()` 譁ケ豕<EFBDB9>
|
||
- [ ] 螳樒鴫 `stopSync()` 譁ケ豕<EFBDB9>
|
||
- [ ] 豕ィ蜀係orker<65>啻iit:redcap:poll`
|
||
|
||
### 髦カ谿オ5<EFBDB5>夐寔謌先オ玖ッ包シ<E58C85>1蟆乗慮<E4B997>?
|
||
|
||
- [ ] 驟咲スョngrok/RequestBin豬玖ッ疋ET
|
||
- [ ] 霑占。<E58DA0> `test-redcap-api.ts`
|
||
- [ ] 霑占。<E58DA0> `test-redcap-integration.ts`
|
||
- [ ] 鬪瑚ッ∫ォッ蛻ー遶ッ豬∫ィ?
|
||
- [ ] 隶ー蠖墓オ玖ッ慕サ捺棡
|
||
|
||
### 髦カ谿オ6<EFBDB5>壽枚譯」譖エ譁ー<E8AD81><EFBDB0>30蛻<30>帖<EFBFBD>?
|
||
|
||
- [ ] 譖エ譁ーMVP蠑蜿台ササ蜉。貂<EFBDA1>黒<EFBFBD><E9BB92>ay 2螳梧<E89EB3><E6A2A7>?
|
||
- [ ] 隶ー蠖柊PI Token蜥碁<E89CA5>鄂ョ菫。諱?
|
||
- [ ] 譖エ譁ー譫カ譫<EFBDB6><E8ADAB>?
|
||
- [ ] 謠蝉コ、Git
|
||
|
||
---
|
||
|
||
## 笞<><E7AC9E><EFBFBD> 蜈ウ髞ョ豕ィ諢丈コ矩。ケ
|
||
|
||
### 1. DET驟咲スョ隕∫せ
|
||
|
||
**蟶ク隗<EFBDB8>漠隸ッ<E99AB8>?*
|
||
- 笶?蠢倩ョー蝨ィControl Center蜷ッ逕ィDET蜈ィ螻蠑蜈?
|
||
- 笶?Webhook URL蝪ォ蜀咎漠隸ッ<E99AB8>亥、壻コ<E5A3BB>万譚<E4B887>縲∝ー台コ<E58FB0>キッ蠕<EFBDAF>シ<EFBFBD>
|
||
- 笶?譛ャ蝨ー蠑蜿醍識蠅<E8AD98>裏豕墓磁謾カ螟也ス糎ebhook
|
||
|
||
**隗」蜀ウ譁ケ譯茨シ?*
|
||
- 笨?菴ソ逕ィngrok蛻帛サコ荳エ譌カ蜈ャ鄂繕RL豬玖ッ<E78E96>
|
||
- 笨?蠑蜿醍識蠅<E8AD98>庄蜈育畑RequestBin鬪瑚ッ ̄ayload譬シ蠑<EFBDBC>
|
||
- 笨?逕滉コァ邇ッ蠅<EFBDAF>。ョ菫拔RL蜿ッ隶ソ髣ョ<E9ABA3>磯亟轣ォ蠅吶?TTPS<50>?
|
||
|
||
### 2. API Token螳牙<E89EB3>
|
||
|
||
**螳牙<E89EB3>螳櫁キオ<EFBDB7>?*
|
||
- 笨?蟄伜お蝨ィ邇ッ蠅<EFBDAF>序驥乗<E9A9A5>謨ー謐ョ蠎灘刈蟇<E58888>ュ玲ョ?
|
||
- 笨?螳壽悄霓ョ謐「Token
|
||
- 笨?譛蟆乗揀髯仙次蛻呻シ亥宵蠑蜷ッ髴隕∫噪譚<E599AA>剞<EFBFBD>?
|
||
- 笶?荳崎ヲ∵署莠、蛻ーGit
|
||
- 笶?荳崎ヲ∝惠譌・蠢嶺クュ謇灘魂螳梧紛Token
|
||
|
||
### 3. 諤ァ閭ス莨伜喧
|
||
|
||
**Webhook蜩榊コ疲慮髣エ<E9ABA3>?*
|
||
- 笨?蠢<>。サ < 100ms<6D>育ォ句叉霑泌<E99C91>?00<30>?
|
||
- 笨?菴ソ逕ィ `setImmediate()` 蠑よュ・螟<EFBDA5>炊
|
||
- 笶?荳崎ヲ∝惠Webhook荳ュ謇ァ陦瑚玲慮謫堺ス<E5A0BA>
|
||
|
||
**API隹<49>畑鬚醍紫<E9868D>?*
|
||
- 笨?蠅樣㍼諡牙叙<E78999>井スソ逕ィdateRangeBegin<69>?
|
||
- 笨?謖<>ョ壼ュ玲ョオ<EFBDAE>亥㍼蟆第焚謐ョ驥擾シ?
|
||
- 笨?蜷育炊隶セ鄂ョtimeout<75>?0遘抵シ<E68AB5>
|
||
|
||
### 4. 髞呵ッッ螟<EFBDAF>炊
|
||
|
||
**DET Webhook螟ア雍・<E99B8D>?*
|
||
- REDCap莨夐㍾隸?谺。<E8B0BA>磯龍髫<E9BE8D>1蛻<31>帖<EFBFBD>?
|
||
- 螯よ棡莉榊、ア雍・<E99B8D>軍EDCap莨夊ョー蠖募芦譌・蠢<EFBDA5>
|
||
- 謌台サャ逧<EFBDAC>スョ隸「譛コ蛻カ蜿ッ莉・陦・蜈<EFBDA5>@貍冗噪謨ー謐ョ
|
||
|
||
**API隹<49>畑螟ア雍・<E99B8D>?*
|
||
- 螳樒鴫驥崎ッ墓惻蛻カ<E89BBB>?谺。<E8B0BA>梧欠謨ー騾驕ソ<E9A995><EFBDBF>
|
||
- 隶ー蠖募、ア雍・譌・蠢<EFBDA5>
|
||
- 蜻願ュヲ騾夂衍邂。逅<EFBDA1><E98085>?
|
||
|
||
---
|
||
|
||
## <20>投 謌仙粥鬪梧噺譬<E599BA>㊥
|
||
|
||
### Day 2螳梧<E89EB3>譬<EFBFBD>㊥
|
||
|
||
- [ ] 笨?REDCap API Token蟾イ逕滓<E98095>蟷カ鬪瑚ッ<E7919A>
|
||
- [ ] 笨?RedcapAdapter蜿ッ莉・諡牙叙豬玖ッ墓焚謐ョ
|
||
- [ ] 笨?DET蟾イ驟咲スョ蟷カ謌仙粥謗・謾カWebhook
|
||
- [ ] 笨?Webhook蜿ッ莉・隗ヲ蜿羨PI諡牙叙
|
||
- [ ] 笨?霓ョ隸「譛コ蛻カ蜿ッ莉・螳壽慮霑占。<E58DA0>
|
||
- [ ] 笨?謨ー謐ョ謗ィ騾∝芦雍ィ謗ァ髦溷<E9ABA6>
|
||
- [ ] 笨?蜊募<E89C8A>豬玖ッ募<EFBDAF>驛ィ騾夊ソ<E5A48A>
|
||
- [ ] 笨?遶ッ蛻ー遶ッ髮<EFBDAF><E9ABAE>豬玖ッ暮夊ソ<E5A48A>
|
||
|
||
### 諤ァ閭ス謖<EFBDBD><E8AC96><EFBFBD>
|
||
|
||
- [ ] Webhook蜩榊コ疲慮髣エ < 100ms
|
||
- [ ] API隹<49>畑謌仙粥邇?> 99%
|
||
- [ ] 遶ッ蛻ー遶ッ蟒カ霑?< 5遘抵シ<E68AB5>ET隗ヲ蜿<EFBDA6> 竊?莨∝セョ騾夂衍<E5A482>?
|
||
|
||
---
|
||
|
||
## <20>迫 逶ク蜈ウ譁<EFBDB3>。」
|
||
|
||
- **MVP蠑蜿台ササ蜉。貂<EFBDA1>黒<EFBFBD><E9BB92>** `MVP蠑蜿台ササ蜉。貂<EFBDA1><E8B282>?md`
|
||
- **螳梧紛謚譛ッ譁ケ譯茨シ<E88CA8>** `IIT Manager Agent 螳梧紛謚譛ッ蠑蜿第婿譯?(V1.1).md`
|
||
- **REDCap莠梧ャ。蠑蜿第欠蜊暦シ<E69AA6>** `../../Redcap/03-API蟇ケ謗・荳主シ蜿?33-REDCap莠梧ャ。蠑蜿第キア蠎ヲ謖<EFBDA6><E8AC96>?md`
|
||
- **REDCap驛ィ鄂イ謇句<E8AC87><E58FA5>?* `../../Redcap/01-驛ィ鄂イ荳朱<E88DB3>鄂?10-REDCap_Docker驛ィ鄂イ謫堺ス懈焔蜀<E78494>.md`
|
||
|
||
---
|
||
|
||
## <20>統 譖エ譁ー譌・蠢<EFBDA5>
|
||
|
||
| 譌・譛<EFBDA5> | 迚域悽 | 譖エ譁ー蜀<EFBDB0>ョケ | 譖エ譁ー莠?|
|
||
|------|------|---------|--------|
|
||
| 2026-01-02 | V1.0 | 蛻晏ァ狗沿譛ャ<E8AD9B>悟ョ梧<EFBDAE>謚譛ッ隹<EFBDAF><E99AB9>泌柱譁ケ譯郁ョセ隶。 | AI Assistant |
|
||
|
||
---
|
||
|
||
**霑呎弍IIT Manager Agent逧<74>橿譛ッ蝓コ遏ウ譁<EFBDB3>。」<EFBDA1>瑚ッキ螯・蝟<EFBDA5>ソ晉ョ。<EFBDAE><EFBDA1>** 箝絶ュ絶ュ絶ュ絶ュ?
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|