From 38c08f2ddf98accb35f5568e628ec82e39f94691 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Tue, 8 Sep 2026 13:44:14 +0900 Subject: [PATCH] =?UTF-8?q?docs(detach):=20=EC=9B=90=EB=B3=B8=EA=B3=BC=20?= =?UTF-8?q?=EA=B0=92=EC=9D=B4=20=EB=8B=A4=EB=A5=B8=20=EC=9D=B4=EC=9C=A0?= =?UTF-8?q?=EB=A5=BC=20=ED=8C=8C=EC=9D=BC=EC=97=90=20=EB=82=A8=EA=B8=B4?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 헤더가 "detachment_detection.py 1:1 포팅"에 "둘 다 threshold 미만이면 미부착"이라고 돼 있었는데 둘 다 사실이 아니다. 임계는 100→30, 규칙은 AND→OR 로 바뀌었고 근거는 커밋 메시지에만 있었다 — 파일만 보면 왜 다른지 알 수 없고, 다음 사람이 "원본과 맞추자" 며 되돌리기 딱 좋은 상태였다. 두 변경 다 실기기 증상을 보고 내린 결정이다: · 100→30 (258bad9) — 원본의 100 은 팬텀에서 미부착 vs incorrect 두 조건으로 뽑은 값이다. 실기기에는 "붙었는데 신호가 약한" 세 번째 조건이 있고, 100 을 쓰면 그것까지 미부착으로 잡아 측정 중인 사용자에게 경고가 계속 뜬다. · AND→OR (b2c1ba2) — 분리된 상태인데 공기 중 EMI 로 std 가 30 을 넘어 판정이 지연. parity 규칙이 여기 걸리지 않는다는 것도 적었다. SPEC 의 대조 계약은 §5·§8 등재 항목에 적용되는데 detachment 는 SPEC 에 없고, Python 쪽에서도 이 모듈을 부르는 코드가 하나도 없다(파이프라인 밖 독립 유틸). 남은 숙제도 같이 적었다 — 두 변경이 서로를 가린다. OR 의 근거("std 가 30 이상인 경우가 흔하다")는 임계가 30 이라서 생긴 문제라, 100 이었다면 AND 로도 즉시 판정됐을 것이다. 세 조합을 실기기에서 비교한 적이 없으므로, 채널 접촉 칸의 mean·std 실측값을 모아 정하기로 한다. Co-Authored-By: Claude Opus 5 --- .../vesiscan/managers/DetachmentDetection.kt | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/medithings/vesiscan/managers/DetachmentDetection.kt b/app/src/main/java/com/medithings/vesiscan/managers/DetachmentDetection.kt index 37942fa..f816c45 100644 --- a/app/src/main/java/com/medithings/vesiscan/managers/DetachmentDetection.kt +++ b/app/src/main/java/com/medithings/vesiscan/managers/DetachmentDetection.kt @@ -4,12 +4,44 @@ import kotlin.math.abs import kotlin.math.sqrt /** - * 센서 미부착 감지 — detachment_detection.py 1:1 포팅 + * 센서 미부착 감지. * - * ADC 신호의 [20:80] 구간에서 baseline([80:100] median)을 빼고, - * mean(|y|)과 std(y)가 모두 threshold 미만이면 미부착으로 판정. + * 원본: `vesiscan_test/library/alignment/detachment_detection.py` * - * 미부착 시 LED state 4 (DETACH_WARNING) 전송. + * ADC 신호의 [20:80] 구간에서 baseline([80:100] median)을 빼고, CH0~CH3 의 + * mean(|y|)·std(y) 를 평균 내 임계와 비교한다. 미부착 시 LED state 4 + * (DETACH_WARNING) 전송. + * + * ## ⚠ 원본과 값이 다르다 — 되돌리지 말 것 + * + * | | Python | 여기 | + * |---|---|---| + * | 임계 | 100 | **30** | + * | 규칙 | AND | **OR** | + * + * 최초 포팅(`ea19b74`, 2026-04-24)은 100·AND 로 원본과 같았다. 두 번 바뀌었고 + * **둘 다 실기기 증상을 보고 내린 결정**이다. + * + * **100 → 30** (`258bad9`, 2026-05-04) — "완전 탈착만 감지, 거리 약화 무시". + * 원본의 100 은 **팬텀** 실측 분포에서 나온 값이고, 그 두 조건은 *미부착* 과 + * *incorrect(잘못 붙음)* 이다. 실기기에는 조건이 하나 더 있다 — **붙었는데 신호가 + * 약한 경우**(밀착 부족·체형·젤 상태). 그건 미부착이 아닌데 100 을 쓰면 그것까지 + * 잡아서, 멀쩡히 측정 중인 사용자에게 경고가 계속 뜬다. + * + * **AND → OR** (`b2c1ba2`, 2026-08-11) — 사용자 보고. 센서가 아예 분리됐는데 공기 중 + * EMI/RF 로 std 가 30 을 넘어, 둘 다 30 미만을 요구하는 AND 에서는 판정이 몇 초 늦었다. + * + * ## parity 규칙이 여기엔 걸리지 않는다 + * `SPEC.md` 의 Python↔Kotlin 대조 계약은 §5(파이프라인)·§8(정렬) 같은 등재 항목에 + * 적용된다. detachment 는 SPEC 에 없고, **Python 쪽에서도 이 모듈을 부르는 코드가 + * 하나도 없다**(파이프라인 밖 독립 유틸). 검증도 팬텀에서만 했다. + * + * ## 남은 숙제 + * 두 변경이 서로를 가린다. OR 로 바꾼 근거("std 가 30 이상인 경우가 흔하다")는 + * **임계가 30 이라서** 생긴 문제다 — 100 이었다면 std<100 은 쉽게 걸려 AND 로도 + * 즉시 판정됐을 것이다. 30+OR / 30+AND / 100+AND 중 실기기에서 무엇이 나은지는 + * 비교된 적이 없다. 임상 화면의 채널 접촉 칸이 mean·std 실측값을 띄우므로 + * ([DetachWatcher]), 실제로 뜬 순간과 약하게 붙은 순간의 숫자를 모아 정할 것. */ object DetachmentDetection { @@ -17,8 +49,14 @@ object DetachmentDetection { const val WINDOW_END = 80 const val BASELINE_WINDOW_START = 80 const val BASELINE_WINDOW_END = 100 + + /** Python `DEFAULT_THR_MEAN` = 100. 위 "원본과 값이 다르다" 참조. */ const val THR_MEAN = 30.0 + + /** Python `DEFAULT_THR_STD` = 100. 위 "원본과 값이 다르다" 참조. */ const val THR_STD = 30.0 + + /** Python `N_CENTER_CH`. CH0~CH3 만 본다 — lateral 은 임계 근거가 없다. */ const val N_CENTER_CH = 4 data class DetachmentResult(