diff --git a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/AnchorAlignView.kt b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/AnchorAlignView.kt index c88b070..f0096f1 100644 --- a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/AnchorAlignView.kt +++ b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/AnchorAlignView.kt @@ -173,6 +173,25 @@ fun AnchorAlignView(appState: AppState) { val done = last?.done == true + // ── 연결이 끊기면 기기에서 온 값을 비운다 ─────────────────────────────── + // 병원 모드와 같은 이유다 — 끊긴 프로브의 BV·파형·접촉이 새 프로브의 것으로 읽힌다. + // + // 다만 **판정 기록(guide·records·last)은 비우지 않는다.** 여기까지 잰 위치가 한 + // 위치당 20 cycle 인데, 연결이 잠깐 끊겼다고 날리면 그 환자를 다시 눕혀야 한다. + // 재연결 후 이어서 재는 것이 정상 경로다. + // + // 대신 프로브를 **바꿔서** 다시 연결하면 그 세션은 섞인 데이터가 된다 — + // align_result.json 의 hw_preset 은 한 번만 기록되므로 절반이 잘못 라벨링된다. + // 그때는 [처음부터 다시 정렬] 을 눌러야 한다. 화면이 그 사실을 말해 준다. + var disconnectedMidRun by remember { mutableStateOf(false) } + LaunchedEffect(isConnected) { + if (isConnected) return@LaunchedEffect + bv = null + liveChannels = emptyList() + detachWatcher.reset(); detachSnapshot = null + if (records.isNotEmpty()) disconnectedMidRun = true + } + // ── 떠날 때 정렬을 올린다 ─────────────────────────────────────────────── // 종전에는 정렬 업로드가 **수동**이었다. 그 버튼의 원래 용도가 "파형이 이상할 때 // 개발자에게 보내기"(메모를 받는 다이얼로그)라, 정상적으로 정렬을 끝내면 601 이 @@ -623,6 +642,21 @@ fun AnchorAlignView(appState: AppState) { Spacer(Modifier.height(16.dp)) } + // 프로브를 바꿔 끼웠으면 이 세션을 이어 쓸 수 없다. hw_preset 이 한 번만 + // 기록되므로 절반이 다른 기기의 데이터인데 한 기기로 라벨링된다. + if (disconnectedMidRun && isConnected) { + Text( + "측정 도중 연결이 끊겼습니다. 같은 프로브면 이어서 재도 됩니다. " + + "프로브를 바꿨다면 [처음부터 다시 정렬] 을 눌러 주세요 — " + + "기기마다 빔 각도와 프리셋이 달라 섞이면 기록이 잘못됩니다.", + fontSize = 12.sp, color = MlWarning, + modifier = Modifier.fillMaxWidth() + .background(MlWarning.copy(alpha = 0.10f), RoundedCornerShape(8.dp)) + .padding(10.dp), + ) + Spacer(Modifier.height(10.dp)) + } + if (!done) { Button( onClick = { measuring = true }, @@ -751,6 +785,7 @@ fun AnchorAlignView(appState: AppState) { guide.reset(); cm = 0; last = null; records = emptyList(); error = null lateral.reset(); lrCommit = null; lrAccum = 0 lrConfirmed = false; lrError = null; lrRunning = false + disconnectedMidRun = false }, enabled = !lrRunning, modifier = Modifier.fillMaxWidth(), diff --git a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/BvMeasureSection.kt b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/BvMeasureSection.kt index 22915d8..0541dae 100644 --- a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/BvMeasureSection.kt +++ b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/BvMeasureSection.kt @@ -125,6 +125,21 @@ fun BvMeasureSection( var saveFailed by remember { mutableStateOf(false) } + // 연결이 끊기면 이 구간의 값도 비운다. "마지막 측정"과 연속 대표값이 남아 있으면 + // 새로 꽂은 프로브의 값으로 읽힌다 — 프리셋이 다르면 전혀 다른 수다. + // + // 측정 중이었으면 모드도 내린다. 끊긴 채로 돌면 3초마다 시간 초과만 쌓인다. + LaunchedEffect(isConnected) { + if (isConnected) return@LaunchedEffect + mode = null + outcome = null + live = emptyList() + window = emptyList() + tick = 0 + error = null + // 저장 결과 문구는 남긴다 — 방금 남긴 기록이 사라지면 남았는지 의심하게 된다. + } + LaunchedEffect(mode) { val m = mode ?: return@LaunchedEffect lastMode = m diff --git a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt index 67b5eff..9807da8 100644 --- a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt +++ b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt @@ -142,6 +142,26 @@ fun HospitalModeView(appState: AppState) { mutableStateOf(null) } + // ── 연결이 끊기면 기기에서 온 값을 비운다 ─────────────────────────────── + // 버튼에만 넣지 않고 isConnected 를 본다. 프로브가 스스로 끊기는 경우가 실제로 + // 있다(VBTFW0206 freeze — 2026-09-08 로그에서 advertising 까지 멈췄다). 그때도 + // 화면은 마지막 값을 계속 보여 주므로 구분이 안 된다. + // + // 왜 비워야 하나: **프리셋이 다른 프로브로 갈아 끼우면 같은 신호가 전혀 다른 용적이 + // 된다.** PiezoHW.activePreset 이 dps·delay 를 정하고, 실측에서 같은 데이터가 + // 125mL 와 490mL 로 갈렸다. 끊긴 프로브의 187mL 가 "방광 용적"이라는 이름으로 남아 + // 있으면 새 프로브의 값으로 읽힌다. + // + // 비우는 것은 **기기에서 온 값만**이다. 자세·충만도·반복·두께는 조작자가 고른 + // 프로토콜 설정이고, 환자명과 미업로드 목록은 기기와 무관하다 — 특히 + // lastRunDir/pendingUploads 를 비우면 아직 못 올린 파일을 다시 올릴 길이 끊긴다. + LaunchedEffect(isConnected) { + if (isConnected) return@LaunchedEffect + runBv = null + liveChannels = emptyList() + detachWatcher.reset(); detachSnapshot = null + } + val repeats = repeatsText.toIntOrNull()?.coerceIn(1, 200) ?: HOSPITAL_DEFAULT_REPEATS val canStart = isConnected && patient.isNotBlank() && !running @@ -375,6 +395,10 @@ fun HospitalModeView(appState: AppState) { // 기기가 바뀌면 **정렬도 무효**다. R100/R200/R300 은 빔 각도가 // 서로 달라(0/-7/-14/-21 vs 0/-9/-18/-27) 같은 자리에서도 // nch·cap_frac 이 달라진다 — 최적 부착 위치 자체가 다르다. + // + // BV·파형·접촉은 여기서 비우지 않는다. 위의 + // LaunchedEffect(isConnected) 가 **끊기는 모든 경우**를 + // 맡는다 — 프로브가 스스로 끊길 때도 같아야 한다. appState.anchorCm = null appState.anchorBasis = null bleManager.disconnect()