feat(align): 부착 오프셋 0 — best 자리에서 바로 좌우 밸런스 (이번 임상부터 모든 자세에서 정렬)
임상팀 결정(2026-10-01, e.yu · kai.han): 모든 자세에서 정렬을 하므로 "best + 1cm 올리고
거기서 좌우" 단계를 빼고, 확인 측정한 best 자리에서 바로 좌우를 맞추고 그대로 붙인다.
· AnchorConfig.FINAL_OFFSET_CM = 1 → 0. anchor_cm = best_cm. 2026-10-01 이전 세션
(final_offset_cm: 1)과 대조할 때는 Python select_supine_anchor(offset_cm=0).
· 지시문: 확인 측정 뒤 "치골 위 N cm 그대로 — 좌우 맞추기 / 옮기지 말고". 오프셋이
0 이 아닐 때의 "N+offset cm 에 붙이세요" 는 남겨 둔다(상수만 되돌리면 그대로 동작).
· AnchorGuide STOP 문구도 "이 자리(N cm)에 그대로 부착합니다".
· 재부착 확인의 기준(좌우 확인 창)은 이제 확인 측정과 같은 높이 — "떼기 직전" 이라 그대로 둔다.
시험 AlignInstructionTest 오프셋 0 문구 1건 추가 — 전체 192건 · failures 0.
문서: CLINICAL_ALGORITHM §3.5/3.6/상수표, LABDB_DATATYPES final_offset_cm/anchor_cm, README.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -92,8 +92,15 @@ object AnchorConfig {
|
||||
/** 후보 자격: per-trace CH3 검출률 하한. */
|
||||
const val CH3_HIT_MIN = 0.80
|
||||
|
||||
/** 최종 안내 = best + 이 값. **선택이 아니라 안내**다(아래 [AnchorGuide.anchorCm] 참고). */
|
||||
const val FINAL_OFFSET_CM = 1
|
||||
/**
|
||||
* 최종 안내 = best + 이 값. **선택이 아니라 안내**다(아래 [AnchorGuide.anchorCm] 참고).
|
||||
*
|
||||
* **0 (2026-10-01~).** 이번 임상부터 모든 자세에서 정렬을 하기로 하면서(e.yu · kai.han)
|
||||
* "best + 1cm 올리고 거기서 좌우" 를 빼고 **best 자리에서 바로 좌우 밸런스**를 맞춘다.
|
||||
* 그 전까지는 1 이었다 — 2026-10-01 이전 세션의 `anchor_cm` 은 `best_cm + 1` 이고,
|
||||
* Python `select_supine_anchor(offset_cm=)` 로 대조할 때는 0 을 넘겨야 같다.
|
||||
*/
|
||||
const val FINAL_OFFSET_CM = 0
|
||||
|
||||
/** 하강 전환 문턱. `nch < max_nch − NCH_DROP` 이면 지나친 것으로 본다. */
|
||||
const val NCH_DROP = 1.0
|
||||
@@ -398,7 +405,8 @@ class AnchorGuide(
|
||||
AnchorAction.STOP,
|
||||
"정렬 완료 — 최적 위치 ${best}cm" +
|
||||
(b?.let { " (nch ${it.nch} · cap ${fmt3(it.capFrac)})" } ?: "") +
|
||||
"\n여기서 ${offsetCm}cm 더 올려 ${anchorCm}cm 에 부착하세요.",
|
||||
(if (offsetCm == 0) "\n이 자리(${anchorCm}cm)에 그대로 부착합니다."
|
||||
else "\n여기서 ${offsetCm}cm 더 올려 ${anchorCm}cm 에 부착하세요."),
|
||||
rec,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,10 +27,12 @@
|
||||
* ...
|
||||
* 치골 위 1cm 에 다시 붙이고 측정 [1cm 측정하기] (확인 측정)
|
||||
* 치골 위 1cm 에서 한 번 더 측정 [1cm 측정하기] (이미 그 자리)
|
||||
* **치골 위 2cm 에 붙이세요 (재지 말고 좌우로)** — 버튼 없음
|
||||
* **치골 위 1cm 그대로 — 좌우 맞추기** — 버튼 없음 (오프셋 0, 2026-10-01~)
|
||||
*
|
||||
* 방향(올리세요/내리세요·화살표)은 어디에도 쓰지 않는다 — 치골 위 N cm 하나면 된다.
|
||||
*
|
||||
* 2026-10-01 부터 오프셋이 0 이다 — 확인 측정한 best 자리에서 바로 좌우를 맞추고 그대로
|
||||
* 붙인다(모든 자세에서 정렬하기로 한 임상 결정). 아래 문단은 오프셋이 1 이던 때의 설명이다:
|
||||
* 마지막 +1cm 자리는 **측정하지 않는다**. 설계상 그 자리는 지표가 나쁘므로, 재확인하면
|
||||
* 반드시 "미달"이 떠서 도달 전에 멈추게 된다.
|
||||
*/
|
||||
@@ -209,8 +211,9 @@ fun AnchorAlignView(appState: AppState) {
|
||||
var lrAttempt by remember { mutableIntStateOf(1) }
|
||||
/**
|
||||
* 좌우 확인 완료 순간의 창(부착 위치 N+1cm)에서 잡힌 채널 CH0~3 — 재부착 확인의 기준.
|
||||
* 확인 측정(N cm)이 아니라 **같은 높이**의 마지막 프레임으로 잡는다. 오프셋 자리는 설계상
|
||||
* 지표가 다른 쪽이라 N cm 기준으로 대조하면 멀쩡한 재부착도 "채널 없음"이 될 수 있다.
|
||||
* 확인 측정(N cm)이 아니라 **좌우를 맞춘 높이**의 마지막 프레임으로 잡는다 — 떼기 직전의
|
||||
* 신호다. (오프셋이 1 이던 때는 높이가 달라 N cm 기준으로 대조하면 멀쩡한 재부착도
|
||||
* "채널 없음"이 될 수 있었다. 지금은 오프셋 0 이라 같은 높이지만 "떼기 직전"이 더 맞다.)
|
||||
*/
|
||||
var lrRefChannels by remember { mutableStateOf<List<Boolean>?>(null) }
|
||||
/** 확인 측정(N cm)의 IMU 평균 기울기. Supine 이면 Sitting 기준각이 된다(2026-10-01). */
|
||||
@@ -1184,10 +1187,10 @@ private fun LateralSection(
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
// 1단계가 끝난 시점에 프로브는 **최적 위치**에 있고, 부착할 자리는 그보다 1cm
|
||||
// 위다(오프셋 자리는 측정하지 않는다). 좌우 균형은 그 높이의 단면에서 정해지는
|
||||
// 값이라 높이를 바꾸면 u4·u5 가 달라진다 — 옮기기 **전에** 맞추면 헛일이 된다.
|
||||
// 화면이 이걸 말해 주지 않으면 간호사마다 순서가 갈린다.
|
||||
// 1단계가 끝난 시점에 프로브는 **최적 위치**에 있고, 부착할 자리는 거기서
|
||||
// FINAL_OFFSET_CM 만큼 위다 — 2026-10-01 부터 0 이라 같은 자리다. 좌우 균형은 그
|
||||
// 높이의 단면에서 정해지는 값이라 높이를 바꾸면 u4·u5 가 달라진다 — 부착할 높이에서
|
||||
// 맞춰야 한다. 화면이 이걸 말해 주지 않으면 간호사마다 순서가 갈린다.
|
||||
Box(
|
||||
Modifier.fillMaxWidth()
|
||||
.background(MlPrimary.copy(alpha = 0.10f), RoundedCornerShape(10.dp))
|
||||
@@ -1384,7 +1387,7 @@ private fun RecordRow(
|
||||
* | 첫 측정 전 | 치골 위 0cm 에 붙이고 측정 |
|
||||
* | 탐색 중 | 치골 위 N cm 에 붙이고 측정 |
|
||||
* | 판정된 자리로 | 치골 위 N cm 에 다시 붙이고 측정 (이미 그 자리면 "에서 한 번 더 측정") |
|
||||
* | 확정 | 치골 위 N+오프셋 cm 에 붙이세요 — **재지 말고** 좌우로 |
|
||||
* | 확정 | 오프셋 0: 치골 위 N cm 그대로 — 좌우 맞추기 / 오프셋>0: 치골 위 N+오프셋 cm 에 붙이세요 |
|
||||
* | 재부착 | 더 아래(치골 쪽)에 다시 붙이기 — 갈 자리가 없어 이것만 상대적이다 |
|
||||
*
|
||||
* @param nextCm 화면이 다음에 재려는 위치(치골 위 cm). 호출부가 판정 뒤 이미 갱신해 둔다.
|
||||
@@ -1417,7 +1420,11 @@ internal fun instructionOf(
|
||||
// 확인 측정까지 끝났다 — 오프셋 자리에 붙이고 좌우로. 그 자리는 재지 않는다.
|
||||
confirmDone -> {
|
||||
val attach = (confirmTarget ?: step.record?.cm ?: nextCm) + offsetCm
|
||||
AlignInstruction("치골 위 ${attach}cm 에 붙이세요",
|
||||
// 오프셋 0(2026-10-01~): 확인 측정한 그 자리에서 바로 좌우. 옮기라는 말을 하면 안 된다.
|
||||
if (offsetCm == 0) AlignInstruction("치골 위 ${attach}cm 그대로 — 좌우 맞추기",
|
||||
"이 자리의 측정은 끝났습니다. 프로브를 옮기지 말고 아래 좌우 맞추기로 넘어갑니다.",
|
||||
done = true)
|
||||
else AlignInstruction("치골 위 ${attach}cm 에 붙이세요",
|
||||
"여기서는 측정하지 않습니다. 붙인 뒤 좌우 맞추기로 넘어갑니다.",
|
||||
done = true)
|
||||
}
|
||||
|
||||
@@ -141,6 +141,19 @@ class AlignInstructionTest {
|
||||
assertEquals("치골 위 3cm 에 붙이세요", i.headline)
|
||||
}
|
||||
|
||||
@Test fun `오프셋이 0 이면 그 자리에서 바로 좌우로 가라고 하고 옮기라는 말을 안 한다`() {
|
||||
// 2026-10-01: 모든 자세에서 정렬 → best 자리에서 바로 좌우. "붙이세요"가 나오면
|
||||
// 간호사가 프로브를 뗀다.
|
||||
val i = instructionOf(step(2, searching = false, bestCm = 2),
|
||||
nextCm = 2, offsetCm = 0, confirmTarget = 2, confirmDone = true)
|
||||
assertEquals("치골 위 2cm 그대로 — 좌우 맞추기", i.headline)
|
||||
assertFalse(i.headline.contains("붙이세요"))
|
||||
assertTrue(i.sub!!.contains("옮기지 말고"))
|
||||
assertTrue(i.sub!!.contains("좌우"))
|
||||
assertTrue(i.done)
|
||||
assertNoPositionWords(i)
|
||||
}
|
||||
|
||||
@Test fun `오프셋이 바뀌면 문구도 따라간다`() {
|
||||
// FINAL_OFFSET_CM 을 2 로 바꾸면 화면도 2cm 이라고 말해야 한다. 박아 두면 거짓이 된다.
|
||||
val i = instructionOf(step(1, searching = false, bestCm = 1),
|
||||
|
||||
Reference in New Issue
Block a user