feat(clinical): 병원 임상만 최종 알고리즘으로 · 전역은 건드리지 않는다

병원 임상 화면은 최종 보고본(레퍼런스)이 기본이어야 하는데, AlgoMode.reference 는
프로세스 전역이라 그냥 켜면 **일반 측정 화면의 BV 까지** 바뀐다. 그쪽은 현행 임상에서
쓰이고 있어 임의로 건드리면 안 된다.

AlgoMode.withReference(ref) { } 범위 실행을 두고 ClinicalBv 가 그 안에서만 돈다.
계산이 끝나면 전역값은 원래대로다 — 임상 화면을 다녀왔다는 이유로 다른 화면의 값이
달라지지 않는다. 되돌리는지도 테스트로 고정했다. 임상 측정은 코루틴에서 돌아 두 계산이
겹칠 수 있으므로 동기화한다: 겹치면 한쪽의 복원이 다른 쪽의 설정을 지워 엉뚱한 경로로
계산된 값이 나오는데, 그 한 건이 검증 기록에 섞이면 나중에 찾아낼 방법이 없다.

ClinicalBv.compute(reference = true) 가 기본. BV 측정 화면의 스위치는 이제 전역을
쓰지 않고 그 화면의 계산에만 적용된다(두 경로 비교용).

**앞 커밋(21b0fca)의 숫자를 정정한다.** "align_cm1 기존 125.52 vs 레퍼런스 122.25 mL"
는 잘못된 측정이었다 — PiezoHW.activePreset 을 안 잡고 돌려 dps·delay 가 다른 프리셋
값으로 계산됐다(실측 자원은 VBT26050202=V1). 프리셋을 고정하면 이 데이터에서는 두
경로가 **같은 값**을 낸다(cm0 410.65 / cm1 490.63, 양쪽 동일).

두 경로가 같다는 뜻은 아니다. AlgoModeSwitchTest 실측으로 **cycle 44개 중 38개**의
검출·BV 가 갈린다. 여러 cycle 을 평균한 mean-scan 에서 차이가 묻힌 것이다. 즉 입력에
따라 같기도 다르기도 하므로, 값에 경로 표시를 붙이는 이유는 그대로 유효하다.

같은 함정을 테스트에도 반영했다 — ClinicalBvTest·AlgoPathReportTest 가 프리셋을 V1 로
고정한다. 다른 테스트가 남긴 전역 프리셋에 끌려가면 값이 통째로 흔들린다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 10:14:52 +09:00
parent 21b0fcaed8
commit e17391ca9f
5 changed files with 177 additions and 62 deletions
@@ -47,5 +47,32 @@ object AlgoMode {
var reference: Boolean = false
/** 화면에 표시할 이름. */
val label: String get() = if (reference) "레퍼런스 (Python 1:1)" else "기존 (현행 임상)"
val label: String get() = labelOf(reference)
/** 전역 상태가 아니라 **그 계산에 실제로 쓰인** 값의 이름. */
fun labelOf(ref: Boolean): String =
if (ref) "레퍼런스 (Python 1:1)" else "기존 (현행 임상)"
private val lock = Any()
/**
* [ref] 경로로 [block] 을 실행하고 **원래 값으로 되돌린다.**
*
* ## 왜 전역 토글을 그냥 켜지 않나
* [reference] 는 프로세스 전역이라, 병원 임상 화면이 켜면 **일반 측정 화면의 BV 까지**
* 바뀐다. 그쪽은 현행 임상에서 쓰이고 있어 임의로 바꾸면 안 된다.
*
* 그래서 임상 경로([ClinicalBv])만 이 범위 안에서 돌린다. 계산이 끝나면 전역값은
* 원래대로다 — 임상 화면을 다녀왔다는 이유로 다른 화면의 값이 달라지지 않는다.
*
* ## 동기화하는 이유
* 임상 화면의 측정은 코루틴에서 돈다. 두 계산이 겹치면 한쪽의 복원이 다른 쪽의
* 설정을 지워 **엉뚱한 경로로 계산된 값**이 나온다. 겹칠 일이 드물어도, 그때 나온
* 한 건이 검증 기록에 섞이면 나중에 찾아낼 방법이 없다.
*/
fun <T> withReference(ref: Boolean, block: () -> T): T = synchronized(lock) {
val prev = reference
reference = ref
try { block() } finally { reference = prev }
}
}