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>
This commit is contained in:
2026-05-26 12:26:49 +09:00
parent 6e2dba749b
commit ab25b3c65d
8 changed files with 304 additions and 380 deletions
@@ -85,21 +85,22 @@ class BleDebugLogger private constructor(private val context: Context) {
// 임상 모드: 세션 step 폴더의 ble.log로 라우팅 (step 전환 시 새 파일)
// 일반 모드: Downloads/VesiScan_BLE_*.log (BLE 연결 1회당 1파일)
private var liveLogFile: File? = null
private var liveLogStepLabel: String = "" // 임상 모드 step 변경 감지
private var liveLogLabel: String = "" // 임상 모드 측정 단위 변경 감지
private fun ensureLiveLogFile(): File {
val clinicalStepDir = ClinicalSessionStore.stepLogDir()
if (clinicalStepDir != null) {
val clinicalDir = ClinicalSessionStore.currentLogDir()
if (clinicalDir != null) {
clinicalDir.mkdirs()
val currentLabel = ClinicalSessionStore.currentLabel()
// step이 바뀌었으면 새 파일로 전환
if (liveLogFile == null || liveLogStepLabel != currentLabel) {
liveLogFile = File(clinicalStepDir, "ble.log")
liveLogStepLabel = currentLabel
// 측정 단위가 바뀌었으면 새 파일로 전환
if (liveLogFile == null || liveLogLabel != currentLabel) {
liveLogFile = File(clinicalDir, "ble.log")
liveLogLabel = currentLabel
}
} else if (liveLogFile == null || liveLogStepLabel.isNotEmpty()) {
} else if (liveLogFile == null || liveLogLabel.isNotEmpty()) {
// 일반 모드 진입 또는 임상 모드 종료
val downloadsDir = android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS)
liveLogFile = File(downloadsDir, "VesiScan_BLE_${dateFmt.format(Date())}.log")
liveLogStepLabel = ""
liveLogLabel = ""
}
return liveLogFile!!
}