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
343 lines
6.8 KiB
JSON
343 lines
6.8 KiB
JSON
{
|
|
"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 로 재전송 공격 차단",
|
|
"죽은 노드는 자기가 죽었다고 신고할 수 없다 — 크론이 하트비트의 부재로 판정",
|
|
"통보는 링크만 — 현장 이미지는 메일로 나가지 않는다"
|
|
]
|
|
}
|
|
]
|
|
}
|