CLINICAL: Subject ID / Device 입력 제거 — BLE 기기 이름이 device 식별자
사용자 요청: "subject ID 빼고, Device도 BLE 기기 이름따라 기기가 다른거라
따로 설정안해도 될듯."
변경:
- ClinicalSession 모델: subjectId 필드 제거, ClinicalDevice enum 제거,
device: ClinicalDevice → deviceName: String (BLE 연결된 기기 이름 자동 채움)
- folderName: "{deviceName}_{posture}_{step}_{timestamp}"
예: "VBT26050202_SUPINE_ALIGN_0CM_2026-05-28_14-30-15"
- label: "{deviceName}_{posture}_{step}" (예: "VBT26050202_SUPINE_ALIGN_0CM")
- ClinicalSessionStore: lastSubjectId 제거 (Examiner/Notes만 기억)
- ClinicalHomeView:
* Subject ID TextField 제거
* Device segmented control 제거
* BLE 연결 카드에 deviceName 그대로 표시 (자동 사용)
* Start 버튼 enabled = isConnected 만으로 충분
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,8 +8,9 @@ import java.util.Locale
|
|||||||
* 임상 R&D 측정 — "한 측정 단위" 모델.
|
* 임상 R&D 측정 — "한 측정 단위" 모델.
|
||||||
*
|
*
|
||||||
* 한 ClinicalSession = 한 라벨 = 한 폴더 = 한 측정 사이클.
|
* 한 ClinicalSession = 한 라벨 = 한 폴더 = 한 측정 사이클.
|
||||||
* 사용자가 BLE 연결 후 매번 라벨(Subject/Device/Posture/Step)을 입력하고
|
* 사용자가 BLE 연결 후 매번 자세/위치를 입력하고 측정 진행 → Back 시 폴더 finalize.
|
||||||
* 측정을 진행하면, 측정 종료 시점에 폴더가 finalize됨.
|
*
|
||||||
|
* 디바이스 식별자는 BLE 연결된 기기 이름(예: VBT26050202)을 자동으로 사용.
|
||||||
*
|
*
|
||||||
* 데이터 구조 (한 측정당):
|
* 데이터 구조 (한 측정당):
|
||||||
* Downloads/VesiScan_Sessions/{folderName}/
|
* Downloads/VesiScan_Sessions/{folderName}/
|
||||||
@@ -19,10 +20,6 @@ import java.util.Locale
|
|||||||
* └─ ble.log
|
* └─ ble.log
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum class ClinicalDevice(val label: String) {
|
|
||||||
A("Device A"), B("Device B"), C("Device C")
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class ClinicalPosture(val label: String) {
|
enum class ClinicalPosture(val label: String) {
|
||||||
SUPINE("Supine"), SITTING("Sitting"), STANDING("Standing")
|
SUPINE("Supine"), SITTING("Sitting"), STANDING("Standing")
|
||||||
}
|
}
|
||||||
@@ -42,8 +39,7 @@ enum class ClinicalStep(val label: String, val isAlignment: Boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class ClinicalSession(
|
data class ClinicalSession(
|
||||||
val subjectId: String, // "S001"
|
val deviceName: String, // BLE 기기 이름 (예: "VBT26050202")
|
||||||
val device: ClinicalDevice,
|
|
||||||
val posture: ClinicalPosture,
|
val posture: ClinicalPosture,
|
||||||
val step: ClinicalStep,
|
val step: ClinicalStep,
|
||||||
val examiner: String = "",
|
val examiner: String = "",
|
||||||
@@ -53,11 +49,11 @@ data class ClinicalSession(
|
|||||||
) {
|
) {
|
||||||
private val folderFmt = SimpleDateFormat("yyyy-MM-dd_HHmmss", Locale.US)
|
private val folderFmt = SimpleDateFormat("yyyy-MM-dd_HHmmss", Locale.US)
|
||||||
|
|
||||||
/** "S001_A_SUPINE_ALIGN_0CM_2026-05-28_14-30-15" */
|
/** "VBT26050202_SUPINE_ALIGN_0CM_2026-05-28_14-30-15" */
|
||||||
val folderName: String
|
val folderName: String
|
||||||
get() = "${subjectId}_${device.name}_${posture.name}_${step.name}_${folderFmt.format(Date(startedAt))}"
|
get() = "${deviceName}_${posture.name}_${step.name}_${folderFmt.format(Date(startedAt))}"
|
||||||
|
|
||||||
/** clinical_label 컬럼/필드 값 (라벨링 자동) */
|
/** clinical_label 컬럼/필드 값 (라벨링 자동) */
|
||||||
val label: String
|
val label: String
|
||||||
get() = "${subjectId}_${device.name}_${posture.name}_${step.name}"
|
get() = "${deviceName}_${posture.name}_${step.name}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,14 +34,12 @@ object ClinicalSessionStore {
|
|||||||
var lastFinalizedFolder: File? = null
|
var lastFinalizedFolder: File? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
/** 다음 측정 시 기본값으로 자동 채울 사용자 입력 (subjectId/examiner/notes) */
|
/** 다음 측정 시 기본값으로 자동 채울 사용자 입력 (examiner/notes) */
|
||||||
var lastSubjectId: String = ""
|
|
||||||
var lastExaminer: String = ""
|
var lastExaminer: String = ""
|
||||||
var lastNotes: String = ""
|
var lastNotes: String = ""
|
||||||
|
|
||||||
fun startMeasurement(context: Context, session: ClinicalSession) {
|
fun startMeasurement(context: Context, session: ClinicalSession) {
|
||||||
currentSession = session
|
currentSession = session
|
||||||
lastSubjectId = session.subjectId
|
|
||||||
lastExaminer = session.examiner
|
lastExaminer = session.examiner
|
||||||
lastNotes = session.notes
|
lastNotes = session.notes
|
||||||
currentLogDir()?.mkdirs()
|
currentLogDir()?.mkdirs()
|
||||||
@@ -63,8 +61,7 @@ object ClinicalSessionStore {
|
|||||||
val dir = currentLogDir() ?: return
|
val dir = currentLogDir() ?: return
|
||||||
dir.mkdirs()
|
dir.mkdirs()
|
||||||
val meta = JSONObject().apply {
|
val meta = JSONObject().apply {
|
||||||
put("subject_id", s.subjectId)
|
put("device_name", s.deviceName)
|
||||||
put("device", s.device.name)
|
|
||||||
put("posture", s.posture.name)
|
put("posture", s.posture.name)
|
||||||
put("step", s.step.name)
|
put("step", s.step.name)
|
||||||
put("is_alignment", s.step.isAlignment)
|
put("is_alignment", s.step.isAlignment)
|
||||||
|
|||||||
+7
-24
@@ -43,6 +43,7 @@ fun ClinicalHomeView(appState: AppState) {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val bleManager = remember { BleManager.getInstance(context) }
|
val bleManager = remember { BleManager.getInstance(context) }
|
||||||
val isConnected by bleManager.isConnected
|
val isConnected by bleManager.isConnected
|
||||||
|
val deviceName = bleManager.connectedDeviceName.value
|
||||||
|
|
||||||
// 진입 시 flag set, 화면 떠날 때 unset (DEVICE_SCAN/측정화면 라우팅용)
|
// 진입 시 flag set, 화면 떠날 때 unset (DEVICE_SCAN/측정화면 라우팅용)
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
@@ -54,8 +55,6 @@ fun ClinicalHomeView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 라벨 입력 state — 마지막 입력값 기억 (Store에서 자동 채움)
|
// 라벨 입력 state — 마지막 입력값 기억 (Store에서 자동 채움)
|
||||||
var subjectId by remember { mutableStateOf(ClinicalSessionStore.lastSubjectId) }
|
|
||||||
var device by remember { mutableStateOf(ClinicalDevice.A) }
|
|
||||||
var posture by remember { mutableStateOf(ClinicalPosture.SUPINE) }
|
var posture by remember { mutableStateOf(ClinicalPosture.SUPINE) }
|
||||||
var step by remember { mutableStateOf(ClinicalStep.ALIGN_0CM) }
|
var step by remember { mutableStateOf(ClinicalStep.ALIGN_0CM) }
|
||||||
var examiner by remember { mutableStateOf(ClinicalSessionStore.lastExaminer) }
|
var examiner by remember { mutableStateOf(ClinicalSessionStore.lastExaminer) }
|
||||||
@@ -144,18 +143,6 @@ fun ClinicalHomeView(appState: AppState) {
|
|||||||
Text("Measurement Label", fontWeight = FontWeight.Bold, fontSize = 16.sp)
|
Text("Measurement Label", fontWeight = FontWeight.Bold, fontSize = 16.sp)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = subjectId,
|
|
||||||
onValueChange = { subjectId = it },
|
|
||||||
label = { Text("Subject ID (예: S001)") },
|
|
||||||
singleLine = true,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
|
|
||||||
LabeledSegmented("Device", ClinicalDevice.values().toList(), device, { device = it }) { it.label }
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
|
|
||||||
LabeledSegmented("Posture", ClinicalPosture.values().toList(), posture, { posture = it }) { it.label }
|
LabeledSegmented("Posture", ClinicalPosture.values().toList(), posture, { posture = it }) { it.label }
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
@@ -180,10 +167,10 @@ fun ClinicalHomeView(appState: AppState) {
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
// 라벨 preview
|
// 라벨 preview — BLE 기기 이름이 device 식별자 역할
|
||||||
val previewLabel = if (subjectId.isNotBlank())
|
val previewLabel = if (isConnected)
|
||||||
"${subjectId}_${device.name}_${posture.name}_${step.name}"
|
"${deviceName}_${posture.name}_${step.name}"
|
||||||
else "(Subject ID 입력 필요)"
|
else "(BLE 연결 후 활성화)"
|
||||||
Text("Label: $previewLabel",
|
Text("Label: $previewLabel",
|
||||||
fontSize = 12.sp, color = MlSecondaryText, fontWeight = FontWeight.SemiBold)
|
fontSize = 12.sp, color = MlSecondaryText, fontWeight = FontWeight.SemiBold)
|
||||||
|
|
||||||
@@ -195,8 +182,7 @@ fun ClinicalHomeView(appState: AppState) {
|
|||||||
ClinicalSessionStore.startMeasurement(
|
ClinicalSessionStore.startMeasurement(
|
||||||
context,
|
context,
|
||||||
ClinicalSession(
|
ClinicalSession(
|
||||||
subjectId = subjectId.trim(),
|
deviceName = deviceName,
|
||||||
device = device,
|
|
||||||
posture = posture,
|
posture = posture,
|
||||||
step = step,
|
step = step,
|
||||||
examiner = examiner.trim(),
|
examiner = examiner.trim(),
|
||||||
@@ -206,7 +192,7 @@ fun ClinicalHomeView(appState: AppState) {
|
|||||||
appState.currentScreen = if (step.isAlignment) AppScreen.PLACEMENT_GUIDE
|
appState.currentScreen = if (step.isAlignment) AppScreen.PLACEMENT_GUIDE
|
||||||
else AppScreen.PIEZO_MONITORING
|
else AppScreen.PIEZO_MONITORING
|
||||||
},
|
},
|
||||||
enabled = isConnected && subjectId.isNotBlank(),
|
enabled = isConnected,
|
||||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||||
shape = RoundedCornerShape(14.dp),
|
shape = RoundedCornerShape(14.dp),
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = MlPrimary)
|
colors = ButtonDefaults.buttonColors(containerColor = MlPrimary)
|
||||||
@@ -220,9 +206,6 @@ fun ClinicalHomeView(appState: AppState) {
|
|||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
Text("→ BLE 연결 후 활성화", fontSize = 11.sp, color = Color(0xFFFF9800))
|
Text("→ BLE 연결 후 활성화", fontSize = 11.sp, color = Color(0xFFFF9800))
|
||||||
} else if (subjectId.isBlank()) {
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
Text("→ Subject ID 입력 필요", fontSize = 11.sp, color = Color(0xFFFF9800))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|||||||
Reference in New Issue
Block a user