Files
VesiscanClinicalAndroid/docs/LABDB_DATATYPES.md
T
dw.jang 2770d0296a feat(labdb): dataType 600·601 배정 반영 · subject 를 최상위로
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>
2026-09-09 10:41:04 +09:00

10 KiB
Raw Blame History

labdb dataType — VesiScan 병원 임상 (600 · 601)

2026-09-09 배정 완료. 임시로 쓰던 901·902(사용자 정의 구간)에서 옮겼습니다.

코드 의미 앱 측 정의
600 병원 임상 측정 services/labdb/HospitalLabdbPayload.kt (DATA_TYPE)
601 부착 위치 정렬 services/labdb/AlignLabdbPayload.kt (DATA_TYPE)

서버에 dataType 레지스트리 테이블은 없습니다(schema.sql:47 의 data_type 은 자유 문자열). "등록"은 실제로 ① 코드 배정 ② session.html 의 VIZ_HANDLERS 분기 ③ 목록 컬럼 ④ 이 문서 네 가지입니다.


공통

  • 엔드포인트: POST /upload/json (기존과 동일)
  • 세션 1개 = 요청 1건. records[] 는 세션 안의 반복 측정.
  • testId 는 ^[A-Za-z0-9_-]{1,40}$. 파일명이 길면 앞 31자 + _ + 파일명 SHA-1 앞 8자로 축약(충돌 방지).
  • 채널 데이터는 12-bit ADC 원시값(0~4095), 채널당 최대 100 샘플. 연결이 끊긴 회차는 짧은 배열로 올 수 있습니다(0 패딩하지 않음).
  • peak / peakIdx 는 그 채널 배열의 최초 최대값과 그 인덱스.
  • subject 는 최상위에 넣습니다. 서버가 params.subject 는 읽지 않아 sessions.subject 컬럼이 비어 버립니다(2026-09-09 확인 — 기존 72세션이 그 상태). 그 컬럼이 export 파일명 prefix 와 목록 표시에 쓰입니다. params 안에도 그대로 둡니다.

1) dataType 600 — VesiScan 병원 임상 측정

데이터 의미

병원 임상 프로토콜의 한 조합입니다. 자세 · 방광 충만도 · 주파수 · cycle 수가 하나로 고정된 상태에서 같은 조건을 20회 반복 측정합니다. 한 환자 한 세션(자세×충만도)당 6조합(주파수 2 × cycle 3)이 돌므로 세션 6개가 생깁니다.

환자 폴더 전체를 한 세션으로 묶지 않는 이유는, 그러면 1,440 record 짜리 덩어리가 되어 조건별 비교가 불가능해지기 때문입니다.

업로드 시점: 조합이 끝나는 즉시 자동. 오프라인이면 앱에 쌓아 두었다가 버튼으로 일괄 전송.

세션 필드

필드 타입 설명
testId string 조합 CSV 파일명 기반 (축약 규칙 위 참조)
dataType string "600"
sessionName string 조합 CSV 파일명(확장자 제외)
savedAt ISO8601 그 조합의 마지막 반복 시각
recordCount int = records.length
params object 아래
records array 아래

params

필드 타입 설명
protocol string "hospital_clinical_2026" 고정
subject string 환자 식별자(가명)
posture string Supine / Sitting / …
fill_pct number 방광 충만도 % (0·20·40·60·80·100)
freq_mhz number 예 2.3
freq_option number 펌웨어 주파수 코드
cycles number 버스트 cycle 수 (3·5·7)
device string 프로브 이름 (예 VBT26080001)
firmware_version string
repeats_saved int 실제로 저장된 반복 수
source_file string 원본 CSV 파일명
avg, delay_us, samples number 측정 파라미터(매니페스트에 있을 때만)

records[]

{
  "rowIndex": 0,
  "datetime": "2026-09-08T10:00:00.000+09:00",
  "commandType": "MTB",
  "sensor": {},
  "channels": [
    { "ch": 0, "peak": 3500, "peakIdx": 30, "data": [ 900, 901, ... ] }
  ]
}
필드 타입 설명
rowIndex int 반복 번호(0..N). 원본 CSV 의 repeat_idx 그대로
datetime ISO8601 그 반복의 캡처 시각
commandType string "MTB" 고정
sensor object 항상 빈 객체. 병원 CSV 에 IMU·배터리·온도 열이 없습니다. 0 으로 채우지 않습니다
channels array 6개 (CH0~CH5)

시각화 요구사항

000(VesiScan 초음파)과 같은 파형 뷰면 충분합니다. 채널 구조가 동일합니다. sensor 가 비어 있으므로 배터리·온도·IMU 위젯은 숨겨 주시면 좋겠습니다.

조건 비교를 자주 하므로, 세션 목록에서 params.posture / fill_pct / freq_mhz / cycles 를 열로 볼 수 있으면 유용합니다.


2) dataType 601 — VesiScan 부착 위치 정렬

데이터 의미

임상 측정 전 단계인 부착 위치 정렬입니다. 치골 위 0cm 부터 1cm 씩 올리며 위치마다 20 cycle 을 재고, 지표를 비교해 부착 위치 하나를 고릅니다.

600 과 구조가 다릅니다 — 저쪽은 한 조건에서 20 반복, 이쪽은 여러 위치 × 20 cycle 입니다. 그래서 별도 코드가 필요합니다.

업로드 시점: 자동 아님. 간호사가 파형을 보고 이상하다고 판단했을 때만 버튼으로 보냅니다. 개발자 피드백 요청 용도입니다.

세션 필드

필드 타입 설명
testId string <환자폴더>_align 기반
dataType string "601"
sessionName string <환자폴더>_align
memo string 간호사가 적은 증상. 없으면 필드 자체가 없음
savedAt ISO8601 업로드 시각
recordCount int 전체 위치의 cycle 합계
params object 아래
records array 아래

params

필드 타입 설명
protocol string "hospital_align_2026" 고정
subject string 환자 식별자
save_name string 저장 폴더명
positions_measured int 측정한 위치 수
cycles_total int = recordCount
upload_reason string "nurse_review" — 사람이 올린 세션 표시
device, firmware_version, hw_preset string
freq_mhz, freq_option, probe_cycles number 정렬 전용 고정 조건 (2.3MHz · cycle 3)
cycles_per_position int 20 고정
avg, delay_us, samples number
ch3_hit_min number CH3 검출률 하한 (0.8)
final_offset_cm int best 에 더하는 오프셋 (1)
best_cm int / null 알고리즘이 고른 최적 위치
anchor_cm int / null 실제 부착 위치 = best_cm + final_offset_cm
action string STOP / REATTACH / …
stop_reason string / null 탐색 종료 사유
max_nch int 관측된 최대 검출 채널 수
patient_unnamed bool 환자명 없이 잰 데이터인가(폴더가 unnamed_*)
selection_verified_against_reference bool 앱 판정이 Python 레퍼런스와 대조 완료인가
positions array 위치별 지표 — 아래
lateral object 좌우 정렬 결과 — 아래

params 는 align_result.json 을 통째로 옮깁니다(patient → subject, save_name 만 이름이 바뀝니다). 앱에서 요약에 필드를 추가하면 별도 작업 없이 같이 올라갑니다 — 위 표에 없는 키가 보여도 정상입니다.

params.positions[] (위치별 판정 근거):

필드 타입 설명
align_cm int 위치(치골 위 cm)
n_trace int 슬라이딩 trace 수 (20 cycle → 11)
nch int CH0~CH3 중 검출 채널 수
ch3 "O" / "X" mean-scan 기준 CH3 검출 여부
ch3_hit int CH3 가 검출된 trace 수
ch3_tot int 전체 trace 수
ch3_rate number ch3_hit / ch3_tot
cap_frac number 기하 지표 (BV 파이프라인 산출)
eligible bool 후보 자격 = ch3 == "O" AND ch3_rate >= ch3_hit_min

params.lateral (좌우 정렬):

필드 설명
done 좌우 확인 단계를 거쳤는가. false 는 "안 맞췄다"가 아니라 "확인 안 했다"
lat_tol 허용 오차 (8)
accum_k 판정에 쓴 프레임 수 (10)
action STOP / MOVE_LEFT / MOVE_RIGHT / PROBE_LR / MOVE_UP
u4, u5 CH4(좌)·CH5(우) urine_len. null = 미검출
imbalance |u4−u5|. 한쪽이라도 미검출이면 null
ch3 판정 시점 CH3 검출 여부

records[]

{
  "rowIndex": 0,
  "align_cm": 0,
  "cycle_idx": 0,
  "commandType": "MTB",
  "channels": [
    { "ch": 0, "peak": 3500, "peakIdx": 30, "data": [ 900, ... ] }
  ]
}
필드 타입 설명
rowIndex int 위치를 가로질러 0..N 연속 (labdb 규약)
align_cm int 이 cycle 을 잰 위치(치골 위 cm)
cycle_idx int 그 위치 안에서의 순번 (0..19)
commandType string "MTB" 고정
channels array 6개

600 과 달리 datetime · sensor 가 없습니다. 원본 정렬 파일에 시각 열이 없습니다.

⚠️ 서버의 records.timestamp 는 NOT NULL 이라 업로드 시각으로 채워집니다. 전 레코드가 거의 같은 시각이 되므로 조회·시각화는 반드시 rowIndex 로 정렬해야 합니다.

시각화 요구사항

같은 세션 안에 여러 위치가 섞여 있는 것이 이 타입의 핵심입니다.

  1. align_cm 으로 그룹핑해 위치별로 파형을 나란히 볼 수 있으면 가장 유용합니다.
  2. params.positions 를 표로 띄우고 best_cm 행을 강조해 주시면, "왜 이 위치를 골랐나"를 한 화면에서 판단할 수 있습니다.
  3. memo 를 세션 목록에서 바로 보이게 해 주세요 — 간호사가 무엇을 이상하게 봤는지가 이 세션의 존재 이유입니다.

이력

항목 내용
코드 배정 2026-09-09 · 600 임상 / 601 정렬
기존 적재분 901 로 올렸던 72세션 · 1,440레코드(2026-09-04)는 서버에서 600 으로 마이그레이션 완료
902 정렬 1세션 같은 testId 로 601 재업로드하여 갱신
앱 반영 상수 2개 변경 + 최상위 subject 추가

이전 판에 "279건(2026-09-05)"이라고 적혀 있었습니다. 오기입니다 — 279 는 일반 앱의 미업로드 세션 수였고 병원 적재량과 무관합니다. 실제는 72세션 · 1,440레코드입니다.