- Harness: kintex-mobile-dev agent + kintex-mobile-orchestrator skill (WISE mobile ref, Stitch-first design rule, dual app targets B2B/B2C) - design.md v2.1: full 84-screen inventory (web 51 / admin 10 / public 8 / mobile 15) with Stitch prompts incl. ticketing (SCR-P7/P8, M14/M15) - PLANNING v3.1: unified account + split signup tracks (2FA required for staff, light signup/guest for visitors), one codebase / two app targets - Deliverables: dev plan (21s), user/operator/developer guides (17/14/15s), program spec (44s, 65 programs, 8 flowcharts), DA (DB design 14s + table spec xlsx 35 tables/299 cols) - Benchmark: ticketing-app-benchmark.md (7 apps) -> IMPLEMENTATION_BACKLOG Phase F (14 items) - Stitch: 23 generated screens saved (mobile 10, admin 6, web core 5, ticket 2) - mobile/: Expo scaffold (SDK 51, expo-router, secure store JWT) - frontend: SCR-13~17 QA fixes, icons.tsx, kintexEvents, V10 seed migration - ci/: KINTEX CI logo assets Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
786 lines
41 KiB
Python
786 lines
41 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
킨텍스 자동전시시스템 구축 — 개발계획서 PPTX 생성기
|
||
- 소스: docs/PLANNING.md(v3.0), docs/IMPLEMENTATION_BACKLOG.md, docs/WORK_STATUS.md,
|
||
docs/architecture/*, CLAUDE.md 기술 스택표
|
||
- 디자인: KINTEX Blue #0066B3 / AI Purple #6D4AFF / 중립 그레이 / 맑은 고딕 / 16:9
|
||
- 도식(추진체계도·시스템 구성도·간트)은 python-pptx 도형으로 직접 그림(외부 이미지 금지)
|
||
- 보안 불변: 비밀번호/API키/서버IP/SSH 계정 미기재
|
||
재실행: python gen_dev_plan_pptx.py
|
||
"""
|
||
import os
|
||
from pptx import Presentation
|
||
from pptx.util import Emu, Pt
|
||
from pptx.dml.color import RGBColor
|
||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||
from pptx.enum.shapes import MSO_SHAPE
|
||
from pptx.enum.text import MSO_AUTO_SIZE
|
||
from pptx.oxml.ns import qn
|
||
|
||
# ---------------- 팔레트 ----------------
|
||
BLUE = RGBColor(0x00, 0x66, 0xB3) # KINTEX Blue (primary)
|
||
BLUE_DK = RGBColor(0x00, 0x44, 0x7A) # deep
|
||
BLUE_LT = RGBColor(0xE1, 0xEF, 0xF9) # light tint
|
||
PURPLE = RGBColor(0x6D, 0x4A, 0xFF) # AI accent
|
||
PURPLE_LT = RGBColor(0xEC, 0xE8, 0xFF)
|
||
INK = RGBColor(0x10, 0x18, 0x28) # text
|
||
MUTED = RGBColor(0x66, 0x70, 0x85) # secondary text
|
||
LINE = RGBColor(0xE4, 0xE7, 0xEC) # borders
|
||
BG = RGBColor(0xF9, 0xFA, 0xFB) # page bg
|
||
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
||
GREEN = RGBColor(0x12, 0x9E, 0x63) # 완료
|
||
AMBER = RGBColor(0xE0, 0x8A, 0x00) # 진행
|
||
SLATE = RGBColor(0x98, 0xA2, 0xB3) # 예정
|
||
CARD = RGBColor(0xFF, 0xFF, 0xFF)
|
||
HALL_BG = RGBColor(0xF2, 0xF4, 0xF7)
|
||
|
||
FONT = "맑은 고딕"
|
||
|
||
# 16:9
|
||
EMU_W = 12192000
|
||
EMU_H = 6858000
|
||
|
||
prs = Presentation()
|
||
prs.slide_width = EMU_W
|
||
prs.slide_height = EMU_H
|
||
BLANK = prs.slide_layouts[6]
|
||
|
||
IN = 914400
|
||
def inch(v): return int(v * IN)
|
||
|
||
# ---------------- 헬퍼 ----------------
|
||
def slide():
|
||
return prs.slides.add_slide(BLANK)
|
||
|
||
def _set_fill(shape, color):
|
||
if color is None:
|
||
shape.fill.background()
|
||
else:
|
||
shape.fill.solid()
|
||
shape.fill.fore_color.rgb = color
|
||
|
||
def _no_line(shape):
|
||
shape.line.fill.background()
|
||
|
||
def rect(s, x, y, w, h, fill=None, line=None, line_w=1.0, shape=MSO_SHAPE.RECTANGLE, shadow=False):
|
||
sp = s.shapes.add_shape(shape, inch(x), inch(y), inch(w), inch(h))
|
||
_set_fill(sp, fill)
|
||
if line is None:
|
||
_no_line(sp)
|
||
else:
|
||
sp.line.color.rgb = line
|
||
sp.line.width = Pt(line_w)
|
||
sp.shadow.inherit = False # creates an empty <a:effectLst/>
|
||
if shadow:
|
||
el = sp._element.spPr
|
||
ef = el.find(qn('a:effectLst'))
|
||
if ef is None:
|
||
ef = el.makeelement(qn('a:effectLst'), {})
|
||
el.append(ef)
|
||
sh = ef.makeelement(qn('a:outerShdw'),
|
||
{'blurRad':'90000','dist':'38100','dir':'5400000','rotWithShape':'0'})
|
||
clr = sh.makeelement(qn('a:srgbClr'), {'val':'101828'})
|
||
alp = clr.makeelement(qn('a:alpha'), {'val':'18000'})
|
||
clr.append(alp); sh.append(clr); ef.append(sh)
|
||
return sp
|
||
|
||
def _apply_runs(tf, para, segs, align, default_size, default_color, default_bold):
|
||
para.alignment = align
|
||
first = True
|
||
for seg in segs:
|
||
text = seg.get("t","")
|
||
r = para.add_run() if not first else para.runs[0] if para.runs else para.add_run()
|
||
# ensure a run exists
|
||
if first and not para.runs:
|
||
r = para.add_run()
|
||
r.text = text
|
||
r.font.name = FONT
|
||
r.font.size = Pt(seg.get("size", default_size))
|
||
r.font.bold = seg.get("bold", default_bold)
|
||
r.font.color.rgb = seg.get("color", default_color)
|
||
# set east asian font
|
||
rPr = r._r.get_or_add_rPr()
|
||
ea = rPr.makeelement(qn('a:ea'), {'typeface':FONT})
|
||
rPr.append(ea)
|
||
first = False
|
||
|
||
def text(s, x, y, w, h, content, size=14, color=INK, bold=False, align=PP_ALIGN.LEFT,
|
||
anchor=MSO_ANCHOR.TOP, line_spacing=1.0, space_after=2, wrap=True):
|
||
"""content: str 또는 list[para], para = str 또는 list[seg], seg={'t','size','color','bold'}"""
|
||
tb = s.shapes.add_textbox(inch(x), inch(y), inch(w), inch(h))
|
||
tf = tb.text_frame
|
||
tf.word_wrap = wrap
|
||
tf.vertical_anchor = anchor
|
||
tf.margin_left = 0; tf.margin_right = 0; tf.margin_top = 0; tf.margin_bottom = 0
|
||
if isinstance(content, str):
|
||
content = [content]
|
||
for i, para in enumerate(content):
|
||
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
|
||
p.alignment = align
|
||
p.line_spacing = line_spacing
|
||
p.space_after = Pt(space_after)
|
||
p.space_before = Pt(0)
|
||
if isinstance(para, str):
|
||
segs = [{"t": para}]
|
||
elif isinstance(para, dict):
|
||
segs = [para]
|
||
else:
|
||
segs = para
|
||
for seg in segs:
|
||
r = p.add_run()
|
||
r.text = seg.get("t","")
|
||
r.font.name = FONT
|
||
r.font.size = Pt(seg.get("size", size))
|
||
r.font.bold = seg.get("bold", bold)
|
||
r.font.color.rgb = seg.get("color", color)
|
||
rPr = r._r.get_or_add_rPr()
|
||
ea = rPr.makeelement(qn('a:ea'), {'typeface':FONT})
|
||
rPr.append(ea)
|
||
return tb
|
||
|
||
def page_bg(s, color=BG):
|
||
rect(s, -0.1, -0.1, 13.53, 7.7, fill=color)
|
||
|
||
PAGE = {"n": 0}
|
||
def content_header(s, kicker, title, idx):
|
||
"""본문 슬라이드 공통 헤더 + 푸터"""
|
||
page_bg(s, BG)
|
||
# top accent bar
|
||
rect(s, 0, 0, 13.333, 0.12, fill=BLUE)
|
||
# section number chip
|
||
rect(s, 0.55, 0.5, 0.62, 0.62, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.55, 0.5, 0.62, 0.62, f"{idx:02d}", size=20, color=WHITE, bold=True,
|
||
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 1.32, 0.5, 10.5, 0.28, kicker, size=11, color=PURPLE, bold=True)
|
||
text(s, 1.32, 0.74, 11.4, 0.44, title, size=22, color=INK, bold=True)
|
||
rect(s, 0.55, 1.28, 12.23, 0.02, fill=LINE)
|
||
# footer
|
||
text(s, 0.55, 7.06, 8, 0.3, "킨텍스 자동전시시스템 구축 · 개발계획서", size=8.5, color=MUTED)
|
||
text(s, 11.3, 7.06, 1.48, 0.3, "2026-07-11", size=8.5, color=MUTED, align=PP_ALIGN.RIGHT)
|
||
|
||
def chip(s, x, y, w, h, label, fill, txtcolor=WHITE, size=9.5, bold=True):
|
||
rect(s, x, y, w, h, fill=fill, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x, y, w, h, label, size=size, color=txtcolor, bold=bold,
|
||
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
def card(s, x, y, w, h, fill=CARD, line=LINE):
|
||
return rect(s, x, y, w, h, fill=fill, line=line, line_w=1.0,
|
||
shape=MSO_SHAPE.ROUNDED_RECTANGLE, shadow=True)
|
||
|
||
# ================================================================
|
||
# 1. 표지
|
||
# ================================================================
|
||
s = slide()
|
||
rect(s, -0.1, -0.1, 13.53, 7.7, fill=BLUE_DK)
|
||
# 대각 그라디언트 느낌: 겹치는 반투명 도형
|
||
rect(s, 6.4, -1.5, 9, 11, fill=BLUE, shape=MSO_SHAPE.PARALLELOGRAM)
|
||
rect(s, 9.2, -2.0, 7, 12, fill=PURPLE, shape=MSO_SHAPE.PARALLELOGRAM)
|
||
# 좌상 브랜드
|
||
rect(s, 0.85, 0.8, 0.42, 0.42, fill=WHITE, shape=MSO_SHAPE.OVAL)
|
||
rect(s, 0.97, 0.92, 0.18, 0.18, fill=PURPLE, shape=MSO_SHAPE.OVAL)
|
||
text(s, 1.42, 0.78, 6, 0.46, "KINTEX · WISE AI", size=15, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
# 문서 종류 배지
|
||
chip(s, 0.85, 2.35, 1.75, 0.42, "개발계획서", PURPLE, WHITE, size=12)
|
||
chip(s, 2.72, 2.35, 2.1, 0.42, "Development Plan", RGBColor(0x2B,0x4C,0x7E), WHITE, size=10.5)
|
||
# 타이틀
|
||
text(s, 0.85, 2.95, 11, 1.9, [
|
||
[{"t":"킨텍스 자동전시시스템", "size":46, "color":WHITE, "bold":True}],
|
||
[{"t":"구축 개발계획서", "size":46, "color":WHITE, "bold":True}],
|
||
], line_spacing=1.02)
|
||
text(s, 0.88, 4.78, 11, 0.5,
|
||
"Exhibition Automation Platform — 다중 전시관 멀티테넌트 SaaS",
|
||
size=16, color=RGBColor(0xC7,0xDD,0xF2), bold=False)
|
||
# 폐루프 한 줄
|
||
text(s, 0.88, 5.35, 11.4, 0.4,
|
||
"판매 → AI 설계·시각화 → 공사 옥션 → 시공 → 운영 → 경영분석 폐루프 자동화",
|
||
size=12.5, color=RGBColor(0xB9,0xC7,0xEA))
|
||
# 하단 메타 바
|
||
rect(s, 0.85, 6.15, 11.63, 0.02, fill=RGBColor(0x3A,0x5A,0x8C))
|
||
text(s, 0.85, 6.35, 8, 0.4, [
|
||
[{"t":"사업명 ","size":11,"color":RGBColor(0x8FA,0x00,0x00) if False else RGBColor(0x90,0xA6,0xC9)},
|
||
{"t":"킨텍스 자동전시시스템 구축","size":11.5,"color":WHITE,"bold":True}],
|
||
])
|
||
text(s, 0.85, 6.72, 9, 0.4, [
|
||
[{"t":"기술스택 ","size":11,"color":RGBColor(0x90,0xA6,0xC9)},
|
||
{"t":"React(Vite)+TS · Spring Boot 3.x(Java17)+MyBatis · PostgreSQL(PostGIS) · Redis · 나노바나나 워커 · Claude AI","size":10.5,"color":WHITE}],
|
||
])
|
||
text(s, 9.7, 6.35, 2.78, 0.4, "작성일 2026-07-11", size=11, color=WHITE, bold=True, align=PP_ALIGN.RIGHT)
|
||
text(s, 9.7, 6.72, 2.78, 0.4, "planner · designer · dev team", size=9.5, color=RGBColor(0x90,0xA6,0xC9), align=PP_ALIGN.RIGHT)
|
||
|
||
# ================================================================
|
||
# 2. 목차
|
||
# ================================================================
|
||
s = slide()
|
||
content_header(s, "CONTENTS", "목차", 0)
|
||
# 위 헤더의 00 chip을 목차용으로 덮기
|
||
rect(s, 0.55, 0.5, 0.62, 0.62, fill=PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.55, 0.5, 0.62, 0.62, "C", size=22, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
toc = [
|
||
("01", "사업 개요", "배경·목적·범위 / 전시 생애주기 폐루프"),
|
||
("02", "시스템 구성", "아키텍처 개요 / 역할별 포털 / 나노바나나 파이프라인"),
|
||
("03", "개발 범위", "모듈 M1~M18 + 공통·시스템관리 레이어(§5B)"),
|
||
("04", "추진 체계", "거버넌스·아키텍트·개발·QA·DevOps 조직"),
|
||
("05", "추진 일정", "Phase A~E 마스터 일정(완료/진행/예정)"),
|
||
("06", "개발 방법론·품질관리", "에이전트 파이프라인 · 점진 QA · CI/CD"),
|
||
("07", "보안 · 비기능요건(NFR)", "외부API 통제 · AI 워터마크 · 접근성·다국어"),
|
||
("08", "리스크 및 대응", "기술·데이터·연동·보안 리스크"),
|
||
("09", "기대 효과", "정량·정성 효과 / 확장 로드맵"),
|
||
]
|
||
col_w = 5.85
|
||
for i, (no, t, d) in enumerate(toc):
|
||
col = i // 5
|
||
row = i % 5
|
||
x = 0.7 + col * (col_w + 0.35)
|
||
y = 1.62 + row * 1.02
|
||
rect(s, x, y, col_w, 0.88, fill=CARD, line=LINE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
rect(s, x, y, 0.09, 0.88, fill=BLUE if col==0 else PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x+0.28, y, 0.9, 0.88, no, size=24, color=(BLUE if col==0 else PURPLE), bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, x+1.15, y+0.15, col_w-1.3, 0.35, t, size=14.5, color=INK, bold=True)
|
||
text(s, x+1.15, y+0.5, col_w-1.3, 0.32, d, size=9.5, color=MUTED)
|
||
|
||
# ================================================================
|
||
# 섹션 간지 헬퍼
|
||
# ================================================================
|
||
def divider(num, kr, en, points):
|
||
s = slide()
|
||
rect(s, -0.1, -0.1, 13.53, 7.7, fill=BLUE_DK)
|
||
rect(s, 8.6, -1.5, 8, 11, fill=BLUE, shape=MSO_SHAPE.PARALLELOGRAM)
|
||
rect(s, 10.9, -2, 6, 12, fill=PURPLE, shape=MSO_SHAPE.PARALLELOGRAM)
|
||
text(s, 1.0, 2.15, 5, 2.2, num, size=150, color=RGBColor(0x2C,0x4E,0x82), bold=True)
|
||
rect(s, 1.15, 3.9, 0.9, 0.09, fill=PURPLE)
|
||
text(s, 1.15, 4.05, 8, 0.8, kr, size=34, color=WHITE, bold=True)
|
||
text(s, 1.18, 4.85, 8, 0.4, en, size=13, color=RGBColor(0x9C,0xB4,0xD8), bold=True)
|
||
# 우측 포인트
|
||
for i, p in enumerate(points):
|
||
yy = 2.35 + i*0.62
|
||
rect(s, 8.05, yy+0.06, 0.16, 0.16, fill=PURPLE, shape=MSO_SHAPE.OVAL)
|
||
text(s, 8.35, yy, 4.4, 0.5, p, size=12.5, color=WHITE, anchor=MSO_ANCHOR.MIDDLE)
|
||
return s
|
||
|
||
# ================================================================
|
||
# 3. 사업 개요 (간지 + 본문)
|
||
# ================================================================
|
||
divider("01", "사업 개요", "PROJECT OVERVIEW",
|
||
["배경 · 목적 · 범위", "전시 생애주기 폐루프", "다중 전시관 SaaS 비전"])
|
||
|
||
s = slide()
|
||
content_header(s, "01 · 사업 개요", "배경 · 목적 · 추진 범위", 1)
|
||
# 좌: 배경/목적
|
||
card(s, 0.55, 1.5, 6.05, 2.55)
|
||
text(s, 0.85, 1.7, 5.5, 0.35, "추진 배경 (As-Is)", size=13, color=BLUE, bold=True)
|
||
for i, t in enumerate([
|
||
"전시면적 108,011㎡ 국내 최대 전시장이나 준비 실무는 HWP 서식·이메일 제출에 정체",
|
||
"CAD 수작업 배치도 → 홀매니저 육안 검수, 수기 위치표시도 기반 유틸리티 신청",
|
||
"참가업체 유틸리티 신청이 행사별 사무국 시스템으로 파편화(공통 플랫폼 부재)",
|
||
"시공 결과를 개장일에 처음 확인 — 사전 시각화 수단 없음",
|
||
]):
|
||
y = 2.1 + i*0.46
|
||
rect(s, 0.9, y+0.07, 0.1, 0.1, fill=MUTED, shape=MSO_SHAPE.OVAL)
|
||
text(s, 1.12, y, 5.25, 0.44, t, size=10.5, color=INK, line_spacing=1.0)
|
||
|
||
card(s, 6.75, 1.5, 6.03, 2.55)
|
||
text(s, 7.05, 1.7, 5.5, 0.35, "추진 목적 (To-Be)", size=13, color=PURPLE, bold=True)
|
||
for i, t in enumerate([
|
||
"전시장 운영 전 과정을 AI로 자동 설계·검증하는 베뉴 운영 플랫폼 구축",
|
||
"핵심 차별화: 나노바나나(Gemini)로 시공 전 '공사 후 결과 사진' 선(先)시각화",
|
||
"설계 → 시각화 → 발주(옥션) → 시공 → 운영 → 분석의 폐루프 완성",
|
||
"KINTEX 기준 테넌트 + 코엑스 등 다중 전시관 멀티테넌트 SaaS 확장",
|
||
]):
|
||
y = 2.1 + i*0.46
|
||
rect(s, 7.1, y+0.07, 0.1, 0.1, fill=PURPLE, shape=MSO_SHAPE.OVAL)
|
||
text(s, 7.32, y, 5.25, 0.44, t, size=10.5, color=INK, line_spacing=1.0)
|
||
|
||
# 하단: 비전 배너 + 폐루프
|
||
rect(s, 0.55, 4.28, 12.23, 0.62, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.85, 4.28, 11.6, 0.62,
|
||
"비전 “신청서를 내는 순간, 시공 후 사진을 먼저 본다”",
|
||
size=17, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
text(s, 0.55, 5.12, 6, 0.3, "전시 생애주기 폐루프", size=12, color=INK, bold=True)
|
||
loop = [("판매·기획", BLUE, "M1·M16"), ("AI 설계·시각화", PURPLE, "M2~M5"),
|
||
("공사 옥션", BLUE, "M15"), ("시공", BLUE, "M7·M8"),
|
||
("운영", BLUE, "M10·M14"), ("경영분석", PURPLE, "M16")]
|
||
bw = 1.83; gap = 0.19; x0 = 0.55; yb = 5.5
|
||
for i, (lb, cl, mod) in enumerate(loop):
|
||
x = x0 + i*(bw+gap)
|
||
rect(s, x, yb, bw, 0.78, fill=(PURPLE_LT if cl==PURPLE else BLUE_LT), line=cl, line_w=1.2, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x, yb+0.1, bw, 0.34, lb, size=11.5, color=(PURPLE if cl==PURPLE else BLUE_DK), bold=True, align=PP_ALIGN.CENTER)
|
||
text(s, x, yb+0.44, bw, 0.28, mod, size=9, color=MUTED, align=PP_ALIGN.CENTER)
|
||
if i < len(loop)-1:
|
||
rect(s, x+bw+0.01, yb+0.28, gap+0.02, 0.22, fill=None)
|
||
text(s, x+bw-0.03, yb+0.16, 0.28, 0.5, "→", size=15, color=SLATE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
# ================================================================
|
||
# 4. 시스템 구성 (간지 + 아키텍처 도식)
|
||
# ================================================================
|
||
divider("02", "시스템 구성", "SYSTEM ARCHITECTURE",
|
||
["3-Tier + 나노바나나 사이드카", "역할별 6개 포털 분리", "PostGIS 공간 데이터 모델"])
|
||
|
||
s = slide()
|
||
content_header(s, "02 · 시스템 구성", "전체 아키텍처 개요도", 2)
|
||
|
||
def band(y, h, fill, line, label, labelcolor):
|
||
rect(s, 0.55, y, 12.23, h, fill=fill, line=line, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
rect(s, 0.55, y, 1.55, h, fill=line, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.55, y, 1.55, h, label, size=10.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
def node(x, y, w, h, title, sub, fill, tcol, line=None):
|
||
rect(s, x, y, w, h, fill=fill, line=(line or LINE), line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x, y+0.08, w, 0.3, title, size=10, color=tcol, bold=True, align=PP_ALIGN.CENTER)
|
||
if sub:
|
||
text(s, x, y+0.36, w, 0.24, sub, size=7.8, color=MUTED, align=PP_ALIGN.CENTER)
|
||
|
||
# Presentation tier — 포털
|
||
band(1.5, 0.86, BLUE_LT, BLUE, "프론트\n(React)", WHITE)
|
||
ports = ["주최자 콘솔","참가업체 포털","업체 포털","운영 대시보드","관리자 백오피스","공개사이트·앱"]
|
||
pw = 1.62
|
||
for i,p in enumerate(ports):
|
||
node(2.25+i*1.72, 1.66, pw, 0.54, p, "", WHITE, INK, BLUE)
|
||
|
||
# API tier
|
||
band(2.55, 0.9, WHITE, BLUE_DK, "백엔드\n(Spring)", WHITE)
|
||
node(2.25, 2.72, 2.7, 0.56, "REST + WebSocket(STOMP)", "JWT·RBAC·2FA(OTP)", WHITE, INK, BLUE)
|
||
node(5.1, 2.72, 2.4, 0.56, "룰 엔진 / 배치·배선 엔진", "요율·규정·PostGIS 연산", WHITE, INK, BLUE)
|
||
node(7.65, 2.72, 2.2, 0.56, "AiTextRouter", "Claude 기본·설정형 전환", WHITE, PURPLE, PURPLE)
|
||
node(10.0, 2.72, 2.65, 0.56, "RenderJob 큐 발행", "Redis 비동기", WHITE, INK, BLUE)
|
||
|
||
# AI / 워커 tier
|
||
band(3.62, 0.9, PURPLE_LT, PURPLE, "AI·시각화", WHITE)
|
||
node(2.25, 3.79, 4.15, 0.56, "나노바나나 Python 워커 (사이드카)", "google-genai · gemini-3.1-flash-image-preview", PURPLE_LT, PURPLE, PURPLE)
|
||
node(6.55, 3.79, 3.0, 0.56, "Claude API (AI 지능)", "설계·검수·예측 · Ollama 폴백", PURPLE_LT, PURPLE, PURPLE)
|
||
node(9.72, 3.79, 2.93, 0.56, "WebSocket 완료 푸시", "렌더 결과 실시간 통지", WHITE, INK, PURPLE)
|
||
|
||
# Data tier
|
||
band(4.69, 0.9, HALL_BG, BLUE_DK, "데이터", WHITE)
|
||
node(2.25, 4.86, 3.3, 0.56, "PostgreSQL + PostGIS", "부스 폴리곤·트렌치·배선 LineString", WHITE, INK, BLUE_DK)
|
||
node(5.7, 4.86, 2.4, 0.56, "Redis", "작업 큐·세션·캐시", WHITE, INK, BLUE_DK)
|
||
node(8.25, 4.86, 2.05, 0.56, "오브젝트 스토리지", "렌더 이미지·문서", WHITE, INK, BLUE_DK)
|
||
node(10.45, 4.86, 2.2, 0.56, "감사로그·마스터", "룰셋·요율 버전", WHITE, INK, BLUE_DK)
|
||
|
||
# 외부 연동
|
||
text(s, 0.55, 5.86, 12, 0.3, "외부·기존 시스템 연동", size=10.5, color=INK, bold=True)
|
||
ext = ["kxwp 작업신고","등록업체 DB 739","행사일정 시스템","PG 결제","Gemini","사이니지/wayfinding"]
|
||
for i,e in enumerate(ext):
|
||
node(0.55+i*2.06, 6.16, 1.96, 0.5, e, "", WHITE, MUTED, LINE)
|
||
|
||
# ================================================================
|
||
# 5. 개발 범위 (모듈 표)
|
||
# ================================================================
|
||
divider("03", "개발 범위", "DEVELOPMENT SCOPE",
|
||
["코어 M2~M5 (P0)", "도메인 M1·M6~M18", "공통·시스템관리 §5B"])
|
||
|
||
s = slide()
|
||
content_header(s, "03 · 개발 범위", "모듈 맵 M1 ~ M18", 3)
|
||
|
||
def module_card(x, y, w, h, code, name, pr, cl):
|
||
rect(s, x, y, w, h, fill=CARD, line=LINE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
rect(s, x, y, w, 0.07, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x+0.14, y+0.14, w-0.28, 0.26, [[{"t":code+" ","size":11,"color":cl,"bold":True},
|
||
{"t":pr,"size":8,"color":WHITE,"bold":True}]], )
|
||
# priority chip
|
||
rect(s, x+w-0.72, y+0.13, 0.58, 0.24, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x+w-0.72, y+0.13, 0.58, 0.24, pr, size=8, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, x+0.14, y+0.4, w-0.28, 0.5, name, size=9.2, color=INK, line_spacing=0.95)
|
||
|
||
# 계열별 컬럼
|
||
groups = [
|
||
("설계·시각화 코어", PURPLE, [
|
||
("M2","플로어플랜 스튜디오","P0"),
|
||
("M3","부스 설계 스튜디오","P0"),
|
||
("M4","유틸리티 설계(전기·조명·네트워크)","P0"),
|
||
("M5","나노바나나 시각화","P0"),
|
||
]),
|
||
("판매·발주·정산", BLUE, [
|
||
("M1","홀 배정·자동 견적","P1"),
|
||
("M6","서류·마일스톤 워크플로","P1"),
|
||
("M7","등록업체 매칭·견적","P1"),
|
||
("M15","공사/장치 옥션(역경매)","P1"),
|
||
("M8·M9","반입/반출 물류 · 정산·결제","P1·P2"),
|
||
]),
|
||
("관람·참가·마케팅", BLUE, [
|
||
("M10","관람객 등록·배지·리드캡처","P1"),
|
||
("M12","마케팅·공개 홍보 사이트","P1"),
|
||
("M11·M13","비즈매칭 · wayfinding","P2"),
|
||
("M14","현장운영(혼잡·안전·에너지)","P2"),
|
||
]),
|
||
("경영·콘텐츠·관리", PURPLE, [
|
||
("M16","경영분석 BI(P&L·가동률·예측)","P1"),
|
||
("M17","CMS·마이크로사이트·다국어","P1"),
|
||
("M18","관리자 백오피스·마스터데이터","P1"),
|
||
]),
|
||
]
|
||
cw = 2.94; gap = 0.16; x0 = 0.55
|
||
for gi,(gname, gcolor, mods) in enumerate(groups):
|
||
x = x0 + gi*(cw+gap)
|
||
rect(s, x, 1.5, cw, 0.44, fill=gcolor, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x, 1.5, cw, 0.44, gname, size=11, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
for mi,(code,name,pr) in enumerate(mods):
|
||
y = 2.06 + mi*0.9
|
||
module_card(x, y, cw, 0.82, code, name, pr, gcolor)
|
||
|
||
# 범례
|
||
text(s, 0.55, 6.62, 12, 0.3, [[
|
||
{"t":"P0 ","size":9.5,"color":PURPLE,"bold":True},{"t":"핵심 차별화·첫 상용 필수 ","size":9.5,"color":MUTED},
|
||
{"t":"P1 ","size":9.5,"color":BLUE,"bold":True},{"t":"운영 효율 핵심 ","size":9.5,"color":MUTED},
|
||
{"t":"P2 ","size":9.5,"color":SLATE,"bold":True},{"t":"확장 │ 총 18개 도메인 모듈 + 공통·시스템관리 레이어(§5B)","size":9.5,"color":MUTED},
|
||
]])
|
||
|
||
# 개발 범위 2 — 공통 레이어 §5B
|
||
s = slide()
|
||
content_header(s, "03 · 개발 범위", "공통 · 시스템관리 레이어 (§5B · UIWS/WISE 표준 이식)", 3)
|
||
text(s, 0.55, 1.45, 12.2, 0.4,
|
||
"GUARDiA 표준 프레임워크(UIWS)와 동일 스택 — 시스템관리·공통 업무기능·인증을 재설계 없이 이식하여 전 도메인 모듈의 선행 기반으로 정착",
|
||
size=11, color=MUTED)
|
||
# 3개 카드 영역
|
||
def scard(x, y, w, h, title, tcolor, items):
|
||
card(s, x, y, w, h)
|
||
rect(s, x, y, w, 0.5, fill=tcolor, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x, y, w, 0.5, title, size=12.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
for i,it in enumerate(items):
|
||
yy = y+0.66+i*0.4
|
||
rect(s, x+0.2, yy+0.07, 0.09, 0.09, fill=tcolor, shape=MSO_SHAPE.OVAL)
|
||
text(s, x+0.4, yy, w-0.55, 0.38, it, size=9.6, color=INK, line_spacing=0.95)
|
||
|
||
scard(0.55, 2.0, 3.95, 4.35, "시스템관리 (M18 통합)", BLUE, [
|
||
"사용자 관리 (6역할 + 등록업체·관람객 계정)",
|
||
"역할/권한 RBAC (플랫폼·행사 이중 평가)",
|
||
"공통코드 (홀·부스유형·공종14분류·요금)",
|
||
"메뉴 관리 (역할별 포털 IA 구동)",
|
||
"감사로그 (승인·낙찰·룰셋개정·리드접근)",
|
||
"시스템설정 (요율/규정 룰셋 버전)",
|
||
"킨텍스 마스터데이터 (홀·요율·규정·업체)",
|
||
])
|
||
scard(4.65, 2.0, 3.95, 4.35, "공통 업무 기능", PURPLE, [
|
||
"worklog 업무일지 · schedule 일정",
|
||
"message 쪽지 · notice 공지",
|
||
"opinion 의견 · search 통합검색",
|
||
"meeting 회의록(STT→Jasper PDF)",
|
||
"report/stats 업무보고·통계(PDF)",
|
||
"notification 알림센터(WebSocket)",
|
||
"preference 개인화(즐겨찾기·최근방문)",
|
||
])
|
||
scard(8.75, 2.0, 4.03, 4.35, "인증 (JWT + 2FA/OTP)", BLUE_DK, [
|
||
"JWT + RBAC 역할 게이트",
|
||
"2차 인증 OTP (TOTP RFC6238)",
|
||
"최초 QR 등록 · 재설정 · 관리자 초기화",
|
||
"로그인 실패 잠금 + 관리자 해제",
|
||
"admin 비번 env 주입 (AES-256-GCM)",
|
||
"admin123 하드코딩 시드 금지",
|
||
"TB_USER·OTP·TB_AUDIT_LOG 멱등 이식",
|
||
])
|
||
|
||
# ================================================================
|
||
# 6. 추진 체계 (조직도)
|
||
# ================================================================
|
||
divider("04", "추진 체계", "PROJECT ORGANIZATION",
|
||
["거버넌스 (PM·개발PM·PMO)", "아키텍트 5인 (AA/SA/TA/DA/NA)", "구현·품질·배포 팀"])
|
||
|
||
s = slide()
|
||
content_header(s, "04 · 추진 체계", "프로젝트 조직 및 역할", 4)
|
||
|
||
def org(x, y, w, h, title, sub, fill, tcol, line=None, big=False):
|
||
rect(s, x, y, w, h, fill=fill, line=(line or fill), line_w=1.2, shape=MSO_SHAPE.ROUNDED_RECTANGLE, shadow=big)
|
||
text(s, x, y+(0.1 if sub else 0), w, (0.32 if sub else h), title, size=(12 if big else 10.5),
|
||
color=tcol, bold=True, align=PP_ALIGN.CENTER, anchor=(MSO_ANCHOR.TOP if sub else MSO_ANCHOR.MIDDLE))
|
||
if sub:
|
||
subcol = MUTED if fill == WHITE else WHITE
|
||
text(s, x, y+0.36, w, h-0.4, sub, size=8, color=subcol, align=PP_ALIGN.CENTER)
|
||
|
||
# 최상위 거버넌스
|
||
org(4.9, 1.5, 3.55, 0.72, "프로젝트 총괄 (PM)", "범위·일정·리스크·게이트(G1·G2)", BLUE, WHITE, big=True)
|
||
# 연결선
|
||
rect(s, 6.66, 2.22, 0.02, 0.3, fill=SLATE)
|
||
# 2단: 개발PM / PMO
|
||
org(2.95, 2.55, 3.35, 0.66, "개발 PM", "WBS·스프린트·번다운·품질게이트", PURPLE, WHITE)
|
||
org(7.05, 2.55, 3.35, 0.66, "PMO", "관리표준·형상관리·QA 거버넌스·변경관리", PURPLE, WHITE)
|
||
rect(s, 6.66, 2.52, 0.02, 0.16, fill=SLATE)
|
||
|
||
# 3단: 아키텍트 그룹 + 구현 그룹
|
||
rect(s, 0.55, 3.5, 12.23, 0.4, fill=BLUE_LT, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.7, 3.5, 6, 0.4, "아키텍트 그룹", size=11, color=BLUE_DK, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
arch = [("AA","앱 아키텍처"),("SA","시스템·NFR"),("TA","기술표준"),("DA","데이터·ERD"),("NA","네트워크·보안")]
|
||
for i,(a,d) in enumerate(arch):
|
||
org(0.55+i*2.46, 4.0, 2.32, 0.66, a, d, WHITE, BLUE, BLUE)
|
||
|
||
rect(s, 0.55, 4.86, 12.23, 0.4, fill=PURPLE_LT, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.7, 4.86, 6, 0.4, "구현 그룹", size=11, color=PURPLE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
impl = [("공통/시스템","common-dev"),("백엔드","backend-dev"),("프론트","frontend-dev"),
|
||
("DB","db-engineer"),("도메인","bidding·visitor\ncms·bi·admin"),("AI·시각화","ai-dev·visualizer")]
|
||
for i,(a,d) in enumerate(impl):
|
||
org(0.55+i*2.05, 5.36, 1.93, 0.72, a, d, WHITE, PURPLE, PURPLE)
|
||
|
||
# 하단: 품질·배포
|
||
org(3.4, 6.2, 3.0, 0.66, "품질 (kintex-qa)", "점진 QA·경계면·보안 불변", GREEN, WHITE)
|
||
org(6.95, 6.2, 3.0, 0.66, "배포 (devops-dev)", "CI/CD·systemd·env", RGBColor(0x2B,0x4C,0x7E), WHITE)
|
||
|
||
# ================================================================
|
||
# 7. 추진 일정 (간트)
|
||
# ================================================================
|
||
divider("05", "추진 일정", "PROJECT SCHEDULE",
|
||
["Phase A~E 마스터 일정", "완료 / 진행 / 예정 구분", "공통 레이어 선행 원칙"])
|
||
|
||
s = slide()
|
||
content_header(s, "05 · 추진 일정", "Phase A ~ E 마스터 일정 (WORK_STATUS 반영)", 5)
|
||
|
||
# 상태 범례
|
||
for i,(lb,cl) in enumerate([("완료",GREEN),("진행",AMBER),("예정",SLATE)]):
|
||
rect(s, 9.8+i*1.0, 1.42, 0.24, 0.24, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 10.08+i*1.0, 1.4, 0.9, 0.28, lb, size=9.5, color=INK, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
# 간트 좌표계
|
||
gx = 3.55 # 바 시작 x
|
||
gw = 9.15 # 바 영역 폭
|
||
units = 10 # 타임 단위
|
||
uw = gw/units
|
||
# 타임 눈금 라벨
|
||
for i in range(units+1):
|
||
text(s, gx+i*uw-0.4, 1.72, 0.8, 0.24, f"T{i}", size=7.5, color=SLATE, align=PP_ALIGN.CENTER)
|
||
rect(s, gx+i*uw, 1.98, 0.008, 4.2, fill=RGBColor(0xF0,0xF1,0xF4))
|
||
|
||
rows = [
|
||
("Phase A", "아키텍처·거버넌스 (AA/SA/TA/DA/NA)", 0, 1.5, GREEN, "완료"),
|
||
("Phase B", "공통·시스템관리 레이어 (2FA/OTP·공통기능)", 1.2, 2.0, GREEN, "완료"),
|
||
("Phase C", "P0 부스 코어 M2~M5 (배치·설계·배선·시각화)", 2.6, 2.6, AMBER, "진행"),
|
||
("Phase C", " ├ 스캐폴드·인증·매퍼 배선·룰엔진·나노바나나", 2.6, 1.6, GREEN, "완료"),
|
||
("Phase C", " └ 3안 생성·병합·실데이터 전환·모바일", 4.2, 1.4, AMBER, "진행"),
|
||
("Phase D", "P1 도메인 — 옥션 M15·관람객 M10", 4.8, 1.9, SLATE, "예정"),
|
||
("Phase D", "P1 도메인 — CMS/공개사이트 M12·M17·BI M16·관리자 M18", 5.4, 2.2, SLATE, "예정"),
|
||
("Phase D", "P1 배정·서류·매칭·정산 M1·M6·M7·M9", 5.8, 1.8, SLATE, "예정"),
|
||
("Phase E", "P2 확장 (매칭 M11·wayfinding M13·현장운영 M14)", 7.2, 1.6, SLATE, "예정"),
|
||
("Phase E", "빌드·배포 (역할별 번들·CI/CD·멀티테넌시)", 7.6, 2.2, AMBER, "진행"),
|
||
]
|
||
ry = 2.12
|
||
rh = 0.42
|
||
for i,(ph, name, start, dur, cl, st) in enumerate(rows):
|
||
y = ry + i*rh
|
||
if i % 2 == 0:
|
||
rect(s, 0.55, y-0.02, 12.23, rh-0.02, fill=RGBColor(0xFC,0xFC,0xFD))
|
||
text(s, 0.6, y, 0.85, rh-0.06, ph, size=8.5, color=BLUE_DK, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 1.45, y, 2.05, rh-0.06, name, size=8.2, color=INK, anchor=MSO_ANCHOR.MIDDLE)
|
||
bx = gx + start*uw
|
||
bw = max(dur*uw, 0.3)
|
||
rect(s, bx, y+0.06, bw, rh-0.18, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, bx, y+0.04, bw, rh-0.14, st, size=7.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
# 현재 시점 마커
|
||
nowx = gx + 4.9*uw
|
||
rect(s, nowx, 1.98, 0.02, 4.2, fill=PURPLE)
|
||
text(s, nowx-0.5, 6.2, 1.0, 0.24, "현재", size=8, color=PURPLE, bold=True, align=PP_ALIGN.CENTER)
|
||
|
||
text(s, 0.55, 6.55, 12.2, 0.3,
|
||
"원칙 Phase B(공통 레이어)는 전 도메인 선행 기반 — 인증·시스템관리·공통기능 정착 후 코어/도메인 착수. 각 모듈 완성 직후 점진 QA.",
|
||
size=9, color=MUTED)
|
||
|
||
# ================================================================
|
||
# 8. 개발 방법론·품질
|
||
# ================================================================
|
||
divider("06", "개발 방법론 · 품질관리", "METHODOLOGY & QUALITY",
|
||
["에이전트 파이프라인", "점진 QA · 보안 불변 게이트", "CI/CD 자동배포"])
|
||
|
||
s = slide()
|
||
content_header(s, "06 · 개발 방법론 · 품질관리", "에이전트 파이프라인 · 점진 QA · CI/CD", 6)
|
||
|
||
# 파이프라인
|
||
text(s, 0.55, 1.5, 8, 0.3, "① 에이전트 기반 개발 파이프라인", size=12, color=BLUE, bold=True)
|
||
pipe = [("기획","planner",PURPLE),("설계","AA/SA/TA/DA/NA",BLUE),("디자인","designer",PURPLE),
|
||
("구현","backend·frontend·db·domain",BLUE),("검증","kintex-qa",GREEN),("배포","devops-dev",BLUE_DK)]
|
||
pw2 = 1.9; g2 = 0.14
|
||
for i,(t,d,cl) in enumerate(pipe):
|
||
x = 0.55+i*(pw2+g2)
|
||
rect(s, x, 1.9, pw2, 0.74, fill=WHITE, line=cl, line_w=1.3, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x, 1.98, pw2, 0.3, t, size=11, color=cl, bold=True, align=PP_ALIGN.CENTER)
|
||
text(s, x, 2.3, pw2, 0.3, d, size=7.6, color=MUTED, align=PP_ALIGN.CENTER)
|
||
if i < len(pipe)-1:
|
||
text(s, x+pw2-0.05, 1.9, 0.24, 0.74, "›", size=16, color=SLATE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
# ② 점진 QA
|
||
card(s, 0.55, 2.95, 6.05, 2.05)
|
||
text(s, 0.8, 3.12, 5.5, 0.3, "② 점진 QA — 모듈 완성 직후 검증", size=12, color=PURPLE, bold=True)
|
||
for i,t in enumerate([
|
||
"백엔드 응답 shape ↔ 프론트 훅/컴포넌트 호출 경계면 교차 비교",
|
||
"공간 로직(배치·배선·규정검증) 정합 · PostGIS 연산 검증",
|
||
"보안 불변: 자격증명·PII·스택트레이스 미노출 강제",
|
||
"AI 생성 이미지 워터마크·고지 강제 · 등록업체 응찰 게이트",
|
||
"빌드 회귀(compileJava·tsc) · 통과까지 반려",
|
||
]):
|
||
y=3.5+i*0.29
|
||
rect(s, 0.85, y+0.06, 0.09, 0.09, fill=GREEN, shape=MSO_SHAPE.OVAL)
|
||
text(s, 1.05, y, 5.4, 0.28, t, size=9.3, color=INK)
|
||
|
||
# ③ CI/CD
|
||
card(s, 6.75, 2.95, 6.03, 2.05)
|
||
text(s, 7.0, 3.12, 5.5, 0.3, "③ CI/CD 자동배포 파이프라인", size=12, color=BLUE, bold=True)
|
||
cicd = ["push","Gitea webhook","deploy_kintex.sh","백엔드 bootJar + 프론트 Vite 빌드","배포 → health check","실패 시 롤백"]
|
||
for i,c in enumerate(cicd):
|
||
y=3.5+i*0.245
|
||
rect(s, 7.05, y, 0.28, 0.2, fill=BLUE, shape=MSO_SHAPE.OVAL)
|
||
text(s, 7.06, y-0.01, 0.28, 0.22, str(i+1), size=8, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 7.45, y-0.02, 5.2, 0.24, c, size=9.3, color=INK, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
# 하단 지표 스트립
|
||
metrics = [("18","도메인 모듈"),("6","역할별 포털"),("39+","Flyway 테이블(현재)"),("A~E","5개 Phase"),("3-Tier","+ AI 사이드카")]
|
||
mw=2.34
|
||
for i,(v,l) in enumerate(metrics):
|
||
x=0.55+i*(mw+0.09)
|
||
rect(s, x, 5.2, mw, 0.95, fill=BLUE if i%2==0 else PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x, 5.34, mw, 0.5, v, size=26, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
|
||
text(s, x, 5.86, mw, 0.26, l, size=9.5, color=WHITE, align=PP_ALIGN.CENTER)
|
||
text(s, 0.55, 6.5, 12.2, 0.3,
|
||
"AI 지능 = Claude 기본 + 설정형 전환(AiTextRouter/AiConfig) · Ollama 폴백 │ 이미지 = 나노바나나(Gemini) 라이브",
|
||
size=9, color=MUTED)
|
||
|
||
# ================================================================
|
||
# 9. 보안·NFR
|
||
# ================================================================
|
||
divider("07", "보안 · 비기능요건", "SECURITY & NFR",
|
||
["외부 API 통제 (예외 명시)", "자격증명 보호 · AI 워터마크", "접근성·다국어·테마"])
|
||
|
||
s = slide()
|
||
content_header(s, "07 · 보안 · 비기능요건(NFR)", "보안 불변 원칙 및 품질 속성", 7)
|
||
|
||
# 보안 불변 (좌)
|
||
card(s, 0.55, 1.5, 6.05, 3.05)
|
||
rect(s, 0.55, 1.5, 6.05, 0.5, fill=BLUE_DK, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.55, 1.5, 6.05, 0.5, "보안 불변 (Security Invariants)", size=13, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
sec = [
|
||
("외부 API 통제","온프레미스 원칙 · 예외 = Claude(승인)·Gemini(G1). 키는 서버 env 로드, 코드·커밋·로그 미기재"),
|
||
("자격증명 보호","IP·SSH계정·비밀번호를 API 응답·에러메시지에 미노출"),
|
||
("암호화 저장","민감 자격증명 AES-256-GCM · admin 비번 env 주입(admin123 금지)"),
|
||
("AI 이미지 고지","나노바나나 생성 전 이미지 워터마크·실물 상이 고지 강제"),
|
||
("에러 응답","스택트레이스 미노출 — 요약 메시지만 전달"),
|
||
]
|
||
for i,(h,d) in enumerate(sec):
|
||
y=2.15+i*0.47
|
||
rect(s, 0.8, y+0.04, 0.11, 0.11, fill=BLUE, shape=MSO_SHAPE.OVAL)
|
||
text(s, 1.02, y, 5.45, 0.44, [[{"t":h+" — ","size":9.6,"color":INK,"bold":True},
|
||
{"t":d,"size":9,"color":MUTED}]], line_spacing=0.95)
|
||
|
||
# NFR (우)
|
||
card(s, 6.75, 1.5, 6.03, 3.05)
|
||
rect(s, 6.75, 1.5, 6.03, 0.5, fill=PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 6.75, 1.5, 6.03, 0.5, "비기능요건 (NFR)", size=13, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
nfr = [
|
||
("웹 접근성","WCAG 준수 · 키보드·스크린리더 대응"),
|
||
("다국어(i18n)","한 · 영 · 중 · 일 (공개사이트·CMS 우선)"),
|
||
("라이트/다크 테마","전 화면 테마 토큰 · 반응형 풀스크린"),
|
||
("반응형 채널 분리","데스크톱=설계/에디터 · 모바일=현장/조회/승인"),
|
||
("성능·확장","Redis 비동기 큐 · 렌더 워커 사이드카 분리"),
|
||
("멀티테넌시 격리","tenant_id 전파 · 전시관 단위 데이터·권한 격리"),
|
||
]
|
||
for i,(h,d) in enumerate(nfr):
|
||
y=2.15+i*0.4
|
||
rect(s, 7.0, y+0.04, 0.11, 0.11, fill=PURPLE, shape=MSO_SHAPE.OVAL)
|
||
text(s, 7.22, y, 5.4, 0.38, [[{"t":h+" — ","size":9.6,"color":INK,"bold":True},
|
||
{"t":d,"size":9,"color":MUTED}]], line_spacing=0.95)
|
||
|
||
# 하단: 3중 권한 구조
|
||
rect(s, 0.55, 4.78, 12.23, 0.42, fill=INK, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.55, 4.78, 12.23, 0.42, "3중 권한 구조 테넌트(전시관) 격리 ⊃ 행사(Event) 워크스페이스 ⊃ 행사 RBAC",
|
||
size=11.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
# 인증 요약 3칩
|
||
for i,(t,d,cl) in enumerate([("JWT + RBAC","6역할 게이트",BLUE),("2FA / OTP","TOTP RFC6238",PURPLE),
|
||
("감사로그","전수 기록·개인정보 접근",BLUE_DK)]):
|
||
x=0.55+i*4.11
|
||
rect(s, x, 5.4, 3.97, 0.72, fill=WHITE, line=cl, line_w=1.3, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x+0.2, 5.5, 3.7, 0.3, t, size=12, color=cl, bold=True)
|
||
text(s, x+0.2, 5.82, 3.7, 0.26, d, size=9.5, color=MUTED)
|
||
|
||
# ================================================================
|
||
# 10. 리스크 및 대응
|
||
# ================================================================
|
||
divider("08", "리스크 및 대응", "RISK MANAGEMENT",
|
||
["기술·데이터·연동 리스크", "영향도·발생가능성 평가", "완화 방안"])
|
||
|
||
s = slide()
|
||
content_header(s, "08 · 리스크 및 대응", "주요 리스크 및 대응 방안", 8)
|
||
|
||
# 표 헤더
|
||
hx=[0.55, 1.3, 5.9, 8.0] # 구분/리스크/영향/대응 시작
|
||
cols = [(0.55,0.75,"구분"),(1.3,4.6,"리스크"),(5.9,2.1,"영향"),(8.0,4.78,"대응 방안")]
|
||
rect(s, 0.55, 1.5, 12.23, 0.44, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
for x,w,h in cols:
|
||
text(s, x+0.12, 1.5, w-0.15, 0.44, h, size=10.5, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
risks = [
|
||
("기술","나노바나나 생성 이미지가 실제 시공과 상이","높음",AMBER,"전 이미지 워터마크·고지 강제, 참조이미지 구조보존 파이프라인, 검수 단계"),
|
||
("데이터","홀 실측 CAD·트렌치 좌표 미확보","높음",AMBER,"확보 자산(평면도 15장+CAD) 활용, 미확보 시 공개규격 근사도면으로 Phase 진행"),
|
||
("연동","kxwp 내부 API 미공개(폐쇄형)","중간",SLATE,"초기 제출용 파일 생성+업로드 안내 릴레이, 연동은 킨텍스 협의 후"),
|
||
("보안","외부 API 호출·자격증명 노출","높음",AMBER,"Claude·Gemini만 예외 승인, env 로드·미커밋, 응답 미노출, 감사로그"),
|
||
("규정","소방·구조 최종 유권해석은 시스템 밖","중간",SLATE,"AI는 사전 필터(위반 플래깅)까지, 최종 판정은 관할기관·구조기술사"),
|
||
("일정","공통 레이어 지연 시 도메인 착수 지연","중간",SLATE,"Phase B 선행 완료(현재 완료), 모듈 병렬화·점진 QA로 흡수"),
|
||
("시장","옥션 업체 참여율·응찰 품질 불확실","중간",SLATE,"등록업체 DB 검증 게이트, AI 자료 기반 동일사양 비교로 참여 유인"),
|
||
]
|
||
ry=1.98; rh=0.56
|
||
for i,(g,r,imp,impc,resp) in enumerate(risks):
|
||
y=ry+i*rh
|
||
if i%2==0:
|
||
rect(s, 0.55, y, 12.23, rh, fill=RGBColor(0xFC,0xFC,0xFD))
|
||
rect(s, 0.55, y, 12.23, 0.01, fill=LINE)
|
||
text(s, 0.67, y, 0.7, rh, g, size=9.5, color=BLUE_DK, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 1.42, y, 4.4, rh, r, size=9.5, color=INK, anchor=MSO_ANCHOR.MIDDLE)
|
||
rect(s, 5.9, y+0.16, 0.95, 0.24, fill=impc, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 5.9, y+0.15, 0.95, 0.26, imp, size=8.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 8.0, y, 4.7, rh, resp, size=8.8, color=MUTED, anchor=MSO_ANCHOR.MIDDLE, line_spacing=0.92)
|
||
|
||
# ================================================================
|
||
# 11. 기대 효과
|
||
# ================================================================
|
||
divider("09", "기대 효과", "EXPECTED BENEFITS",
|
||
["정량 효과 (리드타임)", "정성 효과 (의사결정·투명성)", "확장 로드맵"])
|
||
|
||
s = slide()
|
||
content_header(s, "09 · 기대 효과", "정량 · 정성 기대 효과", 9)
|
||
|
||
text(s, 0.55, 1.48, 8, 0.3, "정량 효과 — 리드타임 단축", size=12.5, color=BLUE, bold=True)
|
||
quant = [
|
||
("홀 배정 견적","수일","즉시"),
|
||
("배치도 초안","수일~수주","수 분"),
|
||
("규정 검수","육안 검토","자동 플래깅"),
|
||
("유틸리티 신청","수기 작도","클릭 + 자동견적"),
|
||
("시공 결과 예측","불가(외주)","표준 샷 자동생성"),
|
||
]
|
||
qw=2.4
|
||
for i,(k,a,b) in enumerate(quant):
|
||
x=0.55+i*(qw+0.06)
|
||
card(s, x, 1.85, qw, 1.85)
|
||
text(s, x, 2.0, qw, 0.3, k, size=10, color=INK, bold=True, align=PP_ALIGN.CENTER)
|
||
rect(s, x+0.3, 2.34, qw-0.6, 0.02, fill=LINE)
|
||
text(s, x, 2.42, qw, 0.26, "As-Is", size=7.5, color=SLATE, align=PP_ALIGN.CENTER)
|
||
text(s, x, 2.62, qw, 0.3, a, size=10.5, color=MUTED, align=PP_ALIGN.CENTER)
|
||
text(s, x, 2.98, qw, 0.24, "↓", size=12, color=PURPLE, bold=True, align=PP_ALIGN.CENTER)
|
||
text(s, x, 3.2, qw, 0.3, b, size=11, color=BLUE, bold=True, align=PP_ALIGN.CENTER)
|
||
|
||
text(s, 0.55, 3.95, 8, 0.3, "정성 효과", size=12.5, color=PURPLE, bold=True)
|
||
qual = [
|
||
("의사결정 가속","도면을 읽지 못해도 사진 수준 시각화로 주최자·참가업체 의사결정"),
|
||
("영업 자료화","완공 예상 이미지로 참가업체 유치·컨펌 커뮤니케이션 비용 절감"),
|
||
("가격 투명화","옥션 역경매로 동일 사양 위 공정 비교 → 시공 발주 투명·최적화"),
|
||
("데이터 일원화","공간 데이터(PostGIS) 단일 원천에서 검증·시각화·정산·BI·wayfinding"),
|
||
]
|
||
for i,(h,d) in enumerate(qual):
|
||
x=0.55+(i%2)*6.15
|
||
y=4.32+(i//2)*0.66
|
||
card(s, x, y, 6.05, 0.58)
|
||
rect(s, x, y, 0.08, 0.58, fill=PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x+0.24, y+0.08, 5.7, 0.28, h, size=10.5, color=INK, bold=True)
|
||
text(s, x+0.24, y+0.34, 5.7, 0.22, d, size=8.6, color=MUTED)
|
||
|
||
# 확장 로드맵
|
||
rect(s, 0.55, 5.78, 12.23, 0.44, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.7, 5.78, 4, 0.44, "확장 로드맵", size=11, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
road = ["KINTEX 기준 운영","코엑스 등 다중 전시관 온보딩","전시테크 SaaS 사업화","kxwp·PG·사이니지 정식 연동"]
|
||
for i,r in enumerate(road):
|
||
text(s, 2.6+i*2.55, 5.78, 2.5, 0.44, "› "+r, size=9.3, color=WHITE, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
text(s, 0.55, 6.4, 12.2, 0.5,
|
||
"종합 “신청서를 내는 순간 시공 후 사진을 먼저 본다” — 설계·시각화·발주·운영·분석 폐루프를 하나의 데이터·계정 체계로 자동화하여 킨텍스를 전시 운영 표준 플랫폼으로 전환",
|
||
size=10, color=INK, bold=True, line_spacing=1.0)
|
||
|
||
# ================================================================
|
||
# 저장
|
||
# ================================================================
|
||
OUT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "개발계획서.pptx")
|
||
prs.save(OUT)
|
||
print("SAVED:", OUT)
|
||
print("SLIDES:", len(prs.slides._sldIdLst))
|