test(align): 폴백 규칙으로 정답지(anchor_cases.tsv)를 Python 에서 다시 뽑고 대조 테스트를 원래대로 되돌린다
입력 200개는 그대로 두고 답만 piezo-phantom-test select_supine_anchor(폴백 포함, 로컬 브랜치 no-ch3-fallback 54a25b7)로 다시 썼다 — 재부착 19 → 3, no_ch3_max_nch 16, 나머지 181개 불변. 대조 테스트의 임시 폴백 분기를 지우고 assertEquals 하나로 되돌렸다. 폴백 케이스 수(16)를 잠가 옛 파일이 섞이면 바로 깨지게 했다. 생성 절차가 어디에도 안 남아 있어 tools/gen_anchor_cases.py 로 넣었다. 패키지 __init__ 가 이 체크아웃에 없는 analysis 를 import 해 깨지므로 껍데기 모듈을 등록하고 상대 import 로 읽는다. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,10 @@ import org.junit.Test
|
||||
*
|
||||
* 케이스: `app/src/test/resources/anchor_cases.tsv`
|
||||
* `best<TAB>cm,nch,ch3,hit,tot,cap|...` · best='-' 는 재부착(선택 불가)
|
||||
* 규칙이 바뀌면 Python 으로 파일을 다시 생성해야 한다(생성 절차는 커밋 메시지 참조).
|
||||
* 규칙이 바뀌면 Python 으로 **정답 열만** 다시 뽑는다: `tools/gen_anchor_cases.py <in> <out>`
|
||||
* (입력 200개는 그대로 두고 piezo-phantom-test 의 `select_supine_anchor` 로 답을 다시 쓴다).
|
||||
* 2026-10-14: ⓪ CH3 전무 폴백(no_ch3_max_nch)을 Python 에도 넣고 다시 뽑았다 — 재부착 19 → 3,
|
||||
* 폴백 16. 나머지 181개는 답이 그대로다.
|
||||
*
|
||||
* JSON 대신 TSV 인 이유: `org.json` 은 Android 단위테스트에서 스텁이라 호출하면
|
||||
* "not mocked" 로 죽는다. 임상 일정 중이라 테스트 의존성을 늘리지 않고 형식을 낮췄다.
|
||||
@@ -51,24 +54,16 @@ class AnchorSelectionParityTest {
|
||||
.filter { it.isNotBlank() && !it.startsWith("#") }.toList()
|
||||
|
||||
var nullBest = 0
|
||||
var fallbackCases = 0
|
||||
lines.forEachIndexed { i, line ->
|
||||
val (want, recs) = parse(line)
|
||||
val d = AnchorSelection.selectBest(recs)
|
||||
if (want == null) {
|
||||
// 2026-10-13 임시: Python 정답은 "재부착"이지만 앱은 ⓪ CH3 전무 폴백(nch 최대 →
|
||||
// cap 최소 → 낮은 cm)을 탄다. Python 에 같은 폴백이 들어가 이 파일을 다시 뽑으면
|
||||
// 이 분기를 지우고 위 assertEquals 하나로 돌아간다.
|
||||
val maxNch = recs.maxOf { it.nch }
|
||||
val expect = if (maxNch <= 0) null else recs.filter { it.nch == maxNch }
|
||||
.minWith(compareBy({ if (it.capFrac.isFinite()) it.capFrac else Double.POSITIVE_INFINITY }, { it.cm }))
|
||||
.cm
|
||||
assertEquals("case #$i no-CH3 fallback (records=$line)", expect, d.bestCm)
|
||||
assertEquals("case #$i rule", if (expect == null) AnchorSelectRule.NONE else AnchorSelectRule.NO_CH3_MAX_NCH, d.rule)
|
||||
nullBest++
|
||||
} else {
|
||||
assertEquals("case #$i best (records=$line)", want, d.bestCm)
|
||||
if (want == null) nullBest++
|
||||
if (d.rule == AnchorSelectRule.NO_CH3_MAX_NCH) fallbackCases++
|
||||
}
|
||||
}
|
||||
// ⓪ 폴백 가지도 레퍼런스로 잠겨 있어야 한다 — 0 이면 파일이 폴백 이전 것이다.
|
||||
assertEquals("no_ch3_max_nch 가지를 타는 레퍼런스 케이스", 16, fallbackCases)
|
||||
assertEquals("케이스 수", 200, lines.size)
|
||||
// 2026-10-07 ①′(nch=4 후보 3곳 이상 → 가운데)는 Python 에 아직 없다. 이 파일에는 그 가지를
|
||||
// 타는 케이스가 없어 대조가 그대로 유효하다. 파일을 다시 뽑아 이 가정이 깨지면 Python 쪽에도
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
1 0,4,X,5,6,0.5|1,3,O,0,1,0.5|2,1,O,0,1,nan
|
||||
0 0,4,O,0,0,0.3646|1,1,O,0,0,0.5|2,4,X,6,6,0.8704|3,1,X,5,6,0.5
|
||||
1 0,3,O,6,11,0.1022|1,3,O,9,11,0.6024|2,1,X,6,11,0.5
|
||||
- 0,1,X,4,11,0.5009|1,0,X,2,6,0.2937
|
||||
0 0,1,X,4,11,0.5009|1,0,X,2,6,0.2937
|
||||
1 0,3,X,0,0,nan|1,2,O,0,1,0.1872|2,1,O,4,6,0.5|3,0,O,4,11,0.5|4,4,X,6,11,0.5
|
||||
0 0,4,O,1,6,0.5
|
||||
0 0,3,O,4,11,0.4301
|
||||
@@ -14,7 +14,7 @@
|
||||
2 0,1,O,1,1,0.5|1,4,O,0,1,0.6467|2,2,O,1,1,0.5|3,0,O,7,11,0.5595|4,3,O,0,0,0.3441
|
||||
2 0,1,X,1,1,nan|1,1,X,11,11,0.5|2,0,O,2,6,0.5
|
||||
0 0,4,O,6,6,0.7561|1,3,X,0,1,nan|2,0,O,2,6,0.5
|
||||
- 0,0,X,6,11,0.5|1,4,X,0,0,0.8669|2,3,X,1,1,0.5
|
||||
1 0,0,X,6,11,0.5|1,4,X,0,0,0.8669|2,3,X,1,1,0.5
|
||||
2 0,1,O,0,0,nan|1,4,O,2,6,0.7495|2,2,O,10,11,0.2726|3,4,O,0,0,0.5
|
||||
0 0,2,O,6,11,0.5|1,0,X,1,1,0.3374|2,0,X,0,1,0.5|3,2,O,4,6,0.7666
|
||||
3 0,0,O,11,11,0.5|1,1,O,7,11,0.5|2,0,O,1,6,nan|3,1,O,1,1,0.5|4,4,O,0,0,0.5
|
||||
@@ -23,10 +23,10 @@
|
||||
0 0,4,O,3,6,0.212|1,4,O,0,11,0.2505|2,3,O,2,11,nan|3,4,X,2,6,0.2563
|
||||
1 0,1,X,1,1,0.5|1,3,O,0,0,0.1525
|
||||
2 0,3,O,0,1,0.5|1,4,X,11,11,0.5|2,1,O,10,11,0.5|3,2,O,1,11,0.5|4,3,O,2,6,0.5
|
||||
- 0,3,X,5,6,0.7826|1,4,X,1,11,nan
|
||||
1 0,3,X,5,6,0.7826|1,4,X,1,11,nan
|
||||
0 0,4,O,0,11,0.5
|
||||
1 0,2,X,0,0,0.5|1,3,O,5,6,0.5|2,3,O,0,0,0.4853|3,1,X,1,1,0.5
|
||||
- 0,3,X,1,1,0.5
|
||||
0 0,3,X,1,1,0.5
|
||||
0 0,2,O,9,11,0.5715|1,2,O,0,0,0.5
|
||||
2 0,0,O,0,1,0.5|1,0,X,5,6,0.5|2,4,O,0,11,nan
|
||||
3 0,3,X,0,0,0.5|1,0,O,0,0,0.5|2,4,X,0,0,nan|3,2,O,0,1,0.5|4,0,X,0,11,0.5
|
||||
@@ -39,7 +39,7 @@
|
||||
1 0,1,O,1,1,0.5|1,3,O,6,6,nan
|
||||
0 0,0,O,4,11,0.5|1,4,X,1,1,0.5
|
||||
0 0,1,O,0,0,0.5|1,0,O,0,0,nan
|
||||
- 0,3,X,0,6,0.5
|
||||
0 0,3,X,0,6,0.5
|
||||
0 0,3,O,0,0,0.4641
|
||||
1 0,2,O,4,6,0.5|1,3,O,0,0,0.5|2,0,X,0,0,nan
|
||||
0 0,4,O,0,1,0.174|1,4,X,5,6,0.5
|
||||
@@ -68,11 +68,11 @@
|
||||
1 0,2,O,0,0,nan|1,3,O,1,1,0.3858|2,4,O,0,1,nan
|
||||
2 0,3,X,0,0,0.5|1,1,X,0,1,nan|2,1,O,0,0,nan|3,4,X,4,6,0.2745
|
||||
- 0,0,X,0,0,0.5
|
||||
- 0,4,X,0,1,nan|1,2,X,4,6,nan
|
||||
0 0,4,X,0,1,nan|1,2,X,4,6,nan
|
||||
1 0,4,X,3,6,0.5|1,3,O,0,0,0.5
|
||||
0 0,0,O,0,0,nan
|
||||
2 0,1,O,1,6,0.0554|1,0,X,1,1,0.8507|2,2,O,0,0,nan
|
||||
- 0,4,X,8,11,nan
|
||||
0 0,4,X,8,11,nan
|
||||
0 0,4,O,0,6,0.5|1,3,O,0,0,0.2693|2,1,X,0,0,nan|3,0,O,0,1,nan
|
||||
1 0,1,O,2,11,nan|1,2,O,0,0,nan|2,4,X,0,0,nan|3,3,X,1,6,0.5
|
||||
0 0,1,O,1,1,0.5|1,3,O,2,6,nan|2,3,X,4,11,0.5
|
||||
@@ -95,7 +95,7 @@
|
||||
1 0,2,X,0,0,0.7831|1,1,O,0,0,0.7691
|
||||
0 0,2,O,0,0,0.4112
|
||||
1 0,0,X,0,0,0.5|1,1,O,4,11,nan
|
||||
- 0,3,X,8,11,0.5
|
||||
0 0,3,X,8,11,0.5
|
||||
2 0,0,X,0,1,0.5|1,4,O,0,0,nan|2,4,O,1,1,0.5|3,0,X,1,1,0.5
|
||||
0 0,1,O,4,11,0.3517
|
||||
0 0,0,O,0,0,0.5
|
||||
@@ -103,9 +103,9 @@
|
||||
3 0,1,O,0,1,0.5|1,2,O,0,1,nan|2,0,O,4,6,0.5|3,2,O,2,6,0.5|4,4,X,7,11,0.5
|
||||
0 0,3,O,10,11,nan|1,3,X,0,0,nan|2,4,O,1,11,0.8167|3,2,O,1,1,0.5
|
||||
0 0,2,O,1,1,nan|1,1,O,0,6,0.5|2,2,X,0,1,0.5|3,4,X,7,11,0.4087
|
||||
- 0,3,X,0,0,0.5
|
||||
- 0,3,X,8,11,0.5|1,4,X,3,6,nan
|
||||
- 0,2,X,3,11,0.0733|1,4,X,0,0,nan|2,4,X,1,1,nan|3,3,X,4,6,nan
|
||||
0 0,3,X,0,0,0.5
|
||||
1 0,3,X,8,11,0.5|1,4,X,3,6,nan
|
||||
1 0,2,X,3,11,0.0733|1,4,X,0,0,nan|2,4,X,1,1,nan|3,3,X,4,6,nan
|
||||
1 0,0,X,1,1,0.5|1,4,O,1,1,0.5|2,4,X,0,11,0.8716|3,1,O,6,11,nan
|
||||
0 0,2,O,0,1,0.5|1,0,O,0,6,0.8683|2,2,O,1,6,nan
|
||||
4 0,3,O,4,6,0.5|1,3,O,0,0,0.5|2,3,O,0,0,0.5|3,3,X,1,1,0.5|4,4,O,0,1,0.5
|
||||
@@ -120,7 +120,7 @@
|
||||
1 0,4,X,0,0,0.5|1,4,O,0,1,0.4258|2,1,O,0,0,0.8755|3,3,O,0,1,0.5|4,0,X,0,6,nan
|
||||
0 0,3,O,0,0,0.5
|
||||
0 0,4,O,8,11,0.5|1,2,O,0,1,0.2788|2,3,O,0,1,nan|3,4,O,8,11,0.9269|4,2,O,0,6,nan
|
||||
- 0,1,X,3,6,0.6197
|
||||
0 0,1,X,3,6,0.6197
|
||||
0 0,4,O,1,1,nan|1,1,X,0,0,0.6865|2,0,O,5,11,0.5|3,0,O,6,6,0.5
|
||||
1 0,0,O,0,0,0.6664|1,4,O,1,1,nan|2,1,O,0,0,nan
|
||||
1 0,0,O,6,6,nan|1,0,O,11,11,0.3174|2,4,O,0,1,0.5
|
||||
@@ -132,7 +132,7 @@
|
||||
1 0,1,X,0,0,nan|1,3,O,3,6,0.5|2,2,X,0,0,nan|3,2,O,0,1,0.5
|
||||
2 0,1,O,3,11,0.5|1,0,O,4,6,0.5|2,3,O,9,11,nan|3,2,X,6,6,nan|4,0,O,7,11,0.5
|
||||
2 0,2,O,8,11,0.7672|1,4,X,0,0,0.5|2,4,O,0,0,0.4113|3,4,X,0,0,0.5
|
||||
- 0,4,X,0,0,0.5
|
||||
0 0,4,X,0,0,0.5
|
||||
0 0,4,O,5,6,0.5|1,1,O,1,1,0.5
|
||||
0 0,3,O,1,1,0.5|1,3,X,1,1,0.748|2,1,X,1,6,0.1778
|
||||
3 0,0,O,0,0,0.5|1,4,X,0,0,nan|2,4,O,1,11,0.5|3,0,O,11,11,0.5
|
||||
@@ -143,7 +143,7 @@
|
||||
1 0,2,O,11,11,0.5564|1,4,O,9,11,0.5|2,4,X,0,1,0.4846
|
||||
0 0,2,O,0,1,nan|1,2,X,1,6,nan|2,0,O,0,0,0.5
|
||||
1 0,1,O,0,1,0.5|1,1,O,1,1,0.8336|2,4,O,4,6,0.5148|3,4,X,3,6,0.5|4,0,X,0,0,0.5
|
||||
- 0,3,X,5,6,0.3578
|
||||
0 0,3,X,5,6,0.3578
|
||||
0 0,3,O,5,11,0.6709
|
||||
1 0,2,O,0,0,0.5|1,2,O,6,6,0.8069|2,4,X,5,11,0.5
|
||||
1 0,2,X,10,11,0.789|1,2,O,4,6,0.5
|
||||
@@ -152,7 +152,7 @@
|
||||
1 0,4,O,0,0,0.5|1,4,O,1,1,nan|2,0,O,0,0,nan|3,3,O,5,11,0.5
|
||||
2 0,4,X,0,1,0.5|1,1,X,0,0,0.5|2,2,O,6,11,nan|3,0,O,0,6,0.5
|
||||
0 0,2,O,9,11,nan|1,3,O,0,0,nan|2,3,X,0,0,nan
|
||||
- 0,3,X,5,6,0.5738|1,1,X,0,1,0.5
|
||||
0 0,3,X,5,6,0.5738|1,1,X,0,1,0.5
|
||||
- 0,0,X,0,6,0.2474
|
||||
0 0,0,O,0,0,0.175
|
||||
0 0,2,O,6,6,0.5|1,2,X,1,1,0.5|2,4,X,0,0,0.7646
|
||||
@@ -160,7 +160,7 @@
|
||||
3 0,0,O,2,6,nan|1,3,X,1,11,0.5356|2,1,X,1,6,0.5|3,0,O,1,6,0.5
|
||||
0 0,3,O,4,6,0.5|1,4,X,1,11,0.5|2,4,X,0,1,0.5|3,4,X,0,0,0.5|4,0,O,7,11,0.5
|
||||
1 0,2,O,0,1,0.1486|1,0,O,1,1,nan|2,2,O,4,6,nan
|
||||
- 0,1,X,1,1,0.5|1,2,X,3,11,nan
|
||||
1 0,1,X,1,1,0.5|1,2,X,3,11,nan
|
||||
0 0,2,O,3,11,0.5
|
||||
- 0,0,X,11,11,nan|1,0,X,0,1,0.5
|
||||
1 0,0,O,0,0,nan|1,2,O,0,11,0.6383|2,2,O,0,0,nan|3,1,O,0,1,0.5
|
||||
|
||||
|
@@ -0,0 +1,45 @@
|
||||
"""anchor_cases.tsv 정답 열 재생성 — 입력 200개는 그대로, 답만 Python select_supine_anchor 로 다시 뽑는다.
|
||||
|
||||
사용: python gen_anchor_cases.py <in.tsv> <out.tsv>
|
||||
행 형식: best<TAB>cm,nch,ch3,hit,tot,cap|... (best='-' 는 재부착)
|
||||
"""
|
||||
import io, sys, math, os, types
|
||||
|
||||
# library/__init__.py 가 이 체크아웃에 없는 analysis 패키지를 import 해 깨진다 — 패키지 껍데기만 등록하고
|
||||
# 필요한 모듈만 상대 import 로 읽는다(runners 등은 실제 파일에서 로드).
|
||||
_root = r"C:\Projects\piezophantomtest\piezo-phantom-test"
|
||||
for _name, _path in (("vesiscan_test", os.path.join(_root, "vesiscan_test")),
|
||||
("vesiscan_test.library", os.path.join(_root, "vesiscan_test", "library"))):
|
||||
_m = types.ModuleType(_name); _m.__path__ = [_path]; _m.__package__ = _name; sys.modules[_name] = _m
|
||||
import vesiscan_test.library.alignment.alignment_selection as sel
|
||||
|
||||
src, dst = sys.argv[1], sys.argv[2]
|
||||
raw = io.open(src, encoding="utf-8", newline="").read()
|
||||
nl = "\r\n" if "\r\n" in raw else "\n" # 원본 줄바꿈 유지 — diff 가 답 열만 보이게
|
||||
lines = raw.splitlines()
|
||||
out, changed, null_before, null_after, fallback = [], 0, 0, 0, 0
|
||||
for line in lines:
|
||||
if not line.strip() or line.startswith("#"):
|
||||
out.append(line)
|
||||
continue
|
||||
best_old, recs_raw = line.split("\t", 1)
|
||||
recs = []
|
||||
for f in recs_raw.split("|"):
|
||||
cm, nch, ch3, hit, tot, cap = f.split(",")
|
||||
capv = float(cap) if cap not in ("nan", "") else math.nan
|
||||
recs.append(dict(align_cm=int(cm), nch=int(nch), ch3=ch3, ch3_hit=int(hit), ch3_tot=int(tot), cap_frac=capv))
|
||||
d = sel.select_supine_anchor(recs, tie="cap", offset_cm=0)
|
||||
best_new = "-" if d.align_cm is None else str(d.align_cm)
|
||||
if best_old == "-":
|
||||
null_before += 1
|
||||
if best_new == "-":
|
||||
null_after += 1
|
||||
if d.reasons and d.reasons[0] == sel.NO_CH3_FALLBACK_TAG:
|
||||
fallback += 1
|
||||
if best_new != best_old:
|
||||
changed += 1
|
||||
assert best_old == "-", f"non-reattach answer changed: {line}"
|
||||
out.append(best_new + "\t" + recs_raw)
|
||||
io.open(dst, "w", encoding="utf-8", newline="").write(nl.join(out) + nl)
|
||||
print(f"cases={sum(1 for l in lines if l.strip() and not l.startswith('#'))} reattach before={null_before} after={null_after} "
|
||||
f"fallback={fallback} changed={changed} -> {dst}")
|
||||
Reference in New Issue
Block a user