Commit Graph

341 Commits

Author SHA1 Message Date
dw.jang f3cca9a4c7 Clinical #1: Subject ID 자유입력 → Phantom/Human/ETC 카테고리 (회의 결정)
회의 결정: 자유 텍스트 ID 입력 대신 측정 대상을 PHANTOM/HUMAN/ETC 세
카테고리로 명확히 구분. JSON/labdb 저장 방식도 함께 변경.

데이터 모델 (ClinicalSession.kt):
  - SubjectType enum 추가: PHANTOM / HUMAN / ETC
  - val subjectId: String 제거 → val subjectType: SubjectType
  - 폴더명: "{subjectId}_{...}" → "{SUBJECT_TYPE}_{...}"
    예: PHANTOM_VBT26050202_SUPINE_ALIGN_0CM_2026-06-08_15-30-00

JSON 변경 (meta.json + measurement.json):
  - "subject_id": "S001" → "subject_type": "PHANTOM"

labdb payload (LabdbUploader):
  - params.subject_id → params.subject_type (서버는 free JSON이라 코드 변경 불필요)

UI (ClinicalHomeView):
  - Subject ID OutlinedTextField 제거
  - LabeledSegmented "Subject Type" 3개 버튼 (Phantom/Human/ETC)
  - 마지막 선택 기억 (ClinicalSessionStore.lastSubjectType)

ClinicalLive 상단 sub line: "S=S001 · TV=300mL · Ab=22mm"
                          → "Phantom · TV=300mL · Ab=22mm"

호환성: 기존 측정 폴더는 그대로. 새 폴더부터 새 형식 적용.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 09:03:22 +09:00
dw.jang f00aef50e7 UI #5: 도넛차트 크기 dev/일반 통일 + ml 폰트 키움 + Logs & Charts 일반 모드 노출
회의 피드백:
  - 도넛차트 화면 비율 이상함 → 화면에 꽉 차게
  - ml 숫자 너무 작아서 안 보임 → 키움
  - dev mode와 일반 모드 도넛 크기 다름 → 통일
  - 아래로 내렸을 때 로그/그래프 확인 가능하게

도넛 sizing 통일:
  - nonDonutBudget: dev=280 / 일반=360 → 일관 280dp (도넛에 공간 더 할당)
  - donutFitSize: coerceIn(120, 360) → (160, 420) — 화면 꽉 차게 max 키움
  - baseDonutSize: dev 분기 제거 → 모두 donutFitSize 사용
  - donutLineWidth/bladdySize: dev 분기 제거, 일반 모드 방식으로 통일
  - volFontSize: dev=18 / 일반=24 → 일관 40sp (ml 크게)

Logs & Charts 토글:
  - 토글 버튼: dev 모드 한정 → 일반 모드에서도 표시
    (라벨: dev이면 "Developer Tools", 일반이면 "Logs & Charts")
  - useScrollLayout = appState.isDevMode && showDevPanels → showDevPanels만
  - verticalScroll: dev 조건 제거 → useScrollLayout 따름
  - dev panels 본체: appState.isDevMode && showDevPanels → showDevPanels만

→ 일반 사용자도 토글로 아래 스크롤하여 BLE 로그/채널 그래프 확인 가능.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 18:02:44 +09:00
dw.jang 12f1919bc6 UX #4: HOME 진입 시 BLE 연결 유지 + Start 시 설정/Placement skip
회의 피드백: BLE 연결된 상태에서 HOME으로 가면 연결이 끊겨 다음 측정 때
DeviceScan부터 다시 거쳐야 하는 불편. 연결을 유지하고, Start 시 설정 단계
모두 skip하고 바로 도넛차트로 진입할 수 있도록.

AppState.goHome():
  - bleManager.disconnect() 제거 — BLE 연결 유지
  - isDeviceConnected reset도 제거 (실제 연결 상태 유지)
  - Demo 모드인 경우만 isDeviceConnected=false (가짜 연결 정리)
  - NIRS 스트리밍 정지 / 데모 종료 처리는 유지

AppState.startFromHome():
  - 연결됨(isDeviceConnected==true) → PIEZO_MONITORING 직진 (도넛차트)
  - 미연결                          → DEVICE_SCAN (기존 흐름)

HomeView:
  - 연결된 상태이면 "● Connected — Start to resume" chip 표시
    사용자가 다음 Start가 바로 도넛차트로 갈 것임을 인지 가능

기존 동작 유지:
  - ClinicalHome Unpair → appState.isDeviceConnected=false (어제 fix 그대로)
    → HOME 진입 시 chip 안 보임 → Start 시 DeviceScan으로 정상 진입
  - PiezoMonitoring Disconnect 다이얼로그 → goHome() 그대로 호출되지만
    이젠 BLE 끊기지 않음. 의도적으로 끊고 싶으면 disconnectAndUnbond 사용.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 17:58:18 +09:00
dw.jang 6c3b88cf2d UI #2: AutoScan Interval → Scan Interval + SEC/MIN 단위 토글
회의 피드백:
  - 제목 'Auto Scan Interval' → 'Scan Interval'
  - 한글 설명 → '측정 간격 조절'
  - ms 단위 표시 → sec 기본 + min 토글
  - 최소 600ms 고정 (펌웨어 응답 시간 + 안전 마진)

구현:
  - SEC 모드: 0.6 ~ 5.0 s (0.2s step) — 짧은 간격, 실시간
  - MIN 모드: 1 ~ 10 min (30s step)  — 장시간 측정, 배터리 절약
  - 토글 버튼(SEC/MIN) 활성 시 진입 모드의 최소값으로 reset
  - 슬라이더/스테퍼/표시값 모두 모드에 맞춰 동적 변환
  - 기본 모드 추론: 저장된 ms < 60000이면 SEC, 아니면 MIN

PiezoMonitoring in-monitoring settings 슬라이더는 sec-only로 그대로 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 17:55:18 +09:00
dw.jang e7076ed2a0 UI: 회의 피드백 묶음 1 — Placement 가독성 + Voiding 표현/표시 + catheter 간격
회의 피드백 5건:

#6 PlacementGuide(Sensor Alignment) 글자 가독성
  - Step 1/3 indicator: 14sp MlPrimary → 22sp 검정
  - "Sensor Alignment" 타이틀: 28sp Bold → 36sp ExtraBold (이미 검정)
  - 노란/초록 hint 박스 글자: 20sp hintFg(노랑/초록) → 26sp Bold 검정
    (박스 배경 색은 의미 유지 — 노랑=조정 필요, 초록=Green zone)

#7 Start Alignment 버튼 텍스트: White → Black

#8 도넛차트 Voiding 버튼: "Voiding" → "Voiding / Catheterization"
   (12sp · TextAlign.Center · maxLines=2로 줄바꿈 허용)

#9 도넛차트 mL 표시: Voiding 직후 displayMaxVolumeMl=0 + estimatedVolume=0
   상태에서 "0 mL" 대신 "— mL" 표시 — 측정값이 없음을 명확히

#10 catheter "left" 간격: 2dp → 6dp (숫자와 텍스트 사이 여유)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 17:52:43 +09:00
dw.jang 35887802fd ClinicalHome Unpair: appState.isDeviceConnected를 false로 동기화
증상: Clinical → Unpair → 메인 → Start → 도넛차트 진입 시 BLE 끊긴 상태로
도착. 새 페어링 없이 측정 시도 → 응답 없음.

원인: ClinicalHomeView의 Unpair 다이얼로그가 BleManager.disconnectAndUnbond()
만 호출하고 appState.isDeviceConnected는 안 건드림. BleManager.isConnected.value
는 false로 reset되지만 appState 측은 stale true 상태로 남아 있음.

AppState.startFromHome()이 isDeviceConnected==true 분기에서 DeviceScan을
건너뛰고 곧장 PIEZO_PERSONALIZATION으로 이동하기 때문에, 재페어링 단계
자체가 누락된 채 MaxCapacity → AutoScan → Placement(skip) → 도넛차트까지
미연결 상태로 진행.

수정: Unpair 확인 시 appState.isDeviceConnected = false 한 줄 추가.
이제 Start → DeviceScan 화면으로 가서 정상적으로 재페어링.

참고: PiezoMonitoring의 Disconnect 다이얼로그는 appState.goHome() 호출하고
그 안에서 isDeviceConnected=false 처리되므로 별도 fix 불필요.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 16:34:33 +09:00
dw.jang fb6f722b53 BLE fix: msn? ASCII " " 포맷 변경 + PiezoMonitoring 진입 시 강제 polling
사용자 검증: ble.log에 rsn 응답이 보이지만 도넛차트는 0% 표시.

원인 1 — msn? 송신 포맷:
  기존: CRC16.buildCommandBE("msn", intArrayOf(0))
  → bytes: 6D 73 6E 3F 00 00 03 FE (BE param 2B)
  → 펌웨어가 응답 안 함 (rsn: 자체가 안 옴)

  다른 query 명령(mid?, mtb?, maa?, mqq?, mag?)은 모두 ASCII " " 포맷.
  msn?만 BE param 포맷이라 펌웨어가 인식 못 한 듯.

  수정: CRC16.buildCommandASCII("msn", " ")
  → bytes: 6D 73 6E 3F 20 ?? ?? (다른 query와 동일 패턴)

원인 2 — Clinical → Unpair → 새 연결 후 도넛차트 진입 시 batteryLevel=0:
  disconnectAndUnbond()에서 batteryLevel.value=0 reset됨.
  새 BLE 연결의 onDescriptorWrite → startBatteryPolling 호출은 PiezoMonitoring
  진입 전에 이미 완료. PiezoMonitoring의 LaunchedEffect에서 등록하는
  onConnectionStateChanged 콜백은 '상태 변경 시점'에만 트리거되므로
  이미 connected인 상태로 진입하면 호출 안 됨.

  수정: PiezoMonitoring 진입 시 isConnected.value==true이면 명시적으로
  startBatteryPolling() 한 번 더 호출 (idempotent — stopBatteryPolling 후 재시작).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 16:21:41 +09:00
dw.jang 95572ac076 BLE fix: 배터리 polling 실제로 polling — 첫 응답 재시도 + 30s 주기
증상: 연결 직후 가끔 배터리 값이 0%로 남아 표시 안 됨 / 의미 없는 값.

원인: startBatteryPolling()이 이름과 달리 연결 시 1회 msn? 송신만 함.
그 한 번이 priority HIGH 협상 직전 큐로 들어가거나, 직후 mtb?/maa? 측정
루프와 큐 경합으로 손실되면 영원히 batteryLevel.value = 0 으로 남음.
이후 측정 응답에 배터리가 안 들어있어 갱신 기회 없음.

수정: 세 단계 보장 구조로 변경
  1. 즉시 1회 송신 (기존 동작 유지)
  2. 3s 뒤 batteryLevel == 0 이면 최대 2회 재송신 (첫 응답 누락 보강)
  3. 30s 주기 정상 polling 추가 (충전 반영 + 장기 추적)

stopBatteryPolling()도 retry/poll 양쪽 타이머 정리하도록 확장.
끊김 시 isConnected.value == false이면 콜백에서 조기 return하여
좀비 송신 방지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-05 15:37:23 +09:00
dw.jang 2ae5eecb20 Setup: Auto Scan Interval을 별도 화면(AUTO_SCAN_SETUP)으로 분리
사내 피드백: PiezoPersonalization 한 화면에 카드 3개를 모은 게 아니라
MaxCapacity 다음 단계로 Auto Scan Interval 화면을 독립적으로 띄우길 원함.

흐름 변경:
  before: DeviceScan → PiezoPersonalization (MaxCapacity + Catheter + AutoScan) → Placement
  after:  DeviceScan → PiezoPersonalization (MaxCapacity + Catheter) → AutoScanSetup → Placement

변경:
  - PiezoPersonalizationView 원복: 2카드 / verticalScroll 제거 / spacing 64dp 중앙 정렬
  - AutoScanIntervalSetupView 신규 — Bladdy hero + 큰 카드 + Continue 버튼
  - AppScreen.AUTO_SCAN_SETUP enum 추가
  - personalizationComplete() → AUTO_SCAN_SETUP으로 이동
  - autoScanSetupComplete() 신설 — PlacementGuide로 진행, piezoSettings persist
  - backFromPlacementGuide() — setup 흐름이면 AUTO_SCAN_SETUP으로 돌아감
  - MainActivity 라우팅 + Back handler 추가 (AUTO_SCAN_SETUP → PiezoPersonalization)

PiezoMonitoring의 in-monitoring settings 슬라이더는 그대로 유지 — 측정 중에도
같은 PiezoSettings.autoScanIntervalMs 값을 조정 가능.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 16:15:39 +09:00
dw.jang 9b090a4c8e Setup에 Auto Scan Interval 카드 추가 + BLE 끊김 banner 제거 (silent reconnect)
PiezoPersonalizationView:
  - AutoScanIntervalCard 신규 — 600~5000ms 슬라이더 + ±200ms 스테퍼
    기존 MaxVolume / CatheterThreshold 카드와 동일한 톤
  - 3카드가 화면을 넘칠 수 있어 verticalScroll로 전환, spacing 64→24dp

PiezoMonitoringView / VivamyoMonitoringView:
  - 사내 테스트 피드백: BLE 끊김 banner가 자주 튀어 거슬림. BleManager 측
    scheduleAutoReconnect()가 이미 백그라운드로 재연결을 시도하므로 UI
    알림은 불필요.
  - onUnexpectedDisconnect/onReconnectionFailed/onConnectionStateChanged
    콜백에서 banner state 토글 제거. 측정 일시정지 및 batteryPolling 재시작은
    그대로 유지.
  - 미사용 state (showDisconnectionBanner, reconnectionState) + 관련 import
    정리. BleDisconnectionBanner.kt 파일 자체는 후속 사용 가능성 위해 남김.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 14:50:23 +09:00
dw.jang 0185d55f4b PiezoMonitoring fix: 도넛 색깔(urgency)을 표시값과 동일한 maxVolumeMl 기준으로 통일
증상: Single scan 309ml(노란색) 후 Auto scan에서 trimmed mean 280s가 잡히면
도넛 표시값은 maxVolumeMl=309로 유지되는데 색깔만 초록색으로 떨어지는 불일치.

원인: 도넛 progress/표시값은 maxVolumeMl(세션 최대) 기준이고 PiezoMonitoringView
1098 코멘트도 그렇게 명시하는데, bladderLevel.currentLevel(=urgency=색깔)만
lastVolumeMl(가장 최근 trimmed mean) 기반으로 업데이트되어 source mismatch.

수정: bladderLevel 업데이트도 maxVolumeMl 기반으로 통일. 한 번 도달한 충만도
색깔은 auto scan 노이즈로 인한 작은 측정값 변동에 영향받지 않고 유지됨.
resetLevel(카테터 처치) / 화면 재진입 시에만 초기화되는 기존 동작은 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 12:39:55 +09:00
dw.jang 547e834714 Labdb fix: sensor.imu에 IMU FIFO 마지막(최신) 샘플 사용
cycle당 IMU 다수 샘플(FIFO 순)이 오는데, 첫 샘플을 쓰면 piezo 캡처
시점보다 ~240~300ms 전의 오래된 자세값이 labdb에 들어감. 마지막
샘플로 바꿔 piezo 측정 시점과 가장 가까운 자세값이 기록되도록 함.
imu_samples 전체는 그대로 보존.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 11:23:46 +09:00
dw.jang ccd4dcbbc3 Clinical: V4.1 wall detection으로 채널별 ant/post peak 그래프 오버레이
사내 임상 테스트 진행자가 실시간 측정 중 wall peak 위치를 눈으로 확인하고
싶다는 요청 반영. raw 데이터 저장 우선 정책은 그대로 유지하고, 시각화 측면에만
PiezoMonitoring에서 쓰는 V4.1(walldetect.V41Detector) 알고리즘을 사이클마다
호출하여 결과를 채널 그래프 위에 오버레이.

표시:
  - 각 채널 그래프 위 세로선 마커 — ant=green, post=orange (alpha 0.75)
  - 채널 헤더에 'a{antIdx} p{postIdx}' 텍스트 (두 줄 한 칸 차지)
  - V4.1 detection 실패 시(둘 중 하나 null) 마커/텍스트 모두 미표시

동작:
  - 매 mtb 사이클(piezoCollector.onMultiChannelComplete) 시점에 detect 호출
  - SweepInput probe profile은 PiezoMonitoring과 동일 (PiezoHW.presetName/degreeAll)
  - runCatching으로 감싸 detect 실패가 측정 자체에 영향 없도록

저장 데이터는 변경 없음. measurement.json/labdb 업로드 payload는 그대로 raw ADC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 11:13:40 +09:00
dw.jang dadf80a778 BLE: reb 패킷 신구조(210B) 적용 — ch_info 2B + 명시적 채널 식별
펌웨어 업데이트로 reb 응답 패킷이 208B → 210B로 확장됨.
새 payload 앞에 ch_info 2B (= ch_session 1B + ch_num 1B) 필드 추가.

변경 내용:
  reb (old): [tag 4B][num_sample 2B][adc 200B][crc 2B] = 208B
  reb (new): [tag 4B][ch_session 1B][ch_num 1B][num_sample 2B][adc 200B][crc 2B] = 210B

수정 위치:
  - BleManager.kt:970 — 디버그 로그용 sample count 신구조 기반 계산,
    session/ch 정보를 ble.log에 함께 노출
  - PiezoPacketCollector.processRebPacket — 전면 재작성
    * 모든 offset +2 (num_sample → bytes[6..7], ADC → bytes[8..])
    * Endian 감지 첫 ADC 샘플 위치 bytes[8..9]로 이동
    * 채널 저장 방식: 도착 순서(currentChannel++) → ch_num 직접 indexing
    * ch_session 추적 추가 — 같은 session의 6개 reb를 하나의 set으로 묶음
  - processEndPacket(raa:) — 완전성 검사 강화
    * isSetDropped == true → 콜백 안 부르고 폐기
    * channelResults 중 null 존재 (불완전 set) → 폐기
    * 6채널 모두 채워진 경우만 onMultiChannelComplete 호출
    * 폐기/완료 후 currentSetSession=null로 reset → 다음 set 진입 대비
  - red:, ree: — multi-channel 모드에서는 ch_num indexing이라 무시 (single-channel만 유지)
  - currentChannel 필드 제거 (도착 순서 의존 폐기)

호환성:
  - 신버전 펌웨어 전용. 구버전(208B) 단말 미지원.
  - rbb/raa/rim 구조 변동 없음 — 손대지 않음.
  - 온도 센서(TMP235 → IMU register) 변경은 FW 내부일 뿐 클라이언트 포맷 그대로.

무효 패킷 정책:
  - session 불일치 (set 진행 중 다른 session 도착): 이전 set 폐기, 새 set 시작
  - 같은 set 내 duplicate ch_num: isSetDropped=true, raa에서 무콜백 처리
  - invalid ch_num (out of 0..5): set 폐기

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 17:58:38 +09:00
dw.jang c0394f63b7 Clinical: BLE Connected 카드에 Unpair 버튼 추가 — 측정 후 페어링 끊기 편의
PiezoMonitoring의 'Delete' 패턴과 동일: confirm dialog → bleManager.disconnectAndUnbond().
ClinicalHome에 머무르므로 곧바로 BLE 카드가 Not Connected 상태로 전환되어
다음 사람 측정 전에 Connect 버튼 활성화.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 17:46:54 +09:00
dw.jang 010e9acfbd Labdb fix: payload sanitize for PostgreSQL JSONB compatibility
서버 로그에서 두 원인 확인:
  [error] POST /devices/register invalid byte sequence for encoding UTF8: 0x00
  [error] POST /upload/json    unsupported Unicode escape sequence

PostgreSQL JSONB는 NULL escape를 거부 (HTTP 500). BLE rid: 응답에서
US-ASCII 디코딩 후 .trim()은 whitespace만 제거하고 NULL/제어문자는
남기므로, firmware/hw/serial 문자열에 padding NULL이 묻은 채 payload로 직렬화됨.

두 군데 방어:
  1. BleManager.rid: 파싱에서 0x00~0x1F,0x7F를 공백으로 치환 후 trim
  2. LabdbClient.sanitizeForJsonb 추가 — register/uploadJson 직전에
     payload를 재귀적으로 순회하며 모든 string에서 C0 control + DEL 제거
     (탭/개행/캐리지리턴은 보존)

원인 조사: 서버 SSH로 /var/log/labdb.log 직접 확인 +
server.js의 /upload/json 핸들러 코드 검토 결과 서버는 받은 sensor/channels
JSON을 그대로 JSONB 컬럼에 INSERT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 17:30:19 +09:00
dw.jang b7475a3b6f Labdb: debug logging — register payload + HTTP 응답 body 출력
INTERNAL_ERROR(500) 원인 좁히기 위해 LabdbClient에 logcat 추가:
- POST /devices/register 보내는 정확한 payload (TAG=LabdbClient)
- 모든 응답의 HTTP code + raw body (앞 500자)
- IOException 발생 시 스택 trace

logcat 필터: tag:LabdbClient

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 14:41:27 +09:00
dw.jang 3b96e5b4cb Clinical: dataType "001" (내부 임상) + measurement.json/meta.json에 data_type 필드 노출
labdb 자료 구분 코드를 "000"(외부 일반 VesiScan) → "001"(내부 임상 R&D)로
변경. ClinicalSessionStore에 const DATA_TYPE을 두고, meta.json과
measurement.json의 meta 블록에 "data_type": "001"을 함께 기록 — 폴더만
보고도 내부 임상 데이터임을 즉시 식별 가능.

LabdbUploader는 measurement.json의 meta.data_type을 우선 사용하고
없으면 "001" fallback. 향후 dataType이 늘어나도 단일 진실 출처(meta)에서
파생되도록 분리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 12:34:18 +09:00
dw.jang b3d9c16780 Clinical: labdb.medithings.net REST 업로드 통합 (Phase A+B)
End Session 시 measurement.json을 https://labdb.medithings.net/api/v1/upload/json
에 자동 업로드. dataType="000" (VesiScan 6채널). cycle 1개 = record 1개.
testId는 폴더명 그대로 사용 (40자 초과 시 prefix+hash로 축약).

Files:
  - LabdbCredentials: apiKey/deviceId를 EncryptedSharedPreferences에 안전 저장
  - LabdbClient: register/checkStatus/uploadJson + 5가지 에러 분기
    (PendingApproval / Reregister / RateLimit / ApiError / Network)
  - LabdbUploader: measurement.json → payload 변환 + fire-and-forget 업로드
    성공 시 labdb_upload.json, 실패 시 labdb_upload_error.json 마커 작성

ClinicalSessionStore.endMeasurement: cycle>0 + active 상태일 때만 자동 트리거.
ClinicalHomeView: labdb 상태 카드(Register/Refresh) + 직전 측정 업로드 상태
표시 + 수동 Upload 재시도 버튼.

오프라인 큐는 아직 없음 (Phase C는 후속).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 12:31:56 +09:00
dw.jang 6f9ce00c65 Clinical: measurement.json 통합 저장 (사이클 누적 + End 시점 finalize)
ClinicalSessionStore에 MeasurementCycle 누적 버퍼 추가. End Session 시
6채널 piezo + 15 IMU 샘플을 단일 measurement.json으로 묶어 저장하여
분석 PC에서 한 줄(json.load) 로드 가능. 기존 CSV 로거는 유지.

ClinicalLiveView는 pendingPiezo 패턴으로 piezo(reb×6+raa)와
imu(rim)를 한 cycle로 묶어 addCycle() 호출.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 12:18:43 +09:00
dw.jang 38fa93c8b2 feat: 임상 Live 6채널 시각화 화면 (ClinicalLiveView)
vesiscan_test Section 2 프로토콜 패턴을 안드로이드 앱 안에서 직접 구현.
실제 몸에 센서 붙인 채 6채널 실시간 그래프 보면서 측정 + 저장 가능.

신규 화면 ClinicalLiveView:
- 600ms 간격 mtb? 자동 송신 (Piezo 6ch + IMU 동시)
- 2×3 grid 라이브 차트 (raw ADC waveform, CH0~CH5 색상별)
  · y축 0~4095 (12-bit ADC) 고정 + 가로 grid 4분할
  · 각 채널 peak 값 표시
- IMU 카드 — ImuAnalysis 기반 자세 + tilt + shake (흔들림 시 빨강 경고)
- 상단: 세션 라벨 + S=Subject TV=TrueVol Ab=Abdomen 한 줄 + #cycle ✓captured
- 컨트롤: Pause/Resume · Capture (수동 저장) · Auto Capture 토글 · End Session

ClinicalSession 모델 확장:
- subjectId / trueVolumeMl / abdomenThicknessMm 필드 추가
- folderName 에 subjectId prefix (예: "S001_VBT26050202_SUPINE_ALIGN_0CM_...")
- meta.json 에 subject_id / true_volume_ml / abdomen_thickness_mm 자동 기록

ClinicalSessionStore:
- lastSubjectId / lastTrueVolumeMl / lastAbdomenThicknessMm 기억 (다음 측정 자동 채움)

ClinicalHomeView 입력 폼 확장:
- Subject ID 텍스트필드
- True Volume (mL) + Abdomen (mm) 2-컬럼 숫자 입력
- Start Live Measurement 버튼 → CLINICAL_LIVE 화면으로 직행
  (이전: alignment/BV step에 따라 PlacementGuide/PiezoMonitoring 분기)

AppState/MainActivity:
- AppScreen.CLINICAL_LIVE 추가
- MainActivity Back: LIVE 시 endMeasurement + ClinicalHome 복귀

데이터 흐름:
  Start Live → ClinicalLiveView 진입 → mtb? 600ms loop
    → piezoCollector → 6채널 buffer (자동 또는 수동 capture)
    → AdcCsvLogger.log → adc.csv 누적
    → imuCollector → 15 IMU samples
    → ImuCsvLogger.log → imu.csv 누적
  End/Back → endMeasurement → meta.json finalize → ClinicalHome 복귀

장점:
- 검사자가 실제 시연 중 6채널 신호 품질 즉시 확인
- Auto Capture로 매 사이클 자동 저장 (Pause로 일시 멈춤)
- subject_id 기반 폴더명으로 피험자별 grouping 자연스럽게

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 11:17:55 +09:00
dw.jang ec3be6fa5d feat: Sensor Alignment에 IMU 통합 (mtb? + rim: 정식 처리)
PPT 'VesiScan-Basic IMU FW 진행사항_260521' 사양 기반 구현.
실측 1회 (2026-05-27 12:24, 평평한 책상)로 endian/필드 순서/시간 순서 확정 후 정식 적용.

확정 사양 (실측):
- rim: 패킷 188B = [tag 4B "rim:"] [num 2B BE] [data 180B = 15 samples × 12B] [crc 2B]
- 1 sample = 12B: Ax 2B + Ay 2B + Az 2B + Gx 2B + Gy 2B + Gz 2B (모두 BE int16)
- Accel ±4g sensitivity: /8192 g/LSB
- Gyro ±500 dps sensitivity: /65.536 dps/LSB
- 샘플 순서: samples[0] = oldest (FIFO 순)

신규 파일:
- ble/ImuPacketCollector.kt — rim: 파서. ImuSample data class + 15-sample list 콜백
- services/ImuCsvLogger.kt — Clinical 모드 한정 imu.csv 자동 저장
  컬럼: scan_id, timestamp, clinical_label, firmware_version, sample_idx, ax_g, ay_g, az_g, gx_dps, gy_dps, gz_dps
- managers/ImuAnalysis.kt — 자세 추정/Tilt/흔들림 RMS 유틸 (간이, 임상 보정 필요)

수정:
- BleManager: imuCollector 필드 + sendMtb() 정식 명령
  (canSendMaa() 게이트 maa와 공유. piezo + IMU 동시 캡처)
- BleManager: rim: 핸들러 정식화 (imuCollector.parseRim 위임)
- PlacementGuideView: sendAllChannels → sendMtb 교체, imuCollector.onComplete 등록
  Clinical 모드일 때 ImuCsvLogger.log() 자동 호출
  Dev 모드 한정 상단 칩에 "IMU: Supine · tilt=N° · shake=N.Ndps" 표시

데이터 흐름:
  performScan → sendMtb? → 응답:
    1) reb × 6 (Piezo, 기존 처리)
    2) raa (Piezo end, 기존 onMultiChannelComplete 트리거)
    3) rim (IMU, 신규 imuCollector.onComplete → lastImuSamples 갱신 + imu.csv)

분석:
- ImuAnalysis.estimatePosture: Accel 평균으로 Supine/Sitting/Standing/Tilted/Moving 판정
- shakeRmsDps > 30 → "Moving" (자세 판정 보류)
- tiltDegrees: z축 vs 중력 각도

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:19:20 +09:00
dw.jang 020a45ba92 feat: 펌웨어 버전을 모든 로그 파일에 직접 노출
기존: meta.json에만 기록 (파일 단독으로 보면 펌웨어 확인 불가)
변경: adc.csv 컬럼 / placements.jsonl·events.jsonl 필드 / ble.log 헤더 블록 모두 노출

검사자/분석자가 각 파일 단독으로 열어도 어느 펌웨어 데이터인지 즉시 확인 가능.

변경:
- BleManager.current: companion에 instance accessor 추가 (logger 같은 context-free 컴포넌트에서 BLE state 읽기용)
- BleManager rid: 핸들러에서 debugLogger.deviceInfo(fw, hw, sn) 호출 (Headers 블록 기록)
- AdcCsvLogger: CSV 헤더에 firmware_version 컬럼 추가, 모든 행에 값 채움
  scan_id,timestamp,clinical_label,firmware_version,volume_ml,lr_ratio,...
- MeasurementLogService.logMeasurement / logPlacement: JSON에 firmware_version 필드 추가
- BleDebugLogger.deviceInfo(): 새 메서드. rid: 응답 받은 후 ble.log에 다음 블록 기록:
  ─── Device Info ───
  Firmware:  VBTFW0116
  Hardware:  VBTHW0100
  Serial:    VBT26040001
  ───────────────────

BLE 미연결 / mid 응답 안 옴 시엔 필드/값 비어있음 (정상). 응답 받자마자 자동 갱신.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:19:18 +09:00
dw.jang de3177e5e0 feat: ClinicalSession meta.json에 firmware/hardware/serial 자동 기록
R&D 분석 시 펌웨어 버전별 데이터 grouping 가능 (VBTFW0111 vs VBTFW0116 등).

writeMeta()에서 BleManager 인스턴스의 firmwareVersion/hardwareVersion/serialNumber
state를 읽어 meta.json에 기록:
- "firmware_version": "VBTFW0116"
- "hardware_version": "VBTHW0100"
- "serial_number": "VBT26040001"

값이 비어있으면(연결 안 됨/응답 안 옴) 필드 생략. 측정 종료 시점(endMeasurement)에
한 번 더 writeMeta 호출되므로 측정 중 BLE 응답 받으면 자동 갱신됨.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:09:22 +09:00
dw.jang e1fa0d1620 feat: 펌웨어 버전 앱 내 표시 (Settings + ClinicalHome)
연결 시 자동으로 받던 mid?/rid: device_info를 UI에 노출. 이전에는 BLE 디버그 로그에만
기록되어 사용자가 접근 불가했음.

BleManager:
- firmwareVersion / hardwareVersion / serialNumber 3개 MutableState 추가
- rid: 응답 파싱 — "VBTHW0100   VBT26040001 VBTFW0111" 형식을
  공백 split + prefix 매칭(VBTHW/VBTFW/VBT)으로 안전 추출
- disconnect 시 3 필드 모두 clear

PiezoMonitoringView Settings overlay:
- 기존 "Version" 행을 "App" + "Firmware" 두 행으로 분리
- FW 없으면 "—" 표시 (연결 대기 중)
- dev 모드 한정 "Serial" 행 추가 (회사 자산 추적용)

ClinicalHomeView BLE 연결 카드:
- deviceName 아래 "FW: VBTFW0116" 한 줄 추가
- 검사자가 별도 클릭 없이 펌웨어 버전 즉시 확인 가능

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 09:41:59 +09:00
dw.jang 3b8fc9ca5d UI: Sensor Alignment 방향 화살표 4종 복원 (사용자 요청)
빌드 111(e0f92eb)에서 의도적으로 제거했던 4방향 화살표(↑↓←→)를
사용자 요청으로 복원. directionIcon에 따라 센서/타원 바깥에 그려짐.
Hint 텍스트와 함께 시각적 방향 안내 제공.

색상 #FF5722 · arrowLen 70f · sw 9f (Charles 833c110 원본 사양 그대로)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:51:40 +09:00
dw.jang 652a123aec revert: PlacementGuide Skip 버튼 복원
Phase 3 #12에서 Dev 모드 한정으로 격리했던 Skip 버튼을 원본대로 복원.
일반 사용에서도 필요한 기능이었음. 임상 안전 우려는 별도 방식으로 해결 필요.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:40:36 +09:00
dw.jang 83f63c62ca UX Phase 3: Polish — 라벨/안전/실루엣 정리
스크린샷에서 발견된 디테일 4건 fix.

9. Status badge 임상 모드 분기
   - 일반 모드: urgency.message ("Enjoy your day!" 등) 그대로
   - 임상 모드: "Supine · 0 cm" 같은 측정 라벨 표시
   - 임상 시연 시 캐주얼 메시지가 부적절했던 문제 해결
   - 검사자가 현재 어느 condition인지 즉시 인지 가능

10. 카테터 카운트에 "left" 라벨 추가
    - 이전: 아이콘 + 숫자만 (의미 모호 — 잔여? 추가? 누적?)
    - 새로: 아이콘 + 숫자 + "left" 텍스트 (10×fontScale, 0.7 alpha)
    - 한눈에 "잔여 카테터" 의미 전달

11. 토르소 실루엣 모래시계 완화
    - chestW 0.42 → 0.40 (어깨 살짝 축소)
    - waistW 0.32 → 0.36 (허리 덜 잘록 — 다리처럼 보이던 원인)
    - hipW 0.46 → 0.42 (골반 살짝 축소)
    - 결과: 더 자연스러운 토르소 형태

12. Skip 버튼 Dev 모드 한정
    - 이전: 항상 표시 (임상에선 정렬 우회 위험)
    - 새로: appState.isDevMode일 때만 표시 + "Skip (Dev)" 라벨 + 크기 ↓
    - 폰트 15→13sp, alpha 0.6 — 의도된 dev 도구임을 시각적으로 명시

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:40:36 +09:00
dw.jang 52bd58ac87 UX Phase 2: Important — 위계/일관성 회복
스크린샷에서 발견된 시각 위계/정보 비대칭 4건 fix.

5. Voiding 버튼 fill 격상 (가장 자주 누르는 액션을 primary로)
   - containerColor: MlPrimary.copy(alpha=0.1f) → MlPrimary 솔리드
   - text color: MlPrimary → White
   - Auto Scan(주황) / Single Scan(청록)과 동급 강도로 일관성 회복
   - 환자가 가장 자주 누르는 액션이 옅게 디자인되어 있던 문제 해결

6. InfoCard 우측 라벨 명시 ("Session Max")
   - 이전: title="X mL / Y mL" + value="65%" (라벨 없음, 의미 모호)
   - 새로: title="Session Max", value="X" + unit="mL"
   - 좌측 "Current"와 같은 톤. fill % 정보는 도넛이 이미 시각화 중이라 카드는 raw 숫자만
   - 좌측 title도 "Current Measurement" → "Current"로 단순화

7. Alignment Hint 박스 톤다운 (3중 강조 해소)
   - fontSize 28sp → 20sp
   - padding horizontal 20→16dp, vertical 18→12dp
   - 배경 alpha 1.0 → 0.7
   - corner 16→14dp
   - "pubic bone" 빨강 강조는 유지 (의도된 1점 강조)

8. Step 1/3 도트 indicator 추가 (텍스트 한 줄 → 시각 progress)
   - 이전: 13sp 회색 텍스트 한 줄 (정보 위계 너무 약함)
   - 새로: 14sp Bold MlPrimary + 3-dot indicator
     ○ 완료된 step: MlPrimary 0.6 alpha, 8dp
     ● 현재 step: MlPrimary 솔리드, 12dp (강조)
     ○ 미완료 step: 회색 0.3 alpha, 8dp

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:39:46 +09:00
dw.jang 59267866b4 UX Phase 1: Critical — 폰트/버튼/타이틀 비례 회복
스크린샷에서 발견된 시각 위계 깨짐 4건 일괄 fix.

1. Monitoring "0 mL" 폰트 축소 (도넛 비례 회복)
   - volFontSize: screenWidth × 0.18 cap 96 → screenWidth × 0.12 cap 60
   - Bladdy↔숫자 spacer 63dp → 36dp (수직 균형 회복)
   - 효과: 도넛 안에 Bladdy + 숫자가 적절한 비율로 들어감

2. Voiding 버튼 텍스트 단순화
   - "Voiding/" + "Catheterization" (2줄, 좁은 폭에서 3줄로 깨짐)
     → "Voiding" 단일 라벨 1줄
   - 폰트도 (13×fs) → (14×fs)로 살짝 키워 가독성 확보

3. PlacementGuide 헤더 타이틀 중복 제거
   - 헤더의 작은 "Sensor Alignment" 20sp 제거
   - 본문 큰 타이틀과 중복되어 시각적 잡음이었음

4. PlacementGuide 큰 타이틀 38sp → 28sp
   - ExtraBold → Bold로 weight도 한 단계 낮춤
   - 화면 1/8 차지하던 과한 강조 해소, Hint 박스와의 위계 균형 회복

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:39:46 +09:00
dw.jang f7a036ec34 CLINICAL: Step에 부가 설명 추가 (치골 위로 N cm)
ClinicalStep enum에 description 필드 추가하고 ClinicalHomeView에서
선택된 Step의 설명을 segmented control 아래에 표시.

설명:
- ALIGN_0CM: 기기 아래 끝이 치골 바로 위
- ALIGN_1CM ~ 4CM: 치골 위로 N cm
- BV: Cradle 부착 후 본측정

UI 표시 위치: Step 라디오 그룹 바로 아래 한 줄, MlPrimary 색상 + 화살표(→).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:38:41 +09:00
dw.jang d0977d98ac CLINICAL: Alignment 화면에서도 임상 모드 시 adc.csv 자동 저장
문제: PlacementGuideView(Sensor Alignment, 정렬모드 0~4cm)에서는 AdcCsvLogger.log()를
호출하지 않아 adc.csv가 안 생성됨. R&D 정렬모드 알고리즘 개발은 raw ADC가 핵심인데
placements.jsonl + ble.log만 남고 ADC가 누락되던 상황.

원인: AdcCsvLogger.log()는 PiezoMonitoringView (BV 측정)에서만 호출됨. PlacementGuide는
미호출. 일반 사용에서 1초마다 도는 placement scan마다 ADC를 저장하면 파일이 폭주하므로
의도적으로 빠져 있었던 것.

변경:
- PlacementGuideView의 onMultiChannelComplete 콜백 끝에 임상 모드 한정 조건부 추가:
  ClinicalSessionStore.currentSession != null 이면 raw ADC를 adc.csv에 저장
- 일반 사용 흐름은 영향 없음 (currentSession == null이므로 기존 동작 그대로)
- 임상 모드에선 한 alignment 측정(예: 30초)당 ~30-50개 scan이 adc.csv에 누적됨

부차적 fix:
- MainActivity.kt:87 typo 수정 (androidx.com+-pose → androidx.compose)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:38:41 +09:00
dw.jang 3080cd6b23 CLINICAL: Subject ID / Device 입력 제거 — BLE 기기 이름이 device 식별자
사용자 요청: "subject ID 빼고, Device도 BLE 기기 이름따라 기기가 다른거라
따로 설정안해도 될듯."

변경:
- ClinicalSession 모델: subjectId 필드 제거, ClinicalDevice enum 제거,
  device: ClinicalDevice → deviceName: String (BLE 연결된 기기 이름 자동 채움)
- folderName: "{deviceName}_{posture}_{step}_{timestamp}"
  예: "VBT26050202_SUPINE_ALIGN_0CM_2026-05-28_14-30-15"
- label: "{deviceName}_{posture}_{step}" (예: "VBT26050202_SUPINE_ALIGN_0CM")
- ClinicalSessionStore: lastSubjectId 제거 (Examiner/Notes만 기억)
- ClinicalHomeView:
  * Subject ID TextField 제거
  * Device segmented control 제거
  * BLE 연결 카드에 deviceName 그대로 표시 (자동 사용)
  * Start 버튼 enabled = isConnected 만으로 충분

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:38:40 +09:00
dw.jang ab25b3c65d CLINICAL: 흐름 재설계 — 한 측정 = 한 라벨 = 한 폴더
이전: 한 세션이 9 conditions × 6 steps를 모두 묶고 마지막에 zip
새로: 한 측정마다 라벨 입력 → 측정 → 자동 finalize → 다음 측정 라벨

사용자 요청: "session 들어가면 일단 BLE 연결을 하고, 연결 이후에 내가 어떤 자세로
어느 위치 측정할건지를 직접 적고 테스트를 완료한 뒤에 로그가 저장되도록"

변경:
- ClinicalSession 모델: 단일 측정 단위로 재정의 (subjectId/device/posture/step
  /examiner/notes/startedAt/endedAt). folderName에 라벨 전체 + timestamp 포함:
  "S001_A_SUPINE_ALIGN_0CM_2026-05-28_14-30-15"
- ClinicalSessionStore: 단순화. zip 제거. startMeasurement/endMeasurement.
  inClinicalFlow boolean 추가 (DeviceScan/Back 라우팅용).
  lastSubjectId/Examiner/Notes 기억 (다음 측정 시 자동 채움).
  lastFinalizedFolder (UI에 "Saved: ..." 표시용).
- 3 로거: stepLogDir() → currentLogDir() 단순화. 한 폴더에 모든 파일.
- ClinicalHomeView 전면 재작성:
  * BLE 연결 상태 카드 (미연결 시 Connect 버튼 → DeviceScan)
  * 직전 측정 결과 표시 (lastFinalizedFolder)
  * 라벨 입력 폼 (Subject + Device/Posture/Step segmented control + Examiner + Notes)
  * 라벨 preview 실시간
  * Start Measurement 버튼 (BLE 연결 + Subject ID 있을 때만 활성)
- AppState.deviceConnected: 임상 흐름이면 ClinicalHome 복귀 (Personalization 우회)
- MainActivity Back handler:
  * DEVICE_SCAN Back → inClinicalFlow면 ClinicalHome
  * PLACEMENT_GUIDE/PIEZO_MONITORING Back → 활성 세션이면 endMeasurement +
    ClinicalHome 복귀 (폴더 finalize)
  * CLINICAL_HOME Back → inClinicalFlow 해제 + HOME

데이터 구조 (한 측정당):
  Downloads/VesiScan_Sessions/{subjectId}_{device}_{posture}_{step}_{timestamp}/
    ├─ meta.json   (라벨 + 앱/펌웨어/디바이스 버전 + 시작/종료 시각)
    ├─ adc.csv
    ├─ placements.jsonl (alignment) or events.jsonl (BV)
    └─ ble.log

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:38:40 +09:00
dw.jang 6e2dba749b CLINICAL: Dev 모드 한정 임상 R&D 측정 세션 메뉴 추가
회사 내 알고리즘 개발용 임상 시험(3 devices × 3 postures × 6 steps = 54 measurements
per subject)을 위한 데이터 라벨링/라우팅/zip 자동화. Dev 모드 켠 경우에만 노출.

신규 파일:
- models/ClinicalSession.kt — ClinicalDevice/Posture/Step 모델 + 완료 추적
- services/ClinicalSessionStore.kt — 전역 세션 store + 폴더 라우팅 + meta.json + zip
- ui/views/clinical/ClinicalHomeView.kt — 세션 메타 입력 다이얼로그 + 9-conditions
  그리드 + 6 steps + Mark Complete + End Session(zip) 화면

로거 라우팅 (세션 존재 시 자동 분기):
- AdcCsvLogger: sessionRoot/{device}_{posture}/{step}/adc.csv +
  CSV에 clinical_label 컬럼 추가 (S001_A_SUPINE_ALIGN_0CM 형태)
- MeasurementLogService: 내부 filesDir → Downloads/VesiScan_Events/ 영구 이동.
  세션 시 step 폴더의 events.jsonl/placements.jsonl로 분기.
  BuildConfig 대신 PackageInfo로 versionName/versionCode 정확 주입.
  보관 90일 → 365일 (R&D 장기 보관)
- BleDebugLogger: step 변경 감지해서 ble.log 파일 자동 전환

UI/라우팅:
- AppScreen.CLINICAL_HOME + MainActivity Crossfade/BackHandler 분기
- HomeView에 dev 모드 한정 보라색 "Clinical Mode (Dev)" 버튼
- 측정 화면(PlacementGuide/PiezoMonitoring) Back 시 임상 세션 활성 → ClinicalHome 복귀

Placement 구조화:
- PlacementGuideView가 logService.logPlacement() 호출 시 urineLens/cv/phase/score
  추가 인자 전달 → placements.jsonl에 구조화 기록

데이터 구조 (한 세션당):
  Downloads/VesiScan_Sessions/{S001_2026-05-28_14:30}/
    ├─ meta.json
    ├─ A_SUPINE/align_0cm/{adc.csv, placements.jsonl, ble.log}
    ├─ A_SUPINE/align_1cm/...
    ├─ ...
    ├─ A_SUPINE/bv/{adc.csv, events.jsonl, ble.log}
    ├─ A_SITTING/... (총 9 conditions × 6 steps)
    └─ session.zip (End Session 시 자동 생성)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:38:40 +09:00
dw.jang 63bb8586ec SETTINGS: Auto Scan Interval 슬라이더 추가 (600~5000ms, 200ms 단위)
기존 hardcoded 600ms를 사용자가 Settings에서 조절 가능하도록 변경.

추가:
- PiezoSettings.autoScanIntervalMs (default 600ms) 필드
- AppState.savePiezoSettings() 헬퍼 — storage에 piezoSettings 영속화
- Settings 패널에 슬라이더 (Catheter Threshold 다음, Bladder Capacity와 동일 톤)
  - 범위 600~5000ms, 200ms 단위로 snap (roundToInt + 200 배수)
  - 현재값 mL 표시 패턴과 동일하게 "X ms" 우측 표시
- Settings Save 버튼이 실제로 영속화하도록 savePiezoSettings() 호출 추가
  (이전에는 showSettings=false만 호출 — maxVolume 등도 메모리만 변경되던 버그 동시 fix)

동작:
- PiezoMonitor Auto loop의 remaining = targetIntervalMs - elapsed
- BleManager throttle(600ms)이 하한이라 600 미만 설정해도 막힘 — 의미 있는 최소가 600
- 5000ms로 설정 시 거의 정적 모니터링 (배터리/CPU 절약)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:38:40 +09:00
dw.jang 31e9b8acbb UI: 설정 패널 폰/태블릿 자동 분기 — fontScale 활용
태블릿 시연용으로 키운 28sp가 폰에서 곱창나는 문제 해결.
이미 코드에 정의된 fontScale = (screenWidth / 360f).coerceIn(1.0, 1.8)를
활용해 자동 분기.

- 폰(360dp): 14sp × 1.0 = 14sp (원본)
- 태블릿(648dp+): 14sp × 1.8 = 25.2sp (시연 시인성)
- HorizontalDivider padding: 태블릿(≥600dp)에서만 vertical=12dp

변경:
- showSettings 블록에 isTablet/labelSize/rowSpacing/dividerPadding 변수 도입
- 9개 라벨 + 일부 값 fontSize = 14.sp → labelSize
- 9개 HorizontalDivider에 dividerPadding 적용
- Column spacedBy 6.dp → rowSpacing (폰 6dp, 태블릿 ~10dp)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:36:52 +09:00
dw.jang e716763cc4 UI revert: PiezoMonitoring 설정 패널 라벨 28sp → 14sp 복원
문서 빌드 117 묘사를 그대로 적용한 28sp가 실제 화면에서 너무 커서
Bladder Capacity / Catheter Threshold 라벨이 줄바꿈되고 divider padding
28dp가 카드를 과도하게 늘리는 곱창 결과 발생. 원본 14sp + divider padding
없음으로 일괄 되돌림.

- 9개 라벨 fontSize 28sp → 14sp
- 9개 HorizontalDivider modifier 제거 (padding 28dp 삭제)
- Column verticalArrangement.spacedBy 9.dp → 6.dp

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:35:50 +09:00
dw.jang e370d9c702 PERF: 측정 사이클 간격 1500/1000ms → 600ms 단축
MTU 247 + CONN_PRIORITY HIGH 적용 후 한 사이클 응답이 ~280~420ms로 단축됨
(이전 ~1.2~1.4s 대비 약 4배). 응답 시간 대비 과도하게 보수적이었던
throttle/loop delay를 일괄 단축.

변경:
- BleManager.canSendMaa() 시간 게이트 1500ms → 600ms (응답 평균 + 안전 마진 ~200ms)
- BleManager.canSendMaa() FORCE 게이트 5000ms → 3000ms (응답 빨라진 만큼 deadlock 감지도 빠르게)
- PiezoMonitor Auto loop delay 1500ms → 600ms
- PlacementGuide continuous scan loop delay 1000ms → 600ms

state-based gate(!isComplete)가 안전망 역할을 하므로 응답이 늦어지면
자동으로 차단되어 데이터 유실 위험은 그대로. 정상 경우엔 사이클당 ~0.6~0.7s로
실시간 모니터링 반응성 2배 향상.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:34:51 +09:00
dw.jang c1fac51085 BLE: 연결 직후 CONNECTION_PRIORITY_HIGH 요청
VBTFW0116 펌웨어가 pending slot 1→8로 확장해 TX queue 포화 시
패킷 보관/재전송으로 ADC 유실을 줄였음. 안드로이드 측에서는 Central의
Link Layer ACK 속도를 끌어올려 펌웨어 TX queue 포화 빈도 자체를 낮추는
방향이 가장 효과적.

CCCD write 완료(서비스 ready 시점)에 gatt.requestConnectionPriority(HIGH)
호출. peripheral이 수락하면 connection interval이 ~15ms로 협상되어
한 사이클 응답 시간 단축 가능. 거부되어도 패널티 없음.

실제 적용 여부는 sniffer로만 확인 가능. logcat의 "CONN_PRIORITY HIGH
requested (ok=...)" 로 호출 성공 여부만 확인 가능.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:34:51 +09:00
dw.jang cac3712b8c BLE: maa throttle을 state-based 게이트로 강화
이전 응답이 완료되기 전에 새 maa가 송신되면 piezoCollector.startMultiChannel(6)이
호출되며 collector가 reset → 큐에 떠다니던 reb 패킷이 폐기되어 BV_FAIL/BV_SKIP가
발생하던 문제 대응.

변경:
- 시간 단독 게이트(800ms) → state + 시간 복합 게이트
- (A) piezoCollector.isMultiChannel && !isComplete 이면 차단
       단 5초 넘으면 강제 통과(영영 deadlock 방지)
- (B) 최소 간격 800ms → 1500ms (펌웨어 응답 시간 ~1.2~1.4s 반영)
- sendAllChannels/sendChannelsOnly가 공유하는 canSendMaa() 헬퍼로 일원화

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:34:51 +09:00
dw.jang d507b1f447 UI: PiezoMonitoring 누적 업데이트 (108-122)
- 빌드 108: "Void/" → "Voiding/", Toast "Recorded" → 48sp Dialog 오버레이 (1.5초 자동)
- 빌드 109: Donut 캡 제거, 너비×0.85 적응형 (dev 모드는 0.65)
- 빌드 110: useScrollLayout = isDevMode && showDevPanels — 패널 표시될 때만 스크롤 레이아웃
- 빌드 111+115: Volume Text 24→72sp (3× ExtraBold, letterSpacing -1), Bladdy 0.85→0.483, 흰 bg 0.85→0.78, scroll 40/55→46/63dp
- 빌드 113: Voiding 시 Auto Scan 자동 stop + displayMaxVolumeMl/window/timer 정규화
- 빌드 114: Status 배지 dev 모드 한정 탭 → 60~maxVol-50 더미 측정값 랜덤 주입
- 빌드 117: 설정 패널 9개 라벨 14sp→28sp, 행간 spacedBy 6→9dp, HorizontalDivider 9개 padding(vertical=28dp)
- 빌드 122: 측정 상단 좌측 Home 아이콘 추가 (Placement 옆), Placement 진입은 enterPlacementFromMonitoring() 경유

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:34:14 +09:00
dw.jang 898fb77605 UI: PlacementGuide(Sensor Alignment) 리디자인
빌드 107-121 누적 적용:
- 3-stop vertical gradient 배경 (MlTeal 14% / 흰 / MlPrimary 10%) — GREEN 진입 시 단색 톤으로 덮임
- 큰 "Sensor Alignment" 타이틀 38sp ExtraBold 검정 (Maximum Bladder Capacity 톤 통일)
- Hint 텍스트 "pubic bone" 인라인 빨강 ExtraBold 강조 (AnnotatedString)
- Canvas 방향 화살표 4종 제거 — hint 텍스트로만 방향 안내
- Canvas 치골 아치 라인+halo+endpoint 제거 — pubic bone 강조는 hint 인라인 1곳에만
- 실루엣 chestY 16% UP shift (0.06 → 0.09, 비례 유지)
- Body 상단 Spacer 8dp

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:30:50 +09:00
dw.jang 3e1a875b5b UI: PiezoPersonalizationView 전면 리디자인 — 평행 2-카드
빌드 107-119 적용:
- Bladdy 히어로 BoxWithConstraints 동적 사이징, 카드 영역 수직 중앙 정렬
- Card 1 "Maximum Bladder Capacity" 34sp ExtraBold + 500/mL (64+24sp) + 56dp 솔리드 StepperButton + 슬라이더
- Card 2 "Catheter Threshold" 34sp ExtraBold + Level/7/10 3분할(24+64+24sp) + StepperButton + Level 1~10 슬라이더
- 부제 모두 제거, 섹션 간격 64dp
- Slider 값 부동소수점 절삭 버그 fix: it.toInt() → it.roundToInt()

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:28:59 +09:00
dw.jang 975be77126 UI: HomeView Bladdy 220 → 660dp (3×) + BoxWithConstraints 캡
빌드 114 적용: Home의 Bladdy 히어로를 3배 확대하고 BoxWithConstraints로
가용 영역의 90% 캡을 적용해 작은 화면에서 overflow가 나지 않도록 함.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:28:59 +09:00
dw.jang cb8500f6ce ROUTING: Placement 진입 경로 분기 (Personalization vs Monitoring)
빌드 106 적용: placementFromMonitoring 플래그 + enterPlacementFromMonitoring() /
backFromPlacementGuide() 헬퍼 추가. Personalization → Placement → Monitoring 정방향과
Monitoring → Placement 재진입 경로에서 Back 동작이 올바르게 분기되도록 일괄 정리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:28:59 +09:00
dw.jang 34bb446e0f DEV: 측정 모드 직진입 버튼 + Start 라우팅 fix
빌드 104-105 적용:
- DeviceScanView "No devices found" 화면에 dev 모드 한정 "Enter Measurement Mode" 버튼 추가 (BLE 없이 디자인 작업용)
- AppState.startFromHome() 라우팅 fix — 페어링 후 Personalization → Back → HOME → Start 시 Monitoring 직행 버그를 PIEZO_PERSONALIZATION 부터 시작하도록 변경

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:28:59 +09:00
dw.jang 38c5a3f606 DEMO: PinView 자동통과 + UI 본체 비표시
빌드 101-103 적용: PIN 화면 진입 시 LaunchedEffect로 즉시 appState.pinVerified()
호출(PIN 저장 안 함). 화면 본체는 if(false) {...}로 비표시 — 빈 MlBackground Box만 노출.
원본 UI는 보존하여 데모 후 깨끗한 롤백 가능.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:28:58 +09:00
Charles KWON 833c110d2f feat: 측정 가로모드 대응 + Ant Offset + Scan Log + 다중 sanity check
## 측정 화면 (PiezoMonitoringView)
- BoxWithConstraints + donutFitSize: 가로/작은 화면에서도 donut+Bladdy+buttons
  모두 들어오도록 height-aware sizing (cap 120~360dp)
- nonDevSpacing: available height 기반 단계 (18/22/28/36) — 큰 화면 답답함 해소
- center 4 중 1개라도 missing → 즉시 realign prompt (이전: AutoMeasure 5회 누적)
- 6채널 중 3개 이상 missing 시 위치 이탈 알림 (모드 무관)
- 다이얼로그 문구: "Position Lost or Insufficient Gel"
- V41 결과 후처리 — walls를 PiezoEchoAnalyzer 기준으로 강제 (UI 차트와 일관)
  · ant = analyzer.result.ant
  · post = analyzer.result.lowEnd + 3
- CH3 secondary echo 잡힘 / CH4·CH5 lateral ant too-deep 문제 해결
- Bladdy 3-tap → Developer Mode toggle (측정 화면에서도)

## Placement 가이드 (PlacementGuideView)
- 가로 모드 대응: bodyW = min(w, h*0.7f) — 태블릿 가로에서 portrait 비율 유지
- Sensor 이미지를 Canvas drawImage 로 이동 — bladder oval 좌표계와 정확히 정렬
- Start Alignment 버튼: enabled = true 명시

## Settings overlay
- "Ant Offset" 숫자 입력 필드 (0~20mm, dev mode 전용)
  · BV 계산 시 ant 를 입력값만큼 probe 쪽으로 당김 (chord 증가 → BV 증가)
  · mm→samples 변환은 PiezoHW.distancePerSample
  · V41 SphereFit BV 는 offset≠0 일 때 우회 → 6ch estimate
- "Version" row 추가 (BuildConfig.VERSION_NAME)
- Save 버튼: 실제 UserStorage 영구 저장 + state 재전파
  · AppState.savePiezoSettingsNow() 추가
- 전체 overlay 에 verticalScroll + Card maxHeight 90% — dev 컨텐츠 길어도 Save 도달

## DPS 동기화 (긴급 수정)
설정의 DPS 변경이 V41Detector(Geometry/AnatomicalGate)에 반영 안 되던 문제:
- WdConfig.DPS_DEFAULT: const val → @Volatile @JvmField var
- PiezoHW.distancePerSample setter 가 WdConfig.DPS_DEFAULT 자동 동기화
- 시작 시점 1.968.also { WdConfig.DPS_DEFAULT = it } 로 두 값 일치

## 버전
- versionName: VBTAND0101 → 2026005-101
- versionCode: 2 → 3

## 신규 모듈
- services/ScanLogStore.kt — dev mode 진단 로그 (마지막 Single Scan summary)
  · WALLS CSV (ch,ant,post,urine), DATA CSV (raw ADC), antOffsetMm
- ui/components/ScanLogDialog.kt — 클립보드 복사 다이얼로그
  · xbench (CharlesKWONsLaw) MbbLogDialog 패턴 차용
  · SelectionContainer + monospace + Copy/Clear/Close
- 측정 화면 dev panel 에 "Scan Log (N)" 버튼 추가

## 데이터 모델
- PiezoSettings.antOffsetMm: Int = 0 필드 추가

## 단위 테스트
- V41DetectorCH4Test.kt — 2026-05-06 floating-signal 캡쳐 6채널 raw 를
  V41Detector.detect 에 통과시켜 결과 출력 (CH4 가짜 wall 진단용)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-12 18:01:33 +09:00
dw.jang ef38aa6eaf UI: 앱 아이콘 교체 + Placement 힌트 텍스트 정리
- 앱 아이콘: 방광이.png로 교체 (webp→png, adaptive-icon 제거)
- Placement: "Centering" → "Checking", "LR balanced. Final check" → "Final check"
- 모든 방향 힌트에 화살표(↑↓←→) 추가 — phaseProtected 시 아이콘 추론 보장
- 디바운스 중에도 directionIcon은 최신 guideResult로 갱신 (화살표 누락 수정)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-11 17:21:10 +09:00