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>
This commit is contained in:
2026-09-09 10:41:04 +09:00
parent e17391ca9f
commit 2770d0296a
8 changed files with 136 additions and 33 deletions
@@ -15,12 +15,17 @@ import java.util.Locale
* 정렬 한 세션(위치 여러 개)을 labdb 세션 하나로 만든다.
*
* ## 왜 새 dataType 인가
* 임상 측정(`901`)과 구조가 다르다. 저쪽은 **한 조건에서 20 반복**이고, 정렬은
* 임상 측정(`600`)과 구조가 다르다. 저쪽은 **한 조건에서 20 반복**이고, 정렬은
* **여러 위치 × 20 cycle** 이다. 같은 dataType 에 넣으면 `params` 만으로는 구분이
* 안 되고, 관리자 콘솔이 두 종류를 한 차트로 그리게 된다.
*
* labdb 는 dataType 마다 `records[]` 스키마가 완전히 달라도 된다(`labdb.md` §2-3).
* 900~999 가 사용자 정의 구간이라 여기에 [DATA_TYPE] 을 쓴다.
* labdb 는 dataType 마다 `records[]` 스키마가 완전히 달라도 된다.
*
* ## 레코드 시각이 없다
* 원본 정렬 파일에 시각 열이 없어 `datetime` 을 넣지 못한다. 서버의
* `records.timestamp` 는 NOT NULL 이라 **업로드 시각으로 채워진다** — 전 레코드가
* 거의 같은 시각이 되므로, 조회·시각화는 반드시 `rowIndex` 로 정렬해야 한다
* (2026-09-09 서버 조사 확인).
*
* ## 언제 올리나 — 자동이 아니다
* 간호사가 파형을 보고 **이상하다고 판단했을 때** 버튼으로만 올린다. 목적이 개발자
@@ -41,12 +46,12 @@ import java.util.Locale
object AlignLabdbPayload {
/**
* 정렬 전용 코드. 사용자 정의 구간(900~999).
* 부착 위치 정렬 — labdb 관리자 배정 코드(2026-09-09).
*
* 임상 측정은 `901`([HospitalLabdbPayload.DATA_TYPE]). 관리자가 100~899 를
* 배정해 주면 그 값으로 바꾼다 — 그때 이 KDoc 의 스키마를 그대로 등록 요청에 쓴다.
* 임시로 쓰던 `902`(사용자 정의 구간)에서 옮겼다. 임상 측정은
* `600`([HospitalLabdbPayload.DATA_TYPE]).
*/
const val DATA_TYPE = "902"
const val DATA_TYPE = "601"
private const val PROTOCOL = "hospital_align_2026"
@@ -146,6 +151,10 @@ object AlignLabdbPayload {
put("testId", testId(base))
put("dataType", dataType)
put("sessionName", base)
// 서버는 **최상위** subject 만 읽어 sessions.subject 컬럼에 넣는다
// (params 안의 것은 안 본다). 그 컬럼이 export 파일명 prefix 와 목록
// 표시에 쓰이므로, 빠지면 서버에서 환자 구분이 안 된다.
if (subject.isNotBlank()) put("subject", subject)
if (memo.isNotBlank()) put("memo", memo)
put("savedAt", isoOut.format(Date()))
put("params", params)
@@ -20,10 +20,13 @@ import java.util.TimeZone
* 병원 모드는 형식이 완전히 다르다 — CSV 한 행이 **1 반복 × 1 채널**이고, 조건별로
* 파일이 갈리며, 매니페스트가 따로 있다. 그래서 기존 업로더로는 한 건도 올라가지 않는다.
*
* ## 매핑 — 2026-09-05 수동 업로드(279건)와 **같은 규칙**
* ## 매핑 — 2026-09-04 수동 업로드(72세션 · 1,440레코드)와 **같은 규칙**
* 그때 파이썬 변환기로 올린 것과 모양이 달라지면 labdb 에서 같은 프로토콜 데이터가
* 두 형태로 쌓인다. 분석하는 쪽이 두 벌을 만들어야 하므로 규칙을 그대로 옮긴다.
*
* (한때 이 주석에 "279건"이라고 적혀 있었다. 그건 일반 앱의 **미업로드 세션 수**였고
* 병원 적재량과 무관하다 — 2026-09-09 서버 조사에서 드러났다.)
*
* ```
* CSV 파일 1개 = labdb 세션 1개 (자세·충만도·주파수·cycle 이 고정된 20 반복)
* CSV 6행(CH0~5) = labdb record 1개 → channels[0..5].data = s0..s99
@@ -39,11 +42,12 @@ import java.util.TimeZone
object HospitalLabdbPayload {
/**
* labdb 는 100~899 를 관리자가 배정한다. 병원 임상 프로토콜용 코드는 **아직 없어서**
* 사용자 정의 구간(900~999)을 쓴다. 배정받으면 이 값만 바꾸면 된다 — 2026-09-05
* 수동 업로드분도 같은 값으로 올라가 있다.
* 병원 임상 측정 — labdb 관리자 배정 코드(2026-09-09).
*
* 임시로 쓰던 `901`(사용자 정의 구간)에서 옮겼다. 서버의 기존 적재분
* (72세션 · 1,440레코드)도 같은 날 600 으로 마이그레이션됐다.
*/
const val DATA_TYPE = "901"
const val DATA_TYPE = "600"
private const val PROTOCOL = "hospital_clinical_2026"
private const val SAMPLES_PER_CHANNEL = 100
@@ -144,6 +148,10 @@ object HospitalLabdbPayload {
put("testId", compactTestId(subject, csv.name))
put("dataType", dataType)
put("sessionName", base)
// 서버는 **최상위** subject 만 읽어 sessions.subject 컬럼에 넣는다
// (params 안의 것은 안 본다). 2026-09-09 서버 조사에서 기존 72세션의
// 그 컬럼이 전부 비어 있던 원인이 이것이다.
if (subject.isNotBlank()) put("subject", subject)
put("params", params)
put("recordCount", records.length())
put("records", records)