no change, no sms'); // ── 2) 안정(누수): raw==confirmed==1, 잔여 pending → pending 해제 ── check($eq(leak_debounce_decide(1, 1, $NOW - 40, $ALERT, $RECOV, $NOW), 1, null, null), 'stable leak with stale pending -> clears pending, no sms'); // ── 3) 누수 감지 시작: raw=1, confirmed=0, pending 없음 → 타이머 시작 ── check($eq(leak_debounce_decide(1, 0, null, $ALERT, $RECOV, $NOW), 0, $NOW, null), 'leak onset -> start pending timer, no sms yet'); // ── 4) 감지 확인창 이내(경과 30 < 60) → 대기 유지 ── check($eq(leak_debounce_decide(1, 0, $NOW - 30, $ALERT, $RECOV, $NOW), 0, $NOW - 30, null), 'leak within confirm window -> keep waiting, no sms'); // ── 5) 감지 확인(경과 60 >= 60) → 확정 + [누수감지] ── check($eq(leak_debounce_decide(1, 0, $NOW - 60, $ALERT, $RECOV, $NOW), 1, null, 'leak'), 'leak sustained >= alert window -> confirm leak, sms=leak'); // ── 6) 감지 경계값: 경과 59 → 아직 대기(엄격 <) ── check($eq(leak_debounce_decide(1, 0, $NOW - 59, $ALERT, $RECOV, $NOW), 0, $NOW - 59, null), 'leak window-1 -> still waiting'); // ── 7) 복구 시작: raw=0, confirmed=1, pending 없음 → 타이머 시작 ── check($eq(leak_debounce_decide(0, 1, null, $ALERT, $RECOV, $NOW), 1, $NOW, null), 'recovery onset -> start pending timer, no sms yet'); // ── 8) 복구 확인창 이내(경과 100 < 180) → 대기 유지 ── check($eq(leak_debounce_decide(0, 1, $NOW - 100, $ALERT, $RECOV, $NOW), 1, $NOW - 100, null), 'recovery within confirm window -> keep waiting, no sms'); // ── 9) 복구 확인(경과 180 >= 180) → 확정 + [누수복구] ── check($eq(leak_debounce_decide(0, 1, $NOW - 180, $ALERT, $RECOV, $NOW), 0, null, 'recovery'), 'recovery sustained >= recovery window -> confirm normal, sms=recovery'); // ── 10) [핵심] 확정 누수 중 짧은 dry 블립: 확정상태로 raw 복귀 → pending 해제(복구 확정 안 됨) ── // 시퀀스: 누수 확정(confirmed=1). dry 블립 와서 pending 시작(경과 50<180). 다시 leak 들어옴. // leak 재수신: raw=1==confirmed=1 → pending 해제, sms 없음. => 복구/재감지 SMS 둘 다 안 나감. check($eq(leak_debounce_decide(1, 1, $NOW - 50, $ALERT, $RECOV, $NOW), 1, null, null), 'leak resumes before recovery confirms -> clears pending, NO recovery/re-alert sms (bomb guard)'); // ── 11) [핵심] 정상 중 짧은 leak 블립: 확정상태로 raw 복귀 → pending 해제(감지 확정 안 됨) ── check($eq(leak_debounce_decide(0, 0, $NOW - 20, $ALERT, $RECOV, $NOW), 0, null, null), 'dry resumes before alert confirms -> clears pending, NO alert sms (blip filtered)'); // ── 12) 비대칭 임계 검증: 복구는 alert 창(60)이 아니라 recovery 창(180) 사용 ── // 경과 100: alert 창(60)이면 확정되겠지만 recovery 이므로 아직 대기여야 한다. check($eq(leak_debounce_decide(0, 1, $NOW - 100, $ALERT, $RECOV, $NOW), 1, $NOW - 100, null), 'recovery uses recovery window (100 < 180) not alert window (60)'); if ($failures) { fwrite(STDERR, "Leak debounce test failures:\n"); foreach ($failures as $f) { fwrite(STDERR, "- {$f}\n"); } exit(1); } echo "Leak debounce decision checks passed.\n";