feat(clinical): 전수 판정을 같이 계산해 기록 · 확인 측정이 판정 데이터를 덮지 않게

알고리즘 설계자가 물었다 — "0cm·1cm 만 쟀는데 0cm 가 최적으로 나왔다. 만약 3cm 가
최적이면?"

## 이건 버그가 아니라 설계 가정이다

단일 pass 는 **지표가 cm 에 대해 단봉**이라고 가정한다. 올라가다 나빠지면 봉우리를 지난
것으로 보고 멈춘다. Python 도 같다. 물리적 근거는 있다 — CH3 은 제일 기울어진 채널로
방광 아래쪽을 보므로 위로 가면 점점 벗어난다.

깨질 수 있는 경우는 **한 위치만** 일시적으로 나빠지는 것이다: 장내 가스가 CH3 을 가림,
접촉이 뜸, 호흡으로 방광이 움직임. 그러면 더 위가 진짜 좋았는데 조기 종료한다.

실측에 징후가 있다. 2026-09-09 `kai` 는 **0cm ch3=X → 1cm ch3=O 91%** 로 지표가
올라갔다. 시작 쪽은 종료 조건이 "앞에 eligible 이 있어야" 걸리게 막혀 있지만 **중간에서는
한 칸만 나빠도 끝난다.** 같은 날 `181128` 이 정확히 그 경우다 — 1cm 에서 nch 2→0,
ch3 100%→0% 로 두 조건이 동시에 걸려 best=0cm, 2cm 이후는 모른다.

## 판정은 바꾸지 않고 **측정 가능하게** 만든다

규칙을 바꾸면 레퍼런스 대조(단일 pass 기준)를 다시 해야 한다. 그건 알고리즘 팀이 숫자를
보고 정할 일이다. 그래서 전 위치로 고르면 어디가 뽑히는지 **같이 계산해 기록한다**:

    "best_cm": 0,                  앱이 쓰는 값 (단일 pass · 레퍼런스)
    "best_cm_full_sweep": 3,       잰 위치 전부로 고르면
    "sweep_disagrees": true

갈리면 화면에도 참고 문구가 뜬다 — 그 자리에서 "더 위가 좋아 보인다"를 알면 다시 정렬을
택할 수 있다. 어제 전수 측정으로 바꾼 것이 이 답을 내는 전제였다(조기 종료하면 비교할
위치가 없다).

## 확인 측정이 판정 데이터를 덮고 있었다

부착 위치로 내려가 한 번 더 재면 `align_2cm.csv` 를 **덮어썼다.** 화면 판정은 먼저 잰 것
기준인데 파일은 나중 것이라, 파일로 재판정하면 앱과 답이 갈릴 수 있다. 조기 종료 시절에는
드물었지만 전수 측정으로 바꾼 뒤에는 **항상** 일어난다.

`align_2cm_confirm.csv` 로 분리했다. 판정 근거가 파일에 그대로 남는다.

테스트 139개 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 12:05:16 +09:00
parent 2ec874d7f8
commit b71a04374f
3 changed files with 93 additions and 2 deletions
+35
View File
@@ -401,6 +401,41 @@ val anchorCm: Int? get() = bestCm?.let { it + offsetCm } // offsetCm = 1
계산하고, 이후 `step()` 은 `recs` 에만 쌓기 때문입니다. `AnchorSweepTest` 가 이 성질을
고정합니다.
#### 단일 pass 가 가정하는 것 — 그리고 그 가정을 측정하는 방법
단일 pass 는 **지표가 cm 에 대해 단봉(unimodal)** 이라고 가정합니다. 올라가다 나빠지면
봉우리를 지난 것으로 보고 멈춥니다. Python 도 같습니다 — *"수직 → 좌우 순서로 한 번만
가고 되돌아가지 않는다."*
물리적 근거는 있습니다. CH3 은 제일 기울어진 채널로 **방광 아래쪽**을 보므로, 위로
올라가면 점점 방광을 벗어납니다.
**깨질 수 있는 경우**는 *한 위치만* 일시적으로 나빠지는 것입니다 — 장내 가스가 CH3 을
가림, 그 위치에서 접촉이 떴음, 호흡으로 방광이 움직임. 그러면 더 위가 진짜 좋았는데
조기 종료합니다.
실측에 징후가 있습니다. 2026-09-09 `kai` 세션은 **0cm `ch3=X` → 1cm `ch3=O 91%`** 로
지표가 **올라갔습니다.** 시작 쪽은 종료 조건이 "앞에 eligible 이 있어야" 걸리게 막혀
있지만, **중간에서는 한 칸만 나빠도 끝납니다.**
전수 측정이 이 질문에 답을 냅니다. 세션마다 두 값을 같이 기록합니다:
```json
"best_cm": 0, // 앱이 쓰는 값 (단일 pass · 레퍼런스)
"best_cm_full_sweep": 3, // 잰 위치 전부로 고르면
"sweep_disagrees": true // 갈렸다
```
갈리면 화면에도 참고 문구가 뜹니다. **판정은 바꾸지 않습니다** — 레퍼런스 대조가 단일
pass 기준이라 규칙을 바꾸면 검증을 다시 해야 합니다. 몇십 세션의 `sweep_disagrees` 빈도를
보고 규칙을 바꿀지는 알고리즘 팀이 정합니다.
#### 확인 측정은 판정 데이터를 덮지 않습니다
부착 위치로 내려가 한 번 더 재는 측정은 `align_2cm_confirm.csv` 로 따로 저장합니다.
같은 이름에 쓰면 **판정에 쓰인 데이터가 확인 측정으로 덮여**, 파일로 재판정하면 앱과
답이 갈릴 수 있습니다. 전수 측정으로 바뀐 뒤 이 재측정이 **항상** 일어나므로 중요합니다.
**기록에 경계를 남깁니다** — 안 남기면 재분석하는 쪽이 전 위치를 후보로 넣어 다시 판정하고,
다른 best 가 나와 앱이 틀린 것으로 읽힙니다: