두 유형은 타입 정의(edge/events.py)·서버 검증(EventValidator)·콘솔 필터· 통보 라벨까지 전 구간에 있었으나 생성하는 코드가 어디에도 없었다. 실제로 값을 넣는 것은 seed_demo.php 뿐이었고, 소개서가 내세운 "17종"은 15종이었다. 앞서 고친 EventValidator 누락과 정확히 반대 방향의 같은 병이다. 그때는 감지기가 있는데 서버가 막았고, 이번엔 서버·UI가 열려 있는데 감지기가 없다. 어느 쪽이든 화면은 멀쩡해 보인다. 엣지가 낼 수 없는 유형이라 서버에 둔다 — 전원·회선이 끊긴 노드는 자기가 죽었다고 신고할 수 없다. 하트비트의 부재로만 관측된다. App\Alert\DeviceHealthWatch: - dispatch_alerts.php 1분 크론의 1단계. 에스컬레이션·통보보다 앞에 두어 같은 실행 안에서 발행된 장치 이벤트가 곧바로 통보 대상이 되게 했다. - 멱등은 EscalationWatch와 같은 방식(event_uuid UNIQUE 충돌)이되, 장치 건전성은 연속 상태라 uuid 앵커를 '장애 구간의 시작'으로 잡았다. device_fault는 last_heartbeat_at(장애 내내 불변) → 장애당 정확히 1건. monitoring_degraded는 창 안의 마지막 정상 하트비트 ts. 창 전체가 저하면 시작을 볼 수 없으므로 창 버킷으로 폴백해 시간당 1회 재고지한다 — 의도된 리마인더이고, 매 틱 재발행하는 앵커 드리프트가 아니다. - 성능 저하는 지속을 요구(기본 5분). 한 번 튄 값에 울리면 운영자가 이 유형을 무시하게 되고, 그러면 감시가 멈춘 날에도 아무도 보지 않는다. 엣지 감지기들이 쓰는 규율과 같다. - 하트비트를 한 번도 보낸 적 없는 장치는 장애가 아니다(미설치 노드). 새 현장 첫날이 빨간 벽이 되면 그 사이트는 이 유형을 영영 신뢰하지 않는다. - 죽은 노드에 degraded를 겹쳐 내지 않는다 — 한 고장에 두 알람은 소음이다. - fps NULL을 0으로 강압하지 않는다(측정 실패 ≠ 정지). PdoDeviceMetric Repository가 NULL을 지키는 이유와 같다. - 억제(정비 모드)는 여기서 보지 않는다. 기록은 남기고 통보 여부는 AlertDispatcher가 정한다 — 정비 창은 전화를 끄는 것이지 노드가 죽었던 기록을 지우는 것이 아니다. camera_id는 NOT NULL인데 장치 단위 이벤트에는 카메라가 없다. '__device__' 센티넬을 쓰고, 콘솔은 "장치 전체"로 표시, 구역 편집기 카메라 목록에서는 제외한다(카메라 목록은 보고된 이벤트에서 파생되므로, 두면 존재하지 않는 카메라에 구역을 그리려는 일이 생긴다). 설정 4종 추가(device_stale_seconds 180 · degraded_sustain_seconds 300 · degraded_fps_ratio 0.6 · target_fps 12) — 두 config 템플릿에 한국어 설명과 함께. 낮은 프레임으로 운용하는 현장이 target_fps를 안 맞추면 전 노드가 영구 저하로 보고되므로 그 경고를 템플릿에 적었다. 검증: pytest 308 passed(3 skip: 모델 미동봉·shellcheck 미설치·AF_UNIX, 기존과 동일) · phpunit 240 passed(신규 16). dist 재생성으로 패리티 회복.
7.6 KiB
CHANGELOG — move-detect (작업장 안전 AI CCTV)
버전 표기는 저장소 루트 VERSION 파일이 단일 출처입니다.
납품 산출물 파일명에도 같은 버전이 들어갑니다:
move-detect-cafe24-<VERSION>-<YYYYMMDD>.zip / move-detect-pi-<VERSION>-<YYYYMMDD>-<arch>.tar.gz
미출시 (main)
수정 — 아무도 발행하지 않던 이벤트 2종
monitoring_degraded(감시 성능 저하)와 device_fault(장치 이상)는 타입 정의·서버 검증·
콘솔 필터·통보 라벨까지 전 구간에 있었으나 생성하는 코드가 어디에도 없었습니다. 실제로
값을 넣는 것은 데모 시더뿐이었고, 소개서가 내세우던 "17종"은 실제로 15종이었습니다.
엣지가 낼 수 없는 유형입니다 — 전원이나 회선이 끊긴 노드는 자기가 죽었다고 신고할 수 없습니다. 그래서 서버가 하트비트의 부재로 판정합니다.
App\Alert\DeviceHealthWatch추가. 1분 크론(dispatch_alerts.php)의 첫 단계로 실행되어, 같은 실행 안에서 발행된 장치 이벤트가 곧바로 통보 대상이 됩니다.- 장애당 1건. event_uuid 앵커가 장애 구간의 시작(
device_fault는 마지막 하트비트 시각)이라 크론이 60번 돌아도 이벤트는 하나입니다. 복구 후 재발은 별건으로 잡힙니다. - 성능 저하는 지속을 요구합니다(기본 5분). 한 번 튄 프레임률로 알람이 울리면 운영자는 이 유형을 무시하게 되고, 그러면 정작 감시가 멈춘 날에도 아무도 보지 않습니다.
- 한 번도 하트비트를 보낸 적 없는 장치는 장애가 아닙니다(미설치 노드). 새 현장 첫날이 빨간 벽이 되지 않도록.
- 설정:
device_stale_seconds(180) ·degraded_sustain_seconds(300) ·degraded_fps_ratio(0.6) ·target_fps(12). 노드를 낮은 프레임으로 운용하는 현장은target_fps를 실제 값으로 맞춰야 합니다.
장치 단위 이벤트는 카메라가 없으므로 camera_id 에 __device__ 센티넬을 씁니다. 콘솔은
"장치 전체"로 표시하고, 구역 편집기의 카메라 목록에서는 제외합니다(존재하지 않는 카메라에
구역을 그리려는 일이 없도록).
1.0.0 — 2026-08-18 · 첫 정식 납품 버전
0.9.0 까지가 "감지하는 시스템"이었다면, 1.0.0 은 "감지에 대응하는 시스템" 입니다. 그 차이를 만드는 것이 아래 첫 항목입니다.
추가 — 통보 (이 버전의 이유)
이전까지 감지는 브라우저 탭이 열려 있고 사람이 보고 있어야만 전달됐습니다. 새벽 3시에 넘어진 작업자는 DB 에 행 하나를 남기고 아침을 맞았습니다.
- 현장 경광등·부저 (엣지 GPIO). 서버도 인터넷도 거치지 않고 감지한 노드가 직접 울립니다. 정전·회선 장애로 업링크가 끊긴 순간은 오히려 위험이 높아지는 때이므로, 어떤 현장이든 이것부터 설정합니다.
- 메일 · 웹훅 채널과 1분 주기 디스패처. 알림에는 현장 이미지를 싣지 않고 링크만 보냅니다 (스냅샷은 개인정보이고, 메일링 리스트로 나가는 순간 인증 프록시의 통제를 벗어납니다).
- 미확인 경보 에스컬레이션. 위급/높음이 정해진 시간 동안 확인되지 않으면 그 사실 자체가 사건이 되어 상위 담당자에게 통보됩니다. "아무도 보지 않았다"가 처음으로 기록에 남습니다.
추가 — 조용한 실패 드러내기
- 카메라 화각 변경 · 가림 · 조도 저하 감지. 위험구역 판정은 카메라가 어제와 같은 곳을 본다는 전제 위에 서 있는데, 청소하다 삼각대를 스치면 제한구역이 엉뚱한 바닥을 가리키면서 시스템은 계속 초록불이었습니다.
- 저장 공간 부족 경고. 디스크가 차면 이미지 저장만 조용히 실패하고 화면은 멀쩡해 보입니다.
- 설정 지연 표시가 실제로 동작하게 됐습니다. 지표는 있었지만 그것을 켜는 사건이 없었습니다.
추가 — 관리 화면 (그전에는 DB 직접 수정이 유일한 방법)
- 감지 임계값 편집 (허용 범위·단위·설명을 화면이 강제)
- 통보 경로 설정
- 정비 중 알람 억제 — 사유 필수, 최대 8시간, 자동 만료. 감지와 기록은 그대로 계속되고 통보만 보류되며, 보류했다는 사실도 기록에 남습니다.
- 위험성평가 연계 · 교대 인수인계 · 설비 성능 추이 · 설치 점검(
/setup)
추가 — 증거와 규정
- 감사로그 위·변조 탐지 (해시체인). 수정·삭제·우회삽입이 전부 드러납니다.
- 사고조사 패키지 1클릭 — 전후 타임라인, 통보 이력, 위험요인, 조치 이력, 서명란. 인쇄해서 그대로 제출할 수 있습니다.
- 현장별 현황 (본사 계정)
변경 — 화면 (UI/UX 점검 21건)
- 감지 스트림이 다시 시간순이 됐습니다. 확인 여부가 순서를 바꾸던 탓에 사고 재구성이 불가능했고, 확인하면 그 행이 사라지며 아래가 밀려 올라와 다른 이벤트를 잘못 확인할 수 있었습니다. 확인은 이제 제자리에서 처리됩니다.
- 이벤트 상세에서 바로 확인/오탐 판정 · 구역 설정에 카메라 선택기 · 날짜로 조회 · 경보음 음소거(새 위급 오면 자동 해제) · 일괄 확인(위급 제외) · 키보드 조작
- 표본이 부족한 수치는 비율 대신 원시 숫자만 보여줍니다. 6건→8건을 "▲33% 악화"로 칠하면 안전회의에서 반박당하고, 그 순간 그 화면 전체가 신뢰를 잃습니다.
도구 (move_detect_app/tools/, 웹에서 접근 불가)
| 도구 | 용도 |
|---|---|
dispatch_alerts.php |
알림 발송 (크론 1분) · --dry-run 지원 |
maintain.php |
보존기간 정리 (크론 일 1회) |
backup.php |
DB 덤프 + 사진 아카이브 + 복구 안내 |
verify_audit_chain.php |
감사로그 무결성 검증 |
seed_demo.php |
시연·시험용 데모 데이터 (운영 DB 실행 금지) |
업그레이드 (0.9.x → 1.0.0)
migrations/2026-08-09-safety-platform.sql 을 적용합니다. 파일 안의 9번 블록은 DB 종류에
따라 문법이 다릅니다 — MySQL 8.0.16+ / MariaDB / 제약 없음 세 경우의 안내가 파일에
적혀 있으니 SELECT VERSION(); 으로 확인 후 맞는 것을 실행하세요.
적용 후 config.php 에 public_base_url, timezone, escalate_after_seconds 를 추가하고
(템플릿 참조), 자동실행(크론) 2개를 등록해야 알림이 동작합니다 — README-UPLOAD.md 8번.
알려진 제약
- 보호구 미착용 · 중장비 근접 · 화재 감지: 감지기 배선과 이벤트 계약은 준비되어 있으나 추론 모델이 확보되지 않아 기본 비활성입니다. 라이선스 조건을 만족하는 가중치와 Hailo 컴파일이 선행되어야 합니다. 켜져 있지 않다는 사실을 화면에서도 숨기지 않습니다.
- 실장비 검증 잔여 항목은
docs/delivery-audit.md의 "OFF-HW 잔여" 절을 참조하세요.
0.9.0 — 2026-08-08
납품 감사(43건) 대응. CSP nonce 도입, 보안 헤더 PHP 이관, /logout CSRF, 계정 잠금 응답에서 사용자명 열거 제거. 엣지 큐 flush 상한, 타이머 env 전수 검증, 프레임 스킵 시 미블러 얼굴 유출 차단, systemd 워치독. PDO 통합 테스트 복구, 라우트 테이블 대조, 산출물↔소스 패리티 검사, CI 신설. 납품물에 version/git_commit/built_at 스탬프.