From 60b630d3de0dadbf64072bcc4783ce1d3673a647 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Fri, 10 Jul 2026 11:25:26 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20v7=20(2026-07-10)=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=20=E2=80=94=20msp=20=EC=A0=9C=EA=B1=B0,=206-stage=20alignment,?= =?UTF-8?q?=20mtb=20queue=20fix,=20labdb=20=EC=9E=90=EB=8F=99=20=EC=9E=AC?= =?UTF-8?q?=EC=8B=9C=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이번 세션 대량 변경 사항을 5개 문서에 반영. 각 문서마다 stale 이던 섹션을 갱신하거나 신규 섹션 추가. USER_GUIDE.md - Sensor Alignment 를 V1 (3-stage) / V2 (6-stage) 로 재구성 - V2 6-stage 표 + relaxed mode / soft hint 설명 - GREEN 진입 5초 hold + 10-strike 리셋 완화 명시 - "최적의 위치입니다!" 문구 반영 docs/BLE_PROTOCOL_REFERENCE.md - msp 명령 취소선 처리 + mim 신규 명령 문서화 - Watchdog timeout 25초 연장 명시 (2.5) - §2.6 신규: firmware VBTFW0121 mls mode 0 freeze 취약점 + 앱 측 3-layer 회피 (isMtbBusy / mtb 3초 timeout / 자동 재연결) VesiScan_Android_Pipeline_Summary.md - v7 (2026-07-10) 섹션 신규 추가 — BLE / Alignment / UI / labdb / tools 5개 카테고리로 변경 사항 정리 - 권장 펌웨어 표기 VBTFW0116 → VBTFW0120+ 로 갱신 labdb.md - §12b 신규: 앱 측 Auto Retry Policy — endMeasurement 자동 업로드 조건 완화, LabdbAutoRetry object, UI 배너, 재시도 안전성 tools/README.md - labdb_upload.py 섹션 신규 — 사용법 / 폴더 구조 / 재실행 안전성 / buildPayload 로직 / 활용 예 정리 Co-Authored-By: Claude Opus 4.7 --- USER_GUIDE.md | 65 ++++++++------ VesiScan_Android_Pipeline_Summary.md | 69 ++++++++++++++- docs/BLE_PROTOCOL_REFERENCE.md | 48 ++++++++++- labdb.md | 122 +++++++++++++++++++++++++++ tools/README.md | 95 +++++++++++++++++++++ 5 files changed, 367 insertions(+), 32 deletions(-) diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 5bdb847..06a8bf4 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -74,40 +74,53 @@ This step ensures the sensor is positioned correctly over your bladder for accur - Apply ultrasound gel between the sensor and your skin - Tap the **Start Alignment** button (large orange button) -### Step 1/3: Vertical Alignment +### 알고리즘 V1 (일반 사용자, 3-stage) / V2 (Clinical Alignment 세션, 6-stage) -The app will begin scanning and provide direction: -- **"Slide up ↑"** — slide the device upward toward your navel -- **"Slide down ↓"** — slide the device downward toward your pubic bone -- **"Slide up slightly ↑"** / **"Slide down slightly ↓"** — make small adjustments -- Move the sensor **slowly, 1-2mm at a time** -- When the vertical position is correct, the app will show **Step 2/3** +일반 진입 (모니터링 → Settings → 재측정 등) 은 **V1 (3-stage)**, Clinical +Home 의 **Sensor Alignment** 버튼으로 진입한 세션은 자동으로 **V2 (6-stage)** +로 활성화됩니다. Step indicator ("단계 N/M") 로 구분 가능. -> The hint text will show animated dots (e.g., "Slide up ↑.", "Slide up ↑..", "Slide up ↑...") to indicate the system is actively scanning. +### V1: 3-stage (일반 진입) -### Step 2/3: Lateral Alignment +**Step 1/3: Vertical Alignment** +- **"Slide up ↑"** / **"Slide down ↓"** / **"Slide up slightly ↑"** 등의 방향 안내 +- 1~2mm 씩 천천히 이동, 위치가 잡히면 Step 2/3 -1. The app will first show **"Checking..."** for a few seconds -2. Then it will guide you: - - **"Slide left ←"** — slide the device to the left - - **"Slide right →"** — slide the device to the right -3. Adjust until both lateral sensors (CH4 and CH5) detect the bladder equally -4. When balanced, the app will show **Step 3/3** +**Step 2/3: Lateral Alignment** +- **"Checking..."** → **"Slide left ←"** / **"Slide right →"** — CH4/CH5 균형 +- 균형 잡히면 Step 3/3 -### Step 3/3: Final Check (Green Zone) +**Step 3/3: Final Check (Green Zone)** +- CV + LR deviation 확인 → **"최적의 위치입니다!"** (2026-07-07 문구 개선) +- 배경 초록 + **5초 hold** (2026-07-07: 7초 → 5초 유지, 조기 unlock 방지 강화) +- **Start Scanning** 활성 → 측정 화면으로 -1. The app checks overall alignment quality (CV + LR deviation) -2. If everything is optimal, the hint will show **"In position!"** -3. The screen background turns green and holds this state for **7 seconds** to confirm stability -4. The **Start Scanning** button (green) becomes active -5. Tap **Start Scanning** to proceed to the measurement screen +### V2: 6-stage (Clinical Alignment 세션 전용) + +방광 하부/후벽 각도 오차를 실시간 보정하는 정밀 정렬 알고리즘. + +| 단계 | 화면 문구 예 | 조건 | +|:---:|---|---| +| 1/6 INITIAL_ACCUM | "잠시 그대로 두세요 (n/10)" | baseline 10 cycle 누적 | +| 2/6 VERTICAL_CLIMB | "↑ 위로 조금씩 올리세요 (hit N/6)" | CH3 sliding window majority — 최근 6 프레임 중 3회 hit | +| 3/6 CH3_STABILIZE | "✓ ch3 검출 — 위치 유지 (n/10)" | CH3 안정 확인, 단발 flick 무시 (window=4, majority=3) | +| 4/6 CENTER_OPTIMIZE | "↑/↓ 조금 (CV=0.xx)" | CH0~CH3 chord CV 임계 0.15 | +| 5/6 LR_BALANCE | "← / → \|Δ\|=n" | CH4/CH5 균형 | +| 6/6 FINAL_CONFIRM | "✓ 최적의 위치입니다!" | 5 프레임 재확인 통과 → GREEN | + +**추가 안전장치**: +- **Relaxed mode**: VERTICAL_CLIMB 에 20 프레임 (~5초) 이상 갇히면 CH3 없이도 + 상단 3채널 (CH0~CH2) 로 CENTER_OPT 진입. CH3 flicker 심한 자세에서 무한 대기 방지. +- **Soft hint**: 12 프레임 (~3초) 이상 반복되면 "↑ 위로" → "CH3 신호 확인 중 · 위치 유지" + 로 문구 자동 완화. +- **6단계 완료 후 GREEN 5초 hold**: 사용자가 "측정 시작" 버튼 누를 시간 확보. ### If Alignment is Lost -- If the sensor shifts significantly after reaching "In position!": - - The app allows up to **3 consecutive failures** before exiting GREEN - - After 3 failures: **"Position lost. Restart above pubic bone."** - - The scanning will stop automatically - - Re-place the sensor and tap **Start Alignment** again +- Green Zone 유지 중 실패해도 즉시 리셋 X — **10 consecutive failures** (약 5초+) + 까지 관대하게 유지 (2026-07-07 완화, 이전 3-strike → 10-strike). 이 사이는 + GREEN 유지 + "최적의 위치입니다!" 표시. +- 10회 연속 실패: **"위치를 놓쳤습니다. 치골 위에서 다시 시작해주세요"** → + 자동 재시작 대기. ### If the Sensor is Detached - If the sensor loses contact with skin: diff --git a/VesiScan_Android_Pipeline_Summary.md b/VesiScan_Android_Pipeline_Summary.md index b96e1bb..04e0c54 100644 --- a/VesiScan_Android_Pipeline_Summary.md +++ b/VesiScan_Android_Pipeline_Summary.md @@ -2,8 +2,73 @@ ## 작성일: 2026-04-23 ## 작성자: dwjang -## 마지막 업데이트: 2026-05-26 (v6) — 1.0.0-design (versionCode 24) -## 권장 펌웨어: **VBTFW0116** (pending slot 1→8 확장으로 ADC drop 안정) +## 마지막 업데이트: 2026-07-10 (v7) — 1.2.0-demo (versionCode 26) +## 권장 펌웨어: **VBTFW0120+** (mim FIFO 지원 필수. VBTFW0121 은 `mls mode 0` handler 취약) + +### v7 주요 변경 요약 (2026-07-06 ~ 2026-07-10) + +**BLE / Firmware 대응** +- **`msp` 완전 제거 → `mim` (FIFO 15 sample) 로 통일** (커밋 34ae4d7/deae8a7/9907931). + 신 firmware 는 mim 만 사용. `rsp:` 파서는 legacy 응답 대비 유지. +- **mtb queue overrun freeze 3-layer fix** (커밋 9907931). 실측 로그에서 mtb 응답 + stream 중 `msn`/`mim` 이 끼어들어 firmware GATT queue 꼬임 → `mls mode 0` + 에서 완전 freeze 확인. 상세: [BLE_PROTOCOL_REFERENCE.md](docs/BLE_PROTOCOL_REFERENCE.md) §2.6. + - `isMtbBusy` 프로퍼티 + battery/mim polling 시 skip + - mtb 3초 timeout + `consecutiveMtbTimeouts` state + - 3회 연속 실패 시 자동 `forceDisconnectAndReconnect()` +- **Watchdog timeout 15초 → 25초 연장** (커밋 0383a21). 재연결 후 첫 RX 가 14초 + 지연 도착하는 케이스 허용. +- **좀비 세션 방지**: `onConnectionStateChanged` 콜백을 CCCD write 완료 시점으로 + 이동 (BleManager). `fwFallbackTimer` / `cccdRetryTimer` / `mtbTimeoutRunnable` + disconnect 시 취소. +- **Auto scan 명령 `maa` → `mtb` 로 전환** (커밋 29b6ca5). maa 는 IMU 응답 + 없음 (posture 갱신 X). mtb 는 reb×6 + raa + rim 이라 auto scan 중에도 posture + 실시간 업데이트. +- **lastBleRxAt 갱신 확대** (커밋 3a41864). `processReceivedData` 진입점에서 + 모든 유효 응답에 대해 갱신 → "기기 응답 없음" false-positive 배너 해소. + +**Alignment (V2 6-stage)** +- **CH3 flicker 무한 대기 방지** (커밋 e808b5b). VERTICAL_CLIMB 진입 조건을 + "3연속 hit" → "최근 6프레임 중 3회 hit" sliding window majority 로 완화. + 20 프레임 대기 시 상단 3채널 relaxed mode 진입 (CH3 없이도 정렬 완료). + 12 프레임 반복 시 문구 자동 완화 ("↑ 위로" → "CH3 확인 중"). +- **6단계 완료 후 "측정 시작" 버튼 활성화 fix** (커밋 49aab7b). 기존 + `phase == LR_BALANCE` 조건이 실제 완료 phase (`FINAL_CONFIRM`) 를 못 잡아 + 버튼 disable 되던 버그. `state=="commit" && phase==FINAL_CONFIRM` 로 수정. +- **GREEN 진입 후 5초 hold** (커밋 159116d). V2 에도 V1 처럼 hold 로직 추가. + 순간 flick 으로 GREEN 즉시 풀리는 UX 문제 해소. +- **GREEN zone 조기 리셋 완화** (커밋 7484077). V1 의 3-strike → **10-strike** + (약 5초+). 미세 자세 흔들림으로 "0/3 정렬시작" 리셋되던 버그 완화. +- **문구 개선**: "제 위치입니다!" → **"최적의 위치입니다!"** / "In position!" + → "Optimal position!" (커밋 7484077). + +**UI / UX** +- **Posture 라벨**: "서있음" → **"일어남"** (`piezo_posture_upright`, 한글). + 영문 "Upright" 유지. +- **도넛차트 posture chip stuck 버그** (커밋 149e511/3abc024): `imuCollector.onComplete` + 콜백이 DisposableEffect race 로 즉시 null 되던 문제. 세 화면 + (PiezoMonitoring / PlacementGuide / ClinicalLive) 의 DisposableEffect 에서 + 콜백 null 정리 제거. stale 콜백은 다음 화면 진입 시 자기 콜백으로 덮어씀. + +**Clinical 세션 / labdb** +- **labdb 자동 업로드 강화** (커밋 db6e7e1/93b8650/8c17407): + - `endMeasurement` 자동 업로드 조건 완화 (`isRegistered` 만 체크, `lastStatus == "active"` 조건 제거) + - 신규 `LabdbAutoRetry` object — ClinicalHome 진입 시 미업로드 폴더 자동 재시도 + - 상단 배너 UI (진행 중 / 완료 결과) +- **KnownDeviceStore clinical flow 제한 제거** (커밋 b822b49). 일반 모드에서도 + 연결한 기기 자동 저장 + DeviceScan 상단에 표시. + +**Python tools** +- **`tools/labdb_upload.py` 신규** — LabdbUploader.kt 의 `buildPayload` 로직을 + Python 으로 이식. 외부 저장 세션 폴더 일괄 업로드용. + 자세한 사용법: [tools/README.md](tools/README.md). + +**참고** +- 이번 세션에서 발견되었으나 아직 미조치: PiezoMonitoringView (2900L) / + PlacementGuideView (2200L) 대형 파일 분해, Design Token 중앙화, ViewModel + 도입 등 아키텍처 개선 사항은 별도 로드맵으로 진행 예정. + +### v6 주요 변경 요약 (2026-05-12 ~ 2026-05-26) ### v6 주요 변경 요약 (2026-05-12 ~ 2026-05-26) diff --git a/docs/BLE_PROTOCOL_REFERENCE.md b/docs/BLE_PROTOCOL_REFERENCE.md index fb3a936..a60ad3e 100644 --- a/docs/BLE_PROTOCOL_REFERENCE.md +++ b/docs/BLE_PROTOCOL_REFERENCE.md @@ -96,10 +96,49 @@ VesiScan Basic Android 앱이 VBT 디바이스(VBTFW0116+ 펌웨어)와 주고 - 측정 중이었으면 측정 일시정지, 복구 시 사용자가 다시 시작 ### 2.5 Watchdog (좀비 감지) -파일: [`BleManager.kt:528+` watchdogJob](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L528) +파일: [`BleManager.kt` watchdogJob](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt) -- 별도 스레드로 30초 주기 RX 마지막 수신 시간 확인 -- N초 이상 무응답 + isConnected.value = true 라면 GATT 강제 reset → 재연결 +- 별도 스레드가 5초 tick 으로 RX 마지막 수신 시각 확인. +- **2026-07-07**: watchdog timeout **15 → 25초** 연장. 실측 로그에서 재연결 + 후 첫 RX 가 14초 지연 후 도착하는 케이스 확인 — peripheral / OS BLE + 스택의 좀비 회복 시간 허용. 15초로는 회복 직전에 forced reconnect 발동 + → 무한 재연결 루프 문제. +- Silence 10초~timeout 사이면 **heartbeat 발동**: 2026-07-08 부터 `msp` 대신 + `sendImuFifoQuery()` (mim). `isMtbBusy` 시 skip. +- 정리는 UI thread 로 위임 (`handler.post`) — watchdog thread 에서 직접 + GATT close 하면 UI thread 의 sendRaw 와 race. + +### 2.6 ⚠ 알려진 firmware freeze (VBTFW0121) — mtb queue overrun + +**증상**: `mtb` 응답 stream (reb×6 + raa + rim) 진행 중에 다른 명령 +(`msn`, `mim`) 이 TX 로 끼어들면 firmware GATT queue 가 꼬여 이후 명령 +응답이 실종. 특히 그 상태에서 `mls mode 0` 이 결정타가 되어 BLE +advertising 까지 죽는 **완전 freeze** 실측 확인 (2026-07-07 11:26 세션 로그). + +**앱 측 3-layer 회피** (2026-07-08 커밋 9907931): + +**Layer 1 — BleManager gating (`isMtbBusy`)** +```kotlin +val isMtbBusy: Boolean + get() = piezoCollector.isMultiChannel && !piezoCollector.isComplete +``` +- `batteryTimer` / `batteryRetryTimer`: `sendBatteryQuery` 앞에 skip +- Watchdog silence heartbeat: `sendImuFifoQuery` 앞에 skip +- 상위 (View 레이어) 도 `mim` 폴링 앞에서 `isMtbBusy` 체크 + +**Layer 2 — mtb 3초 timeout** +- `sendMtb()` 후 3초 timeout runnable, `raa` 응답 오면 취소. +- Timeout 시 `consecutiveMtbTimeouts` 증가 + `piezoCollector.reset()` / + `imuCollector.reset()` 해제. + +**Layer 3 — UI 안내 + 자동 재연결** +- `PlacementGuideView` 가 `consecutiveMtbTimeouts` 관찰. +- 1~2회: `"기기 응답 지연"` 배너 +- **3회 연속: `forceDisconnectAndReconnect()` 자동 호출** — 사용자가 앱을 + 방치해도 자동 복구. + +**Firmware 팀 리포트 대상**: VBTFW0121 의 `mls mode 0` handler 가 큐 +스트레스 상태에서 취약. 앱 fix 로 회피는 되지만 근본은 firmware. --- @@ -164,7 +203,8 @@ fun verify(data: ByteArray): Boolean | `msn?` | BE [0] | `sendBatteryQuery()` ([L463](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L463)) | `rsn:` | 배터리 mV 조회 | | `mid?` | ASCII " " | `sendDeviceInfoQuery()` ([L467](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L467)) | `rid:` | FW/HW/Serial 조회 | | `mls?` | BE [state] | `sendLedMode(state)` ([L471](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L471)) | `rls:` | LED 모드 변경 | -| `msp?` | ASCII " " | `sendImuQuery()` ([L475](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L475)) | `rsp:` | IMU 단일 샘플 조회 | +| ~~`msp?`~~ | ~~ASCII " "~~ | ~~`sendImuQuery()`~~ | `rsp:` | ⚠ **2026-07-08 완전 제거** — 신 firmware 는 `mim` 만 사용. `rsp:` 파서는 legacy 응답 대비 유지. | +| `mim?` | ASCII " " | `sendImuFifoQuery()` | `rim:` (15 sample) | ★ IMU FIFO — piezo 무음, walking detector 시계열 확보용. `imuCollector.reset()` 을 먼저 호출하므로 mtb 진행 중 (isMtbBusy=true) 이면 caller 가 반드시 skip 해야 함 (mtb 의 rim 파괴 방지). | ### 4.1 maa / mtb / maa 송신 throttle (canSendMaa) diff --git a/labdb.md b/labdb.md index 41fcccd..7d3c297 100644 --- a/labdb.md +++ b/labdb.md @@ -1512,6 +1512,128 @@ async function upload(key, session) { +\## 12b. 앱 측 Auto Retry Policy (Android app, 2026-07-09) + + + +Android 앱 (VesiScan-Basic demo-final / tab-navigation / cloud-mvp) 은 매 + +session 마다 사용자가 Upload 버튼을 누르지 않아도 되도록 **자동 업로드 + + +재시도 큐** 를 내장합니다. + + + +\### 12b.1 endMeasurement 자동 업로드 + + + +Clinical 세션 종료 시 (`ClinicalSessionStore.endMeasurement()`) `apiKey` 가 + +등록되어 있으면 (`LabdbCredentials.isRegistered`) 무조건 `LabdbUploader. + +uploadAsync(context, folder)` 를 호출. **fire-and-forget** 이므로 응답 대기 + +없이 다음 화면으로 진행. + + + +\- 이전 정책: `lastStatus == "active"` 조건 필요 → 상태 갱신 안 된 첫 진입 시 + + 자동 업로드 skip 되던 문제. + +\- 2026-07-09 이후: `isRegistered` 만 체크. 서버가 401/403 을 반환하면 상위 + + 로직에서 status 재조회. + + + +\### 12b.2 LabdbAutoRetry object (`app/src/main/java/.../labdb/LabdbAutoRetry.kt`) + + + +Clinical Home 진입 시 (`LaunchedEffect(Unit)`) `retryPending(context)` 자동 + +호출. 로직: + + + +``` + +1. LabdbCredentials.isRegistered 확인 → 없으면 no-op + +2. ClinicalSessionStore.recentFinalizedFolders (tab-nav/cloud-mvp) 또는 + + listOfNotNull(lastFinalizedFolder) (demo-final) 스캔 + +3. labdb_upload.json 마커 없는 폴더만 pending + +4. 순차 (병렬 X) LabdbUploader.uploadBlocking 실행 + +5. 결과: labdb_upload.json (성공) / labdb_upload_error.json (실패) + +``` + + + +\### 12b.3 UI 배너 + + + +Clinical Home 상단: + +\- 진행 중 (`pendingCount > 0` 또는 `uploadingName != null`): + + 파란 배너 "자동 업로드 진행 중 · 남은 세션 N · " + +\- 완료 (`lastResultMessage != null`): + + 초록/주황 배너 "N uploaded, M failed" + 확인 버튼 + + + +\### 12b.4 State 노출 + + + +```kotlin + +object LabdbAutoRetry { + + val pendingCount: MutableIntState // UI 배너 카운트 + + val uploadingName: MutableState // 현재 업로드 중 폴더 + + val lastResultMessage: MutableState // 완료 후 결과 요약 + + fun retryPending(context: Context) // 트리거 + + fun dismissResult() // 배너 확인 + +} + +``` + + + +\### 12b.5 재시도 안전성 + + + +\- 이미 업로드된 폴더 (`labdb_upload.json` 있음) 는 skip + +\- 실패 폴더는 `labdb_upload_error.json` 만 남기고 다음 진입 시 재시도 + +\- 네트워크 순간 이슈 → 다음 진입에서 자동 복구 + +\- 앱 종료 후 재실행 → `ensureHistoryLoaded` 로 recent 폴더 복원 → 재시도 + + + +\--- + + + \## 13. 지원 diff --git a/tools/README.md b/tools/README.md index 500c546..322b067 100644 --- a/tools/README.md +++ b/tools/README.md @@ -75,3 +75,98 @@ scan_id, timestamp, ..., volume_ml, lr_ratio, ..., channel, s0..s99 2. **알고리즘 변경 검증**: 새 fix 후 같은 CSV 로 `--ablation` 돌려 회귀 여부 확인 3. **Device-to-device variance**: 두 device 의 같은 phantom 측정 → CV 비교 4. **lr_ratio 패턴**: human cohort 의 `--plot` 으로 lr 분포 시각화 + +--- + +## labdb_upload.py — 세션 폴더 일괄 업로드 + +앱이 저장한 세션 폴더 (`measurement_.json` 포함) 를 labdb REST API +로 일괄 업로드. 앱 안의 `LabdbUploader.kt` (buildPayload) 로직을 그대로 +Python 으로 옮긴 것으로, 폰이 없는 상황에서 데스크톱에 백업한 데이터를 +바로 서버로 넣을 때 사용. + +### Setup + +```bash +# 별도 의존성 없음 (표준 라이브러리만 사용) +export LABDB_API_KEY=xbk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx +``` + +apiKey 는 labdb admin 콘솔 또는 backend 담당자로부터 발급. + +### Usage + +```bash +# 기본 — 폴더 안의 모든 세션 업로드 +python tools/labdb_upload.py "C:/Users/장동우/Desktop/newdata" + +# apiKey 를 명령줄로 전달 +python tools/labdb_upload.py ./sessions --api-key xbk_live_xxx... + +# dry-run — 전송하지 않고 payload 검증만 +python tools/labdb_upload.py ./sessions --dry-run + +# 이미 업로드된 폴더도 재전송 (labdb_upload.json 마커 무시) +python tools/labdb_upload.py ./sessions --force + +# meta.data_type 없을 때 사용할 fallback (기본 "001") +python tools/labdb_upload.py ./sessions --data-type 002 +``` + +### 폴더 구조 요구사항 + +각 세션 폴더는 앱이 만든 표준 형식: + +``` +/ + meta_.json + measurement_.json ← 이걸 payload 로 변환 + adc.csv + imu.csv + ble.log + labdb_upload.json ← 성공 시 생성 (sessionId, inserted 개수 등) + labdb_upload_error.json ← 실패 시 생성 (다음 실행 시 참고) +``` + +`measurement.json` 또는 `measurement_.json` 이 있으면 대상. 없으면 +"measurement json not found" 로 실패. + +### 재실행 안전성 + +- 이미 업로드된 폴더 (`labdb_upload.json` 있음) 는 자동 skip +- `--force` 로 강제 재전송 가능 +- 실패한 폴더는 다음 실행 시 자동 재시도 + +### 출력 예시 + +``` +[OK ] HUMAN-kai_VBT26040302_SITTING_ALIGN_0CM_2026-07-08_110903: uploaded: 130/130 (dup=0) +[OK ] HUMAN-kai_VBT26040302_SITTING_ALIGN_1CM_2026-07-08_111049: uploaded: 3/3 (dup=0) +[FAIL] HUMAN-kai_VBT26050202_SITTING_ALIGN_2CM_2026-07-08_161928: upload fail: HTTP 401 +[OK ] ... (skip already uploaded) + +summary: 19 uploaded, 0 skipped, 1 failed (total 20) +``` + +### buildPayload 로직 (LabdbUploader.kt 와 동일) + +- **testId**: 폴더명 sanitize (`^[A-Za-z0-9_-]{1,40}$`). 40자 초과 시 앞 + 31자 + `_` + 8자 SHA-1 prefix 로 축약 (uniqueness 유지). +- **dataType**: `meta.data_type` 우선, 없으면 `--data-type` fallback. +- **records[]**: `measurement.json` 의 각 cycle → labdb record 로 변환: + - `sensor.imu`: 마지막 (가장 최근) IMU sample + - `sensor.imu_samples`: 전체 시계열 + - `channels[0..5]`: peak / peakIdx / data 배열 + - alignment session 은 align_phase / align_score / align_hint 등 부가 필드. +- **params**: posture / step / is_alignment / examiner / firmware / hardware / subject 등 + meta 요약. + +### 활용 예 + +1. **개발용 백업 재전송**: 폰이 없는 상황에서 데스크톱에 백업한 세션 폴더를 + labdb 로 일괄 업로드. +2. **자동 재시도 실패 복구**: 앱의 `LabdbAutoRetry` 도 실패한 케이스 (예: + apiKey 만료) → apiKey 갱신 후 스크립트로 일괄 재전송. +3. **다른 폰 → labdb 이관**: A 폰에서 측정 → `Downloads/VesiScan_Sessions/` + 폴더 → PC 로 복사 → 스크립트로 서버 반영. +