docs: 설계 도해 3종 (구성 · 낙상 시퀀스 · 낙상 상태기계)
archify 소스 JSON 만 커밋한다. HTML 은 생성물이라 사용설명서와 같은 이유로 gitignore — 소스가 바뀐 뒤에도 낡은 사본이 최신처럼 보이기 때문. 시퀀스를 그리면서 드러난 것: 경광등은 확정 즉시(3초)지만 메일·웹훅은 크론 주기를 기다려 최대 63초다. 문서가 둘을 "통보"로 묶어 쓰던 표현이 실제로는 20배 차이나는 두 경로였다. 상태기계는 확정 창 3초와 추락 연결 창 3초가 겹쳐 있는 구조를 한 장에 담았다 — 4차 감사에서 잡은 CRITICAL(추락 승격 미발화)의 원인이고, 시간축을 그렸다면 코드를 읽기 전에 보였을 충돌이다. 검증: deliver 9/9 · 오류 0 · 경고 0, visual-check 1440x900 / 1600x1000 / 1920x1080 / 2048x1320 양 테마 containment·가독성 통과. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EBjuR7DW6PNNMows4KnbYU
This commit is contained in:
parent
5db0a4a403
commit
34506e150d
6 changed files with 836 additions and 0 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -49,3 +49,9 @@ docs/deck/clips/*
|
|||
# 프로비저닝 자격증명 — 납품 폴더(dist/) 밖에 둔다.
|
||||
# dist/ 안에 있으면 폴더째 압축할 때 같이 나간다.
|
||||
/secrets/
|
||||
|
||||
# archify 생성물. 소스는 docs/*.archify.json 이고 `archify deliver` 로 언제든 다시 만든다.
|
||||
# 사용설명서와 같은 이유로 커밋하지 않는다 — 소스가 바뀐 뒤에도 낡은 HTML 이 최신처럼 보인다.
|
||||
# visual-check 는 검증 증거(PNG·리포트)라 저장소가 아니라 그때의 판정 기록으로만 쓴다.
|
||||
docs/move-detect-*.html
|
||||
docs/move-detect-*.visual-check.*
|
||||
|
|
|
|||
|
|
@ -168,3 +168,8 @@
|
|||
- 검증: pytest 322 passed(5 skip: 환경·Pi번들 미빌드) · phpunit 263 passed
|
||||
|
||||
- 2026-09-05 — 1.2.0 4차 재감사: 확정 23건 전량 해소, 반증 5건 기록. CRITICAL 4건은 전부 "구현·단위테스트는 초록인데 납품 경로에서 무동작"이었다 — 추락 승격이 기본 설정에서 미발화(테스트가 prone_confirm_s=0.5 사용), max_workers:0·elevated 가 상류 허용 목록 두 곳에서 차단, 동시 다발이 트래커 재획득으로 한 사람을 두 명으로 셈(구조 지연). 재발 방지 테스트 추가: BUILD-INFO↔HEAD, README↔마이그레이션, 시더 심각도↔엣지, 프리셋 왕복. 문서 "17종"→실제 20종 정정. 산출물 `move-detect-cafe24-1.2.0-20260905.zip`(BUILD-INFO=93f896a). 검증 pytest 338 / phpunit 269. `93f896a`
|
||||
|
||||
- 2026-09-18 — 설계 도해 3종 작성(archify, showcase 9/9 · 오류 0 · 경고 0, 1440~2048 4해상도 양 테마 브라우저 검증):
|
||||
시스템 구성 · 낙상 시퀀스 · 낙상 상태기계. 시퀀스를 그리다 확인한 사실 — **경광등(3초)과 메일(최대 63초)은
|
||||
같은 "통보"가 아니다.** 상태기계 쪽은 확정 창 3초와 추락 연결 창 3초가 겹쳐 있음을 한 장으로 보여준다
|
||||
(4차 감사의 CRITICAL, 시간을 그렸다면 코드보다 먼저 보였을 충돌). HTML 은 gitignore, 소스 JSON 만 커밋.
|
||||
|
|
|
|||
|
|
@ -100,3 +100,10 @@
|
|||
|
||||
`delivery-audit.md`(감사 43건, 전부 완료 — 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` — 낙상 판정 상태기계. 확정·회복 양쪽이 지속을 요구하는 구조.
|
||||
|
|
|
|||
343
docs/architecture.archify.json
Normal file
343
docs/architecture.archify.json
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
{
|
||||
"schema_version": 1,
|
||||
"diagram_type": "architecture",
|
||||
"meta": {
|
||||
"title": "작업장 안전 AI CCTV — 시스템 구성",
|
||||
"output": "move-detect-architecture.html",
|
||||
"quality_profile": "showcase",
|
||||
"views": [
|
||||
{
|
||||
"id": "detection-chain",
|
||||
"label": "감지 → 대응",
|
||||
"focus": [
|
||||
"cameras",
|
||||
"edge",
|
||||
"beacon"
|
||||
],
|
||||
"note": "카메라에서 경광등까지. 서버도 인터넷도 거치지 않는 즉시 대응 경로."
|
||||
},
|
||||
{
|
||||
"id": "privacy-path",
|
||||
"label": "프라이버시 경로",
|
||||
"focus": [
|
||||
"edge",
|
||||
"blur",
|
||||
"ingest",
|
||||
"db"
|
||||
],
|
||||
"note": "영상은 나가지 않는다. 얼굴 블러 후 스냅샷 3장만, 7일 뒤 삭제."
|
||||
},
|
||||
{
|
||||
"id": "server-issued",
|
||||
"label": "서버가 발행하는 사건",
|
||||
"focus": [
|
||||
"cron",
|
||||
"db",
|
||||
"notify"
|
||||
],
|
||||
"note": "죽은 노드는 자기가 죽었다고 신고할 수 없다 — 하트비트의 부재로 판정한다."
|
||||
}
|
||||
]
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"id": "cameras",
|
||||
"type": "external",
|
||||
"label": "카메라",
|
||||
"sublabel": "CSI · USB",
|
||||
"tag": "노드당 2~4대",
|
||||
"pos": [
|
||||
40,
|
||||
340
|
||||
],
|
||||
"size": [
|
||||
130,
|
||||
64
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "edge",
|
||||
"type": "backend",
|
||||
"label": "엣지 노드",
|
||||
"sublabel": "Pi 5 + Hailo-8",
|
||||
"tag": "판정 16종",
|
||||
"pos": [
|
||||
230,
|
||||
332
|
||||
],
|
||||
"size": [
|
||||
165,
|
||||
80
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "beacon",
|
||||
"type": "external",
|
||||
"label": "현장 경광등",
|
||||
"sublabel": "GPIO 직결",
|
||||
"tag": "네트워크 불필요",
|
||||
"pos": [
|
||||
230,
|
||||
180
|
||||
],
|
||||
"size": [
|
||||
165,
|
||||
64
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "queue",
|
||||
"type": "messagebus",
|
||||
"label": "오프라인 큐",
|
||||
"sublabel": "단절 시 보관",
|
||||
"pos": [
|
||||
230,
|
||||
500
|
||||
],
|
||||
"size": [
|
||||
165,
|
||||
64
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "blur",
|
||||
"type": "security",
|
||||
"label": "얼굴 블러",
|
||||
"sublabel": "전송 전",
|
||||
"tag": "영상 녹화 없음",
|
||||
"pos": [
|
||||
465,
|
||||
336
|
||||
],
|
||||
"size": [
|
||||
160,
|
||||
72
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ingest",
|
||||
"type": "security",
|
||||
"label": "수집 API",
|
||||
"sublabel": "HMAC + nonce",
|
||||
"tag": "재전송 차단",
|
||||
"pos": [
|
||||
715,
|
||||
332
|
||||
],
|
||||
"size": [
|
||||
155,
|
||||
80
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "app",
|
||||
"type": "backend",
|
||||
"label": "대시보드 서버",
|
||||
"sublabel": "PHP 8",
|
||||
"pos": [
|
||||
960,
|
||||
332
|
||||
],
|
||||
"size": [
|
||||
160,
|
||||
80
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "db",
|
||||
"type": "database",
|
||||
"label": "MySQL",
|
||||
"sublabel": "move_* · 7일",
|
||||
"pos": [
|
||||
1150,
|
||||
340
|
||||
],
|
||||
"size": [
|
||||
160,
|
||||
64
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cron",
|
||||
"type": "backend",
|
||||
"label": "1분 크론",
|
||||
"sublabel": "이벤트 발행",
|
||||
"tag": "서버 발행 4종",
|
||||
"pos": [
|
||||
1150,
|
||||
480
|
||||
],
|
||||
"size": [
|
||||
160,
|
||||
76
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "console",
|
||||
"type": "frontend",
|
||||
"label": "운영 콘솔",
|
||||
"sublabel": "바닐라 JS",
|
||||
"tag": "라이브뷰 없음",
|
||||
"pos": [
|
||||
960,
|
||||
180
|
||||
],
|
||||
"size": [
|
||||
160,
|
||||
76
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "notify",
|
||||
"type": "external",
|
||||
"label": "메일 · 사내 웹훅",
|
||||
"sublabel": "링크만 전송",
|
||||
"pos": [
|
||||
1150,
|
||||
630
|
||||
],
|
||||
"size": [
|
||||
160,
|
||||
72
|
||||
]
|
||||
}
|
||||
],
|
||||
"boundaries": [
|
||||
{
|
||||
"kind": "region",
|
||||
"label": "현장 (폐쇄망 가능)",
|
||||
"wraps": [
|
||||
"cameras",
|
||||
"edge",
|
||||
"beacon",
|
||||
"queue",
|
||||
"blur"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "region",
|
||||
"label": "Cafe24 공유호스팅",
|
||||
"wraps": [
|
||||
"ingest",
|
||||
"app",
|
||||
"db",
|
||||
"cron"
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"id": "capture",
|
||||
"from": "cameras",
|
||||
"to": "edge",
|
||||
"label": "프레임"
|
||||
},
|
||||
{
|
||||
"id": "local-beacon",
|
||||
"from": "edge",
|
||||
"to": "beacon",
|
||||
"label": "즉시 점등",
|
||||
"variant": "emphasis",
|
||||
"fromSide": "top",
|
||||
"toSide": "bottom"
|
||||
},
|
||||
{
|
||||
"id": "evidence",
|
||||
"from": "edge",
|
||||
"to": "blur",
|
||||
"label": "이벤트 시"
|
||||
},
|
||||
{
|
||||
"id": "spool",
|
||||
"from": "edge",
|
||||
"to": "queue",
|
||||
"label": "회선 단절 시",
|
||||
"variant": "dashed",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"labelDy": 30
|
||||
},
|
||||
{
|
||||
"id": "upload",
|
||||
"from": "blur",
|
||||
"to": "ingest",
|
||||
"label": "HMAC POST",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "resend",
|
||||
"from": "queue",
|
||||
"to": "ingest",
|
||||
"label": "복구 후 재전송",
|
||||
"variant": "dashed",
|
||||
"fromSide": "right",
|
||||
"toSide": "bottom"
|
||||
},
|
||||
{
|
||||
"id": "accept",
|
||||
"from": "ingest",
|
||||
"to": "app",
|
||||
"label": "검증 통과"
|
||||
},
|
||||
{
|
||||
"id": "persist",
|
||||
"from": "app",
|
||||
"to": "db",
|
||||
"label": "SQL"
|
||||
},
|
||||
{
|
||||
"id": "cron-db",
|
||||
"from": "cron",
|
||||
"to": "db",
|
||||
"label": "하트비트 부재로 판정",
|
||||
"fromSide": "top",
|
||||
"toSide": "bottom"
|
||||
},
|
||||
{
|
||||
"id": "dispatch",
|
||||
"from": "cron",
|
||||
"to": "notify",
|
||||
"label": "1분 주기 통보",
|
||||
"fromSide": "bottom",
|
||||
"toSide": "top",
|
||||
"labelDy": 30
|
||||
},
|
||||
{
|
||||
"id": "serve",
|
||||
"from": "app",
|
||||
"to": "console",
|
||||
"label": "HTTPS · 역할 기반 접근",
|
||||
"fromSide": "top",
|
||||
"toSide": "bottom"
|
||||
}
|
||||
],
|
||||
"cards": [
|
||||
{
|
||||
"dot": "cyan",
|
||||
"title": "현장에서 끝나는 대응",
|
||||
"items": [
|
||||
"판정은 엣지에서 완결 — 서버 왕복 없음",
|
||||
"경광등은 GPIO 직결이라 회선·서버가 죽어도 울린다",
|
||||
"끊긴 동안의 이벤트는 큐에 쌓였다가 복구 시 재전송"
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "rose",
|
||||
"title": "영상을 남기지 않는다",
|
||||
"items": [
|
||||
"링버퍼는 RAM 7.5초 — 디스크에 쓰지 않는다",
|
||||
"이벤트 순간에만 3프레임, 얼굴 블러는 전송 전 노드에서",
|
||||
"스냅샷 7일 후 자동 삭제 · 라이브뷰 화면 자체가 없음"
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "emerald",
|
||||
"title": "서버가 보는 것",
|
||||
"items": [
|
||||
"HMAC + nonce 로 재전송 공격 차단",
|
||||
"죽은 노드는 자기가 죽었다고 신고할 수 없다 — 크론이 하트비트의 부재로 판정",
|
||||
"통보는 링크만 — 현장 이미지는 메일로 나가지 않는다"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
217
docs/fall-lifecycle.archify.json
Normal file
217
docs/fall-lifecycle.archify.json
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
{
|
||||
"schema_version": 1,
|
||||
"diagram_type": "lifecycle",
|
||||
"meta": {
|
||||
"title": "낙상 판정 상태기계 — 지속을 요구하는 이유",
|
||||
"output": "move-detect-fall-lifecycle.html",
|
||||
"quality_profile": "showcase",
|
||||
"views": [
|
||||
{
|
||||
"id": "main-path",
|
||||
"label": "확정까지",
|
||||
"focus": [
|
||||
"standing",
|
||||
"falling",
|
||||
"fallen",
|
||||
"nomotion"
|
||||
],
|
||||
"note": "한 프레임으로는 아무것도 확정하지 않는다. 1초는 위험 자세, 3초라야 넘어짐."
|
||||
},
|
||||
{
|
||||
"id": "recovery",
|
||||
"label": "회복 경로",
|
||||
"focus": [
|
||||
"falling",
|
||||
"nearfall",
|
||||
"fallen",
|
||||
"recovered"
|
||||
],
|
||||
"note": "회복도 지속을 요구한다. 확정 전에 일어서면 니어미스로만 남는다."
|
||||
},
|
||||
{
|
||||
"id": "lost-track",
|
||||
"label": "사라진 사람",
|
||||
"focus": [
|
||||
"fallen",
|
||||
"ghost"
|
||||
],
|
||||
"note": "위험 상태에서 트랙이 사라지면 10초 기다린 뒤 별도 사건으로 올린다."
|
||||
}
|
||||
],
|
||||
"viewBox": [
|
||||
1085,
|
||||
620
|
||||
]
|
||||
},
|
||||
"lanes": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "자세 상태"
|
||||
},
|
||||
{
|
||||
"id": "recovery",
|
||||
"label": "회복"
|
||||
},
|
||||
{
|
||||
"id": "terminal",
|
||||
"label": "종료"
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
{
|
||||
"id": "standing",
|
||||
"type": "start",
|
||||
"label": "정상",
|
||||
"sublabel": "기립 · 보행",
|
||||
"lane": "main",
|
||||
"col": 0,
|
||||
"step": "01"
|
||||
},
|
||||
{
|
||||
"id": "falling",
|
||||
"type": "active",
|
||||
"label": "위험 자세",
|
||||
"sublabel": "쓰러진 자세 1초",
|
||||
"lane": "main",
|
||||
"col": 1,
|
||||
"step": "02",
|
||||
"tag": "알람 없음"
|
||||
},
|
||||
{
|
||||
"id": "fallen",
|
||||
"type": "failure",
|
||||
"label": "넘어짐 확정",
|
||||
"sublabel": "3초 지속",
|
||||
"lane": "main",
|
||||
"col": 2,
|
||||
"step": "03",
|
||||
"tag": "경광등 + 통보"
|
||||
},
|
||||
{
|
||||
"id": "nomotion",
|
||||
"type": "failure",
|
||||
"label": "미동 없음",
|
||||
"sublabel": "정지 10초",
|
||||
"lane": "main",
|
||||
"col": 3,
|
||||
"step": "04",
|
||||
"tag": "최상위 위급"
|
||||
},
|
||||
{
|
||||
"id": "ghost",
|
||||
"type": "failure",
|
||||
"label": "위험 상태 소실",
|
||||
"sublabel": "트랙이 사라짐",
|
||||
"lane": "terminal",
|
||||
"col": 2,
|
||||
"tag": "10초 대기",
|
||||
"yOffset": -16
|
||||
},
|
||||
{
|
||||
"id": "nearfall",
|
||||
"type": "neutral",
|
||||
"label": "니어미스",
|
||||
"sublabel": "확정 전 회복",
|
||||
"lane": "recovery",
|
||||
"col": 0,
|
||||
"tag": "기록만"
|
||||
},
|
||||
{
|
||||
"id": "recovered",
|
||||
"type": "success",
|
||||
"label": "정상 복귀",
|
||||
"sublabel": "정상 자세 1초",
|
||||
"lane": "recovery",
|
||||
"col": 1
|
||||
}
|
||||
],
|
||||
"transitions": [
|
||||
{
|
||||
"id": "to-falling",
|
||||
"from": "standing",
|
||||
"to": "falling",
|
||||
"label": "쓰러진 자세 1초",
|
||||
"labelDy": -46,
|
||||
"route": "straight"
|
||||
},
|
||||
{
|
||||
"id": "to-fallen",
|
||||
"from": "falling",
|
||||
"to": "fallen",
|
||||
"label": "3초 지속",
|
||||
"variant": "emphasis",
|
||||
"labelDy": -46,
|
||||
"route": "straight"
|
||||
},
|
||||
{
|
||||
"id": "to-nomotion",
|
||||
"from": "fallen",
|
||||
"to": "nomotion",
|
||||
"label": "정지 10초",
|
||||
"variant": "emphasis",
|
||||
"labelDy": -46,
|
||||
"route": "straight"
|
||||
},
|
||||
{
|
||||
"id": "to-ghost",
|
||||
"from": "nomotion",
|
||||
"to": "ghost",
|
||||
"label": "재출현 없음",
|
||||
"variant": "security",
|
||||
"route": "right-channel",
|
||||
"channelX": 720,
|
||||
"labelSegment": 0
|
||||
},
|
||||
{
|
||||
"id": "near-miss",
|
||||
"from": "falling",
|
||||
"to": "nearfall",
|
||||
"label": "정상 1초",
|
||||
"route": "drop"
|
||||
},
|
||||
{
|
||||
"id": "fallen-recover",
|
||||
"from": "fallen",
|
||||
"to": "recovered",
|
||||
"label": "정상 1초",
|
||||
"route": "drop"
|
||||
},
|
||||
{
|
||||
"id": "back-from-nearmiss",
|
||||
"from": "nearfall",
|
||||
"to": "standing",
|
||||
"label": "복귀",
|
||||
"variant": "dashed",
|
||||
"route": "bottom-channel"
|
||||
}
|
||||
],
|
||||
"cards": [
|
||||
{
|
||||
"dot": "emerald",
|
||||
"title": "지속을 요구하는 이유",
|
||||
"items": [
|
||||
"한 프레임으로는 확정하지 않는다",
|
||||
"1초는 위험 자세 — 알람 없음",
|
||||
"앉는 동작과 가르는 조건이 이것이다"
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "rose",
|
||||
"title": "회복도 지속을 요구한다",
|
||||
"items": [
|
||||
"정상 자세 1초라야 회복",
|
||||
"가려지면 카운트다운이 멈춘다",
|
||||
"확정 전 회복은 니어미스로만"
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "amber",
|
||||
"title": "같은 3초가 두 번",
|
||||
"items": [
|
||||
"확정 창 3초 · 추락 연결 창 3초",
|
||||
"겹쳐서 추락 승격이 미발화했다",
|
||||
"시간을 그렸다면 보였을 충돌"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
258
docs/fall-sequence.archify.json
Normal file
258
docs/fall-sequence.archify.json
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
{
|
||||
"schema_version": 1,
|
||||
"diagram_type": "sequence",
|
||||
"meta": {
|
||||
"title": "낙상 1건 — 감지에서 통보까지",
|
||||
"output": "move-detect-fall-sequence.html",
|
||||
"quality_profile": "showcase",
|
||||
"column_fit": "spread",
|
||||
"views": [
|
||||
{
|
||||
"id": "on-site",
|
||||
"label": "현장에서 끝나는 3초",
|
||||
"focus": [
|
||||
"cam",
|
||||
"edge",
|
||||
"beacon"
|
||||
],
|
||||
"note": "확정부터 점등까지 서버를 거치지 않는다. 회선이 끊겨도 여기까지는 동작한다."
|
||||
},
|
||||
{
|
||||
"id": "what-leaves",
|
||||
"label": "밖으로 나가는 것",
|
||||
"focus": [
|
||||
"edge",
|
||||
"api",
|
||||
"db"
|
||||
],
|
||||
"note": "영상이 아니라 얼굴 블러된 3프레임. 블러는 전송 전, 노드에서."
|
||||
},
|
||||
{
|
||||
"id": "notify-lag",
|
||||
"label": "통보까지의 실제 지연",
|
||||
"focus": [
|
||||
"cron",
|
||||
"db",
|
||||
"staff"
|
||||
],
|
||||
"note": "경광등은 즉시, 메일은 크론 주기만큼 늦다. 그 간격이 이 그림의 요점이다."
|
||||
}
|
||||
],
|
||||
"viewBox": [
|
||||
1060,
|
||||
560
|
||||
]
|
||||
},
|
||||
"participants": [
|
||||
{
|
||||
"id": "cam",
|
||||
"type": "external",
|
||||
"label": "카메라",
|
||||
"sublabel": "12fps"
|
||||
},
|
||||
{
|
||||
"id": "edge",
|
||||
"type": "backend",
|
||||
"label": "엣지 노드",
|
||||
"sublabel": "Pi 5 + Hailo-8"
|
||||
},
|
||||
{
|
||||
"id": "beacon",
|
||||
"type": "external",
|
||||
"label": "경광등",
|
||||
"sublabel": "GPIO"
|
||||
},
|
||||
{
|
||||
"id": "api",
|
||||
"type": "security",
|
||||
"label": "수집 API",
|
||||
"sublabel": "HMAC 검증"
|
||||
},
|
||||
{
|
||||
"id": "db",
|
||||
"type": "database",
|
||||
"label": "MySQL",
|
||||
"sublabel": "move_events"
|
||||
},
|
||||
{
|
||||
"id": "cron",
|
||||
"type": "backend",
|
||||
"label": "1분 크론",
|
||||
"sublabel": "dispatch_alerts"
|
||||
},
|
||||
{
|
||||
"id": "staff",
|
||||
"type": "external",
|
||||
"label": "담당자",
|
||||
"sublabel": "메일 · 웹훅"
|
||||
}
|
||||
],
|
||||
"segments": [
|
||||
{
|
||||
"from": 152,
|
||||
"to": 190,
|
||||
"label": "감지 — 자세 3초"
|
||||
},
|
||||
{
|
||||
"from": 198,
|
||||
"to": 320,
|
||||
"label": "현장 대응 · 기록"
|
||||
},
|
||||
{
|
||||
"from": 330,
|
||||
"to": 366,
|
||||
"label": "미동 없음 13초"
|
||||
},
|
||||
{
|
||||
"from": 374,
|
||||
"to": 466,
|
||||
"label": "통보 — 크론 주기 지연"
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"id": "frames",
|
||||
"from": "cam",
|
||||
"to": "edge",
|
||||
"y": 168,
|
||||
"label": "프레임 · 12fps",
|
||||
"variant": "default",
|
||||
"note": "0.0초 쓰러짐 → 1초 위험 자세 → 3초 확정"
|
||||
},
|
||||
{
|
||||
"id": "gpio",
|
||||
"from": "edge",
|
||||
"to": "beacon",
|
||||
"y": 206,
|
||||
"label": "넘어짐 확정(3초) → GPIO 점등",
|
||||
"variant": "emphasis",
|
||||
"note": "3.0초 · 서버도 인터넷도 거치지 않는다"
|
||||
},
|
||||
{
|
||||
"id": "post",
|
||||
"from": "edge",
|
||||
"to": "api",
|
||||
"y": 244,
|
||||
"label": "HMAC POST · 3프레임",
|
||||
"variant": "security",
|
||||
"note": "얼굴 블러는 전송 전, 노드에서"
|
||||
},
|
||||
{
|
||||
"id": "insert",
|
||||
"from": "api",
|
||||
"to": "db",
|
||||
"y": 276,
|
||||
"label": "INSERT",
|
||||
"variant": "default"
|
||||
},
|
||||
{
|
||||
"id": "accepted",
|
||||
"from": "api",
|
||||
"to": "edge",
|
||||
"y": 306,
|
||||
"label": "202 Accepted",
|
||||
"variant": "return"
|
||||
},
|
||||
{
|
||||
"id": "nomotion",
|
||||
"from": "edge",
|
||||
"to": "api",
|
||||
"y": 344,
|
||||
"label": "미동 없음 · 10초",
|
||||
"variant": "emphasis",
|
||||
"note": "13.0초 — 최상위 위급"
|
||||
},
|
||||
{
|
||||
"id": "poll",
|
||||
"from": "cron",
|
||||
"to": "db",
|
||||
"y": 384,
|
||||
"label": "통보 대상 조회 · 최대 60초 뒤",
|
||||
"variant": "default",
|
||||
"note": "최대 60초 뒤 — 크론이 1분마다 돈다"
|
||||
},
|
||||
{
|
||||
"id": "send",
|
||||
"from": "cron",
|
||||
"to": "staff",
|
||||
"y": 418,
|
||||
"label": "메일 · 웹훅 (링크만)",
|
||||
"variant": "emphasis"
|
||||
},
|
||||
{
|
||||
"id": "escalate",
|
||||
"from": "cron",
|
||||
"to": "db",
|
||||
"y": 452,
|
||||
"label": "5분 미확인 → 상위 확대",
|
||||
"variant": "dashed"
|
||||
}
|
||||
],
|
||||
"activations": [
|
||||
{
|
||||
"participant": "edge",
|
||||
"from": 196,
|
||||
"to": 352,
|
||||
"type": "backend"
|
||||
},
|
||||
{
|
||||
"participant": "beacon",
|
||||
"from": 201,
|
||||
"to": 232,
|
||||
"type": "external"
|
||||
},
|
||||
{
|
||||
"participant": "api",
|
||||
"from": 239,
|
||||
"to": 312,
|
||||
"type": "security"
|
||||
},
|
||||
{
|
||||
"participant": "db",
|
||||
"from": 271,
|
||||
"to": 300,
|
||||
"type": "database"
|
||||
},
|
||||
{
|
||||
"participant": "cron",
|
||||
"from": 379,
|
||||
"to": 458,
|
||||
"type": "backend"
|
||||
},
|
||||
{
|
||||
"participant": "staff",
|
||||
"from": 413,
|
||||
"to": 442,
|
||||
"type": "external"
|
||||
}
|
||||
],
|
||||
"cards": [
|
||||
{
|
||||
"dot": "cyan",
|
||||
"title": "현장에서 끝나는 3초",
|
||||
"items": [
|
||||
"자세가 3초 지속돼야 확정한다",
|
||||
"확정 즉시 GPIO 점등 — 서버 불필요",
|
||||
"서버가 죽어도 여기까지는 동작"
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "rose",
|
||||
"title": "밖으로 나가는 것",
|
||||
"items": [
|
||||
"영상이 아니라 3프레임",
|
||||
"얼굴 블러는 전송 전, 노드에서",
|
||||
"블러 전 이미지는 서버에 없다"
|
||||
]
|
||||
},
|
||||
{
|
||||
"dot": "amber",
|
||||
"title": "두 지연은 다르다",
|
||||
"items": [
|
||||
"경광등은 즉시",
|
||||
"메일은 크론 주기만큼 늦다",
|
||||
"\"1분 통보\"는 대기를 포함한 값"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue