Clinical: SubjectType 아래 Subject(optional 자유 식별자) 필드 추가

회의 추가 결정: Phantom/Human/ETC 카테고리 안에서도 표본을 구분하기 위한
자유 텍스트 식별자가 필요. 예: "500mL phantom", "S001", "small phantom v2".

ClinicalSession 모델:
  - val subject: String = "" (optional, 기본값 빈 문자열)
  - folderName: prefix로 포함 (비어있으면 생략)
    예: PHANTOM-500mL_VBT26050202_SUPINE_ALIGN_0CM_2026-06-09_15-30-00
    예 (subject 없음): PHANTOM_VBT26050202_SUPINE_ALIGN_0CM_2026-06-09_15-30-00
  - 폴더명 안전 문자만 (특수문자 → '-'), testId 형식과 일관성 유지

ClinicalSessionStore:
  - lastSubject: String 추가 (다음 측정 자동 prefill)
  - startMeasurement에서 저장

JSON 저장:
  - meta.json + measurement.json의 meta 블록에 "subject" 필드 추가
  - 비어있으면 필드 자체 생략 (기존 호환)

labdb payload:
  - params.subject 필드 추가 (subject_type과 함께)

UI (ClinicalHomeView):
  - SubjectType segmented 아래에 OutlinedTextField
  - 라벨: 'Subject (선택) — 예: 500mL, S001'
  - IME action Done + onDone hide
  - 마지막 입력 유지 (lastSubject prefill)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 17:26:37 +09:00
parent 82ba3bbcf6
commit 7fa18ec05a
4 changed files with 44 additions and 3 deletions
@@ -53,6 +53,7 @@ object ClinicalSessionStore {
var lastNotes: String = ""
var lastSubjectType: com.example.medilightv2android.models.SubjectType =
com.example.medilightv2android.models.SubjectType.PHANTOM
var lastSubject: String = "" // 카테고리 내 식별자 (예: "500mL", "S001")
var lastTrueVolumeMl: String = "" // 문자열로 보관 (입력 폼과 동일 형식)
var lastAbdomenThicknessMm: String = ""
// 회의 결정: Step/Posture 마지막 선택 유지 — 매번 0cm로 리셋되는 불편 제거
@@ -69,6 +70,7 @@ object ClinicalSessionStore {
lastExaminer = session.examiner
lastNotes = session.notes
lastSubjectType = session.subjectType
lastSubject = session.subject
lastTrueVolumeMl = session.trueVolumeMl?.toString() ?: ""
lastAbdomenThicknessMm = session.abdomenThicknessMm?.toString() ?: ""
lastPosture = session.posture
@@ -124,6 +126,7 @@ object ClinicalSessionStore {
put("label", s.label)
// 측정 대상 분류 (회의 결정: Phantom/Human/ETC)
put("subject_type", s.subjectType.name)
if (s.subject.isNotBlank()) put("subject", s.subject)
// 임상 시험 참값 (외부 측정기, vesiscan_test 프로토콜)
s.trueVolumeMl?.let { put("true_volume_ml", it) }
s.abdomenThicknessMm?.let { put("abdomen_thickness_mm", it) }
@@ -196,6 +199,7 @@ object ClinicalSessionStore {
val meta = JSONObject().apply {
put("data_type", DATA_TYPE)
put("subject_type", s.subjectType.name)
if (s.subject.isNotBlank()) put("subject", s.subject)
put("device_name", s.deviceName)
put("posture", s.posture.name)
put("step", s.step.name)
@@ -107,6 +107,7 @@ object LabdbUploader {
put("is_alignment", meta.optBoolean("is_alignment"))
put("examiner", meta.optString("examiner"))
meta.optString("subject_type").takeIf { it.isNotBlank() }?.let { put("subject_type", it) }
meta.optString("subject").takeIf { it.isNotBlank() }?.let { put("subject", it) }
if (meta.has("true_volume_ml")) put("true_volume_ml", meta.optDouble("true_volume_ml"))
if (meta.has("abdomen_thickness_mm")) put("abdomen_thickness_mm", meta.optDouble("abdomen_thickness_mm"))
meta.optString("firmware_version").takeIf { it.isNotBlank() }?.let { put("firmware_version", it) }