Files
AIclinicalresearch/docs/03-业务模块/IIT Manager Agent/04-开发计划/REDCap对接技术方案与实施指南.md
HaHafeng 1b53ab9d52 feat(aia): Complete AIA V2.0 with universal streaming capabilities
Major Changes:
- Add StreamingService with OpenAI Compatible format
- Upgrade Chat component V2 with Ant Design X integration
- Implement AIA module with 12 intelligent agents
- Update API routes to unified /api/v1 prefix
- Update system documentation

Backend (~1300 lines):
- common/streaming: OpenAI Compatible adapter
- modules/aia: 12 agents, conversation service, streaming integration
- Update route versions (RVW, PKB to v1)

Frontend (~3500 lines):
- modules/aia: AgentHub + ChatWorkspace (100% prototype restoration)
- shared/Chat: AIStreamChat, ThinkingBlock, useAIStream Hook
- Update API endpoints to v1

Documentation:
- AIA module status guide
- Universal capabilities catalog
- System overview updates
- All module documentation sync

Tested: Stream response verified, authentication working
Status: AIA V2.0 core completed (85%)
2026-01-14 19:15:01 +08:00

30 KiB
Raw Blame History

REDCap蟇ケ謗・謚€譛ッ譁ケ譯井ク主ョ樊命謖<EFBFBD>

*迚域悽<EFBFBD>? V1.0
*蛻帛サコ譌・譛滂シ? 2026-01-02
*騾ら畑髦カ谿オ<EFBFBD>? IIT Manager Agent MVP - Day 2
*<EFBFBD>。」諤ァ雍ィ<EFBFBD>? 譬ク蠢<EFBDB8>橿譛ッ蝓コ遏? *驥崎ヲ∫ィ句コヲ<EFBFBD>? 箝絶ュ絶ュ絶ュ絶ュ?


<EFBFBD>搭 譁<>。」逶ョ譬<EFBDAE>

譛ャ譁<EFBFBD>。」譏ッIIT Manager Agent荳山EDCap髮<70><E9ABAE>逧?<EFBFBD>ィ∵橿譛ッ謖<EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>遑ョ<EFBFBD><EFBFBD>

  1. 笨?REDCap蟇ケ謗・譁ケ蠑冗噪謚€譛ッ騾牙梛
  2. 笨?Data Entry Trigger (DET) 逧<>ェ瑚ッ∽ク朱<EFBDB8>鄂ョ
  3. 笨?REST API逧<49>スソ逕ィ譁ケ豕?
  4. 笨?螳樊慮雍ィ謗ァ逧<EFBDA7>ョ梧紛譫カ譫?
  5. 笨?Day 2逧<32>€蜿大ョ樊命豁・鬪?

<EFBFBD>識 譬ク蠢<EFBDB8>サ楢ョコ<EFBDAE><EFBDBA>xecutive Summary<72>?

笨?€譛ッ騾牙梛<EFBFBD>咼ET<EFBFBD>亥ョ樊慮隗ヲ蜿托シ<EFBFBD> + REST API<50>域焚謐ョ隸サ蜀呻シ<E591BB>

荳埼㊦逕ィ<EFBFBD><EFBFBD> External Module (EM)
*蜴溷屏<EFBFBD>? EM髴€隕 ̄HP蠑€蜿代€∽セオ蜈・REDCap貅千<E8B285>€∫サエ謚、謌先悽鬮倥€∽ク埼€ょ粋螟夜Κ邉サ扈滄寔謌<E5AF94>

*<EFBFBD>畑譁ケ譯茨シ?

<EFBFBD>サカ 謚€譛? 逕ィ騾? 螳樊慮諤?
Data Entry Trigger REDCap蜴溽函蜉溯<EFBFBD> 謨ー謐ョ菫晏ュ俶慮螳樊慮騾夂衍 0遘貞サカ霑?
REST API (Export) HTTP + JSON 諡牙叙謨ー謐ョ縲∝<EFBFBD>謨ー謐ョ 謖蛾怙隹<EFBFBD>
REST API (Import) HTTP + JSON 蝗槫<EFBFBD>雍ィ謗ァ諢剰ァ<EFBFBD><EFBFBD>hase 2<>? 謖蛾怙隹<EFBFBD>
霓ョ隸「譛コ蛻カ pg-boss螳壽慮莉サ蜉。 陦・蜈<EFBFBD>ET驕玲シ乗焚謐ョ 30蛻<EFBFBD>

<EFBFBD>剥 謚€譛ッ隹<EFBDAF><E99AB9>皮サ捺<EFBDBB>?

<EFBFBD><EFBFBD><EFBFBD>1<EFBFBD>啌EDCap謠蝉セ帷噪蟇ケ謗・譁ケ蠑?

*<EFBFBD><EFBFBD>疲擂貅撰シ?

  • REDCap 15.8.0貅千<EFBFBD><EFBFBD>
  • External Module Framework螳俶婿譁<E5A9BF>。」
  • REDCap莠梧ャ。蠑€蜿第キア蠎ヲ謖<EFBFBD><EFBFBD>?

蜿醍鴫逧<EFBFBD>ッケ謗・譁ケ蠑擾シ<EFBFBD>

譁ケ蠑<EFBFBD> €譛ッ譬<EFBFBD> 騾ら畑蝨コ譎ッ 謌台サャ譏ッ蜷ヲ騾ら畑
External Module PHP + Hook REDCap蜀<EFBFBD>Κ蜉溯<EFBFBD>謇ゥ螻輔€ゞI螳壼宛 笶?荳埼€ら畑
REST API HTTP + JSON 螟夜Κ邉サ扈滄寔謌舌€∵焚謐ョ蜷梧ュ? 笨?騾ら畑
Data Entry Trigger Webhook 螳樊慮騾夂衍螟夜Κ邉サ扈<EFBFBD> 笨?騾ら畑

<EFBFBD><EFBFBD><EFBFBD>2<EFBFBD>咼ata Entry Trigger (DET) 鬪瑚ッ<E7919A>

*貅千<EFBFBD>∽ス咲スョ<EFBFBD>?

/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

*蜈ウ髞ョ莉」遐<EFBFBD>シ?

// 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;

*鬪瑚ッ∫サ楢ョコ<EFBFBD>? 笨?DET譏ッREDCap蜴溽函蜉溯<E89C89><E6BAAF>檎悄螳槫ュ伜惠荳泌ケソ豕帑スソ逕ィ


<EFBFBD><EFBFBD>?螳梧紛譫カ譫<EFBDB6>ョセ隶。

譫カ譫<EFBFBD><EFBFBD>?

笏娯楳笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏?
笏? 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>?                          笏?
笏披楳笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏€笏?

螳樊慮諤ァ蟇ケ豈?

譁ケ譯<EFBFBD> 蟒カ霑滓慮髣エ 謌台サャ逧<EFBFBD>€画叫
*郤ッ霓ョ隸「<EFBFBD>域ッ?蛻<><EFBFBD>? 譛€鬮?蛻<> 笶?荳肴サ。雜ウ螳樊慮諤ァ隕∵ア?
*郤ッ霓ョ隸「<EFBFBD>域ッ?蛻<><EFBFBD>? 譛€鬮?蛻<> <EFBFBD><EFBFBD><EFBFBD><>コ先オェ雍ケ縲∝サカ霑滉サ埼ォ?
DET + API 0遘定ァヲ蜿?+ 3-5遘貞、<E8B29E><EFBDA4>? 笨?*譛€莨俶婿譯?
DET + 霓ョ隸「陦・蜈<EFBDA5> 螳樊慮 + 蜿ッ髱<EFBDAF> 笨?*譛€扈域楔譫?

*扈楢ョコ<EFBFBD>? CRC轤ケ蜃サ"菫晏ュ<E6998F>" 竊?5遘貞<E98198>謾カ蛻ー莨∽ク壼セョ菫。騾夂衍 笨?


<EFBFBD>逃 謚€譛ッ螳樒鴫隸ヲ隗?

1. Data Entry Trigger (DET) 驟咲スョ

豁・鬪、1<EFBFBD>壼惠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>鄂ョ

謨ー謐ョ蠎灘ュ玲ョオ<EFBFBD><EFBFBD> redcap_config.data_entry_trigger_enabled = 1

豁・鬪、2<EFBFBD>壼惠鬘ケ逶ョ荳ュ驟咲スョ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>

謨ー謐ョ蠎灘ュ玲ョオ<EFBFBD><EFBFBD> redcap_projects.data_entry_trigger_url

豁・鬪、3<EFBFBD>夐ェ瑚ッET驟咲スョ

*菴ソ逕ィRequestBin/ngrok鬪瑚ッ<E7919A>シ?

# 菴ソ逕ィ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蜿鷹€∫噪隸キ豎ゑシ?

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/

*蟄玲ョオ隸エ譏趣シ?

蟄玲ョオ 邀サ蝙<EFBFBD> 隸エ譏<EFBFBD> 遉コ萓<EFBFBD>
project_id string REDCap鬘ケ逶ョID "16"
record string 隶ー蠖肘D "101"
instrument string 陦ィ蜊募錐遘ー "demographics"
redcap_event_name string 莠倶サカ蜷咲ァー<EFBFBD>育コオ蜷醍<EFBFBD>皮ゥカ<EFBFBD><EFBFBD> "baseline_arm_1"
[instrument]_complete string 陦ィ蜊募ョ梧<EFBFBD>迥カ諤<EFBFBD><EFBFBD>0/1/2<>? "2"
redcap_url string REDCap螳樔セ偽RL "http://localhost:8080/"

2. REDCap REST API 菴ソ逕ィ

API遶ッ轤ケ

http://localhost:8080/api/

API隶、隸<EFBFBD>シ啜oken

*逕滓<EFBFBD>豁・鬪、<EFBFBD>?

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蟄伜お<EFBFBD>?

  • 笨?邇ッ蠅<EFBDAF>序驥擾シ域耳闕撰シ<E692B0>
  • 笨?謨ー謐ョ蠎灘刈蟇<E58888>ュ玲ョオ<EFBDAE><EFBDB5>itProject.redcapApiToken<65>?
  • 笶?荳崎ヲ∵署莠、蛻ーGit

API譁ケ豕<EFBFBD>1<EFBFBD>壼ッシ蜃コ隶ー蠖?(exportRecords)

逕ィ騾費シ<EFBFBD> 諡牙叙謨ー謐ョ<E8AC90>域髪謖∝「樣㍼蜷梧ュ・<EFBDAD><EFBDA5>

隸キ豎ら、コ萓具シ<EFBFBD>url<EFBFBD>会シ<EFBFBD>

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"

*蜿よ焚隸エ譏趣シ?

蜿よ焚 <EFBFBD>。ォ 隸エ譏<EFBFBD> 遉コ萓<EFBFBD>
token 笨? API Token "ABC123..."
content 笨? 蝗コ螳壼€? "record"
format 笨? 霑泌屓譬シ蠑<EFBFBD> "json"
type 笨? 謨ー謐ョ扈捺桷 "flat"
records 笶? <EFBFBD>ョ夊ョー蠖肘D ["101", "102"]
fields 笶? <EFBFBD>ョ壼ュ玲ョオ ["age", "gender"]
dateRangeBegin 笶? 譌カ髣エ霑<EFBFBD>サ、<EFBFBD>亥「樣㍼蜷梧ュ・蜈ウ髞ョ<EFBFBD><EFBFBD> "2026-01-01 00:00:00"
dateRangeEnd 笶? 扈捺據譌カ髣エ "2026-01-31 23:59:59"

*蜩榊コ皮、コ萓具シ?

[
  {
    "record_id": "101",
    "redcap_event_name": "baseline_arm_1",
    "first_name": "蠑?,
    "last_name": "荳?,
    "age": "30",
    "gender": "1",
    "demographics_complete": "2"
  }
]

API譁ケ豕<EFBFBD>2<EFBFBD>壼ッシ蜃コ蜈<EFBFBD>焚謐ョ (exportMetadata)

逕ィ騾費シ<EFBFBD> 闔キ蜿門ュ玲ョオ螳壻ケ峨€∬。ィ蜊慕サ捺<EFBDBB>?

*隸キ豎ら、コ萓具シ?

curl -X POST http://localhost:8080/api/ \
  -F "token=YOUR_API_TOKEN" \
  -F "content=metadata" \
  -F "format=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譁ケ豕<EFBFBD>3<EFBFBD>壼ッシ蜈・隶ー蠖?(importRecords) - Phase 2

逕ィ騾費シ<EFBFBD> 蝗槫<E89D97>謨ー謐ョ<E8AC90>亥ヲりエィ謗ァ諢剰ァ√€、I蟒コ隶ョ<E99AB6>?

*隸キ豎ら、コ萓具シ?

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>

*<EFBFBD>サカ<EFBFBD>? backend/src/modules/iit-manager/adapters/RedcapAdapter.ts

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>

*<EFBFBD>サカ<EFBFBD>? backend/src/modules/iit-manager/controllers/webhookController.ts

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>

*<EFBFBD>サカ<EFBFBD>? backend/src/modules/iit-manager/services/SyncManager.ts

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 霍ッ逕ア驟咲スョ

*<EFBFBD>サカ<EFBFBD>? backend/src/modules/iit-manager/routes/index.ts

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>

*<EFBFBD>サカ<EFBFBD>? backend/src/modules/iit-manager/index.ts

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');
}

<EFBFBD>ァェ 豬玖ッ暮ェ瑚ッ<E7919A>

豬玖ッ<EFBFBD>1<EFBFBD>夐ェ瑚ッET驟咲スョ

*豁・鬪、<EFBFBD>?

  1. 驟咲スョngrok<EFBFBD>啻ngrok http 3001`
  2. <EFBFBD>grok URL驟咲スョ蛻ーREDCap DET
  3. 蝨ィREDCap荳ュ菫晏ュ倅ク€譚。隶ー蠖?
  4. €譟・ngrok謗ァ蛻カ蜿ー譏ッ蜷ヲ謾カ蛻ーPOST隸キ豎<EFBFBD>

*<EFBFBD>悄扈捺棡<EFBFBD>?

  • 笨?ngrok謾カ蛻ーPOST隸キ豎<EFBDB7>
  • 笨?Payload蛹<64>性project_id縲〉ecord遲牙ュ玲ョ?

豬玖ッ<EFBFBD>2<EFBFBD>夐ェ瑚ッ、PI Token

*豬玖ッ戊<EFBFBD>譛ャ<EFBFBD>? test-redcap-api.ts

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();

豬玖ッ<EFBFBD>3<EFBFBD>夂ォッ蛻ー遶ッ髮<EFBFBD><EFBFBD>豬玖ッ<EFBFBD>

*豬玖ッ戊<EFBFBD>譛ャ<EFBFBD>? test-redcap-integration.ts

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!');
}

<EFBFBD>搭 Day 2螳樊命貂<E591BD>

髦カ谿オ1<EFBFBD>夂識蠅<EFBFBD>㊥螟<EFBFBD><EFBFBD>30蛻<EFBFBD><EFBFBD>?

  • 蝨ィREDCap Control Center蜷ッ逕ィDET蜉溯<E89C89>
  • 蝨ィtest0102鬘ケ逶ョ逕滓<EFBFBD>API Token
  • 驟咲スョ邇ッ蠅<EFBFBD>序驥擾シ?
    REDCAP_BASE_URL=http://localhost:8080
    REDCAP_API_TOKEN_TEST=YOUR_TOKEN_HERE
    
  • 菴ソ逕ィcurl豬玖ッ柊PI譏ッ蜷ヲ蜿ッ逕ィ

髦カ谿オ2<EFBFBD>壼シ€蜿然edcapAdapter<EFBFBD>?.5蟆乗慮<EFBFBD>?

  • 蛻帛サコ譁<EFBFBD>サカ<EFBFBD>啻adapters/RedcapAdapter.ts`
  • 螳樒鴫 exportRecords() 譁ケ豕<EFBDB9>
  • 螳樒鴫 exportMetadata() 譁ケ豕<EFBDB9>
  • 螳樒鴫 formatRedcapDate() 蟾・蜈キ譁ケ豕<EFBDB9>
  • 郛門<EFBFBD>蜊募<EFBFBD>豬玖ッ包シ啻RedcapAdapter.test.ts`

髦カ谿オ3<EFBFBD>壼シ€蜿糎ebhookController<EFBFBD>?蟆乗慮<E4B997>?

  • 蛻帛サコ譁<EFBFBD>サカ<EFBFBD>啻controllers/webhookController.ts`
  • 螳樒鴫 handleRedcapWebhook() 譁ケ豕<EFBDB9>
  • 螳樒鴫 processWebhook() 遘∵怏譁ケ豕<EFBDB9>
  • 螳樒鴫 checkDuplicate() 蟷らュ画€ァ譽€譟?
  • 驟咲スョ霍ッ逕ア<EFBFBD>啻POST /api/v1/iit/webhooks/redcap`

髦カ谿オ4<EFBFBD>壼シ€蜿全yncManager<EFBFBD>?.5蟆乗慮<EFBFBD>?

  • 蛻帛サコ譁<EFBFBD>サカ<EFBFBD>啻services/SyncManager.ts`
  • 螳樒鴫 initializeSync() 譁ケ豕<EFBDB9>
  • 螳樒鴫 handlePoll() 譁ケ豕<EFBDB9>
  • 螳樒鴫 stopSync() 譁ケ豕<EFBDB9>
  • 豕ィ蜀係orker<EFBFBD>啻iit:redcap:poll`

髦カ谿オ5<EFBFBD>夐寔謌先オ玖ッ包シ<EFBFBD>1蟆乗慮<EFBFBD>?

  • 驟咲スョngrok/RequestBin豬玖ッ疋ET
  • 霑占。<EFBFBD> test-redcap-api.ts
  • 霑占。<EFBFBD> test-redcap-integration.ts
  • 鬪瑚ッ∫ォッ蛻ー遶ッ豬∫ィ?
  • 隶ー蠖墓オ玖ッ慕サ捺棡

髦カ谿オ6<EFBFBD>壽枚譯」譖エ譁ー<EFBFBD><EFBFBD>30蛻<EFBFBD><EFBFBD>?

  • 譖エ譁ーMVP蠑€蜿台ササ蜉。貂<EFBFBD><EFBFBD><EFBFBD>ay 2螳梧<E89EB3><E6A2A7>?
  • 隶ー蠖柊PI Token蜥碁<E89CA5>鄂ョ菫。諱?
  • 譖エ譁ー譫カ譫<EFBFBD><EFBFBD>?
  • 謠蝉コ、Git

<EFBFBD><EFBFBD><EFBFBD> 蜈ウ髞ョ豕ィ諢丈コ矩。ケ

1. DET驟咲スョ隕∫せ

*蟶ク隗<EFBFBD>漠隸ッ<EFBFBD>?

  • 笶?蠢倩ョー蝨ィ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>

*螳牙<EFBFBD>螳櫁キオ<EFBFBD>?

  • 笨?蟄伜お蝨ィ邇ッ蠅<EFBDAF>序驥乗<E9A9A5>謨ー謐ョ蠎灘刈蟇<E58888>ュ玲ョ?
  • 笨?螳壽悄霓ョ謐「Token
  • 笨?譛€蟆乗揀髯仙次蛻呻シ亥宵蠑€蜷ッ髴€隕∫噪譚<E599AA><EFBFBD>?
  • 笶?荳崎ヲ∵署莠、蛻ーGit
  • 笶?荳崎ヲ∝惠譌・蠢嶺クュ謇灘魂螳梧紛Token

3. 諤ァ閭ス莨伜喧

*Webhook蜩榊コ疲慮髣エ<EFBFBD>?

  • 笨?蠢<>。サ < 100ms<6D>育ォ句叉霑泌<E99C91>?00<30>?
  • 笨?菴ソ逕ィ setImmediate() 蠑よュ・螟<EFBDA5>
  • 笶?荳崎ヲ∝惠Webhook荳ュ謇ァ陦瑚€玲慮謫堺ス<E5A0BA>

*API隹<EFBFBD>畑鬚醍紫<EFBFBD>?

  • 笨?蠅樣㍼諡牙叙<E78999>井スソ逕ィdateRangeBegin<69>?
  • 笨?謖<>ョ壼ュ玲ョオ<EFBDAE>亥㍼蟆第焚謐ョ驥擾シ?
  • 笨?蜷育炊隶セ鄂ョtimeout<75>?0遘抵シ<E68AB5>

4. 髞呵ッッ螟<EFBDAF>

*DET Webhook螟ア雍・<E99B8D>?

  • REDCap莨夐㍾隸?谺。<E8B0BA>磯龍髫<E9BE8D>1蛻<31><EFBFBD>?
  • 螯よ棡莉榊、ア雍・<EFBFBD>軍EDCap莨夊ョー蠖募芦譌・蠢<EFBFBD>
  • 謌台サャ逧<EFBFBD>スョ隸「譛コ蛻カ蜿ッ莉・陦・蜈<EFBFBD>貍冗噪謨ー謐ョ

*API隹<EFBFBD>畑螟ア雍・<EFBFBD>?

  • 螳樒鴫驥崎ッ墓惻蛻カ<EFBFBD>?谺。<E8B0BA>梧欠謨ー騾€驕ソ<E9A995><EFBDBF>
  • 隶ー蠖募、ア雍・譌・蠢<EFBFBD>
  • 蜻願ュヲ騾夂衍邂。逅<EFBFBD><EFBFBD>?

<EFBFBD>投 謌仙粥鬪梧噺譬<E599BA>

Day 2螳梧<E89EB3><EFBFBD>

  • 笨?REDCap API Token蟾イ逕滓<E98095>蟷カ鬪瑚ッ<E7919A>
  • 笨?RedcapAdapter蜿ッ莉・諡牙叙豬玖ッ墓焚謐ョ
  • 笨?DET蟾イ驟咲スョ蟷カ謌仙粥謗・謾カWebhook
  • 笨?Webhook蜿ッ莉・隗ヲ蜿羨PI諡牙叙
  • 笨?霓ョ隸「譛コ蛻カ蜿ッ莉・螳壽慮霑占。<E58DA0>
  • 笨?謨ー謐ョ謗ィ騾∝芦雍ィ謗ァ髦溷<E9ABA6>
  • 笨?蜊募<E89C8A>豬玖ッ募<EFBDAF>驛ィ騾夊ソ<E5A48A>
  • 笨?遶ッ蛻ー遶ッ髮<EFBDAF><E9ABAE>豬玖ッ暮€夊ソ<E5A48A>

諤ァ閭ス謖<EFBFBD><EFBFBD><EFBFBD>

  • Webhook蜩榊コ疲慮髣エ < 100ms
  • API隹<EFBFBD>畑謌仙粥邇?> 99%
  • 遶ッ蛻ー遶ッ蟒カ霑?< 5遘抵シ<E68AB5>ET隗ヲ蜿<EFBDA6> 竊?莨∝セョ騾夂衍<E5A482>?

<EFBFBD>迫 逶ク蜈ウ譁<EFBDB3>。」

  • MVP蠑€蜿台ササ蜉。貂<EFBFBD><EFBFBD><EFBFBD> MVP蠑€蜿台ササ蜉。貂<EFBFBD><EFBFBD>?md
  • 螳梧紛謚€譛ッ譁ケ譯茨シ<EFBFBD> IIT Manager Agent 螳梧紛謚€譛ッ蠑€蜿第婿譯?(V1.1).md
  • REDCap莠梧ャ。蠑€蜿第欠蜊暦シ<EFBFBD> ../../Redcap/03-API蟇ケ謗・荳主シ€蜿?33-REDCap莠梧ャ。蠑€蜿第キア蠎ヲ謖<EFBDA6><E8AC96>?md
  • *REDCap驛ィ鄂イ謇句<EFBFBD><EFBFBD>? ../../Redcap/01-驛ィ鄂イ荳朱<E88DB3>鄂?10-REDCap_Docker驛ィ鄂イ謫堺ス懈焔蜀<E78494>.md

<EFBFBD>統 譖エ譁ー譌・蠢<EFBDA5>

譌・譛<EFBFBD> 迚域悽 譖エ譁ー蜀<EFBFBD>ョケ 譖エ譁ー莠?
2026-01-02 V1.0 蛻晏ァ狗沿譛ャ<EFBFBD>悟ョ梧<EFBFBD>€譛ッ隹<EFBFBD><EFBFBD>泌柱譁ケ譯郁ョセ隶。 AI Assistant

霑呎弍IIT Manager Agent逧<74>橿譛ッ蝓コ遏ウ譁<EFBDB3>。」<EFBDA1>瑚ッキ螯・蝟<EFBDA5>ソ晉ョ。<EFBDAE><EFBDA1> 箝絶ュ絶ュ絶ュ絶ュ?