Pi 번들 — arm64 빌드 머신이 필요하다고 적어 둔 것은 과한 가정이었다. --cross 모드에서 pip 이 태그로 aarch64/cp311 휠을 받으므로 네트워크만 있으면 된다. 휠 11개 전부 aarch64. "Pi 번들 미빌드"로 스킵되던 테스트가 이제 실제로 돈다. MoveNet — P3-3 의 잔여였던 죽은 원본을 유효한 tf2onnx 변환본으로 교체하고 sha256+size 로 고정했다. 고정 전에 어댑터 계약과 대조했다: 입력 [1,192,192,3] int32 NHWC, 출력 [1,1,17,3]. 레이아웃이 다른 변환본은 onnxruntime 에 그냥 로드되고 좌표만 틀리는 실패라, 파일이 바뀌었는지가 아니라 계약이 맞는지를 테스트로 박았다. 자격증명 회전 — 손으로 적던 SQL 절차를 명령 하나로 바꿨다. DB 에 접속하지 않고 붙여넣을 SQL 만 낸다(운영자의 창구가 phpMyAdmin 이므로 접속 정보 없이 어느 머신에서나 돌아야 한다). 비밀번호는 STDIN 으로만 받는다 — 명령행은 ps 와 셸 히스토리에 남는다. 테스트가 잠그는 것은 서버 키와 Pi 키의 일치, 그리고 앱이 그 해시를 검증하는지 두 가지다. 회전 실행 자체는 운영 계정 조작이라 저장소에서 수행하지 않는다. 검증: phpunit 276 passed(스킵 0, 신규 7) · pytest 스킵 2(환경) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBjuR7DW6PNNMows4KnbYU
110 lines
9.3 KiB
Markdown
110 lines
9.3 KiB
Markdown
# PROJECT_MAP — move_detect
|
|
|
|
> 작업 전 이 문서로 위치를 찾고 필요한 파일만 연다 (전체 재스캔 금지).
|
|
> 새 파일·라우트·스키마 변경 시 해당 섹션을 갱신할 것.
|
|
|
|
## 개요
|
|
|
|
작업장 안전 AI CCTV. Raspberry Pi 5 + Hailo-8 엣지 추론(Python) → HMAC POST → Cafe24
|
|
웹호스팅 PHP+MySQL 대시보드. 감지: 넘어짐 · 넘어짐 후 미동 · 장시간 부동 · 위험구역
|
|
침입(경고/절대금지/협착) · 유령사고(위험 상태로 소실). 스냅샷만 전송, 라이브뷰·영상 없음.
|
|
|
|
## edge/ (Python — Pi에서 구동)
|
|
|
|
| 파일 | 역할 |
|
|
|------|------|
|
|
| `app.py` | `run_once` 파이프라인 (프레임→추론→추적→감지→스냅샷→전송). ambiguous 리셋·stale 필터 |
|
|
| `engine.py` | `EventEngine` — 감지기 오케스트레이션, 쿨다운 dedup, 트랙 forget |
|
|
| `tracking.py` | `IouTracker` — 전역 그리디 IoU 매칭, `ambiguous()` (교차 경합 트랙) |
|
|
| `detectors/` | `fall.py`(FALLING→FALLEN→NO_MOTION, sustained prone) · `immobility.py` · `ghost.py`(위험 상태 소실 + 높이 무장 → 화각 밖 추락) · `zone.py`(`elevated` 고소 표시) · `occupancy.py`(존 규칙: 2인1조/정원/체류상한 — Zone.rules 소비) |
|
|
| `smoothing.py` | 트랙별 키포인트 스무딩 풀 |
|
|
| `snapshot.py` | RingBuffer · FaceBlurrer(PIPA) · Snapshotter |
|
|
| `alert.py` / `serverclient.py` | HMAC+nonce POST, 커스텀 UA, no-redirect |
|
|
| `queue.py` | 오프라인 전송 큐 |
|
|
| `config.py` | env → Config (빈 CAFE24_URL/SECRET는 기동 실패) |
|
|
| `daemon.py` / `runtime.py` / `health.py` | 상주 루프 · 하트비트 텔레메트리 |
|
|
| `adapters/` | hailo_pose · movenet_pose(CPU 폴백) · picamera_source(CSI) · uvc_source(USB) |
|
|
| `probe.py` / `tools/` | 카메라 사전 점검 · HEF/모델 fetch (빌드머신 전용) |
|
|
| `beacon.py` | 현장 경광등·부저 (GPIO). **네트워크 없이 동작하는 유일한 통보 경로** — /api/config 의 `beacons` 로 설정 수신, 하드웨어 없으면 NullBeacon 으로 폴백 |
|
|
| `tuning.py` | 서버 임계값 → 살아 있는 감지기에 적용. 이전에는 `thresholds_from_config()` 가 파싱만 하고 버려서 관리자 변경이 장비에 닿지 않았다 |
|
|
| `detectors/integrity.py` | 카메라 화각 이동·가림·조도 저하 (`camera_moved`/`camera_obstructed`/`low_light`). 존 판정이 조용히 무의미해지는 실패 모드를 드러낸다 |
|
|
| `detectors/height.py` | 추락(`fall_from_height`) 1차 신호. **중심이 아니라 bbox 하단(y2)** 으로 판정 — 쭈그려 앉으면 중심은 내려가도 발은 그 자리다. `landed_from_drop`(착지 시각과 비교)이 핵심: now 로 비교하면 확정이 prone_confirm_s 만큼 늦어 기본값에서 영원히 거짓 |
|
|
| `detectors/pending.py` | 보호구·중장비 근접·화재 — 배선/계약 완료, **모델 미확보로 기본 비활성** (AGPL 아닌 가중치 + HEF 컴파일 필요) |
|
|
|
|
## server/ (PHP 8 + PDO — Cafe24)
|
|
|
|
- 진입: `public/index.php` (라우트 전부 여기),
|
|
뷰: `src/View/{layout,events,login,zones,report,event_detail,admin,compliance,setup,incident_package}.php`
|
|
- `src/Alert/`: `AlertDispatcher`(어떤 통보를 낼지 결정 — 순수 로직, I/O 없음) ·
|
|
`Channels`(email/webhook 발송; `local` 은 엣지가 자체 GPIO 로) · `EscalationWatch`(미확인 위급 →
|
|
`unacked_escalation` 이벤트 발행, event_uuid 로 멱등) ·
|
|
`MultiDownWatch`(같은 구역 짧은 시간 2명 이상 쓰러짐 → `multi_down`; 값어치는 통보 문구에
|
|
있다 — Channels::guidance 의 "공기호흡기 없이 진입 금지") ·
|
|
`DeviceHealthWatch`(하트비트 부재 → `device_fault`, 지속 성능저하 → `monitoring_degraded`;
|
|
엣지가 낼 수 없는 두 유형을 서버가 발행. uuid 앵커 = 장애 구간 시작이라 장애당 1건)
|
|
- `docs/deck/`: 경영진 보고 덱. `deck.json`(내용) + `build_deck.py`(생성기) + `clips/`(3초 재현
|
|
영상, 없으면 스틸로 자동 대체). 출력 `경영진-보고.html` 은 gitignore — 낡은 사본 방지.
|
|
발주 명세는 `docs/영상-브리프.md`.
|
|
- `docs/images/`: 소개서·덱 공용 도판 11장(원본은 `image/`, 최적화본이 여기).
|
|
배치 매니페스트 `docs/소개서-이미지.json`.
|
|
- `src/Api/ZonePresetCatalog.php`: 용도별 구역 프리셋 6종(밀폐공간·고소작업·프레스협착·
|
|
출입금지·지게차통로·화기작업). 새 감지 기능이 아니라 기존 규칙에 현장 어휘를 붙인 것.
|
|
`GET /api/zone-presets` (ZoneConfigController::presets, view_events 게이트).
|
|
- `src/Api/ThresholdCatalog.php`: 편집 가능한 튜너블의 화이트리스트 + 범위 + 한국어 설명.
|
|
목록에 없는 키는 API 로 저장 불가 (장비가 조용히 무시하는 값을 "설정됨"으로 보이지 않게)
|
|
- `tools/`: `dispatch_alerts.php`(크론 1분) · `maintain.php`(크론 일 1회, 보존기간) ·
|
|
`backup.php`(DB 덤프+스냅샷) · `verify_audit_chain.php`(감사 무결성) · `seed_demo.php`(시연·QA) ·
|
|
`rotate_credentials.php`(장치 HMAC 키·관리자 비번 회전 — DB 미접속, 붙여넣을 SQL 만 출력).
|
|
전부 CLI 전용(`bootstrap.php` 가 SAPI 검사), 납품 시 `move_detect_app/tools/` 로 www 밖에 배치
|
|
- 라우트: `POST /api/events`(ingest, HMAC) · `POST /api/heartbeat` · `GET /api/config` ·
|
|
`GET /api/events` · `GET /api/devices` · `GET /api/heatmap/{camera}`(spatial 집계) ·
|
|
`GET /api/fp-stats`(오탐율) · `GET/POST /api/zones*` · `POST /login|/logout` ·
|
|
`GET /snap/{uuid}` · `POST /events/{id}/ack` · `POST /events/{id}/false-positive`(오탐 태그,
|
|
ack 겸함) · `GET /api/report`(운영 지표+시간패턴+전조경보+추이) ·
|
|
`GET /api/export/labels`(라벨 JSONL, 관리자) · `GET/POST /api/actions`(개선 조치·전후 효과) ·
|
|
`GET /api/devices/{id}/metrics`(성능 시계열) · `GET/POST /api/admin/thresholds*`(임계값 편집,
|
|
admin) · `GET/POST /api/admin/channels*`(통보 채널, admin) · `GET/POST /api/suppressions*`(정비
|
|
억제) · `GET/POST /api/risks*`(위험성평가) · `GET/POST /api/handovers`(교대 인수인계) ·
|
|
`GET /api/events/{id}/package`(사고조사 패키지) ·
|
|
페이지 `/`, `/login`, `/zones`, `/report`(안전 리포트), `/compliance`(안전 관리),
|
|
`/admin`(시스템 설정), `/setup`(설치 점검), `/events/{id}/view`, `/events/{id}/package/view`
|
|
- `src/Ingest/` HMAC+nonce+rate-limit+replay 방지 · `src/Auth/` argon2id/세션/RBAC/CSRF ·
|
|
`src/Dashboard/` 이벤트목록·디바이스상태·존편집·스냅샷프록시·시스템설정·억제·규정 ·
|
|
`src/Alert/` 통보 결정·발송·에스컬레이션 · `src/Repository/` 14종 (인터페이스+PDO)
|
|
- 감사로그는 **해시체인**(prev_hash/entry_hash). 수정·삭제·우회삽입이 전부 탐지된다 —
|
|
`tools/verify_audit_chain.php`, 손상 시 종료 코드 1
|
|
- 프런트: `public/assets/app.js`(이벤트 콘솔 — 엄격 시간순 정렬 · 뷰모드(전체/미확인만) ·
|
|
날짜 조회(커서 시딩) · 확인 제자리 전환+토스트 · 오탐 2단계 · 경보음 음소거 · 날짜 구분선) ·
|
|
`device-tree.js`(장치→카메라 트리 레일) · `zone-draw.js`(장치·카메라 선택기 · 존 채움/라벨칩 ·
|
|
삭제 2단계) · `detail.js`(상세 + 확인/오탐 판정) · `report.js`(표본·유의성 게이트) · `app.css`
|
|
- UI 불변식: 스트림 정렬은 **항상 occurred_at 내림차순** — 확인 상태가 순서를 바꾸면 안 됨
|
|
(연대기 = 사고 재구성 근거). 처리 큐는 정렬이 아니라 필터(`filters.unackedOnly`)로만 만든다.
|
|
- 페이지 명칭은 nav 라벨 · `<h1>` · `<title>` 세 곳이 동일 문자열 (`이벤트`/`구역 설정`/`안전 리포트`).
|
|
날짜 표기는 `YYYY-MM-DD` 로 3개 화면 통일 (각 파일의 로컬 `formatTime`/`fmtDate`).
|
|
- DB: `schema.sql` (테이블 prefix `move_`), 마이그레이션 `migrations/`.
|
|
존 행의 `rules_json`(선택) = 점유 규칙 {min_workers,max_workers,max_dwell_s} — 편집기 저장 시
|
|
ZoneConfigController가 검증, /api/config로 엣지 전달
|
|
- 배포: `server/deploy/build_cafe24_package.sh` → zip, `server/docs/server-deploy.md`
|
|
|
|
## deploy/ · dist/ (납품)
|
|
|
|
- `deploy/`: Pi 설치 — `setup_online.sh`, `bundle/build_offline_bundle.sh`(폐쇄망),
|
|
`systemd/move-detect-edge.service`, `device.env`(사전 프로비저닝)
|
|
- `dist/`: `1_cafe24_upload/` · `2_pi_install/` · `PROVISION_SECRETS.md`
|
|
|
|
## tests/
|
|
|
|
- Python: `tests/` 31파일 (pytest) — 하드웨어 필요 테스트는 `tests/hardware/` + skip 마커
|
|
- PHP: `server/tests/` 19파일 (phpunit, in-memory repo; PDO 통합은 skip 게이트)
|
|
|
|
## docs/
|
|
|
|
`delivery-audit.md`(감사 43건 + 재감사 4회, 전부 완료 — OFF-HW 실장비 검증만 잔여) · `pilot-runbook.md` ·
|
|
`hardware-bringup.md` · `INDEX.md`(작업 기록)
|
|
|
|
도해 3종. 소스는 `*.archify.json` 이고 HTML 은 생성물이라 커밋하지 않는다
|
|
(`node ~/.claude/skills/archify/bin/archify.mjs deliver <type> <소스> <출력> --quality showcase`):
|
|
|
|
- `architecture.archify.json` — 시스템 구성(현장 ↔ Cafe24). 무엇이 경계를 넘는가.
|
|
- `fall-sequence.archify.json` — 낙상 1건의 시간축. 경광등 즉시 vs 통보 최대 60초의 간격.
|
|
- `fall-lifecycle.archify.json` — 낙상 판정 상태기계. 확정·회복 양쪽이 지속을 요구하는 구조.
|