labdb 관리자가 코드를 배정했다(2026-09-09). 임시로 쓰던 사용자 정의 구간에서 옮긴다.
901 → 600 병원 임상 측정
902 → 601 부착 위치 정렬
서버 조사에서 나온 지적 셋을 함께 처리했다.
**① 적재량 오기 정정.** 문서·주석·테스트에 "279건(2026-09-05)"이라고 적혀 있었는데
틀렸다. 279 는 일반 앱의 **미업로드 세션 수**였고 병원 적재량과 무관하다 — 그걸
옮겨 적으면서 섞였다. 실제는 **72세션 · 1,440레코드**(2026-09-04)이고 서버 조회로
확인했다. 기존 적재분은 서버에서 이미 600 으로 마이그레이션됐다.
**② subject 가 서버에서 비어 있었다.** 서버는 최상위 `data.subject` 만 읽어
`sessions.subject` 컬럼에 넣는데 앱은 `params` 안에만 넣고 있었다. 그 컬럼이 export
파일명 prefix 와 목록 표시에 쓰이므로, 72세션 전부 환자 구분이 안 되는 상태였다.
두 페이로드 모두 최상위에 추가한다(params 안에도 그대로 둔다 — 분석 쪽이 이미 쓴다).
환자명이 비면 필드 자체를 넣지 않는다: 빈 문자열이 들어가면 목록에서 빈칸과
구분이 안 된다.
**③ 601 은 레코드 시각이 업로드 시각으로 채워진다.** 원본 정렬 파일에 시각 열이 없어
`datetime` 을 못 넣는데 서버의 `records.timestamp` 는 NOT NULL 이다. 동작에는 문제가
없지만 전 레코드가 거의 같은 시각이 되므로, **조회·시각화는 rowIndex 로 정렬해야 한다**
— KDoc 과 스펙 문서에 명시했다.
PC 변환기(align2labdb.py · hospital2labdb.py)도 같이 고쳤다. 기존 902 정렬 1세션은
같은 testId 로 재업로드해 601 로 갱신했다(600 72 · 601 1 로 확인).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
params 를 화이트리스트로 채우고 있었는데, 그 목록에 없던 두 필드가 조용히 빠져 있었다:
· patient_unnamed — 환자명 없이 잰 데이터인가
· selection_verified_against_reference — 앱 판정이 레퍼런스와 대조 완료인가
요약에 필드를 추가할 때마다 여기도 고쳐야 하는데 안 고쳐도 아무 신호가 없다. 이 세션은
"파형이 이상하니 봐 달라"는 진단 요청이라 **덜 보내는 쪽이 위험**하다 — 개발자가 되물어야
하고 그 왕복이 임상에서는 하루다.
align_result.json 을 통째로 옮기고, 이미 다른 이름으로 넣은 것(patient→subject,
save_name)만 건너뛴다. 요약의 모든 키가 params 에 도달하는지 테스트로 고정했다.
문서도 함께 고쳤다. positions[] 표에서 ch3_hit·ch3_tot·eligible 이 빠져 있었고
(실제로는 올라가고 있었다), params 표에 위 두 필드를 추가했다. 표에 없는 키가 보여도
정상이라는 설명도 넣었다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
파형이 이상해 보일 때 그 자리에서 개발자에게 보내는 통로가 없었다. 임상이 끝난 뒤
파일을 꺼내 보내면 회신이 하루 뒤인데, 그때는 이미 그 환자로 다시 잴 수 없다.
정렬 전용 dataType 902 를 새로 쓴다. 임상 측정(901)과 구조가 다르다 — 저쪽은 한
조건에서 20 반복이고 정렬은 여러 위치 × 20 cycle 이다. 같은 코드로 올리면 params
만으로는 구분이 안 되고 콘솔이 두 종류를 한 차트로 그린다. labdb 는 dataType 마다
records[] 스키마가 완전히 달라도 되고(labdb.md §2-3), 900~999 가 사용자 정의 구간이다.
record = 한 위치의 한 cycle
rowIndex : 위치를 가로질러 0..N 연속 (labdb 규약)
align_cm : 그 cycle 을 잰 위치
cycle_idx : 그 위치 안에서의 순번
channels : 6 × {ch, peak, peakIdx, data}
**판정 근거(align_result.json)를 params 에 통째로 넣는다.** 개발자가 답해야 할 질문이
"왜 이 위치를 골랐나"라서 파형만 오면 되묻게 된다 — 지표(nch·ch3·cap_frac)와 선택
결과, 좌우 결과가 같이 가야 한 번에 답이 나온다.
**자동이 아니다.** 간호사가 버튼을 눌러야 올라간다. 목적이 "이상하니 봐 달라"는
요청이라 정상인 것까지 올리면 정작 봐야 할 것이 묻힌다. 무엇이 이상해 보였는지
한 줄을 받아 memo 로 보낸다 — 그게 없으면 "이게 왜 올라왔지"부터 시작한다.
params.upload_reason 에 사람이 올린 것임을 남겨 자동 업로드분과 섞이지 않게 했다.
마커는 남기지 않는다. 같은 정렬을 다시 보낼 수 있어야 한다 — 처음에 못 적은 증상을
memo 에 적어 다시 보내는 흐름이 실제로 생긴다. labdb 는 같은 testId 면 갱신한다.
판정이 끝나기 전에도 보낼 수 있다. 첫 위치만 재고 이상하다 싶은 때가 오히려 물어볼
상황이다 — 요약이 없으면 파형만 보낸다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
병원 임상 모드에는 labdb 업로드 경로가 **아예 없었다**. HospitalRunStore 는 Downloads 에
파일만 쓰고 업로더를 한 번도 부르지 않는다. 업로드는 ClinicalSessionStore.finalize 한
곳에만 붙어 있고 그건 병원 모드가 쓰지 않는 경로다. 2026-09-05 에 279건을 올릴 때
파이썬 변환기를 따로 만들어야 했던 이유가 이것이다.
포맷도 다르다. 기존 업로더는 measurement.json(cycles 배열)을 읽는데, 병원 모드는 CSV
한 행이 1 반복 × 1 채널이고 조건별로 파일이 갈린다. 그래서 그때의 변환 규칙을 앱으로
옮긴다 — 모양이 달라지면 labdb 에 같은 프로토콜 데이터가 두 형태로 쌓이고 분석하는
쪽이 두 벌을 만들게 된다.
CSV 파일 1개 = 세션 1개 (자세·충만도·주파수·cycle 고정 20 반복)
CSV 6행(CH0~5) = record 1개 → channels[0..5].data = s0..s99
rowIndex = repeat_idx
**조합이 끝날 때마다** 올린다. 한 바퀴를 다 돌 때까지 기다리면 중간에 앱이 죽거나
자리를 옮겼을 때 그때까지 잰 것이 통째로 남는다. 단위도 자연스럽다 — labdb 는 요청
1건 = 세션 1개 = record N개이고, 20 반복이 곧 한 요청이다. 측정 1회마다 올리면 한
바퀴가 120건이 되어 분당 10건 제한에 걸린다.
**오프라인이 정상 경로다.** 병원 무선망이 불안정한 곳이 많아 "나중에 올리기"가 예외가
아니다. 성공/실패를 CSV 옆 마커 파일로 남겨 앱이 죽어도 디스크만 보면 무엇이 남았는지
알 수 있게 했다 — 상태를 메모리에 두면 이 기능의 요점이 사라진다. 화면에 "미업로드
N건"과 [지금 업로드] 를 두고, 밀린 것을 올릴 때는 요청 사이를 7.5초 띄운다(분당 ~8건).
한 건이 실패해도 멈추지 않는다. 망이 잠깐 끊긴 것과 그 파일이 잘못된 것을 구분할 수
없는데, 뒤의 멀쩡한 것까지 막으면 손해가 크다. 실패 마커가 남아 다음에 다시 시도된다.
dataType 은 901(사용자 정의 구간)이다. 병원 프로토콜용 코드를 관리자에게 배정받으면
HospitalLabdbPayload.DATA_TYPE 만 바꾸면 된다 — 279건도 같은 값으로 올라가 있다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
demo-final 에서도 clinical mode 테스트 중인데 매 세션마다 Upload 버튼을
눌러야 반영되는 문제. tab-navigation / cloud-mvp 브랜치의 자동 재시도
로직을 demo-final 구조에 맞춰 축소 이식.
원인:
- demo-final ClinicalSessionStore.endMeasurement 자체에 자동 업로드
로직이 아예 없었음 (주석: "labdb 업로드는 자동 X — Upload 버튼으로
수동 트리거").
- recentFinalizedFolders / capturedCount 히스토리 구조도 없어 단순
cherry-pick 은 불가.
수정:
- endMeasurement: isRegistered (apiKey 존재) 면 uploadAsync 자동 호출.
- LabdbAutoRetry object 신설 (services/labdb/LabdbAutoRetry.kt):
demo-final 은 lastFinalizedFolder 단일 값만 있으므로
listOfNotNull(lastFinalizedFolder) 로 축소. tab-nav/cloud-mvp 는
recentFinalizedFolders 리스트 사용 (그쪽 브랜치 코드는 그대로).
- ClinicalHomeView LaunchedEffect(Unit) 에서 자동 트리거 + 상단 배너 UI:
* 진행 중: "자동 업로드 진행 중 · 남은 세션 N · <name>" (파랑, spinner)
* 완료: "N uploaded, M failed" (초록/주황) + 확인 버튼
효과:
- 세션 종료 후 뒤로가기 → 자동 업로드 시도 → 실패해도 Clinical Home
재진입 시 재시도. 사용자 개입 0.
- 여러 세션 밀린 상황도 사용자가 다음 세션 진입할 때마다 이전 세션이
하나씩 재시도됨 (demo-final 구조 특성).
- 기존 폴더 구조 / measurement.json 포맷 100% 유지.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
사용자앱 (feature/tab-navigation) 과 패키지 이름 일치. namespace + applicationId
둘 다 com.medithings.vesiscan 로 변경 (기존 데모 앱은 재설치 필요).
## 변경 범위
- Kotlin 148 파일: package + import 문 (714 occurrences)
- 디렉토리 이동: com/example/medilightv2android → com/medithings/vesiscan
(main, test, androidTest 각각)
- app/build.gradle.kts: namespace, applicationId
- docs/FLAVOR_DEMO_STABLE.md: 참조 갱신
- V41DetectorCH4Test: BvDispatchResult.methodChosen → method (dto field name fix)
## 주의
- applicationId 가 바뀌므로 기존 데모 앱 (com.example.medilightv2android.demo) 은
Android 관점에서 다른 앱으로 취급 — 재설치 시 PIN/설정 초기화됨.
- Fresh install 권장. 기존 앱 (com.example...) 은 별도로 uninstall 필요.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>