fix(alignment): 0/6 walls sanity check · 부착 안 됐을 때 "위로 올려" false positive 방지

증상 (사용자 보고 2026-08-05):
  Alignment 모드에서 센서를 부착하지 않았는데 "↑ 위로 올려" 힌트가 계속 노출.

원인:
  DetachmentDetection 임계 (mean AND std 둘 다 < 30) 를 노이즈로 통과하면
  alignment 알고리즘이 그대로 실행됨. AlignmentAdvice 는 CH3 미검출을
  "misaligned" 로 해석 → MOVE_UP 반환. "detached" 케이스와 구분 못함.

수정 (Option B: sanity check + Option C: 명확 문구):
  PlacementGuideView V2 branch 에서 v2Aligner.push 결과의
    detected.count { it } == 0 (6채널 모두 wall 미검출) 감지 시
  alignment advice 무시하고 placement_check_attachment 힌트로 override.

  strings.xml (KR/EN):
    placement_check_attachment
      · KR: "부착 상태 확인 필요 — 모든 채널에서 신호가 감지되지 않습니다.
             센서를 치골 위에 밀착시켜 주세요."
      · EN: "Attachment check needed — no signal from any channel.
             Ensure sensor is firmly placed above pubic bone."

효과:
  - 노이즈만 있고 실제 부착 없는 경우 "위로 올려" 반복 대신 명확한 부착 안내
  - alignment 알고리즘 자체는 유지 (0/6 만 특별 처리 · 1/6 이상은 정상 흐름)
  - DetachmentDetection 임계는 그대로 (실제 부착·미부착 경계 케이스 영향 없음)

검증: BUILD SUCCESSFUL 17s
This commit is contained in:
2026-08-05 16:25:45 +09:00
parent 13ba88b72b
commit 41ca909888
3 changed files with 15 additions and 1 deletions
@@ -921,11 +921,21 @@ fun PlacementGuideView(appState: AppState) {
} }
} }
bleManager.debugLogger.info("ALIGN_V2 phase=${v2Aligner.phase} state=${st.state} action=${st.action} u4=${st.summary?.u4} u5=${st.summary?.u5} ch3=${st.summary?.ch3} bestSet=${v2Aligner.bestCenterSet}") bleManager.debugLogger.info("ALIGN_V2 phase=${v2Aligner.phase} state=${st.state} action=${st.action} u4=${st.summary?.u4} u5=${st.summary?.u5} ch3=${st.summary?.ch3} bestSet=${v2Aligner.bestCenterSet}")
// 2026-08-05 sanity check: 6채널 모두 wall 미검출 → 부착 이슈 가능성.
// DetachmentDetection AND (mean·std) 임계를 통과했더라도 EMI/노이즈로
// 실제 접촉 없이 alignment 가 "위로 올려" 힌트 반복 노출하는 케이스 방지.
val detectedCount = st.summary?.detected?.count { it } ?: 0
// Localized message via string resource (falls back to st.msg for dev builds) // Localized message via string resource (falls back to st.msg for dev builds)
val localizedMsg = st.msgKey?.let { key -> val localizedMsg = st.msgKey?.let { key ->
context.getString(key, *st.msgArgs.toTypedArray()) context.getString(key, *st.msgArgs.toTypedArray())
} ?: st.msg } ?: st.msg
if (st.state == "accum") { if (detectedCount == 0) {
// 0/6 walls · alignment advice 무시 · 부착 확인 안내로 override.
directionHint = context.getString(R.string.placement_check_attachment)
directionIcon = ""
placementScore = 0
bleManager.debugLogger.info("ALIGN_V2 sanity: 0/6 walls · override to attachment check")
} else if (st.state == "accum") {
directionHint = localizedMsg directionHint = localizedMsg
directionIcon = "" directionIcon = ""
placementScore = 5 placementScore = 5
+2
View File
@@ -335,6 +335,8 @@
<string name="mtb_response_delayed">기기 응답이 지연되고 있습니다. 잠시만 기다려주세요…</string> <string name="mtb_response_delayed">기기 응답이 지연되고 있습니다. 잠시만 기다려주세요…</string>
<string name="mtb_response_reconnect">기기 응답 실패 · 재부팅이 필요할 수 있습니다 (전원 15초 롱프레스).</string> <string name="mtb_response_reconnect">기기 응답 실패 · 재부팅이 필요할 수 있습니다 (전원 15초 롱프레스).</string>
<string name="placement_detached_restart">센서가 분리되었습니다. 치골 위에서 다시 시작해주세요.</string> <string name="placement_detached_restart">센서가 분리되었습니다. 치골 위에서 다시 시작해주세요.</string>
<!-- 2026-08-05 · 0/6 walls sanity check (alignment 알고리즘 override) -->
<string name="placement_check_attachment">부착 상태 확인 필요 — 모든 채널에서 신호가 감지되지 않습니다. 센서를 치골 위에 밀착시켜 주세요.</string>
<string name="placement_signal_lost">신호를 놓쳤습니다 — 위로 올려주세요 ↑</string> <string name="placement_signal_lost">신호를 놓쳤습니다 — 위로 올려주세요 ↑</string>
<string name="placement_sweep_start">배꼽 근처에서 시작해 천천히 아래로 내려주세요 ↓</string> <string name="placement_sweep_start">배꼽 근처에서 시작해 천천히 아래로 내려주세요 ↓</string>
<string name="placement_too_far">너무 멀어요 — 위로 올려주세요 ↑</string> <string name="placement_too_far">너무 멀어요 — 위로 올려주세요 ↑</string>
+2
View File
@@ -335,6 +335,8 @@
<string name="mtb_response_delayed">Device response delayed. Please wait…</string> <string name="mtb_response_delayed">Device response delayed. Please wait…</string>
<string name="mtb_response_reconnect">Device not responding. A reboot may be required (long-press power 15 seconds).</string> <string name="mtb_response_reconnect">Device not responding. A reboot may be required (long-press power 15 seconds).</string>
<string name="placement_detached_restart">Sensor detached. Restart above pubic bone.</string> <string name="placement_detached_restart">Sensor detached. Restart above pubic bone.</string>
<!-- 2026-08-05 · 0/6 walls sanity check (alignment 알고리즘 override) -->
<string name="placement_check_attachment">Attachment check needed — no signal from any channel. Ensure sensor is firmly placed above pubic bone.</string>
<string name="placement_signal_lost">Signal lost — slide up ↑</string> <string name="placement_signal_lost">Signal lost — slide up ↑</string>
<string name="placement_sweep_start">Start near navel, slide down slowly ↓</string> <string name="placement_sweep_start">Start near navel, slide down slowly ↓</string>
<string name="placement_too_far">Too far — slide up ↑</string> <string name="placement_too_far">Too far — slide up ↑</string>