docs(clinical): 1회 측정은 메인의 [단일 측정]과 다르다고 분명히
앞 커밋에서 "메인 화면과 같은 방식"이라고 썼는데 절반만 맞았다. 메인의 [단일 측정]은
1 cycle 이 아니라 **5회를 모아 절사평균**한다 (PiezoMonitoringView: targetCount=5,
maxAttempts=8, 3개 이상이면 값). 임상 화면은 1회만 잰다 — 의도한 차이지만, 주석과
화면 문구가 "같다"고 말하고 있었다.
연속 / 자동 cycle 마다 BV → 최근 10개 절사평균, 5개부터 표시 같음
1회 / 단일 메인 5회 절사평균 vs 여기 1 cycle 그대로 다름
1회로 두는 이유는 검증 항목 ③(위치 민감도)이다. 상·하·좌·우 1~2cm 씩 옮기며 값을
기록하는 작업인데 메인 방식은 한 번에 10초 가까이 걸려 그 시간이 그대로 쌓인다.
**틀린 설명이 코드보다 위험하다.** 숫자가 다른 건 이유를 알면 해석할 수 있지만, "같은
방식"이라고 읽은 사람은 임상 1회 값과 메인 단일 값을 나란히 놓고 기기 차이로 결론낸다.
그래서 주석에 비교표와 "직접 비교하면 안 된다"를 넣고, 기존 초음파 측정기 대조(항목 ①)
에는 **연속 측정**을 쓰라고 못박았다.
버튼도 [단일 측정]→[1회 측정], [자동 측정]→[연속 측정]. 메인과 같은 이름이면 같은
동작으로 읽힌다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,26 +27,30 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 한 번 재는 것(단일/Spot)과 계속 재는 것(자동/Continuous).
|
* 1회 측정(Spot)과 연속 측정(Continuous).
|
||||||
*
|
*
|
||||||
* ## 메인 화면의 [단일 측정]·[자동 측정]과 **같은 방식**이다
|
* ## 메인 화면과 같은 것, 다른 것
|
||||||
* 두 화면의 숫자가 비교 가능해야 한다. 방식이 다르면 같은 프로브·같은 자리에서도 값이
|
* 메인 화면([com.medithings.vesiscan.ui.views.monitoring.PiezoMonitoringView])과 나란히
|
||||||
* 갈려서, "임상 화면은 180 인데 메인은 195"가 기기 차이인지 계산 차이인지 알 수 없다.
|
* 두고 비교한다:
|
||||||
* 검증의 첫 항목이 "기존 초음파 측정기와 비교"라 이 혼동은 치명적이다.
|
|
||||||
*
|
|
||||||
* 그래서 메인 화면([com.medithings.vesiscan.ui.views.monitoring.PiezoMonitoringView])의
|
|
||||||
* 규약을 그대로 옮긴다:
|
|
||||||
*
|
*
|
||||||
* | | 메인 | 여기 |
|
* | | 메인 | 여기 |
|
||||||
* |---|---|---|
|
* |---|---|---|
|
||||||
* | 단일 | 1 cycle → BV 그대로 | 같음 |
|
* | 연속 / 자동 | cycle 마다 BV → 최근 10개 **절사평균**, 5개 이상부터 표시 | **같음** |
|
||||||
* | 자동 | cycle 마다 BV → 최근 [WINDOW_SIZE]개 **절사평균** | 같음 |
|
* | 1회 / 단일 | 5회 목표(최대 8회 시도) → 절사평균, 3개 이상이면 값 | **1 cycle → BV 그대로** |
|
||||||
* | 표시 시작 | 창에 [WINDOW_MIN]개 이상 쌓인 뒤 | 같음 |
|
|
||||||
* | 주기 | 완료 대기 + 남은 간격만 | 같음 |
|
|
||||||
*
|
*
|
||||||
* **평균을 BV 뒤에 낸다**는 점이 핵심이다. 신호를 먼저 평균하면(mean-scan) 값이 더
|
* **1회 측정만 일부러 다르다.** 메인의 [단일 측정]은 5회를 모아 절사평균하느라 한 번에
|
||||||
* 안정적이지만 메인 화면과 다른 수가 되고, 정렬 단계의 20-cycle mean-scan 과도 또
|
* 10초 가까이 걸린다. 검증 항목 ③(위치 민감도)은 상·하·좌·우로 1~2cm 씩 옮기며 값을
|
||||||
* 다른 세 번째 방식이 된다. 여기서는 **비교 가능성이 안정성보다 우선**이라 이쪽을 택했다.
|
* 기록하는 작업이라 그 시간이 그대로 쌓인다. 여기서는 **한 자리에서 즉시 한 값**을
|
||||||
|
* 보는 것이 목적이고, 안정된 대표값이 필요하면 연속 측정을 쓰면 된다.
|
||||||
|
*
|
||||||
|
* 그래서 **1회 측정 값을 메인의 [단일 측정] 값과 직접 비교하면 안 된다.** 저쪽은
|
||||||
|
* 5회 절사평균이라 더 안정적이다. 기존 초음파 측정기와 대조할 때(검증 항목 ①)는
|
||||||
|
* **연속 측정의 절사평균**을 쓰는 것이 맞다 — 그쪽이 메인과 같은 규약이다.
|
||||||
|
*
|
||||||
|
* ## 평균은 BV 뒤에 낸다
|
||||||
|
* 신호를 먼저 평균하면(mean-scan) 값이 더 안정적이지만 메인 화면과 다른 수가 되고,
|
||||||
|
* 정렬 단계의 20-cycle mean-scan 과도 또 다른 세 번째 방식이 된다. 연속 측정이
|
||||||
|
* 메인과 비교 가능해야 하므로 부피끼리 평균한다.
|
||||||
*
|
*
|
||||||
* ## 절사평균인 이유
|
* ## 절사평균인 이유
|
||||||
* 최대·최소 하나씩 버리고 평균낸다. cycle 단위 BV 는 벽 오검출로 가끔 크게 튀는데,
|
* 최대·최소 하나씩 버리고 평균낸다. cycle 단위 BV 는 벽 오검출로 가끔 크게 튀는데,
|
||||||
@@ -59,13 +63,13 @@ import kotlinx.coroutines.withTimeoutOrNull
|
|||||||
private val BV_FREQ = ProbeFrequency.F_2_3
|
private val BV_FREQ = ProbeFrequency.F_2_3
|
||||||
private val BV_CYCLE = ProbeCycle.C3
|
private val BV_CYCLE = ProbeCycle.C3
|
||||||
|
|
||||||
/** 자동 모드 표시 창 크기. 메인 화면 `autoWindowSize` 와 같은 값. */
|
/** 연속 모드 표시 창 크기. 메인 화면 `autoWindowSize` 와 같은 값. */
|
||||||
private const val WINDOW_SIZE = 10
|
private const val WINDOW_SIZE = 10
|
||||||
|
|
||||||
/** 이만큼 쌓여야 절사평균을 띄운다. 메인 화면과 같다. */
|
/** 이만큼 쌓여야 절사평균을 띄운다. 메인 화면과 같다. */
|
||||||
private const val WINDOW_MIN = 5
|
private const val WINDOW_MIN = 5
|
||||||
|
|
||||||
/** 자동 모드 목표 주기. 메인 화면 `piezoSettings.autoScanIntervalMs` 기본값과 같다. */
|
/** 연속 모드 목표 주기. 메인 화면 `piezoSettings.autoScanIntervalMs` 기본값과 같다. */
|
||||||
private const val INTERVAL_MS = 500L
|
private const val INTERVAL_MS = 500L
|
||||||
|
|
||||||
private const val MEASURE_TIMEOUT_MS = 3_000L
|
private const val MEASURE_TIMEOUT_MS = 3_000L
|
||||||
@@ -132,7 +136,7 @@ fun BvMeasureSection(
|
|||||||
bleManager.sendMtb()
|
bleManager.sendMtb()
|
||||||
val got = withTimeoutOrNull(MEASURE_TIMEOUT_MS) { inbox.receive() }
|
val got = withTimeoutOrNull(MEASURE_TIMEOUT_MS) { inbox.receive() }
|
||||||
if (got == null) {
|
if (got == null) {
|
||||||
// 한 회 놓쳤다고 멈추지 않는다 — 자동 모드는 계속 돌아야 한다.
|
// 한 회 놓쳤다고 멈추지 않는다 — 연속 모드는 계속 돌아야 한다.
|
||||||
error = "응답 시간 초과 — 다시 시도합니다."
|
error = "응답 시간 초과 — 다시 시도합니다."
|
||||||
} else {
|
} else {
|
||||||
error = null
|
error = null
|
||||||
@@ -172,7 +176,8 @@ fun BvMeasureSection(
|
|||||||
fontSize = 11.sp, color = MlSecondaryText)
|
fontSize = 11.sp, color = MlSecondaryText)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
"메인 화면의 [단일 측정] · [자동 측정]과 같은 방식입니다.",
|
"연속 측정은 메인 화면의 [자동 측정]과 같은 방식(10회 절사평균)입니다. " +
|
||||||
|
"1회 측정은 말 그대로 한 번만 잽니다 — 메인의 [단일 측정](5회 절사평균)과 다릅니다.",
|
||||||
fontSize = 11.sp, color = MlSecondaryText,
|
fontSize = 11.sp, color = MlSecondaryText,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -204,7 +209,7 @@ fun BvMeasureSection(
|
|||||||
enabled = isConnected && mode != Mode.CONTINUOUS,
|
enabled = isConnected && mode != Mode.CONTINUOUS,
|
||||||
modifier = Modifier.weight(1f).height(46.dp),
|
modifier = Modifier.weight(1f).height(46.dp),
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(12.dp),
|
||||||
) { Text(if (mode == Mode.SPOT) "측정 중…" else "단일 측정", fontWeight = FontWeight.Bold) }
|
) { Text(if (mode == Mode.SPOT) "측정 중…" else "1회 측정", fontWeight = FontWeight.Bold) }
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Button(
|
Button(
|
||||||
onClick = { mode = if (mode == Mode.CONTINUOUS) null else Mode.CONTINUOUS },
|
onClick = { mode = if (mode == Mode.CONTINUOUS) null else Mode.CONTINUOUS },
|
||||||
@@ -214,7 +219,7 @@ fun BvMeasureSection(
|
|||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
containerColor = if (mode == Mode.CONTINUOUS) MlCritical else MlSuccess),
|
containerColor = if (mode == Mode.CONTINUOUS) MlCritical else MlSuccess),
|
||||||
) {
|
) {
|
||||||
Text(if (mode == Mode.CONTINUOUS) "자동 측정 중지" else "자동 측정",
|
Text(if (mode == Mode.CONTINUOUS) "연속 측정 중지" else "연속 측정",
|
||||||
color = Color.White, fontWeight = FontWeight.Bold)
|
color = Color.White, fontWeight = FontWeight.Bold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,7 +229,7 @@ fun BvMeasureSection(
|
|||||||
Text(it, fontSize = 12.sp, color = MlCritical)
|
Text(it, fontSize = 12.sp, color = MlCritical)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 자동 모드 대표값 ──
|
// ── 연속 모드 대표값 ──
|
||||||
// 메인 화면과 같다: 최근 10개의 **절사평균**(최대·최소 하나씩 버림), 5개 이상 쌓여야 표시.
|
// 메인 화면과 같다: 최근 10개의 **절사평균**(최대·최소 하나씩 버림), 5개 이상 쌓여야 표시.
|
||||||
if (mode == Mode.CONTINUOUS || window.isNotEmpty()) {
|
if (mode == Mode.CONTINUOUS || window.isNotEmpty()) {
|
||||||
Spacer(Modifier.height(10.dp))
|
Spacer(Modifier.height(10.dp))
|
||||||
|
|||||||
Reference in New Issue
Block a user