Commit Graph

456 Commits

Author SHA1 Message Date
dw.jang 889c65b038 feat(alignment): 데모 CENTER_OPTIMIZE phase 추가 — 사용자앱 (a2fc1d9) 동기화
CH0~CH3 chord CV 로 최적 vertical 위치 판정하는 신규 phase.
V1 GREEN 판정 CV=0.15 실증값을 5-phase 에 이식.

- Phase4 enum: CENTER_OPTIMIZE 추가 (CH3_STABILIZE ↔ LR_BALANCE 사이)
- AlignSummary: chords Array<Double?> (angle-corrected mm) 추가
- summarize(): per-channel chord 계산 (post-ant × cos(angle) × DPS)
- CENTER_OPTIMIZE step 로직:
  * CH3 잃음 → CH3_STABILIZE 복귀
  * CH0/1/2 미검출 → MOVE_UP
  * 4채널 검출 + CV ≤ 0.15 (3 연속) → LR_BALANCE
  * CV > 0.15: chord[3] > chord[0] → MOVE_DOWN, else MOVE_UP
- AlignmentConstants: CV_THRESHOLD_STRICT=0.15, LOOSE=0.20,
  CENTER_OPT_CONFIRM_STREAK=3
- PlacementGuideView: V2 step indicator 5-stage → 6-stage

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-02 09:59:01 +09:00
dw.jang cd3efc5f24 feat(bv): 데모 lr_ratio_override=1.0 default — 사용자앱 (1add642) 동기화
algorithm 팀 최신 표준 (piezophantomtest 4830e7c) 반영.

- GreenZoneConstants.lrRatioOverride: Double? = 1.0 추가 (default 1.0)
- estimateBladderVolume6ch — override 있으면 computeLrRatio 스킵
- Dev panel toggle "lr=1.0 fixed" — ON/OFF 로 fixed vs computed 전환

Phantom 검증 (2533 scans, 150 mL):
- APP (기존): 147.6 mL (-1.6%)  ✓
- Config F (Python NEW lib + lr=1.0): 147.3 mL (-1.8%)  ← 완전 동일

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-01 11:18:57 +09:00
dw.jang 29cda408a4 feat(alignment): demo AlignGuide 5-phase 로 확장 — FINAL_CONFIRM 추가
feature/tab-navigation 의 AlignmentAdvisorV2 를 demo 에 포팅 (packages 만 rename).

기존 4-phase (INITIAL_ACCUM → VERTICAL_CLIMB → CH3_STABILIZE → LR_BALANCE)
→ 5-phase (+ FINAL_CONFIRM).

FINAL_CONFIRM phase:
- LR_BALANCE 에서 |u4-u5| ≤ 8 도달 시 진입
- buf.clear() 후 5 fresh cycle 누적
- imbal 여전히 ≤ 8 유지 → 최종 STOP
- 실패 시 LR_BALANCE 로 복귀
- 목적: sliding window 잔여 효과 제거 후 재검증

추가 항목:
- Phase 별 dynamic window (accumKVertical=10, accumKLateral=5, accumKConfirm=5)
- lostStreakRequired=3 hysteresis (LR_BALANCE 채널 깜빡임 방지)
- msgKey / msgArgs (i18n 지원)

Sensor alignment default:
- 일반 사용자 (V1): 기존 3-step 유지 (변경 없음)
- Clinical alignment session (V2): 5-phase 사용

PlacementGuideView step indicator: 4 → 5.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-01 10:10:10 +09:00
dw.jang 252692417a tools: analyze_csv.py — App CSV → Python pipeline 비교/검증 CLI
App 측정 시 자동 저장되는 CSV (Downloads/VesiScan_ADC/*.csv) 를
appshare for_app_share / vesiscan_test.library.method_d 로 재계산해
다음을 비교/시각화하는 영구 분석 툴.

4 modes:
  1. Basic compare: App BV vs Python BV (mean/std/bias/CV)
  2. Ablation: 5 algorithm config (DPS, lr floor 등) 개별 영향
  3. Plot: BV timeseries / app↔py scatter / lr_ratio histogram
  4. Cycle inspect: 특정 scan_id 의 raw → walls → BV detail

설계 의도:
  - 알고리즘 변경 후 회귀 자동 검증
  - 임상 측정 정확도 (catheter ground truth) 비교
  - device-to-device variance 분석
  - lr_ratio 분포로 phantom vs human anatomy 패턴 차이 확인

appshare 경로 자동 탐색 + APPSHARE_DIR env var 지원.

검증: phantom 150 mL CSV (2547 scans) 로 smoke test 통과.
  APP mean=147.6 (-1.6%), Python NEW mean=93.0 (-38%, lr=0.67)
  → ablation 으로 lr_ratio 가 차이의 주범 확인.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-30 17:49:28 +09:00
dw.jang 30296f6d62 docs(bv): BvMethod 선택 가이드 — 사용자앱 (98aee75) 동기화
phantom QC vs 임상 인체 사용 가이드 + lr floor 추가 금지 주석.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-30 16:59:49 +09:00
dw.jang a2d111dc5d feat(detection): 데모 METHOD_D dispatch — 사용자앱 (d354a61) 동기화
DetectionMethod.METHOD_D enum 추가 + when 분기 + UI 4-버튼.
detection + BV 둘 다 Method D 가능. appshare Python pipeline 일치.

- enum DetectionMethod: METHOD_C → +METHOD_D
- when (method) METHOD_D 분기: MethodDRunner.detectMultichannel → allWalls
- BV path: detectionIsMethodD 일 때 recompute 생략
- Dev panel Detection toggle 3-button → 4-button (D 보라색)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-30 15:17:18 +09:00
dw.jang b2546d14ca fix(alignment): LR_BALANCE lost 채널 hysteresis — 사용자앱 (50b8620) 동기화
2026-06-30 실측 분석 (244 cycle alignment session) 결과:
LR_BALANCE phase 170 cycle 중 |u4-u5| 항상 LAT_TOL(8) 이하 (좌우 균형 늘 OK).
하지만 CH1=15%, CH3=12% 가 cycle 마다 노이즈로 깜빡임 → 매번 lost.isNotEmpty()
true → "↑위로" / "↓아래로" 화살표 → 다음 cycle 복구 → STOP 반복.
사용자 체감: "어디로 옮길지 모르겠음".

- lostStreakRequired=3 추가
- 같은 채널이 3 연속 commit 에서 lost 여야 회복 모드 발동
- streak 미달 시 STOP 유지 (noise 로 판단)
- full set 복귀 시 lostStreak 0 리셋

예상 효과: LR_BALANCE 화살표 flap rate 55% → ~5%.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-30 13:47:54 +09:00
dw.jang 886d40333d feat(demo): BV METHOD_D toggle + alignment graph 10-cycle 평균 표시
사용자앱 변경 (e9b5d9a) 을 데모에도 적용 + clinical alignment 그래프 개선.

## BV METHOD_D toggle (사용자앱과 동기화)

- BvMethod.METHOD_D 추가 (FRUSTUM/V41/METHOD_D)
- PiezoMonitoringView: useMethodDBv 분기. METHOD_D 선택 시 MethodDRunner walls,
  그 외엔 legacy analyzer walls. (계산 cost 회피 위해 toggle ON 일 때만 실행)
- Dev panel BV toggle: 3-버튼 (Method D 보라색 0xFF7C3AED)
- ANT_OFFSET verify 로그: 어느 wall source 인지 표시

## Clinical alignment graph 10-cycle 평균 표시

이전: 매 cycle 마다 raw 단일 신호 그래프 갱신 → 알고리즘이 실제 보는 신호
(10-frame 평균) 와 시각 불일치.
수정: V2 활성 시 RollingAligner.lastAvg 노출 → PlacementWaveformChart 가
overrideRaw 로 평균 신호 표시. V1 일 땐 기존 raw 단일 cycle 유지.

- AlignmentAdvisorV2: lastAvg: List<DoubleArray>? 노출 (push 마다 set, reset 시 null)
- PlacementWaveformChart: overrideRaw 파라미터 (null=기존 raw, non-null=avg)
- ChannelGraphItem: V2 일 때 v2Aligner.lastAvg?.getOrNull(ch) 전달

알고리즘 ↔ 그래프 일치. 임상 alignment 평가 시 정확한 신호 시각 가능.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-30 12:09:39 +09:00
dw.jang 9d1e0e62ad feat(posture): 데모 idle 시 msp(IMU-only) polling — 사용자앱 동기화 + walking 검출 유지
사용자앱 (feature/tab-navigation b1aecfc, e3c21e6) 의 msp 전환을 데모에도 적용.
demo 의 walking detector 유지 위해 폴링 주기 조정.

배경:
- 기존: PiezoMonitoring idle 에서 mtb 1Hz 폴링 → 매 cycle piezo burst 가청음.
- 사용자앱 실측 (VBTFW0120) 으로 rsp: 18B 정상 응답 확정. 데모 ImuPacketCollector
  는 이미 parseRsp 가지고 있어 (이전에 defensive 작성) BleManager 가 자동 트리거.

수정:
- GreenZoneConstants.posturePollIntervalMs: 1000 → 200ms (5Hz)
  walking detector window (1.5초) 안에 7~8 sample 확보 — mtb 의 15Hz 등가에는
  못 미치지만 사람 보행 freq 1.5~2.5Hz 의 Nyquist 5Hz 충분.
- PiezoMonitoringView LaunchedEffect:
  - idle (auto/single scan 비활성) → sendImuQuery() — 무음
  - auto scan 중 → 기존 mtb 의 rim: 가 자동으로 posture 갱신
- imuCollector.onComplete 는 parseRim / parseRsp 둘 다에서 fire 되니 wiring 그대로.

부하 비교:
  mtb 1Hz  ≈ 1200 B/s  (가청음 발생)
  msp 5Hz  ≈   90 B/s  (무음)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-30 10:09:23 +09:00
dw.jang 64c15a8d46 feat(method-d): appshare 02bed02 (4494098 VBTWD201) 완전 포팅 — wall detection 안정성 확보
Python source: piezo-phantom-test commit 02bed02 (= 4494098 vesiscan-test PR).
Kotlin V2 alignment 결과가 Python 과 1:1 일치하도록 포팅 (값·로직 동일).

[1] MethodDParams (config_d.py 매칭)
  - oscfarWin: 5 → 9
    heavy median 강화로 span_e 안정화 → post 벽 검출 일관성
    (CH0 post SD 1.45→0.36, v2 cycle CV ↓)
  - shoulderPromGamma: 1.0 → 1.5
    peak(promGamma=1.5) 와 정렬해 강한 shoulder 가 약한 peak 에 지지 않음
    peak↔shoulder 재분류 토글 제거 (v2 cycle CV 12.8→9.5%)

[2] MethodDResult (detector.py 매칭)
  - 신규 필드: antCandidates: List<Pair<Int, Double>>
    detect() 가 recovery 전 wall_select 후보를 score 내림차순으로 노출
    Python: tuple((int(c[0]), float(c[4])) for c in sorted(ant_res['candidates'], key=-x[4]))
  - ant / antRefined / urineLen 을 var 로 (tie-break post-process 가 in-place 갱신)

[3] AlignmentConstants (config_6ch.py 매칭)
  - ANT_TIEBREAK_RATIO = 1.3
  - ANT_NEARFIELD_TOL_MM = 8.0

[4] MethodDRunner.applyAntTiebreak() (runners.py _apply_ant_tiebreak 매칭)
  - cross-channel 전벽 보정 (in-place):
    (1) 동률 (top1/top2 < 1.3) → 이웃 center 채널 ant z 합의 median 으로 선택
    (2) near-field 아티팩트 (cz - 8mm 보다 얕음) → 압승이어도 후보 교체
  - 방향성: urine 쪽(깊은) 이탈은 dome 곡률로 정상이라 보존, near-field 만 기각
  - detectMultichannel() 끝에 호출 — Python method_d() 와 동일 순서
  - z(ch, idx) = (DELAY_OFFSET_MM + idx * DISTANCE_PER_SAMPLE) * cos(angle_ch)
  - median 은 numpy.median 동작 매칭 (짝수 길이면 두 가운데 평균)

[5] PiezoBVEstimator.distancePerSample (config_6ch.py 매칭)
  - default 1.968 → 1.936 (HW 샘플링레이트 변경, FW VBTFW118+)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-29 14:52:37 +09:00
dw.jang 11795d1cef revert(alignment): Skip 우상단(top bar) 만 유지 — 하단 Skip 제거
이전 commit (9a6a81c) 의 BottomCenter floating Skip 롤백.
Skip 진입점은 top bar 우측 TextButton 하나만 (단일 위치).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-29 12:02:39 +09:00
dw.jang 9a6a81c9f4 fix(alignment): Skip 버튼 중복 제거 + Box.BottomCenter 고정 (V2 등에서 잘리는 문제 fix)
문제:
- top bar 우측 Skip + 하단 scrollable Skip → 중복
- 일반 사용자/clinical 모드는 Column 이 scroll 비활성 (weight 레이아웃) →
  V2 같이 콘텐츠 많을 때 맨 아래 Skip 이 화면 밖으로 잘려 안 보임

수정:
- top bar Skip 제거 (1개만 유지)
- 하단 Skip 을 scrollable Column 밖, Box.align(BottomCenter) 으로 분리 →
  콘텐츠 길이와 무관하게 항상 화면 하단에 노출

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-29 11:59:43 +09:00
dw.jang 5f2a1b6329 revert(alignment): 일반 사용자 flow 는 V1, V2 는 clinical alignment session 전용으로 복귀
이전 commit (22ef657) 에서 V2 를 모든 진입점 default 로 통일했었으나 — V2 (Method D 4-stage)
는 R&D / clinical alignment session 의 정밀 측정 알고리즘이고, 일반 사용자 (Home → Start →
Personalization → PlacementGuide) 에는 검증된 V1 simple algorithm 이 적합.

복원:
- GreenZoneConstants.alignmentAlgo default V2 → V1
- PlacementGuideView LaunchedEffect: isClinicalAlign ? V2 : V1 (이전 동작)

Step indicator 는 V2 일 때만 4-stage, V1 일 때는 기존 3-stage 표시.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-29 11:54:48 +09:00
dw.jang 6cff741370 feat(locale): DeviceScanView 한글화 — 기기 연결 페이지
이전 sweep 에서 누락됐던 두번째 '기기 연결' 화면 풀 한글화.

추가 strings: 25 신규 (각 언어)
- '기기 연결' 제목, 검색/탭 안내
- 페어링 필요 알림 (다이얼로그 + 에러 메시지)
- Bluetooth off / 권한 필요 경고
- 검색 중 / 기기 없음 / 트러블슈팅 4단계
- 저장된 기기 / 새 기기 섹션
- 권한 허용 / 검색 시작·중지 버튼
- 최근, 페어링 삭제, 신호 좋음/보통/약함

설계:
- signalLabel(rssi) → 영어 — dev/log 용 유지
- signalLabelRes(rssi) 신규 — UI 용 Locale-aware 라벨

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-29 11:31:50 +09:00
dw.jang 3b37c816fc fix(demo): 정렬모드 Skip top bar 우측 노출 + 개인화 페이지 한글화
1. PlacementGuide top bar 에 Skip TextButton 추가
   - 기존: scrollable Column 맨 아래 → 스크롤 안에 묻혀 안 보이던 문제
   - 변경: top bar 우측 (Back / Ready / Skip / [dev] Settings 순) 항상 가시
   - 동작: clinical alignment → endMeasurement + CLINICAL_HOME / 일반 → placementComplete

2. PiezoPersonalizationView (개인화 페이지) 한글화
   - 'Maximum Bladder Capacity' → '최대 방광 용량'
   - 'Voiding Reminder' → '배뇨 알림 기준'
   - 'Auto Scan Interval' → '자동 측정 주기' (기존 key 재사용)
   - 'Continue' → '계속' (기존 calibration_continue 재사용)
   - 'Back' (contentDescription) → '뒤로'

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-29 11:23:11 +09:00
dw.jang 59d4ac0e81 feat(locale): Phase 3 — 전수 sweep, 노인 사용자 기준 영어 잔여 텍스트 한글화
이전 Phase 2 에서 'already English-only' 라며 skip 했던 화면들 — 노인 사용자에게는 영어로
보이면 안 됨. 전체 다시 훑어 모든 user-visible 영어 hardcoded text 를 strings.xml 로 추출.

11 파일 수정:
- MainActivity (Exit dialog)
- HomeView (Smart Bladder Monitoring / Start / Connected / Clinical Mode / dev toast)
- OnboardingView (Welcome, Get Started 등)
- CalibrationView (모든 status + Retry/Skip/Continue)
- SensorSelectView (title, descriptions)
- ShuffledKeypad (Delete contentDescription)
- MeasurementHistoryView (title, tabs, empty state, Today/Yesterday)
- VivamyoMonitoringView (전체 status)
- PiezoMonitoringView (사용자 영역 — Auto/Single Scan, Voiding/Catheterization, 측정 카드, dialog 등)
- PlacementGuideView (V1 hints, Step indicator, Start/Skip/Complete Alignment, In position/Position lost)

추출 strings: ~170 (각 언어). strings.xml 194 → 365 라인.

skip 유지:
- ClinicalHomeView / ClinicalLiveView (R&D dev mode)
- PiezoMonitoring dev panel sliders (3-tap 활성화 dev 도구)
- PlacementGuide dev settings overlay + Echo Waveforms (isDevMode gated)
- BLE Debug Log / Scan Log dialog (dev 전용)
- 브랜드명 (VesiScan-Basic, Vivamyo, Bladdy)
- DEMO 배지, IMU debug 문자열, Log.d / debugLogger 출력

Korean coverage: ~95% (일반 사용자가 절대 보지 않는 dev surface 만 영어 유지).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-29 09:17:23 +09:00
dw.jang 2cf4f4db2f feat(locale): Phase 2 — 모든 사용자 노출 Korean 문자열 → strings.xml 추출 + 영문 번역
Sub-agent 작업 결과:
- values/strings.xml + values-ko/strings.xml 각각 71 → 194 줄 (약 85 신규 string)
- 17 파일 수정 (UI views 9개, components 3개, managers 1개, services 2개)

상세:
- ui/views/{home, consent, registration, reminder, monitoring (3), personalization, connection}
- components/{BatteryWarningBanner, FirmwareWarningBanner, LockableSlider}
- managers/AlignmentAdvisorV2 — Out/AdvisorState 에 msgKey + msgArgs 추가 (UI 가 context.getString 으로 localize)
- services/{ReminderService, BleForegroundService}

의도적으로 skip:
- ClinicalHomeView / ClinicalLiveView (R&D-only dev mode)
- PiezoMonitoring dev panel labels (dev 도구)
- AlignmentAdvice / 2-phase AlignGuide (UI 미사용)
- 이미 영어인 파일들 (Onboarding, Pin, SensorSelect, Calibration 등)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 17:27:05 +09:00
dw.jang bc7843809e feat(locale): Phase 1 — 언어 전환 인프라 (시스템/한국어/English)
신규:
- LocalePref.kt — Mode {SYSTEM, KO, EN} 영속화 + Context wrap
- MainActivity.attachBaseContext — 사용자 선택 locale 으로 Configuration override
- HomeView 우상단 🌐 Language 아이콘 → 선택 다이얼로그 → recreate

Phase 2: 모든 hardcoded Korean text → strings.xml + values-ko/strings.xml 추출 (대량 작업, 별도 commit)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 16:53:02 +09:00
dw.jang 22ef6576f8 feat(demo): V2 default + 그래프/문구 일치 + Slider lock (스크롤 충돌 방지)
1. V2 alignment 를 모든 진입점 default 로
   - GreenZoneConstants.alignmentAlgo = V2
   - PlacementGuideView LaunchedEffect 의 clinical 한정 분기 제거 → 항상 V2
   - 사용자 경험 통일 (4-stage Method D)

2. 그래프 ↔ 문구 일치 — 잔여 모순 fix
   - CH3_STABILIZE 의 STOP action: 이전엔 isLocked=true + GREEN LED + checkmark
     아이콘 → '완료처럼 보이는데 알고리즘은 stabilizing 중' 모순. 변경: phase가
     LR_BALANCE 일 때만 finalStop → 진짜 GREEN. CH3_STABILIZE STOP 은 아이콘 X.
   - PROBE_LR action: 이전엔 arrow.left (한쪽 화살표). msg 의 ↔ 와 불일치.
     변경: 아이콘 없음 (msg 의 ↔ 만으로 양방향 의미 전달).

3. LockableSlider 신규 — 스크롤 충돌 차단
   - 잠금 아이콘 1회 탭 → 활성화 → drag 가능 → 다시 탭하면 잠금
   - 잠김 상태에서는 slider disabled → 위아래 스크롤 손가락 끌림에 영향 X
   - 적용: PiezoMonitoring 의 dev panel 17 sliders +
          ReminderSettings 의 2 sliders (배뇨 알림 기간, 기상 알람 기준 시간)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 16:45:49 +09:00
dw.jang 618267d8c0 fix(posture): 연결 중 ?_ 지속되던 두 원인 fix
원인 A — Auto Scan 켜져 있는 동안 mtb polling 영원 skip:
  isAutoMeasuring 가 Auto Scan 종료까지 계속 true → mtb 안 보냄 → IMU rim 응답 없음
  → currentPosture 갱신 멈춤 → UNKNOWN 표시 지속.
변경: polling 조건에서 isAutoMeasuring 제거. !isMeasuring 만 검사 →
      Auto Scan 의 cycle 사이 idle 시간 (isMeasuring=false) 에는 mtb 가능.
      Spot 은 5회 연속이라 isSpotInProgress 유지 (충돌 회피).

원인 B — 다른 화면 다녀와도 imuCollector.onComplete cleanup 없어 stale callback 남음:
변경: DisposableEffect 로 onDispose 시 onComplete = null.
      다시 진입하면 LaunchedEffect 가 재등록.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 15:20:32 +09:00
dw.jang de3a607f96 ux(alignment v2): hint 문구를 사용자 행동 중심으로 — ch 기술용어 제거
V2 algorithm hint 만 수정 (V1 그대로):
- INITIAL_ACCUM: '측정 준비 중'        → '잠시 그대로 두세요'
- VERTICAL_CLIMB: '↑ 위로 (ch3 미검출)' → '↑ 위로 조금씩 올리세요'
- CH3_STABILIZE hold: '✓ ch3 검출! 위치 유지' → '✓ 좋아요! 그 위치 유지'
- CH3_STABILIZE lost: '↑ ch3 다시 잃음 — 위로' → '↑ 살짝 놓침 — 다시 위로'
- LR_BALANCE lost: '↑ chN 잃음 — 위치 회복' → '↑ 위치 살짝 벗어남 — 위로 약간'
- LR_BALANCE done: '■ 정렬 완료 (ch1+ch2..., |Δ|=N)' → '✓ 정렬 완료! 그대로 측정 가능'
- LR_BALANCE LR: '← chN 유지 + 좌우 균형 (|Δ|=N)' → '← 왼쪽으로 살짝' (or 오른쪽)
- LR_BALANCE probe: '↔ ch... 좌우 천천히 (lateral 미검출)' → '↔ 좌우로 천천히 움직여 보세요'

PlacementGuideView 의 V2-only baseline 메시지:
- waitingForStart V2 branch: '센서를 치골 위에 두고 Start 를 누르세요'

원본 ch/Δ/setLabel 같은 임상 메타는 dev debug log (ALIGN_V2 ...) 로만 유지.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 11:20:54 +09:00
dw.jang 8f895b354a fix(labdb): cycleToRecord 에 alignment metadata 보존
이전: rowIndex/datetime/commandType/sensor/channels 만 → align_phase 등 drop
변경: align_phase / align_score / align_hint / align_icon / align_detected /
      align_urine_lens 도 record 에 포함 (alignment session 일 때만)

→ labdb 서버 raw.json export 에 cycle 별 V2 phase 표기 (INITIAL_ACCUM /
   VERTICAL_CLIMB / CH3_STABILIZE / LR_BALANCE / STEP_0_BASELINE) 가 들어가
   분석 PC 에서 phase 별 frame 분석 가능.

⚠ 이미 업로드된 sessions 는 phase 없음 (재업로드 필요).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 10:44:02 +09:00
dw.jang 122d9631f3 feat(alignment): Option A — V2 활성 시 graph detection 도 method_d 결과 사용 + 2-column 그래프 + Y축 1.5×
Option A:
- V2 활성 시 channelDetected 를 method_d 의 summary.detected 로 덮어씀
- PiezoEchoAnalyzer (wall pair 양쪽 필요) ↔ method_d (low-echo span만) 의 결과 차이 해소
- ch3 같은 한쪽 벽 약한 채널이 "메시지=검출 / 그래프=미검출" 이던 시각 모순 해결

UI:
- 6개 echo waveform 세로 적층 → 2-column (좌: ch0/1/2/3 머리→치골, 우: ch4/5 좌·우 lateral)
- Y축 그래프 높이 80dp → 120dp (1.5×) 로 peak 가시성 ↑

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 10:37:34 +09:00
dw.jang 12a9089102 feat(clinical): labdb 자동 업로드 제거 — Upload 버튼으로 수동만
이전: endMeasurement → labdb 자동 업로드 → 사용자가 Upload 버튼 (Clinical Home) 못 봄
변경: endMeasurement 는 measurement.json 저장만, 업로드는 Upload 버튼으로만 트리거
→ 사용자가 측정 결과 확인 후 의도적으로 업로드

이미 존재하던 Clinical Home 'Last saved' 카드의 Upload 버튼이 항상 노출됨
(labdb 등록 + 업로드 안 된 상태일 때).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 10:12:18 +09:00
dw.jang 48e6d72270 tune(walking): wmean default 15→10 — 더 짧은 보행도 잡음
이전: 15 (stand-up settling motion 5~10 dps 차단용)
변경: 10 (보행 50+ dps 와 충분히 분리되면서 짧은 보행도 잡음)

GreenZoneConstants.walkingStartWmean + PostureTuningPrefs.D_WALK_START_WMEAN
양쪽 같이 갱신.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 09:26:00 +09:00
dw.jang 37d3d0d84a fix(alignment): clinical session Back/Skip 도 endMeasurement (저장)
이전: Back/Skip → discardAndDeleteFolder (저장 X)
변경: Back/Skip → endMeasurement (Complete 와 동일) — measurement.json + labdb 업로드
→ 사용자가 측정 도중 화면 떠나도 그때까지 누적된 baseline + 4-phase frame 보존

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 18:12:54 +09:00
dw.jang 026076aa11 fix(alignment): clinical session — V2 phase label + Step 0 baseline + Back/Skip 분기
#1 placements.jsonl phase 라벨 통일
- 기존: placementPhase.name (V1 phase) 만 저장 → V2 활성이어도 V2 phase 안 보임
- 변경: V2 활성이면 v2Aligner.phase.name 사용 (INITIAL_ACCUM 등 4-stage)
- measurement.json 의 align_phase 와도 동일 라벨로 통일

#2 Step 0 baseline 데이터 캡쳐
- Clinical alignment session 진입 시 isContinuousMode 자동 ON
  (waitingForStart 는 true 유지 — 사용자 Start 누를 때까지)
- baseline cycles 의 phase = "STEP_0_BASELINE" 로 라벨링
- V2 algorithm push 는 !waitingForStart 일 때만 호출 — silent phase 진행 차단
- Start 누를 때 v2Aligner.reset() 추가 (fresh 시작 보장)

#3 Back / Skip 버튼 분기 — clinical alignment session 인 경우 CLINICAL_HOME 으로
- 기존: Back → PIEZO_PERSONALIZATION, Skip → PIEZO_MONITORING (잘못된 화면 이동)
- 변경: clinical alignment session 진입 상태면 discardAndDeleteFolder + CLINICAL_HOME
  (Back/Skip = 저장 의도 X — Complete Alignment 버튼만 저장)
- 일반 placement guide 동작은 그대로 유지

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 18:10:40 +09:00
dw.jang be76f85e34 feat(piezo): 도넛 위 status chip 을 posture 아이콘+텍스트로 교체
- urgency message Text 자리에 자세 chip (아이콘 + 라벨) 배치
- 색·아이콘·라벨이 currentPosture 에 따라 Crossfade 300ms 로 부드럽게 전환
- top bar 의 작은 inline posture Row 제거 (중복 표시 방지)
- UNKNOWN 상태 가시성 ↑ : tint #616161 + 라벨 'Detecting' (회색 투명+'—' 대비)
- Raw BLE 녹화 버튼은 dev 모드에서만 노출 (legacy 유지)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 17:54:54 +09:00
dw.jang 4e85091b90 feat(alignment): 센서 detach 시 주황 경고 팝업 — 텍스트 변경만으로 사용자가 놓치던 문제
- DetachmentDetection.isDetached 가 true 가 되는 rising edge 에 Dialog 팝업
- wakeup 알람과 동일한 주황(Material 500) 톤 + Warning 아이콘
- 재부착 시 팝업 자동 dismiss (사용자 확인 부담 ↓)
- 기존 directionHint / LED mode 4 / phase reset 동작은 그대로 유지

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 16:36:20 +09:00
dw.jang 4dc0b1acb2 fix(dev): Walking slider range — Start Hold default(6s)가 max(5)를 초과해 잠겨있던 문제
- Start Hold: 0.2..5 → 0.5..10 (default 6s 정상 표시 + 8~10s 확장 가능)
- Start Wmean: 1..20 → 1..30 (더 strict 한 튜닝 가능)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 15:31:49 +09:00
dw.jang 280c5671b0 fix(dev): 슬라이더 변경이 앱 재시작 시 사라지던 문제 — SharedPreferences 영속화
증상: Dev settings 의 walking/posture 임계 슬라이더를 조정해도 다음 앱 실행
      시 default 로 리셋. 사용자: "save해도 변경이 안되는 것 같음".

원인: GreenZoneConstants 의 임계들은 @Volatile var (메모리 변수). 슬라이더가
      즉시 값을 GreenZoneConstants 에 쓰지만 SharedPreferences 등 영속
      저장소엔 안 들어감 → 앱 재시작 시 default 로 다시 초기화.

수정:
  1) PostureTuningPrefs.kt 신규
     - loadAll(context): SharedPreferences → GreenZoneConstants 로드
     - saveAll(context): 현재 GreenZoneConstants → SharedPreferences 저장
     - resetAll(context): defaults 로 복원
     - 11개 키 (pitch lying / acc min/max / poll ms / walking 7개)

  2) PiezoMonitoring LaunchedEffect(Unit) 에서 loadAll 호출
     - 앱 진입 시 저장된 사용자 튜닝 값 자동 로드

  3) 11개 slider 모두 onValueChange 마지막에 saveAll 호출
     - 슬라이더 변경 즉시 영속 저장 (별도 save 버튼 불필요)

  4) Reset Thresholds 버튼 추가 (Verification 섹션 위)
     - resetAll 호출 → defaults 로 복원 + prefs 저장
     - Toast 로 안내 ("close & reopen Settings to refresh sliders" —
       slider 의 remember state 는 reopen 해야 갱신됨)

테스트:
  1. Dev settings → Start Wmean = 25 변경 → 앱 종료
  2. 앱 재실행 → Dev settings 다시 열기 → Start Wmean = 25 유지 확인 ✓
  3. 분류기도 25 값으로 동작

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 14:55:16 +09:00
dw.jang 15993aa839 feat(verify): Posture event CSV logger + ground-truth annotation 버튼
알고리즘 검증 인프라.

신규 PostureEventLogger.kt:
  - PiezoMonitoring 진입 시 새 CSV 파일 자동 생성
    경로: Downloads/VesiScan_Posture/yyyy-MM-dd_HHmmss.csv
  - 매 cycle 자세/walking 결과 + 신호값 1 row
  - 알람 발동 시 별도 row (walking_start, walking_end, wakeup_alarm_lying_Ns)
  - 사용자 annotation row (ground truth marker)
  - 컬럼: timestamp_ms, type, posture, isWalking, pitch, wmean, wstd,
          accMag, gyroMag, alarm_event, annotation

PiezoMonitoring 연결:
  - LaunchedEffect(Unit) 에서 startSession 호출
  - imuCollector.onComplete: logCycle 매 cycle 호출
  - WALKING 전이: logAlarm("walking_start" / "walking_end")
  - Wakeup alarm: logAlarm("wakeup_alarm_lying_Ns")

Dev settings — Verification 섹션:
  - 5개 annotation 버튼: LIE / STAND / WALK↑ / WALK↓ / SIT
  - 누르면 timestamp + 라벨이 CSV 에 ANNOTATION row 로 기록
  - Toast 로 "Marked: 걷기시작" 같은 피드백
  - 파일 경로 안내 텍스트

사용 흐름:
  1. PiezoMonitoring 진입 (파일 자동 생성)
  2. 동작 직전 dev settings 의 해당 버튼 누름 (예: WALK↑)
  3. 동작 수행 (걷기)
  4. CSV 회수: adb pull /sdcard/Download/VesiScan_Posture/
  5. Excel / pandas 에서 분석:
     - ANNOTATION row 의 timestamp = ground truth
     - CYCLE row 의 isWalking 변화 = detection
     - 두 시간 차 = latency
     - 미발동 = FN, 과발동 = FP → confusion matrix

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 14:05:43 +09:00
dw.jang 45242f8256 tune(walking): default Start Wmean 4→15, Start Hold 1s→6s (사용자 실측 기준)
증상: 앉아있다 일어서기만 해도 walking false-trigger.

원인: 사용자 실측 데이터 분석 — stand-up + 자세 조정 motion 이 wmean
      5-10 dps 로 약 3-5초 지속. 이전 default (Start Wmean=4, Hold=1s) 는
      이 motion 을 walking 으로 잡음.

수정:
  Start Wmean: 4 → 15  (stand-up 의 typical wmean 보다 위)
  Start Hold:  1초 → 6초 (stand-up settling 보다 김)

근거:
  - 사용자 측정 stand-up motion: wmean 5-10 (저강도) ~ 100 (spike)
  - 진짜 보행 (body-mounted, 예상): wmean 50+ 지속
  - Hold 6초 — 정상 보행은 무한 지속이라 자동 만족, stand-up 은 5초 미만

비고:
  - 임계는 여전히 dev slider 로 조정 가능 (이전 commit 18a4cee)
  - 사용자별 / 디바이스별 튜닝 가능
  - 향후 periodicity check (FFT) 추가 시 임계 다시 완화 가능

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 12:29:14 +09:00
dw.jang 80f3982352 fix(walking): sit-to-stand burst window leak — pitch transition 시 buffer 비움
증상: 앉아있다 일어서기만 해도 WALKING false-trigger (두 번 연속 발생).

원인 (로그 분석):
  앉아있음(pitch=20-30°) → 일어남 burst (gyro 90-220 dps) → 서있음(pitch=82°)

  타임라인:
    T=0  pitch=37°  wmean=49  (burst 시작, pitch < 45 → pitchOk=false)
    T=1  pitch=14°  wmean=110 (더 큰 burst, pitchOk=false)
    T=2  pitch=32°  wmean=174 (burst 최고, pitchOk=false)
    T=3  pitch=82°  wmean=190 ← pitchOk=true 된 순간 window 에 burst 가득
                              → start cond 즉시 만족, 카운트 시작
    T=4  pitch=50°  wmean=82  (burst 빠지는 중)
    T=5  pitch=46°  wmean=12  (거의 빠짐, 그래도 > Start Wmean(4))
    T=6  pitch=57°  wmean=24  → "WALKING start (held 3091ms)" ❌

  핵심: pitch 가 45° 통과한 순간 window 안엔 이미 burst data. burst 가 다
  빠지기 전에 (~1.5초) + 3초 hold 누적 = false-trigger.

수정: WalkingDetector 에 lastPitchOk 추적 + false→true 전이 시
      buffer.clear() + startCondSinceMs=null.

  if (pitchOk && !lastPitchOk && !walking) {
      buffer.clear()           // burst data 폐기
      startCondSinceMs = null  // hold 카운트 리셋
  }

  결과: pitch 가 45° 넘는 순간부터 fresh sample 만 누적. 사용자가 일어서서
        가만히 있으면 wmean=1-2 (정상 정지값) → cond 안 met → trigger 안 됨.
        실제 보행 시작하면 새 wmean 이 30-50+ 까지 올라가 → 3초 hold → trigger.

  walking 중에는 pitchOk false→true 가 발생해도 (잠깐 앉았다 일어남 등) buffer
  안 비움 (walking 상태 유지 — keep cond 가 처리).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 12:18:38 +09:00
dw.jang 18a4cee00d feat(dev): 자세/walking 모든 임계 슬라이더 추가 (튜닝용)
사용자: walking 이 너무 예민하게 뜸. 모든 임계를 dev settings 에서 조절하고 싶음.

추가된 슬라이더 (Pitch Threshold 아래에 그룹별 정렬):

1) Posture
   - Pitch Threshold (lying ↔ non-lying)         기존
   - Poll Interval                                 기존

2) Walking Thresholds
   - Min Pitch (walking 인정 최소 pitch)           기존
   - Start Wmean (시작 평균 gyro)                  기존
   - Start Wstd (시작 표준편차)                    기존
   - Keep Wmean (유지 평균) — range 0.5~30 (이전 10) 신규 범위↑
   - **Start Hold** (시작 조건 hold 시간, 0.2~5초)  ⭐신규
   - **Keep Grace** (종료 grace 시간, 0.5~10초)     ⭐신규
   - **Window** (sliding window 크기, 0.5~5초)     ⭐신규

3) Acc Magnitude Gate
   - **Min** (UNKNOWN 하한, 0.3~1.0g)              ⭐신규
   - **Max** (UNKNOWN 상한, 1.0~3.0g)              ⭐신규

내부 변경:
  - GreenZoneConstants 에 postureAccMagMin/Max 추가
  - ImuPostureClassifier 의 const ACC_MAG_MIN/MAX → GreenZoneConstants 참조
    (런타임 변경 가능)

총 11개 슬라이더 — 사용자가 실측하며 튜닝 가능.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 11:37:46 +09:00
dw.jang 6bf399c7b9 fix(walking): chained failure — acc gate fail 시 walking 잃던 버그
증상: 로그캣 분석 — 활발히 걷는 중 (accMag > 1.2g) 일수록 화면에 안 보임.
  11:24:03 NON_LYING → WALKING 성공
  11:24:04 accMag=1.80 → UNKNOWN, walking 도 grace 들어감
  11:24:05 grace 2초 초과 → walking 종료
  → 활발 보행 = 가장 위험한 순간에 알람 사라짐 (정반대)

원인 두 가지:
  1) Classifier 가 acc gate fail 시 pitch=0 반환
     → WalkingDetector 가 pitch=0 받아서 pitchOk=false → grace 진입
  2) Composite 로직: r.posture==UNKNOWN 이면 WALKING 무시하고 UNKNOWN 표시

수정:
  1) ImuPostureClassifier: pitch 계산을 acc gate 이전으로 옮김
     → UNKNOWN Result 에도 실제 pitch 값 포함
     → WalkingDetector 가 정확한 pitch 로 grace 판단 가능

  2) PiezoMonitoringView composite 순서 변경:
     when {
       isWalkingNow -> WALKING          // 우선 (자체 게이트 신뢰)
       r.posture==UNKNOWN -> UNKNOWN
       else -> r.posture
     }
     walking detector 가 walking 이라고 하면 acc gate 실패해도 표시.
     walking 자체에 시간/wmean/wstd 게이트 있어 false-positive 거의 없음.

테스트 시나리오:
  정지 lying → 흔들면서 걷기 (pitch>45°) → walking 검출 → 격하게 흔들기
  (accMag>1.2g) 도 → "Walking" 계속 표시 + 빨간 팝업 유지.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 11:27:58 +09:00
dw.jang ecdec7c97c feat(posture): Walking detector (flowchart Path B) + 2단계 알람
새 flowchart 의 walking 검출 추가:

1) Posture enum: UNKNOWN/LYING/NON_LYING + **WALKING** (4-state)

2) WalkingDetector.kt 신규 (flowchart Path B 구현)
   - Sliding window (default 1.5초) buffer 에 gyro magnitude 누적
   - 매 cycle 마다 wmean (이동평균) + wstd (이동표준편차) 계산
   - State machine:
     IDLE → WALKING:
       |pitch| > 45° AND wmean > 4 AND wstd > 2
       → 1초 이상 연속 만족 시 전이
     WALKING → IDLE:
       |pitch| > 45° AND wmean > 2
       → 미달 후 2초 grace 지나면 전이

3) GreenZoneConstants 에 walking 임계 7개 추가 (모두 dev 조정 가능):
   - walkingMinPitchDeg=45, walkingStartWmean=4, walkingStartWstd=2,
     walkingStartHoldMs=1000, walkingKeepWmean=2, walkingKeepHoldMs=2000,
     walkingWindowMs=1500

4) PiezoMonitoring:
   - walkingDetector 인스턴스
   - imuCollector.onComplete: classifier + detector 동시 호출,
     composite posture (WALKING > NON_LYING > LYING) 결정
   - NON_WALKING → WALKING 전이 시 critical 알람 trigger

5) UI:
   - 상단 status bar: Walking 아이콘 (DirectionsWalk, 빨강) 추가
   - 기존 wakeup 팝업 색: 빨강 → 주황 (warning level)
   - 신규 walking 팝업: 빨강 + DirectionsWalk + "환자가 걷고 있습니다 — fall risk"

6) Dev settings: walking 임계 슬라이더 4개 추가
   (Min Pitch / Start Wmean / Start Wstd / Keep Wmean)
   sliding window / hold 시간은 default 유지 (필요 시 추가)

2단계 알람 체계 (의료적):
  - 주황 (warning): LYING N초 → NON_LYING/WALKING 전이 (일어남 감지)
  - 빨강 (critical): NON_WALKING → WALKING 전이 (보행 시작 — fall risk 최고)

Tag phase2-pre-walking (28a8769) 이전 상태로 복원 가능.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 10:28:43 +09:00
dw.jang 28a8769fa3 tune(posture): default polling 3s → 1s (실시간 자세 감지)
낙상 예방 시나리오상 자세 변화 즉시 감지 중요 → 1초 polling 으로 단축.
canSendMaa() 500ms throttle + mtb 응답 ~400ms 이라 1초가 사실상 한계.
사용자가 slider 로 더 늦출 수도 있음 (1~15초 범위).

BLE 부담: device active ~40% (mtb 한 cycle ≈ 400ms × 매초).
배터리 영향은 짧은 모니터링 세션 (수십분~수시간) 수준에선 무시 가능.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 09:20:32 +09:00
dw.jang 0bb484ae45 feat(posture): Poll Interval 슬라이더 + default 10s → 3s
증상: 자세 갱신이 10초마다라 너무 느림. 빠르게 테스트하고 싶음.

수정:
  1) GreenZoneConstants.posturePollIntervalMs (default 3000ms) 신규
     - polling loop 의 delay(10000) → delay(GreenZoneConstants.posturePollIntervalMs)
     - 슬라이더 변경 즉시 다음 iteration 부터 적용

  2) PiezoMonitoring dev settings: Poll Interval 슬라이더 추가
     - 위치: Lying Duration 슬라이더 바로 아래
     - 범위: 1초 ~ 15초 (연속)
     - 표시: "X.X초"
     - 변경 시 GreenZoneConstants 에 즉시 반영

default 변경: 10초 → 3초.
  - mtb 한 cycle ≈ 400ms, 3초 polling = device 활동 ≈ 13% (가벼움)
  - 1초까지 줄여도 캐싱/throttle 충돌 거의 없음 (canSendMaa 500ms gate)

BLE 부담 vs 응답성 trade-off:
  | interval | 1시간 device 사용 | 자세 변화 최대 지연 |
  |   1초    |     ~24분 (40%)   |     ~1초           |
  |   3초    |     ~8분 (13%)    |     ~3초           |
  |   5초    |     ~5분 (8%)     |     ~5초           |
  |   10초   |     ~2.5분 (4%)   |     ~10초          |

낙상 예방엔 1-3초가 적정. 평상시 사용엔 5-10초.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 09:14:32 +09:00
dw.jang 91d7f75cbd feat(posture): dev 설정에 Lying Duration 슬라이더 추가 — 테스트 시간 단축
증상: 기상 알람 테스트하려면 LYING 으로 3분 (default 180s) 유지해야 팝업
      뜸 — 매번 너무 길어 테스트 비효율.

수정: PiezoMonitoring dev settings 의 기상 알람 토글 바로 아래에 Lying
      Duration 슬라이더 추가.

  - 범위: 5초 ~ 600초 (10분)
  - 표시: "5초" / "3분 0초" 형식 자동
  - 변경 시:
    1) WakeupAlarmPrefs.setLyingDurationSec(context, sec) — 저장
    2) wakeupDetector.lyingDurationSec = sec — 즉시 적용
  - 테스트: 슬라이더로 5초 정도로 줄임 → 평평히 5초 → 세움 → 즉시 팝업

상수가 GreenZoneConstants 대신 SharedPreferences 라서 앱 재시작 후에도
설정 유지. wakeupDetector 인스턴스에 동시 반영해 즉시 효과 확인 가능.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-24 08:53:09 +09:00
dw.jang a175540291 fix(posture): pitch 분류 invert + PiezoMonitoring 설정에도 기상 알람 토글
증상 1: 디바이스 평평하게 둠 → "Upright" / 세움 → "Lying" (또는 기존 빌드의
        "Sitting") 으로 사용자 직관과 반대.

진단: 사용자 raw 데이터 분석
  - 평평 (AZ dominant, AZ ≈ 1g): pitch ≈ 0° → 코드가 NON_LYING (Upright) 반환
  - 세움 (AX dominant, AX ≈ 1g): pitch ≈ 80~90° → 코드가 LYING 반환
  사용자 직관: 평평=Lying, 세움=Upright → 코드 inversion.

수정 1: ImuPostureClassifier 의 분류 조건 invert
  - 기존: |pitch| > threshold → LYING
  - 신규: |pitch| ≤ threshold → LYING (작은 pitch = AZ 가 중력 = 센서 평평)
  - dev slider hint 도 "|pitch|≤N°→Lying" 으로 동기화

수정 2: PiezoMonitoring 의 dev settings overlay 에도 기상 알람 토글 추가
  - 기존: PiezoPersonalizationView 에만 존재
  - 신규: dev settings panel (Pitch Threshold slider 아래) 에 동일 토글
  - 두 위치 모두 같은 WakeupAlarmPrefs 사용 — sync 됨
  - 사용자가 monitoring 페이지에서 바로 켜고 끌 수 있음

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 17:39:46 +09:00
dw.jang 9a5fa16f94 fix(posture): gyro gate 제거 + UNKNOWN streak 화면 표시
증상: 디바이스를 lying 상태로 둬도 "Upright" 표시가 안 바뀜.

진단 (raw 로그 분석):
  - 디바이스 정지 상태에서도 gyro y 축 ≈ -25 dps 일관 표시
  - 이는 IMU 칩의 **calibration bias** (정지 시 zero offset 안 됨)
  - flowchart 의 gyro gate (9.16 dps) 가 항상 fail → classifier 영구 UNKNOWN
  - currentPosture 가 UNKNOWN 시 업데이트 안 되는 로직 때문에 첫 valid
    값 (NON_LYING, 사용자가 손에 들고 진입한 순간) 으로 stuck
  - 사용자가 눕혀도 분류 자체가 안 되니 "Upright" 그대로

수정:
  1) ImuPostureClassifier: gyro gate 제거
     - accel magnitude gate (0.8-1.2g) 가 큰 모션은 이미 거름
     - gyro magnitude 는 Result/log 에 계속 노출 (디버그용)
     - flowchart 그대로 따르려면 gyro 도 활용해야 하지만, bias 큰
       디바이스에선 무용지물이라 실용성 우선

  2) PiezoMonitoringView: UNKNOWN streak 카운터
     - 3회 연속 UNKNOWN 이면 currentPosture = UNKNOWN 으로 강제 표시
     - silent stuck 상태 (잘못된 자세 계속 보여줌) 방지
     - 잠시 1-2 cycle 동안 motion artifact 로 UNKNOWN 떠도 직전
       자세 유지 (이전 동작 보존)

  3) Log: unknownStreak 도 출력 → 디버그 시 stuck 케이스 가시화

테스트:
  - lying → upright 전환 시 즉시 "Upright" 표시되어야 함
  - 정지 시 "Lying" 또는 "Upright" 안정적 유지
  - 큰 모션 (들어올림 등) 중에는 3 cycle 후 "—" 표시

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 17:25:58 +09:00
dw.jang af469558e6 feat(posture): Phase 2 — 기상 알람 (LYING/NON_LYING + Settings 토글 + 빨간 팝업)
요구사항: 설정 탭에서 알람 on/off → on 상태에서 일어남 감지 시 빨간 팝업.

변경:

1) ImuPostureClassifier 2-state 단순화
   - 기존: SUPINE/SITTING/STANDING (3 state)
   - 신규: LYING / NON_LYING (2 state) + UNKNOWN
   - sitting/standing 구분 불필요 (낙상 예방엔 "누웠나/일어났나" 만 중요)
   - 분류: |pitch| > threshold → LYING, ≤ → NON_LYING

2) GreenZoneConstants.pitchLyingThreshold (default 25°)
   - @Volatile var — runtime 변경 가능
   - dev 모드 설정에서 slider 로 조정 (10~70°, 11 steps)

3) WakeupAlarmPrefs — SharedPreferences 저장
   - isEnabled (default false)
   - lyingDurationSec (default 180 = 3분)

4) WakeupDetector — state machine
   - LYING 진입 시 timestamp 기록
   - LYING → NON_LYING 전이 시 elapsed 측정
   - elapsed ≥ lyingDurationSec 이면 onWakeup 콜백
   - 1회 발동 후 LYING 복귀까지 재무장 안 됨 (스팸 방지)
   - UNKNOWN 은 무시 (motion artifact 흔들림 영향 X)

5) PiezoMonitoringView
   - posture 아이콘 매핑 LYING (Hotel, 파랑) / NON_LYING (AccessibilityNew, 초록) / UNKNOWN
   - imuCollector.onComplete 콜백에서 wakeupDetector.update(posture) 호출
   - showWakeupAlarm Boolean state + 빨간 Dialog (Color 0xFFD32F2F)
     · Warning 아이콘 + "환자가 일어났습니다" 메세지
     · 누운 시간 표시
     · 확인 버튼 (사용자 dismiss 필수, 자동 닫힘 X)
     · dismissOnBackPress/ClickOutside = false (실수 dismiss 방지)
   - Demo 모드: posture LYING 고정 (BLE 없음)

6) PiezoPersonalizationView — WakeupAlarmCard 신규
   - 빨간 톤 카드 + Switch
   - "기상 알람 — 환자가 일어나면 빨간 팝업으로 알림 (낙상 예방)"
   - 토글 즉시 WakeupAlarmPrefs.setEnabled

7) Dev 설정 (PiezoMonitoringView 의 dev panel)
   - "Pitch Threshold" 슬라이더 추가 (10°~70°)
   - GreenZoneConstants.pitchLyingThreshold 직접 조정
   - 실시간 반영 — 다음 cycle 부터 적용

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 17:17:58 +09:00
dw.jang 6a06452d7e feat(debug): Raw BLE capture 토글 버튼 + 로거 추가
용도: IMU FSR / 채널 누락 / packet 구조 진단을 위해 BLE notification 의 raw
      bytes 를 가공 없이 그대로 파일로 저장.

신규 RawBleLogger.kt:
  - startCapture() / stopCapture() / isCapturing
  - 파일: Downloads/VesiScan_RawBLE/yyyy-MM-dd_HHmmss_raw.log
  - 포맷: "HH:mm:ss.SSS dir=RX prefix=rim len=188 hex=72 69 6D 3A ..."
  - 매 notification 마다 한 줄
  - 파일 시작/끝에 헤더/푸터 (start, stop time, packet count)

BleManager.processReceivedData:
  - 진입 직후 RawBleLogger.write(data) 호출
  - capturing 아니면 즉시 return (no overhead)

PiezoMonitoringView 상단 status bar:
  - posture 아이콘 옆에 토글 IconButton
  - 활성: 빨간 RadioButtonChecked, 비활성: 회색 FiberManualRecord
  - 토글 시 Toast 로 파일 경로 / packet count 안내

사용:
  1) PiezoMonitoring 진입 → 빨간 ● 버튼 탭 → "Raw BLE capture started"
  2) BLE 측정 진행 (alignment / live / single scan)
  3) ● 다시 탭 → "N packets saved to YYYY-MM-DD_HHmmss_raw.log"
  4) adb pull /sdcard/Download/VesiScan_RawBLE/ 로 파일 회수
  5) 텍스트 에디터로 hex 분석

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 15:44:32 +09:00
dw.jang eee065a76a debug(imu): rim: raw byte/int16 dump (ImuRaw tag) — FSR/format 진단용
증상:
  - JSON 의 imu_samples 에 gz=0 같은 의심값
  - sqrt(ax²+ay²+az²) 가 1g 아닌 ~0.5g 일관 표시
  - 펌웨어 FSR vs 앱 변환 (/8192) 불일치 가능성

수정: ImuPacketCollector.parseRim 에 raw dump 로깅 추가.
  - 전체 byte hex dump (188B)
  - 첫 sample + 마지막 sample 의 raw int16 값 (division 전)
  - 참고용으로 /8192 시 g 환산값 같이 표시

logcat:
  adb logcat | grep ImuRaw

기대 출력:
  ImuRaw: rim FULL HEX (188B): 72 69 6D 3A 00 0F ...
  ImuRaw: rim sample[0] raw int16: ax=-170 ay=4034 az=735 gx=-8 gy=16 gz=0
          (g if /8192: ax=-0.0208 ay=0.4925 az=0.0898)

진단 판정:
  - raw az ≈ 8192 → ±4g FSR 정상
  - raw az ≈ 4096 → ±8g FSR (앱 변환식과 mismatch, FSR=±8g)
  - raw az ≈ 2048 → ±16g FSR
  - raw gz=0 일관적 → 펌웨어가 GZ 안 보내거나 sensitivity 다름
  - bytes 순서 다르면 endian/offset 문제

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 15:33:03 +09:00
dw.jang a17c6dac69 fix(posture): IMU polling 을 msp → mtb 로 변경 (alignment 명령 우회)
증상: msp 명령이 펌웨어에서 rsp: 응답 안 보내는 듯 — adb logcat 에 rsp/POSTURE
      로그 한 줄도 안 보임. msp 자체가 작동 안 함.

수정: msp 대신 mtb (alignment 모드, rim: 응답 보장) 로 polling.

  PiezoPacketCollector:
    var skipNextComplete: Boolean = false 추가
    raa: complete 시 skipNextComplete 가 true 면 onComplete invoke 건너뛰고
    flag reset. 자동 1회 use.

  PiezoMonitoringView LaunchedEffect:
    - msp(5초) → mtb(10초) 로 전환
    - mtb 전에 piezoCollector.skipNextComplete = true 설정 → user 의
      측정 callback 가 posture poll 때문에 spurious fire 되는 거 차단
    - sendMtb() throttled 면 (canSendMaa fail) flag rollback
    - 측정 중 (isMeasuring/isAutoMeasuring/isSpotInProgress) 일 땐 skip
      → 사용자 측정과 충돌 방지

trade-off: BLE 트래픽 ↑ (10초마다 mtb = piezo 6ch + IMU). msp 보다 무거움.
          하지만 msp 미작동이라 다른 선택지 없음.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 15:04:06 +09:00
dw.jang 33b072c336 debug(posture): logcat 로 IMU 흐름 가시화 — ImuDbg 태그 도입
증상: PiezoMonitoring 진입해도 자세 ?_ 만 표시, logcat 에 rsp/POSTURE
      로그 안 보임.

원인: debugLogger.tx/rx 는 내부 Entry 리스트에만 적재하고 android.util.Log
      호출 안 함 → adb logcat | grep 에 안 보임.

수정: 핵심 IMU 흐름에 android.util.Log.d("ImuDbg", ...) 추가.

  PiezoMonitoringView.LaunchedEffect:
    - 시작 시 isConnected/isDemoMode 값 로그
    - sendImuQuery 매 호출 "→ sendImuQuery (msp)"
    - 연결 안 되어 있으면 "skip (not connected)"
    - imuCollector.onComplete 결과 (자세 + pitch/acc/gyro)
    - imuCollector.onLog 도 ImuDbg 로 forward (parseRsp 로그 보임)

  BleManager:
    - rsp: 응답 시 hex dump 도 ImuDbg 로 (debugLogger 와 별개)
    - rxs: (cmd_not_supported) 응답 시 ImuDbg 로 warn

  rxs: 중복 핸들러 정리 (기존 1개 → 새 1개로 통합)

테스트: "adb logcat | grep ImuDbg" 한 줄로 전체 흐름 확인 가능.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 14:30:50 +09:00
dw.jang 1774681a18 fix(posture): rsp: 응답 (msp 명령 응답) 파싱 추가 — 자세 분류 작동
증상: PiezoMonitoring 진입 시 자세 아이콘이 항상 ?_ (UNKNOWN) 만 뜸.

원인: sendImuQuery() 가 보내는 msp 명령은 rsp: 응답을 받는데, 기존 코드는
      rim: (mtb alignment 응답) 만 imuCollector.parseRim() 으로 처리.
      rsp: 는 onImuReceived callback 으로만 가는데 어디서도 set 안 됨
      (dead callback) → 자세 분류기에 sample 절대 도달 못 함 → UNKNOWN.

수정:
  1) ImuPacketCollector.parseRsp() 신규
     rsp: format spec 미확정이라 defensive parsing 으로 두 변형 시도:
     - 변형 1 (multi-sample): rim: 와 동일 [tag 4B][num 2B][N×12B][crc 2B]
     - 변형 2 (single sample): [tag 4B][12B sample][crc 2B] = 18B
     실패 시 hex dump 로그 출력 (다음 디버깅용)
     성공 시 imuCollector.onComplete 정상 호출 → 분류기 작동.

  2) BleManager rsp: 핸들러
     - parseRsp() 라우팅
     - rx 로그에 hex 추가 (실제 format 확인용)

기존 rim: 처리는 그대로 (alignment 모드 영향 없음).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 14:06:40 +09:00
dw.jang cf476971a5 feat(posture): Phase 1 — flowchart 기반 자세 분류 + 도넛 우상단 아이콘 (demo-final)
allignment flowchart "Posture classification" 알고리즘 적용. Phase 1
범위: 자세 분류 + 화면 표시만 (알람/감지 없음).

1) ImuPostureClassifier.kt 신규
   flowchart 단계 그대로:
   - Acc magnitude 게이트: 0.8 ≤ |a| ≤ 1.2 (motion/bad reject)
   - Gyro magnitude 게이트: |g| ≤ 9.16 dps (회전 reject)
   - Pitch = atan(ax / sqrt(ay² + az²)) × 180/π
   - 분류:
     · |pitch| < 25°  → SUPINE
     · 25-70°         → SITTING
     · > 70°          → STANDING
   - 게이트 실패 시 UNKNOWN + 사유 반환
   - 입력: ImuSample 리스트 (한 응답 15 sample 평균)
   ※ flowchart 의 "|pitch| > 25; supine" 는 typo 로 판단 → < 25 적용

2) PiezoMonitoringView IMU polling 추가
   - 5초 주기로 sendImuQuery() → rim: 응답 → classifier
   - UNKNOWN 결과 시 직전 자세 유지 (artifact 흔들림 방지)
   - demo mode: SUPINE 으로 fix (BLE 없음)

3) 도넛 차트 우상단 자세 아이콘 표시 (Battery 옆)
   - SUPINE   → Icons.Hotel (파랑)
   - SITTING  → Icons.AirlineSeatReclineNormal (주황)
   - STANDING → Icons.AccessibilityNew (초록)
   - UNKNOWN  → Icons.HelpOutline (회색)
   - 아이콘 + 텍스트 라벨 (Lying/Sitting/Standing/—)

Phase 2 (다음): Settings 토글 + wake-up state machine + 빨간 팝업.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 12:24:22 +09:00
dw.jang 1fc1a20bb9 fix(align V2): LR_BALANCE 좌우 화살표 oscillation 차단 (deadband + 연속 악화)
증상: ch3 확인 통과 후 LR_BALANCE 단계에서 좌/우 화살표가 빠른
      간격으로 번갈아 나타남.

원인:
  AlignGuide4Stage.LR_BALANCE 의 hill-climbing 반전 조건이
  imbal > lateralImbal 만 검사 → 1 sample 라도 늘면 즉시 반전.
  - 노이즈/호흡 으로 imbal 가 ±1-2 fluctuate → 잘못된 반전
  - sliding window 평균 (10-frame) 잔여 효과: 방향 바꿔도 직전
    데이터 섞여 imbal 변화 못 따라옴 → 가짜 "악화" 판정
  - 사용자 반응 지연 동안 imbal 더 누적 → 또 반전 → ←→←→ 진동

수정 (A + C 조합):
  1) A. 반전 deadband (lrReverseDeadband = 3 samples ≈ 6 mm)
     imbal 가 직전보다 "deadband 초과" 증가해야 악화로 카운트.
     LAT_TOL(8) 의 약 1/3 — 정렬 완료 조건 위협하지 않으면서 노이즈
     흡수.
  2) C. 연속 N회 악화 요구 (lrReverseConsecutive = 2)
     deadband 초과 증가가 2 commit 연속이어야 방향 반전.
     단발 노이즈/sliding window 잔여 효과 흡수.

  상태 추가: lateralWorseStreak Int (reset 포함).
  로직:
    - 개선 또는 deadband 안 변동 → 같은 방향 유지, streak = 0
    - deadband 초과 증가 → streak++
    - streak >= 2 → 방향 반전, streak = 0
  lateralImbal 은 매 commit 갱신 (기존 동작 유지).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 15:36:19 +09:00