# -*- 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)
