Skip to content

Latest commit

 

History

History
231 lines (189 loc) · 18.4 KB

SPEC.md

File metadata and controls

231 lines (189 loc) · 18.4 KB

재난대응 작전도 시스템 — SPEC (spec-gate 통과본)

  • 상태: spec-gate 통과 (2026-09-07). 다음 단계 = design-gate.
  • 원 브리프: 작전도-CC-핸드오프-v3.md (착수 정본). 이 문서는 그 위에 10절 미결정 6항목 확정과 6절 전제 개정을 얹은 것이다.
  • 규칙: 코딩은 design-gate 통과 후. 11절 구현순서 변경 금지.

A. 가치 명제 (판정 축, 순서=우선순위)

  1. 취합 — 분산된 정보를 하나의 지휘 화면으로. 연동 승인 없이 촬영/수기 입력으로.
  2. 축적 — 현장 데이터를 구조화 자산으로. 사고가 쌓일수록 강해진다.
  3. 근거 — 기억이 아니라 시각 붙은 데이터에 근거한 검토.

판정 기준: 예쁜 화면이 아니라, 취합·축적·근거 중 하나라도 기존 방식으로 불가능한 것을 실제로 만드는가.


B. 10절 미결정 → 확정 (2026-09-07, 사용자 결정)

# 항목 확정 근거
① 스택 FastAPI(파이썬) + 정적 HTML/JS 프론트 + SQLite 로컬 OCR·질의(8절) 최저비용, 태블릿 브라우저만(설치0), 벌집/wherekrx 자산 재사용
② 로컬 서버 형태 노트북=서버 0.0.0.0:PORT, 별도 휴대 트래블 라우터를 AP로, 클라이언트는 브라우저(mDNS *.local)로 접속, 설치 없음 9절 노트북 단일고장점 완화
③ 이동식 스크린 소멸. 현장 대형 스크린 비현실 → 개인 기기(노트북·태블릿) 근거리 판독이 정본. 프로젝터=외부인사 보고 전용, 7단계 이후 작성자(현직·1차 지식) 정정. 2절 원칙상 문서 전제를 이김
④ 촬영 입력 처리 고정 레이아웃 1종 ROI 템플릿 OCR(로컬 Tesseract/PaddleOCR). 값=보고됨→사람 확정. 총시간 이득 미달 시 수기 후퇴(조건부 채택, 11절 4단계) 5.4와 일치
⑤ 지도 2단계는 층·출입구·방면 도식 캔버스 먼저. 오프라인 실지도(MBTiles+Leaflet/MapLibre)는 7단계 10절 시사
⑥ 입력자 역할 상황(기록) 담당 1명이 정본 편집, 나머지 읽기전용. 지금 손 상황판 쓰는 작업을 대체 9절 오프라인 규칙과 일치

C. 6절 전제 개정 (③ 결정의 파급 — 원 브리프를 덮어씀)

  • 6절 물리전제: 이동식 스크린 / 관찰거리 3m / 헬멧 → 개인 기기(노트북·태블릿) 근거리(팔길이~1m) 판독.
  • 6.1 공동/입력 화면 분리는 유효. 단 대형 공유화면 없음 — 각 기기가 공동뷰를 각자 렌더. 노트북=정본 편집, 태블릿=읽기전용 공동뷰.
  • 6.2 시인성: 3m/18~20mm 실측 폐기. → 태블릿 물리크기 기준 근거리 판독으로 재설정. 헬멧·장갑 터치·주광/야간 저휘도 대비는 유지. 글자 시작값은 실측으로 잡는다.
  • 프로젝터 "보고 뷰": 작전 상시 공동화면 아님. 외부보고 전용, 7단계 이후.
  • 6.3(공동화면 급변 금지), 색 규범, 변화표시, 자릿수 고정, 주광/야간 모드 = 그대로 유효.

D. 구현 순서 (브리프 11절, 순서 불변)

  1. 스키마+상태모델 (구조물화재 1종, 4절을 필드정의에 흡수)
  2. 최소 수기 입력 + 공동 화면 (촬영 없이 사건 하나 끝까지) — 근거리 판독 실측 동시
  3. 기록 구조 + 단절·수기 전환
  4. 촬영 입력 (고정화면 1종, 조건부 채택)
  5. 읽기전용 오프라인 공유
  6. 두 종류 재생 + 조건부 체크리스트
  7. 나머지 3유형 스키마 + 실지도 + 보고서 초안 (+프로젝터 보고뷰)

E. 금지 (브리프 12절, 불변)

실사고 데이터·내부망 연동·외부 LLM API키·인터넷 필수기능·외부클라우드 기본저장·자동확정·준수율/진척률 점수 — 전부 금지. 화면에 "개인 제작 검증용 시제품" 책임범위 표기.

F. 완료 판정 (브리프 13절, 시인성만 개정)

  • 기능/가치(취합·유지·축적·근거)/서버 강제종료 60초 수기전환 = 브리프 그대로.
  • 시인성 개정: "3m·헬멧 3초 판독" → 태블릿 근거리 + 장갑 조작으로 핵심 항목 판독, 위험한 오독 시 실패. 야간 저휘도·주광 대비 성립.

G. 2026-09-07 현직 정정 — 한 화면 작전도가 정본이다

근거: 현직 매뉴얼 재난대응 PPT 작전도 활용.pptx 4유형 작전도 + 세부 상황판 (사용자 제공).

  • 뒤집힌 전제: 브리프 3절 "이 목록은 정보 항목 목록이지 한 화면에 동시 표시하라는 제약이 아니다" → 실제로 쓰는 작전도는 한 화면에 전부 있다. 1차 지식이 문서 전제를 이긴다(2절).
  • 정본 화면 = /sheet (작전도 한 화면). 좌(대응상황·대응활동·피해현황·인력장비) / 중(지휘소·무전망 → 지휘 체크리스트 → 작전도 → 하단 박스) / 우(유형전용 → 현장지휘관), 머리에 보차수·제목·대응단계·상태. 4유형이 같은 틀을 쓰고 우측·중하단만 갈린다.
  • 누르면 세부 상황판. 어느 칸이든 눌러 그 블록의 상세를 연다(PPT의 '세부 상황판' 장표). 방면을 누르면 그 방면의 지휘관·임무·자원이 나온다. 딥링크 ?detail=키, ?sector=id.
  • PPT를 이기는 지점은 하나다: 칸마다 근거가 붙는다. 출처·관측/접수/확정 시각·경과·정정이력, 그리고 확인 대기 중인 정정 보고까지 칸이 들고 있다. PPT는 이걸 못 한다.
  • 6.3 급변 금지는 유효 — 배치는 스키마가 고정하고 데이터만 바뀐다.
  • 기존 /board(패널 그리드)는 단절·수기전환 대응 화면으로 남긴다(캐시 확정본·A3 인계). 폐기하지 않는다.
  • 아직 없는 것: 실지도(위성) 자료 → 방면 도식으로 대체 중. 사진 자료 → 자리만 잡아 둠.

H. 2026-09-07 현직 요구 반영 (2차)

  1. 시작 화면(/) — 프로그램을 열면 공장화재·산불·화학사고·실종수색을 먼저 고른다. 유형별 진행 중 사고를 바로 열거나 새 사고를 시작한다. 입력 화면은 /input 으로 옮겼다.
  2. 표본 지역을 전주로 전면 교체 — 전북특별자치도 전주시 덕진구 팔복동 일대(4유형 전부). 무전망·유관기관·지휘소도 전북/전주덕진 기준으로 바꿨다. scripts/seed_sample.py.
  3. 대상물 현황 = 건축물대장 — 첨부된 ㈜전주페이퍼 PM3 공장 대장에서 옮겼다. 연면적 10,708.285㎡ / 건축면적 4,650.73㎡ / 철근콘크리트 지상4층 / 허가 1975.6.4 / 사용승인 1976.12.10 / 내진설계 비적용 / 증축 이력 3건 / 소유자. 내진 비적용은 붕괴위험 판단에 직결되므로 즉시위험 칸에도 함께 적었다.
  4. 지도 = 오프라인 벡터지도 — MapLibre GL + 로컬 PMTiles(전주 일대 z0~16, 2.3MB). 타일·글리프·스프라이트·라이브러리를 전부 opsmap/static/ 에 두어 인터넷 없이 뜬다(12절). 한글 라벨은 내려받은 글리프가 아니라 브라우저 폰트로 그린다(localIdeographFontFamily) — protomaps 폰트에 한글 글리프가 없기 때문이다(✓실측: 해당 범위 파일이 29B 빈 파일). 타일·좌표가 없으면 기존 방면 도식으로 물러난다.
  5. 차량 배치 — 지도 옆 팔레트에 7종(펌프차·물탱크차·사다리차·특수공작차·굴절차·화학차· 구급차). 차량을 고르고 지도를 누르면 그 자리에 놓이고, 놓인 차량은 끌어서 옮긴다. 자동 위치추적이 아니다 — 사람이 놓은 자리이므로 placed_by·placed_at 을 함께 남긴다. 배치는 정본 편집자만 가능하고 나머지 기기는 보기만 한다(구현순서 5와 정합).
  6. 소방용수 — 전북 공공데이터(2017-01-23)에서 화재지점 인접 5개. △미확인: 원자료에 좌표가 없어(종류·번호·주소 3컬럼) 실제 직선거리 순이 아니다. 화면이 그 사실을 스스로 밝힌다. 좌표 포함 자료를 받으면 진짜 거리순으로 바뀐다.
  7. 현장지휘관 5명 — 현장지휘관 소방령(단장) · 팀장 소방경 · 안전담당관 소방위 · 화재조사 정 소방위 / 부 소방교. 계급은 기본값이고 사람이 바꿀 수 있다.

I. 2026-09-07 현직 요구 반영 (3차) — 유형별로 다른 화면

  • 시작 화면의 '새 사고 시작' 제거. 빈 틀만 나오는 경로였다. 사고 등록은 입력·검토 화면에서.
  • 4유형 화면을 갈랐다. 충청북도본부 PPT를 그대로 베끼지 않는다. 우측·중하단 블록과 지도 도구·지도 레이어가 유형마다 다르다.
유형 지도에 그리는 것 전용 블록
공장화재 방어선 · 연소확대범위 · 차량대기선 · 소방용수 반경 대상물 현황(건축물대장) · 소방용수
산불 주불화선(적) · 진화완료선(황) · 방어선(청) = PPT 범례 그대로, 헬기급수지, 비화위험(풍하 부채꼴), 대피장소 진화 · 헬기 운영 · 기상 현재/전망
화학사고 핫존/웜존/콜드존 동심원, 풍하 독성확산 부채꼴, 통제선, 제독소 처리 · 피해거리(CARIS 산정값)
실종수색 수색구역(완료/수색중/미수색 색 구분), 드론 수색범위, 구조견, 최종 목격지점, 도달가능 반경 수색조 편성 · 드론 운영 · 수색 구역 현황

I.1 그리는 방법은 코드가 모른다

스키마의 map_layers 가 draw(point/circle/line/sector)와 색만 알려주고, 렌더러는 그 네 가지를 그릴 뿐이다. map_tools 가 그 유형에서 쓸 수 있는 도구를 정한다. 유형이 늘어도 렌더 코드는 그대로다 — 2층 구조가 지도까지 내려온 것이다. 도형은 spatial_ref.map_basis 의 JSON: {"lat","lon"} · +"r"(원) · {"line":[[lat,lon],…]} · +"bearing","width"(부채꼴).

I.2 바람은 계산해서 그린다

풍향 낱말(북동풍)을 방위각으로 바꾸고 180도 돌려 풍하 방향을 낸다(CARIS 규약: 풍향은 바람이 불어오는 방향). 모르는 낱말이면 None — 지어내지 않는다. 부채꼴 도구가 이 값을 기본값으로 쓴다.

I.3 CARIS는 대체하지 않는다

확산·복사열·과압 계산을 여기서 만들지 않는다. 그건 화학물질안전원 CARIS가 하는 일이고, 근거 없는 자체 모델은 위험한 오독을 만든다. 이 화면은 CARIS가 낸 반경(m)을 옮겨 적고 지도에 그리는 자리다(피해거리(CARIS 산정값) 블록). 출처·산정시각이 다른 칸과 똑같이 붙는다.

I.4 남의 것을 쓴다

지도 MapLibre GL · 타일 Protomaps(OSM) · 도형 계산 turf.js — 전부 이미 있는 것을 로컬로 가져다 썼다. 직접 만든 것은 우리 상황에만 있는 것(작전도 배치, 근거 부착, 편집권, 재생 2종)뿐이다.

I.5 이 과정에서 잡은 함정 (✓실측)

  • 오래된 서버가 포트를 잡고 있어 재시작이 조용히 실패했다. 화면은 옛 코드였고 나는 코드를 의심했다. → 기동 로그(address already in use)를 먼저 본다.
  • 스타일이 다 실리기 전에 도형을 얹으면 소스만 생기고 레이어 추가가 실패한다. 다음 갱신에서 '소스 있음'으로 판정돼 영영 안 그려진다. → isStyleLoaded() 확인 + 레이어 존재까지 확인.
  • 실선에 line-dasharray:[1] 을 넣으면 스타일 검증에서 막혀 그 레이어부터 전부 중단된다. → 실선이면 속성 자체를 넣지 않고, 레이어마다 예외를 가둔다.

J. 2026-09-07 현직 요구 반영 (4차)

  • 주광 → 주간 (주간/야간).
  • 시작 화면의 '새 사고 시작' 제거 — 빈 틀만 나오는 경로였다.
  • 배경을 위성영상으로. Esri World Imagery 타일을 이 지역만 내려받아 로컬에 둔다 (opsmap/static/map/sat, z13~18, 약 27MB). 위성 위에 도로선과 라벨은 남긴다 — "길이 보여야 한다"는 요구가 그것이라 덮개(땅·녹지·물·건물)만 숨긴다. 위성/지도 버튼, 북마크용 ?base=sat|map.
  • 위치를 실측값으로. 전주페이퍼=팔복동2가 180 지오코딩, 홍산=OSM 산봉우리 (35.836164, 127.087128), 휴비스=팔복로 147. 표본은 상대 배치를 유지한 채 기준점만 평행이동한다(relocate()).
  • 차량·도구를 못 쓰던 문제: 편집권이 없는 기기에서는 전부 비활성인데 화면에 그 사실도, 잡을 방법도 없었다. → 상단에 이 기기 이름 + 편집권 잡기 를 두었다. 남의 편집권을 가져갈 때는 사유를 묻는다(§5.3 그대로).
  • 화재지점 옮기기 도구를 모든 유형에 추가 — 위치가 틀리면 사람이 눌러 고친다.

J.1 이 단계에서 잡은 함정 (✓실측)

  • isStyleLoaded() 를 조건으로 쓰면 안 된다. 위성 래스터처럼 타일이 계속 들어오는 소스가 있으면 거의 항상 false 라서 도형이 영영 안 붙는다.
  • load 이벤트는 첫 화면 타일까지 기다린다. 위성이 붙으면서 수십 초 늦어졌고 그 사이 위성 레이어도 도형도 없었다. → styledata 에서 얹는다.
  • 위 둘은 화면을 넓게/좁게 찍을 때 결과가 달라지는 증상으로 나타났다. 창 크기에 따라 타일 수가 달라져 타이밍이 바뀌었기 때문이다.

J.2 못 하는 것 · 필요한 것 (정직하게)

  • 위성영상 구름: Esri 원본이 홍산 일대에서 구름에 덮여 있다. 팔복동(전주페이퍼· 휴비스)은 선명하다. 국내 항공영상(브이월드)이면 해결되지만 API 키가 필요하다 — 받아 주시면 붙인다(무료 발급).
  • 정확한 공장 경계: 전주페이퍼·휴비스는 OSM에 이름이 없어 주소 지오코딩 수준이다 (○추정, 수십 m 오차 가능). 화면에서 화재지점 옮기기 로 고치는 것이 가장 빠르다.
  • 실시간 산불 화선·소방차 위치(AVL) 공개 API는 없다. 화선·방면·인원/장비는 상황실이 그리거나 입력하는 것이 현재 유일한 길이다(외부 조언과 같은 결론).

K. 2026-09-07 현직 요구 반영 (5차)

  • 편집자 이름 = 김소방 (표본 전체).
  • 방면 라벨을 배경 없이 글자만 두었다. 지도를 가리지 않게 흰 테두리(그림자)로 읽힌다. 야간 모드에서는 검은 테두리로 뒤집힌다.
  • 방면·구역을 지도에서 고친다. 편집자는 라벨을 끌어서 위치를 옮기고, 눌러 열린 세부 상황판에서 이름·지휘관·비고·반경을 고치거나 삭제한다. (POST/DELETE /api/sector/{id})
  • 그리기 도구에서 대화상자를 없앴다. 급할 때 이름·반경을 세 번 물으면 못 쓴다. 이제 누르면 바로 그려지고, 이름·반경은 그 뒤에 고친다. 선을 그리는 중에는 찍은 점이 번호와 함께 지도에 보이고 점선으로 이어진다(몇 개 찍었는지 보이지 않던 문제).
  • 헬기 급수지를 실제 물이 있는 자리로. 덕진저수지(35.8474, 127.1211) · 전주천 취수지점 (35.86408, 127.10433) · 기지제(35.84547, 127.06724, OSM 확인). 이 셋은 표본 평행이동 대상에서 뺐다 — 실제 좌표라야 뜻이 있다.
  • 휴비스 대상물 현황을 건축물대장으로. 기린대로 787(팔복동2가 339), 연면적 55,093.945㎡, 건축면적 28,139.035㎡, 철골조 지하1/지상5층, 높이 19.26m, 전용공업지역, 소유자 ㈜휴비스.
  • 화학 화면의 부채꼴을 키우고 강조(풍하 1.2km, 60°, 주황). 원 = 영향 반경(핫·웜·콜드존), 부채꼴 = 풍향을 반영한 확산 방향 — CARIS 화면과 같은 구분이다.

K.1 아직 못 한 것 · 필요한 것

  • 산불 화선 모형(KR-Fireline-3Layer): 지정하신 /home/workdir/artifacts/fireline_model/ 는 이 컴퓨터에 없다(다른 도구의 작업 폴더). fireline_model.py 와 firelines_latest.geojson 을 이 컴퓨터로 옮겨 주시면, 우리 지도는 이미 GeoJSON 의 properties.layer(주불화선/진화완료선/방어선)를 그대로 그릴 수 있게 되어 있다.
  • 휴비스 정확한 부지 좌표: 지오코더가 동 중심만 돌려준다(○추정). 위성이 선명하니 화재지점 옮기기 로 찍어 주시면 그 값으로 확정한다.
  • 브이월드 항공영상 키: 홍산 일대 위성이 구름이다. 키를 주시면 국내 항공영상으로 바꾼다.

L. 2026-09-07 현직 요구 반영 (6차)

  • 차량 라벨도 배경 투명. 방면 라벨과 같은 방식(흰/검은 테두리)으로 지도를 가리지 않는다.
  • 편집자 이름 = 강소방 (표본 전체. 현장지휘관 단장도 소방령 강소방).
  • 산불 화선 모형 연동 (KR-Fireline-3Layer). ~/projects/kr-fireline-3layer 가 계산하고, scripts/import_fireline.py 가 결과 GeoJSON을 작전도로 옮긴다. 확산 계산을 우리가 다시 만들지 않는다 — 화학에서 CARIS를 대하는 방식과 같다(§I.3). 모형의 properties.layer 를 그대로 우리 도형 종류에 대응시킨다: 주불화선 / 진화완료선 / 방어선 / burned→연소추정면.
    • 도형 표현에 polygon 을 추가했다({"polygon":[[lat,lon],…]}). 렌더러는 이제 point/circle/line/sector/polygon 다섯 가지를 안다.
    • 범례 색을 모형 색에 맞췄다(주불화선 #e53935 · 진화완료선 #f9a825 · 방어선 #1e88e5 점선).
    • 우측 '진화' 칸의 숫자도 모형 값으로 갱신한다. 지도와 숫자가 다른 말을 하면 안 된다. 출처는 KR-Fireline-3Layer v1 · 산정시각 · ○추정(지형 미반영) 으로 남는다.
    • 홍산 3시간 결과(표본): 연소면적 216.21ha · 주불화선 8.792km · 진화완료선 3.297km · 진화율 27.3% · 화두 11.33 m/min · 화염길이 3.5m.
    • 재실행: python scripts/import_fireline.py <db> 2 — 모형이 만든 선/면만 갈아끼우고 사람이 그린 도형은 건드리지 않는다.
  • 남은 한계: 경사·사면향은 지형도(DEM)를 안 넣어 ○추정이고, 수관화·비화는 모형에 없다. 소나무림+강풍이면 화선을 과소평가한다 — 모형 제작자의 경고를 그대로 옮긴다.