265 lines
15 KiB
Python
265 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
sources.py — 산출물 생성의 단일 데이터 소스 로더.
|
||
docs/PLANNING.md · docs/IMPLEMENTATION_BACKLOG.md · docs/FEATURE_BACKLOG_100.md · docs/architecture/*
|
||
를 파싱하여 구조화 데이터(모듈·Phase/WBS·기능/요구사항)를 반환한다.
|
||
소스 문서가 갱신되면 _gen 재실행만으로 전 산출물이 갱신된다.
|
||
파싱 실패 시 임베디드 폴백을 사용해 결정적 산출을 보장한다.
|
||
"""
|
||
import os, re
|
||
|
||
DOCS = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) # kintex/docs
|
||
ROOT = os.path.abspath(os.path.join(DOCS, "..")) # kintex
|
||
|
||
META = {
|
||
"project": "킨텍스 자동전시시스템",
|
||
"project_en": "Exhibition Automation Platform",
|
||
"title": "킨텍스 자동전시시스템 구축 사업수행계획서",
|
||
"version": "v1.0",
|
||
"date": "2026-07-11",
|
||
"owner": "지오정보기술(주) — GUARDiA 플랫폼 사업본부",
|
||
"client": "킨텍스(KINTEX, 한국국제전시장)",
|
||
"dev_domain": "kintex.zioinfo.co.kr(개발)",
|
||
"std": "행정안전부 정보화사업 산출물 표준 목차 준용",
|
||
}
|
||
|
||
def _read(rel):
|
||
p = os.path.join(DOCS, rel)
|
||
try:
|
||
with open(p, "r", encoding="utf-8") as f:
|
||
return f.read()
|
||
except Exception:
|
||
return ""
|
||
|
||
def _table_rows(text, min_cols=2):
|
||
"""마크다운 표의 데이터 행(| a | b |)을 셀 리스트로 반환(헤더/구분선 제외)."""
|
||
out = []
|
||
for line in text.splitlines():
|
||
s = line.strip()
|
||
if not s.startswith("|"):
|
||
continue
|
||
if re.match(r"^\|[\s:\-\|]+\|?$", s): # separator
|
||
continue
|
||
cells = [c.strip() for c in s.strip("|").split("|")]
|
||
if len(cells) >= min_cols:
|
||
out.append(cells)
|
||
return out
|
||
|
||
# ---------------- 모듈(M1~M18 + §5B) : PLANNING 기능 우선순위 총괄표 ----------------
|
||
_MOD_FALLBACK = [
|
||
("M2","플로어플랜 스튜디오(부스 배치)","P0"),("M3","부스 설계 스튜디오","P0"),
|
||
("M4","유틸리티 설계 자동화(전기·조명/네트워크·급배수)","P0"),("M5","나노바나나 시각화","P0"),
|
||
("M1","홀 배정·자동 견적","P1"),("M6","서류·마일스톤 워크플로","P1"),
|
||
("M7","등록업체 매칭·견적","P1"),("M9","정산·결제","P1"),
|
||
("M15","공사/장치 옥션(역경매·견적서·낙찰)","P1"),("M10","관람객 등록·배지·체크인·리드캡처","P1"),
|
||
("M12","마케팅·EDM·공개 홍보 사이트","P1"),("M16","경영분석 BI","P1"),
|
||
("M17","CMS(콘텐츠·마이크로사이트·다국어)","P1"),("M18","관리자 백오피스","P1"),
|
||
("§5B","공통/시스템관리 레이어 + JWT+2FA","P1"),
|
||
("M8","반입/반출 물류","P2"),("M11","비즈니스 매칭","P2"),
|
||
("M13","wayfinding·실내 내비","P2"),("M14","현장운영(혼잡·안전·주차·에너지)","P2"),
|
||
]
|
||
def modules():
|
||
text = _read("PLANNING.md")
|
||
mods, seen = [], set()
|
||
for cells in _table_rows(text, min_cols=4):
|
||
mid = cells[0].replace("**","").strip()
|
||
if not re.match(r"^(M\d+|§5B)$", mid):
|
||
continue
|
||
if mid in seen:
|
||
continue
|
||
name = re.sub(r"\*\*|`", "", cells[1]).strip()
|
||
pri = ""
|
||
for c in cells:
|
||
m = re.search(r"P0|P1|P2", c)
|
||
if m: pri = m.group(0); break
|
||
if not name or not pri:
|
||
continue
|
||
seen.add(mid); mods.append((mid, name, pri))
|
||
if len(mods) < 15:
|
||
return list(_MOD_FALLBACK)
|
||
order = {"P0":0,"P1":1,"P2":2}
|
||
mods.sort(key=lambda x: order.get(x[2],9))
|
||
return mods
|
||
|
||
# ---------------- Phase / WBS : IMPLEMENTATION_BACKLOG Phase 표 ----------------
|
||
_PHASE_FALLBACK = [
|
||
("A","아키텍처·거버넌스",[
|
||
("A-1","애플리케이션 아키텍처(모듈 경계·API 표준)","아키텍트(AA)","docs/architecture/app.md"),
|
||
("A-2","시스템 아키텍처·NFR(확장성·HA·성능·보안영역)","아키텍트(SA)","docs/architecture/system.md"),
|
||
("A-3","기술 표준(스택·빌드/배포·관측성·AiTextRouter)","아키텍트(TA)","docs/architecture/tech.md"),
|
||
("A-4","데이터 아키텍처(전사 ERD·공간·마스터·BI마트)","아키텍트(DA)","ERD·data.md"),
|
||
("A-5","네트워크 아키텍처(DMZ/내부망·방화벽·부하분산)","아키텍트(NA)","network.md"),
|
||
("A-6","아키텍처↔PLANNING 정합 검증","reviewer","불일치 0/티켓화"),
|
||
]),
|
||
("B","공통/시스템관리 레이어(전 모듈 선행)",[
|
||
("B-0","스캐폴드(백엔드·프론트·DB·마이그레이션)","BE·FE·DB","빌드·마이그레이션 통과"),
|
||
("B-1","인증 JWT+RBAC+2차인증(OTP)+실패잠금+admin env","COM·BE","2FA 등록/검증/초기화 왕복"),
|
||
("B-2","시스템관리(사용자·역할·공통코드·메뉴·감사·설정)","COM·ADM·DB","CRUD·RBAC 가드·감사"),
|
||
("B-3","공통 업무기능(일지·일정·쪽지·통계·공지·회의·알림)","COM·DB","각 모듈 API 왕복"),
|
||
("B-4","공통 컴포넌트·프론트 공통(2FA·그리드·달력·모달)","COM·FE","아키텍처 표준 정합"),
|
||
("B-5","점진 QA(2FA·RBAC·PII·경계면)","QA","통과까지 반려"),
|
||
]),
|
||
("C","P0 부스 시공 코어",[
|
||
("C-M2","플로어플랜(배치 3안·병합·규정검증 PostGIS)","DB·BE·AI·FE·QA","3안 생성·병합·규정 재검증"),
|
||
("C-M3","부스 설계(조립/독립·규정 사전검증·3안)","BE·AI·FE·QA","위반 사전 플래깅·컨펌"),
|
||
("C-M4","유틸리티 배선(전기/네트워크/급배수·견적·위치표시도)","DB·BE·FE·QA","최단 배선·견적·위치표시도"),
|
||
("C-M5","나노바나나 시각화(RenderJob·워커·워터마크)","BE·VIZ·FE·QA","실생성/미승인 시 목·워터마크"),
|
||
("C-C","역할별 공통 화면(로그인·대시보드·현장 모바일)","FE·QA","역할별 진입·상태 처리"),
|
||
]),
|
||
("D","P1 도메인(역할별 포털)",[
|
||
("D-M15","공사/장치 옥션(견적서 제출·역경매·낙찰)","BID·BE·DB·FE·QA","견적서·역경매·낙찰 왕복"),
|
||
("D-M10","관람객·현장(등록·티켓·배지·체크인·리드)","VIS·BE·DB·FE·QA","등록→배지→체크인→리드"),
|
||
("D-M12/17","마케팅·공개사이트·CMS(SEO·다국어·EDM)","CMS·BE·FE·QA","공개 조회·게시 워크플로"),
|
||
("D-M16","경영분석 BI(가동률·P&L·ROI·수요예측·KPI)","BI·DA·BE·FE·QA","지표 집계·드릴다운"),
|
||
("D-M18","관리자 백오피스(마스터데이터·룰셋 버전)","ADM·BE·FE·QA","RBAC·마스터 CRUD·룰셋"),
|
||
("D-M1/6/7/9","배정·서류·매칭·정산","BE·FE·DB·QA","견적·마일스톤·정산"),
|
||
]),
|
||
("E","P2 + 빌드·배포",[
|
||
("E-M11","비즈니스 매칭(추천·미팅예약)","VIS·AI","추천·미팅예약"),
|
||
("E-M13","wayfinding·실내 내비(M2 재사용)","VIS·FE","부스 검색·경로"),
|
||
("E-M14","현장운영(혼잡·안전·주차·에너지)","BE·FE","대시보드·경보"),
|
||
("E-DEP","역할별 번들·jar·워커 빌드·CI/CD·systemd","DEV","push→배포"),
|
||
]),
|
||
]
|
||
def phases():
|
||
"""[(letter, title, [(id, task, owner, done), ...]), ...]"""
|
||
text = _read("IMPLEMENTATION_BACKLOG.md")
|
||
if not text:
|
||
return _PHASE_FALLBACK
|
||
out = []
|
||
cur = None
|
||
lines = text.splitlines()
|
||
hdr_re = re.compile(r"^##\s+Phase\s+([A-E])\s*[—\-–]\s*(.+)$")
|
||
for line in lines:
|
||
hm = hdr_re.match(line.strip())
|
||
if hm:
|
||
if cur: out.append(cur)
|
||
title = re.sub(r"\([^)]*\)", "", hm.group(2))
|
||
title = re.sub(r"[★((].*$", "", title).strip()
|
||
cur = (hm.group(1), title, [])
|
||
continue
|
||
s = line.strip()
|
||
if cur and s.startswith("|"):
|
||
if re.match(r"^\|[\s:\-\|]+\|?$", s):
|
||
continue
|
||
cells = [c.strip() for c in s.strip("|").split("|")]
|
||
if not cells or cells[0] in ("ID","Phase") or not re.match(r"^[A-E]-", cells[0]):
|
||
continue
|
||
# 표 형태 2종: (ID,작업,담당,완료) 또는 (ID,모듈,작업,담당,완료)
|
||
if len(cells) >= 5:
|
||
_id, task, owner, done = cells[0], cells[2], cells[3], cells[4]
|
||
elif len(cells) == 4:
|
||
_id, task, owner, done = cells[0], cells[1], cells[2], cells[3]
|
||
else:
|
||
continue
|
||
cur[2].append((_id, re.sub(r"\*\*|`","",task), re.sub(r"\*\*","",owner), re.sub(r"\*\*","",done)))
|
||
if cur: out.append(cur)
|
||
ok = [p for p in out if p[2]]
|
||
return ok if len(ok) >= 4 else _PHASE_FALLBACK
|
||
|
||
# 마스터 일정(착수 상대 주차) — 큐레이션(마일스톤)
|
||
SCHEDULE = [
|
||
("A","아키텍처·거버넌스","W1","W3","M1 아키텍처 베이스라인 승인(W3)"),
|
||
("B","공통/시스템관리 레이어","W3","W8","M2 공통 레이어·인증(2FA) 완료(W8)"),
|
||
("C","P0 부스 시공 코어","W8","W16","M3 P0 부스 코어 시연·첫 상용 행사 적용(W16)"),
|
||
("D","P1 도메인","W16","W24","M4 옥션·관람객·BI·관리자 통합(W24)"),
|
||
("E","P2 + 빌드·배포","W24","W28","M5 통합 QA 통과·운영 배포(W28)"),
|
||
]
|
||
|
||
# ---------------- 기능/요구사항 : FEATURE_BACKLOG_100 C1~C12 ----------------
|
||
def features():
|
||
"""[(fid, name, category, priority, ai, module), ...] — 100대 기능(RTM/요구사항 원천)."""
|
||
text = _read("FEATURE_BACKLOG_100.md")
|
||
out, seen = [], set()
|
||
cur_cat = ""
|
||
cat_re = re.compile(r"^###\s+(C\d+)\.\s*(.+?)\s*(?:\(\d+\)|—|$)")
|
||
stop = False
|
||
for line in text.splitlines():
|
||
s = line.strip()
|
||
cm = cat_re.match(s)
|
||
if cm:
|
||
cur_cat = cm.group(1) + " " + re.sub(r"\s*[—-].*$","",cm.group(2)).strip()
|
||
stop = False
|
||
continue
|
||
# C 표가 끝나고 'AI 자동화 맵' 등 다른 섹션이면 중단 표시
|
||
if s.startswith("##") and "카테고리" not in s and not s.startswith("###"):
|
||
stop = True
|
||
if stop:
|
||
continue
|
||
m = re.match(r"^\|\s*(F\d{3})\s*\|(.+)$", s)
|
||
if m and cur_cat:
|
||
fid = m.group(1)
|
||
if fid in seen:
|
||
continue
|
||
cells = [c.strip() for c in m.group(2).strip("|").split("|")]
|
||
name = re.sub(r"\*\*|`","",cells[0]).strip() if cells else fid
|
||
pri = next((re.search(r"P[012]",c).group(0) for c in cells if re.search(r"P[012]",c)), "")
|
||
ai = ""
|
||
for c in cells:
|
||
cc = c.strip()
|
||
if cc in ("Y","N","부분"):
|
||
ai = cc; break
|
||
module = next((re.sub(r"\*\*|`","",c) for c in cells if re.search(r"(M\d+|§5B|§1A|§6)", c)), "")
|
||
seen.add(fid)
|
||
out.append((fid, name, cur_cat, pri, ai, module))
|
||
return out
|
||
|
||
# ---------------- 산출물 목록(표준 단계별) ----------------
|
||
DELIVERABLES = [
|
||
# (단계, 산출물명, 형식, 담당, 상태, 비고)
|
||
("착수(계획)","사업수행계획서","DOCX","PMO","작성완료","본 산출물(gen_docx)"),
|
||
("착수(계획)","WBS 및 마스터 일정","XLSX","PMO","작성완료","gen_xlsx(WBS/일정/투입/산출물)"),
|
||
("착수(계획)","산출물 목록","XLSX","PMO","작성완료","gen_xlsx(산출물목록 시트)"),
|
||
("착수(계획)","착수보고 자료","PPTX","PMO","작성완료","gen_pptx(착수보고)"),
|
||
("분석","요구사항 정의서","DOCX","기획(planner)","작성완료","gen_reqspec(100대 기능 원천)"),
|
||
("분석","요구사항 추적표(RTM)","XLSX","QA/기획","작성완료","gen_xlsx(RTM 시트)"),
|
||
("분석","기획서(PLANNING)","MD","기획(planner)","기보유","docs/PLANNING.md v3.0"),
|
||
("설계","개발계획서","DOCX","PMO/개발리드","작성완료","gen_devplan"),
|
||
("설계","화면설계서·UI 스펙(design)","MD/DOCX","디자이너","기보유","docs/design.md"),
|
||
("설계","아키텍처 설계서(앱/시스템/기술/데이터/네트워크)","MD","아키텍트","기보유","docs/architecture/*"),
|
||
("설계","데이터 모델(전사 ERD·BI 데이터마트)","MD/SQL","DA/DB","진행","설계 트랙"),
|
||
("설계","API 설계서(계약)","MD","AA/백엔드","기보유","docs/API_GUIDE.md"),
|
||
("구현","소스코드(백엔드·프론트·워커)","Git","개발","진행","src/, tools/nanobanana"),
|
||
("구현","DB 스키마·마이그레이션","SQL","DB","진행","Flyway"),
|
||
("시험","시험계획서","DOCX","QA","작성완료","gen_testplan"),
|
||
("시험","시험케이스·시험결과서","XLSX","QA","작성완료","gen_xlsx(시험케이스 시트)"),
|
||
("이관","배포/운영 런북·이관 확인서","MD","DevOps","기보유","docs/OPS_RUNBOOK.md"),
|
||
("교육","사용자 지침서·운영자 지침서","DOCX/PPTX","기술문서","예정","완성+QA 후"),
|
||
("종료","완료(준공) 보고서","DOCX","PMO","예정","사업 종료 시"),
|
||
]
|
||
|
||
# ---------------- 투입 인력 계획(M/M) ----------------
|
||
STAFFING = [
|
||
("사업 PM/PMO","1","특급","A~E 상시","6.5"),
|
||
("아키텍트(AA/SA/TA)","2","특급","A 집중","3.0"),
|
||
("데이터 아키텍트(DA)","1","고급","A·D","2.0"),
|
||
("네트워크 아키텍트(NA)","1","고급","A·E","1.0"),
|
||
("공통/시스템관리 개발","2","중급","B 집중","4.0"),
|
||
("백엔드 개발","3","중·고급","B~D","12.0"),
|
||
("프론트엔드 개발","3","중·고급","B~E","12.0"),
|
||
("DB 엔지니어(PostGIS)","1","고급","B~D","4.5"),
|
||
("AI 개발","1","고급","C~D","3.0"),
|
||
("시각화(나노바나나)","1","고급","C","2.0"),
|
||
("도메인 개발(옥션/관람객/CMS/BI/관리자)","3","중·고급","D 집중","9.0"),
|
||
("QA","2","중급","B~E 상시","8.0"),
|
||
("DevOps","1","고급","B·E","2.5"),
|
||
("디자이너","1","고급","A~E","3.0"),
|
||
]
|
||
|
||
RISKS = [
|
||
("R1","홀 실측 CAD 원본 미확보","배치 정밀도","공개 규격 근사 도면으로 진행, 확보 시 대체"),
|
||
("R2","이미지 생성(외부) 승인 게이트","M5 시각화","미승인 시 목/degraded, 승인 후 라이브"),
|
||
("R3","외부 시스템(작업신고/PG/KT) 연동 미공개","M6/M9/M4b","제출용 파일·릴레이 방식, 협의 후 연동"),
|
||
("R4","등록업체 옥션 참여율·응찰 품질","M15 실효성","등록업체 게이트·AI 자료 공정 비교 유인"),
|
||
("R5","개인정보(리드·배지) 규정 위반","법적 리스크","동의·보존정책·감사·최소 수집"),
|
||
("R6","AI 시각화 오용(계약 서류)","분쟁 리스크","워터마크·고지 강제(UI·응답·PDF)"),
|
||
("R7","공통 레이어 지연","일정","Phase B 인력 선집중·병행 착수"),
|
||
("R8","규정·요율 개정","정합성","룰셋 버전 관리·과거 버전 보존"),
|
||
]
|
||
|
||
if __name__ == "__main__":
|
||
print("modules:", len(modules()))
|
||
ph = phases(); print("phases:", [(p[0], len(p[2])) for p in ph])
|
||
print("features:", len(features()))
|
||
print("sample feature:", features()[0] if features() else None)
|