1116 lines
71 KiB
Python
1116 lines
71 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
킨텍스 자동전시시스템 구축 — 프로그램사양서 PPTX 생성기
|
||
- 소스: docs/PLANNING.md(v3.0 M1~M18 + §5B 공통레이어), docs/design.md(§2.8 화면 80종 SCR),
|
||
docs/architecture/app.md(응답 봉투·에러·감사로그), IMPLEMENTATION_BACKLOG.md
|
||
- 디자인: 개발계획서(gen_dev_plan_pptx.py) 스타일 재사용 — KINTEX Blue #0066B3 / AI Purple #6D4AFF / 맑은 고딕 / 16:9
|
||
- 순서도는 python-pptx 도형(단자·처리·판단·데이터·커넥터)으로 직접 작도(외부 이미지 금지)
|
||
- 보안 불변: 비밀번호/API키/서버IP/SSH 계정 미기재
|
||
재실행: python gen_program_spec_pptx.py
|
||
"""
|
||
import os
|
||
from pptx import Presentation
|
||
from pptx.util import Pt
|
||
from pptx.dml.color import RGBColor
|
||
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
|
||
from pptx.enum.shapes import MSO_SHAPE, MSO_CONNECTOR
|
||
from pptx.oxml.ns import qn
|
||
|
||
# ---------------- 팔레트 (개발계획서와 동일) ----------------
|
||
BLUE = RGBColor(0x00, 0x66, 0xB3)
|
||
BLUE_DK = RGBColor(0x00, 0x44, 0x7A)
|
||
BLUE_LT = RGBColor(0xE1, 0xEF, 0xF9)
|
||
PURPLE = RGBColor(0x6D, 0x4A, 0xFF)
|
||
PURPLE_LT = RGBColor(0xEC, 0xE8, 0xFF)
|
||
INK = RGBColor(0x10, 0x18, 0x28)
|
||
MUTED = RGBColor(0x66, 0x70, 0x85)
|
||
LINE = RGBColor(0xE4, 0xE7, 0xEC)
|
||
BG = RGBColor(0xF9, 0xFA, 0xFB)
|
||
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)
|
||
ROW_ALT = RGBColor(0xFC, 0xFC, 0xFD)
|
||
|
||
FONT = "맑은 고딕"
|
||
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
|
||
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':'16000'})
|
||
clr.append(alp); sh.append(clr); ef.append(sh)
|
||
# 텍스트 없는 도형은 자동맞춤 방지
|
||
sp.text_frame.word_wrap = True
|
||
return sp
|
||
|
||
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):
|
||
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)
|
||
|
||
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)
|
||
|
||
FOOT = "킨텍스 자동전시시스템 구축 · 프로그램사양서"
|
||
def content_header(s, kicker, title, idx, chip_txt=None, chip_col=BLUE):
|
||
page_bg(s, BG)
|
||
rect(s, 0, 0, 13.333, 0.12, fill=BLUE)
|
||
rect(s, 0.55, 0.5, 0.62, 0.62, fill=chip_col, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.55, 0.5, 0.62, 0.62, (chip_txt if chip_txt else f"{idx:02d}"),
|
||
size=(20 if not chip_txt else 22), 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=21, color=INK, bold=True)
|
||
rect(s, 0.55, 1.28, 12.23, 0.02, fill=LINE)
|
||
text(s, 0.55, 7.06, 8, 0.3, FOOT, 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 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
|
||
|
||
# ---------------- 순서도(플로우차트) 도형 헬퍼 ----------------
|
||
def fnode(s, cx, cy, w, h, txt, kind="proc"):
|
||
"""중심좌표 기준 순서도 노드. return dict(cx,cy,l,r,t,b)."""
|
||
x = cx - w/2; y = cy - h/2
|
||
if kind == "term": # 시작/끝 단자
|
||
sh, fill, line, tcol = MSO_SHAPE.FLOWCHART_TERMINATOR, BLUE, BLUE, WHITE
|
||
elif kind == "proc": # 처리
|
||
sh, fill, line, tcol = MSO_SHAPE.FLOWCHART_PROCESS, WHITE, BLUE, INK
|
||
elif kind == "dec": # 판단
|
||
sh, fill, line, tcol = MSO_SHAPE.FLOWCHART_DECISION, PURPLE_LT, PURPLE, PURPLE
|
||
elif kind == "io": # 입출력(데이터)
|
||
sh, fill, line, tcol = MSO_SHAPE.FLOWCHART_DATA, BLUE_LT, BLUE, BLUE_DK
|
||
elif kind == "db": # 데이터베이스
|
||
sh, fill, line, tcol = MSO_SHAPE.FLOWCHART_MAGNETIC_DISK, HALL_BG, SLATE, INK
|
||
elif kind == "wkr": # 사전정의 처리(워커/외부)
|
||
sh, fill, line, tcol = MSO_SHAPE.FLOWCHART_PREDEFINED_PROCESS, PURPLE_LT, PURPLE, PURPLE
|
||
elif kind == "proc_ok": # 성공 처리(강조)
|
||
sh, fill, line, tcol = MSO_SHAPE.FLOWCHART_PROCESS, RGBColor(0xEC,0xF7,0xF0), GREEN, RGBColor(0x0C,0x6B,0x43)
|
||
elif kind == "proc_bad": # 반려/차단 처리
|
||
sh, fill, line, tcol = MSO_SHAPE.FLOWCHART_PROCESS, RGBColor(0xFD,0xF1,0xE3), AMBER, RGBColor(0x8A,0x54,0x00)
|
||
else:
|
||
sh, fill, line, tcol = MSO_SHAPE.FLOWCHART_PROCESS, WHITE, BLUE, INK
|
||
sp = rect(s, x, y, w, h, fill=fill, line=line, line_w=1.3, shape=sh)
|
||
sz = 8.4 if kind != "dec" else 8.0
|
||
text(s, x, y, w, h, txt, size=sz, color=tcol, bold=(kind in ("term","dec")),
|
||
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE, line_spacing=0.95)
|
||
return {"cx":cx,"cy":cy,"l":x,"r":x+w,"t":y,"b":y+h}
|
||
|
||
def _arrowline(cxn, color, w):
|
||
cxn.line.color.rgb = color; cxn.line.width = Pt(w)
|
||
ln = cxn.line._get_or_add_ln()
|
||
tail = ln.makeelement(qn('a:tailEnd'), {'type':'triangle','w':'med','len':'med'})
|
||
ln.append(tail)
|
||
|
||
def arrow(s, x1, y1, x2, y2, color=SLATE, w=1.6, label=None, lcol=MUTED):
|
||
cxn = s.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, inch(x1), inch(y1), inch(x2), inch(y2))
|
||
_arrowline(cxn, color, w)
|
||
if label:
|
||
mx, my = (x1+x2)/2, (y1+y2)/2
|
||
text(s, mx-0.35, my-0.16, 0.9, 0.24, label, size=8, color=lcol, bold=True,
|
||
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
def arrow_elbow(s, x1, y1, x2, y2, first='v', color=SLATE, w=1.6, label=None, lcol=MUTED):
|
||
"""L자 커넥터. first='v'면 수직→수평, 'h'면 수평→수직. 마지막 구간에 화살촉."""
|
||
if first == 'v':
|
||
seg1 = s.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, inch(x1), inch(y1), inch(x1), inch(y2))
|
||
seg1.line.color.rgb = color; seg1.line.width = Pt(w)
|
||
cxn = s.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, inch(x1), inch(y2), inch(x2), inch(y2))
|
||
else:
|
||
seg1 = s.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, inch(x1), inch(y1), inch(x2), inch(y1))
|
||
seg1.line.color.rgb = color; seg1.line.width = Pt(w)
|
||
cxn = s.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, inch(x2), inch(y1), inch(x2), inch(y2))
|
||
_arrowline(cxn, color, w)
|
||
if label:
|
||
text(s, x1-0.05, (y1+y2)/2-0.12, 1.1, 0.24, label, size=8, color=lcol, bold=True,
|
||
anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
def vchain(s, cx, y0, items, w=3.7, h=0.6, gap=0.34):
|
||
"""세로 스택 + 순차 연결. items=[(txt,kind),...] return nodes."""
|
||
nodes = []; y = y0
|
||
for txt, kind in items:
|
||
n = fnode(s, cx, y + h/2, w, h, txt, kind); nodes.append(n); y += h + gap
|
||
for a, b in zip(nodes, nodes[1:]):
|
||
arrow(s, a["cx"], a["b"], b["cx"], b["t"])
|
||
return nodes
|
||
|
||
def fc_legend(s):
|
||
items = [("시작/끝",BLUE,"term"),("처리",WHITE,"proc"),("판단",PURPLE_LT,"dec"),("입출력",BLUE_LT,"io")]
|
||
x0 = 8.0
|
||
for i,(lb,cl,kd) in enumerate(items):
|
||
x = x0 + i*1.2
|
||
sh = {"term":MSO_SHAPE.FLOWCHART_TERMINATOR,"proc":MSO_SHAPE.FLOWCHART_PROCESS,
|
||
"dec":MSO_SHAPE.FLOWCHART_DECISION,"io":MSO_SHAPE.FLOWCHART_DATA}[kd]
|
||
ln = {"term":BLUE,"proc":BLUE,"dec":PURPLE,"io":BLUE}[kd]
|
||
rect(s, x, 1.34, 0.26, 0.2, fill=cl, line=ln, line_w=1.0, shape=sh)
|
||
text(s, x+0.31, 1.31, 0.85, 0.26, lb, size=8, color=MUTED, anchor=MSO_ANCHOR.MIDDLE)
|
||
|
||
# ================================================================
|
||
# 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.95, 0.42, "프로그램사양서", PURPLE, WHITE, size=12)
|
||
chip(s, 2.92, 2.35, 2.35, 0.42, "Program Specification", 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 — 기능별 상세 사양 + 업무 순서도",
|
||
size=16, color=RGBColor(0xC7,0xDD,0xF2))
|
||
text(s, 0.88, 5.35, 11.4, 0.4,
|
||
"프로그램 식별체계 PGM-ID = M{모듈}-{유형}-{일련} (예: M02-BATCH-01)",
|
||
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, 9, 0.4, [
|
||
[{"t":"대상 ","size":11,"color":RGBColor(0x90,0xA6,0xC9)},
|
||
{"t":"모듈 M1~M18 + 공통·시스템관리 레이어(§5B)","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 · 나노바나나 워커","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 · dev team", size=9.5, color=RGBColor(0x90,0xA6,0xC9), align=PP_ALIGN.RIGHT)
|
||
|
||
# ================================================================
|
||
# 2. 목차
|
||
# ================================================================
|
||
s = slide()
|
||
content_header(s, "CONTENTS", "목차", 0, chip_txt="C", chip_col=PURPLE)
|
||
toc = [
|
||
("01","개요 · 식별체계","문서 목적 / PGM-ID 부여 체계 / 프로그램 유형"),
|
||
("02","프로그램 목록 총괄","모듈(M1~M18+공통)별 프로그램 ID·명·유형·화면"),
|
||
("03","핵심 프로그램 상세 사양","20개 대표 프로그램 — 입출력·처리로직·테이블·예외"),
|
||
("04","핵심 업무 순서도","8종 — 배치/검증/배선/렌더/옥션/등록/승인/배포"),
|
||
("05","공통 규약","API 응답 봉투 · 에러 처리 · 감사 로그"),
|
||
]
|
||
for i,(no,t,d) in enumerate(toc):
|
||
y = 1.75 + i*1.0
|
||
rect(s, 0.7, y, 11.9, 0.86, fill=CARD, line=LINE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
rect(s, 0.7, y, 0.09, 0.86, fill=(BLUE if i%2==0 else PURPLE), shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 1.0, y, 0.95, 0.86, no, size=26, color=(BLUE if i%2==0 else PURPLE), bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 2.1, y+0.15, 9.8, 0.34, t, size=15, color=INK, bold=True)
|
||
text(s, 2.1, y+0.52, 9.8, 0.3, d, size=10, color=MUTED)
|
||
|
||
# ================================================================
|
||
# 3. 개요 · 식별체계
|
||
# ================================================================
|
||
divider("01", "개요 · 식별체계", "OVERVIEW & ID SCHEME",
|
||
["문서 목적 · 범위", "PGM-ID 부여 체계", "프로그램 유형 정의"])
|
||
|
||
s = slide()
|
||
content_header(s, "01 · 개요 · 식별체계", "프로그램 식별 체계 및 유형 정의", 1)
|
||
# 목적
|
||
card(s, 0.55, 1.5, 12.23, 1.02)
|
||
text(s, 0.8, 1.66, 11.7, 0.3, "문서 목적", size=12, color=BLUE, bold=True)
|
||
text(s, 0.8, 1.98, 11.75, 0.5,
|
||
"본 사양서는 킨텍스 자동전시시스템(M1~M18 + 공통·시스템관리 §5B)의 프로그램 단위를 식별·정의하고, 핵심 프로그램의 상세 처리 사양과 주요 업무 순서도를 규정한다. 화면(SCR)·모듈 정의의 권위 출처는 PLANNING v3.0·design.md v2.0이며, 본 문서는 이를 프로그램 단위로 분해한다.",
|
||
size=10, color=INK, line_spacing=1.02)
|
||
# ID 체계
|
||
text(s, 0.55, 2.78, 8, 0.3, "PGM-ID 부여 체계", size=12.5, color=PURPLE, bold=True)
|
||
seg = [("M02","모듈 코드 (M1~M18 / SYS·CMN=공통)",BLUE),
|
||
("BATCH","유형·업무 약어",PURPLE),
|
||
("01","모듈 내 일련번호",BLUE_DK)]
|
||
x = 0.55
|
||
for i,(v,d,cl) in enumerate(seg):
|
||
w = 2.0 if i!=1 else 3.5
|
||
rect(s, x, 3.14, w, 0.72, fill=WHITE, line=cl, line_w=1.4, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x, 3.22, w, 0.34, v, size=15, color=cl, bold=True, align=PP_ALIGN.CENTER)
|
||
text(s, x, 3.56, w, 0.26, d, size=8, color=MUTED, align=PP_ALIGN.CENTER)
|
||
if i<2:
|
||
text(s, x+w-0.02, 3.14, 0.34, 0.72, "-", size=20, color=SLATE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
x += w + 0.34
|
||
text(s, 8.5, 3.28, 4.3, 0.5, [[{"t":"예) ","size":11,"color":MUTED},
|
||
{"t":"M02-BATCH-01","size":13,"color":INK,"bold":True}],
|
||
[{"t":"= 플로어플랜 배치 3안 생성 엔진","size":9,"color":MUTED}]], line_spacing=1.1)
|
||
# 유형 정의
|
||
text(s, 0.55, 4.1, 8, 0.3, "프로그램 유형", size=12.5, color=BLUE, bold=True)
|
||
types = [
|
||
("온라인","ONL","사용자 화면 트랜잭션(React↔REST), 조회·입력·처리",BLUE),
|
||
("API","API","엔진·서비스 API(룰·배치·배선·집계), 화면 비종속",BLUE_DK),
|
||
("배치","BAT","스케줄러/야간 적재·역산 알림·주기 수집",PURPLE),
|
||
("워커","WKR","Redis 큐 소비 비동기 워커(나노바나나 렌더 등)",PURPLE),
|
||
]
|
||
tw = 2.98
|
||
for i,(kr,ab,d,cl) in enumerate(types):
|
||
x = 0.55 + i*(tw+0.11)
|
||
card(s, x, 4.46, tw, 1.72)
|
||
rect(s, x, 4.46, tw, 0.5, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x, 4.46, tw, 0.5, f"{kr} ({ab})", size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, x+0.2, 5.1, tw-0.4, 1.0, d, size=9.5, color=INK, line_spacing=1.05)
|
||
text(s, 0.55, 6.4, 12.2, 0.3,
|
||
"표기 원칙 온라인 프로그램은 관련 화면(SCR ID)을 명시. 하나의 화면이 복수 프로그램을, 하나의 엔진 API가 복수 화면을 지원할 수 있다(N:M).",
|
||
size=9, color=MUTED)
|
||
|
||
# ================================================================
|
||
# 4. 프로그램 목록 총괄
|
||
# ================================================================
|
||
divider("02", "프로그램 목록 총괄", "PROGRAM CATALOG",
|
||
["모듈별 PGM-ID·명·유형", "관련 화면(SCR) 매핑", "약 60개 프로그램"])
|
||
|
||
# 총괄 통계
|
||
s = slide()
|
||
content_header(s, "02 · 프로그램 목록 총괄", "프로그램 총괄 개요", 2)
|
||
stats = [("18","도메인 모듈 (M1~M18)"),("60+","프로그램 (온라인·API·배치·워커)"),
|
||
("80","화면 (SCR·웹/모바일/공개/관리자)"),("4","프로그램 유형")]
|
||
mw = 2.94
|
||
for i,(v,l) in enumerate(stats):
|
||
x = 0.55 + i*(mw+0.1)
|
||
rect(s, x, 1.55, mw, 1.15, fill=(BLUE if i%2==0 else PURPLE), shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x, 1.72, mw, 0.6, v, size=34, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
|
||
text(s, x, 2.36, mw, 0.28, l, size=9, color=WHITE, align=PP_ALIGN.CENTER)
|
||
# 모듈군 구성 요약
|
||
groups = [
|
||
("공통 · 시스템관리 (§5B / SYS·CMN)", BLUE_DK,
|
||
"인증(JWT+OTP)·사용자·RBAC·공통코드·메뉴·감사로그·설정 + 업무일지·일정·쪽지·공지·검색·회의록·보고·알림"),
|
||
("설계·시각화 코어 (M2~M5 · P0)", PURPLE,
|
||
"배치 3안 생성·규정검증·부스설계·배선 최단경로·나노바나나 RenderJob 워커"),
|
||
("판매·발주·정산 (M1·M6~M9·M15)", BLUE,
|
||
"자동 견적·서류/마일스톤·등록업체 매칭·물류 슬롯·정산결제·공사 옥션(역경매·낙찰)"),
|
||
("관람·참가·마케팅 (M10~M14)", BLUE,
|
||
"관람객 등록·배지·체크인·리드캡처·비즈매칭·EDM/공개사이트·wayfinding·현장운영"),
|
||
("경영·콘텐츠·관리 (M16~M18)", PURPLE,
|
||
"BI 데이터마트/대시보드·CMS/마이크로사이트·마스터데이터·룰셋·테넌트 관리"),
|
||
]
|
||
y = 2.92
|
||
for i,(t,cl,d) in enumerate(groups):
|
||
rect(s, 0.55, y, 12.23, 0.72, fill=CARD, line=LINE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
rect(s, 0.55, y, 0.1, 0.72, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.82, y+0.1, 5.0, 0.5, t, size=11, color=cl, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 5.9, y+0.1, 6.75, 0.52, d, size=9, color=MUTED, anchor=MSO_ANCHOR.MIDDLE, line_spacing=0.95)
|
||
y += 0.8
|
||
|
||
# --- 프로그램 목록 표 헬퍼 ---
|
||
LIST_COLS = [(0.6,1.55,"PGM-ID"),(2.2,3.35,"프로그램명"),(5.62,1.05,"유형"),
|
||
(6.75,1.45,"화면(SCR)"),(8.25,4.5,"개요")]
|
||
def list_slide(title, rows, note=None):
|
||
s = slide()
|
||
content_header(s, "02 · 프로그램 목록 총괄", title, 2)
|
||
# 헤더
|
||
rect(s, 0.55, 1.5, 12.23, 0.42, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
for x,w,h in LIST_COLS:
|
||
text(s, x+0.1, 1.5, w-0.12, 0.42, h, size=9.5, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
y = 1.96
|
||
rh = 0.445 if len(rows) > 10 else 0.49
|
||
for i,(pid,name,typ,scr,desc) in enumerate(rows):
|
||
if i % 2 == 0:
|
||
rect(s, 0.55, y, 12.23, rh, fill=ROW_ALT)
|
||
rect(s, 0.55, y+rh, 12.23, 0.008, fill=LINE)
|
||
text(s, 0.7, y, 1.5, rh, pid, size=8.6, color=BLUE_DK, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 2.3, y, 3.25, rh, name, size=8.8, color=INK, anchor=MSO_ANCHOR.MIDDLE, line_spacing=0.9)
|
||
tcol = {"온라인":BLUE,"API":BLUE_DK,"배치":PURPLE,"워커":PURPLE,"온라인/WS":BLUE}.get(typ,MUTED)
|
||
text(s, 5.62, y, 1.05, rh, typ, size=8.2, color=tcol, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 6.85, y, 1.35, rh, scr, size=8.0, color=MUTED, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 8.35, y, 4.35, rh, desc, size=8.0, color=MUTED, anchor=MSO_ANCHOR.MIDDLE, line_spacing=0.9)
|
||
y += rh
|
||
if note:
|
||
text(s, 0.55, 6.72, 12.2, 0.3, note, size=8.2, color=MUTED)
|
||
return s
|
||
|
||
# 목록 1 — 공통 · 시스템관리 (SYS/CMN)
|
||
list_slide("프로그램 목록 (1/6) — 공통 · 시스템관리 (§5B)", [
|
||
("SYS-AUTH-01","로그인·JWT 발급","온라인","SCR-01","ID/PW 인증→JWT 발급, 행사 워크스페이스 선택"),
|
||
("SYS-AUTH-02","2차 인증 OTP(TOTP)","API","SCR-51","TOTP RFC6238 검증·QR 등록·재설정"),
|
||
("SYS-AUTH-03","로그인 실패 잠금·해제","API","SCR-01","실패 횟수 잠금 + 관리자 해제"),
|
||
("SYS-USER-01","사용자 관리","온라인","SCR-A1","6역할+등록업체·관람객 계정 CRUD·상태"),
|
||
("SYS-RBAC-01","역할·권한(RBAC) 관리","온라인","SCR-A2","플랫폼·행사 이중 평가 권한 매핑"),
|
||
("SYS-CODE-01","공통코드 관리","온라인","SCR-A3","홀·부스유형·공종14분류·요금코드"),
|
||
("SYS-MENU-01","메뉴 관리","온라인","SCR-A4","역할별 포털 IA 메뉴 트리·권한"),
|
||
("SYS-AUDIT-01","감사로그 조회","온라인","SCR-A5","승인·낙찰·룰셋개정·리드접근 전수 조회"),
|
||
("SYS-CONF-01","시스템설정","온라인","SCR-A6","요율/규정 룰셋 버전·마감정책·AI 게이트"),
|
||
], note="§5B = UIWS/WISE 표준 이식. 시스템관리는 M18 백오피스와 통합(중복 재구현 없음).")
|
||
|
||
list_slide("프로그램 목록 (2/6) — 공통 업무 기능 (§5B)", [
|
||
("CMN-WORK-01","업무일지(worklog)","온라인","SCR-39","홀매니저 현장일지·업체 시공일지·댓글"),
|
||
("CMN-SCHD-01","통합 일정·캘린더","온라인","SCR-40","마일스톤·옥션마감·반입슬롯 통합 뷰"),
|
||
("CMN-MSG-01","쪽지(message)","온라인","SCR-41","행사 내 주최자↔참가↔업체↔홀매니저"),
|
||
("CMN-NOTI-01","공지(notice)","온라인","SCR-42","킨텍스/행사 공지·팝업 (M17 CMS와 정합)"),
|
||
("CMN-SRCH-01","통합검색(search)","API","SCR-44","행사·부스·업체·문서·콘텐츠 크로스 검색"),
|
||
("CMN-MEET-01","회의록(STT→PDF)","배치","SCR-45","사전협의(D-30) 녹음→STT→Jasper PDF"),
|
||
("CMN-RPT-01","업무보고·통계","배치","SCR-46","일/주/월/분기/연 보고·통계 Jasper PDF"),
|
||
("CMN-ALRM-01","알림센터(notification)","온라인/WS","SCR-47","D-데이·낙찰·승인·결제 알림 WebSocket"),
|
||
("CMN-PREF-01","마이페이지·개인화","온라인","SCR-48","즐겨찾기·최근방문·환경설정"),
|
||
])
|
||
|
||
# 목록 3 — M1~M5 코어
|
||
list_slide("프로그램 목록 (3/6) — M1 배정견적 · M2~M5 코어 (P0)", [
|
||
("M01-QUOTE-01","자동 견적 계산 엔진","API","SCR-18","2,250원/㎡×면적×일수×성수기·전시장 계수"),
|
||
("M01-ONL-01","홀 배정 신청","온라인","SCR-18","가용성 캘린더→홀/반홀 선택→신청서 생성"),
|
||
("M01-ONL-02","부스 판매 인벤토리","온라인","SCR-19","부스 재고·상태·셀프선택 판매 관리"),
|
||
("M02-BATCH-01","부스 배치 3안 생성 엔진","배치","SCR-03","제약 솔버+휴리스틱, 목표 다양화 3안"),
|
||
("M02-RULE-01","배치 규정 자동검증","API","SCR-03","통로·비상구·바닥하중·복층 위반 플래깅"),
|
||
("M02-ONL-01","부스 배치 에디터","온라인","SCR-03","PostGIS 폴리곤 캔버스 편집·확정"),
|
||
("M02-ONL-02","배치안 비교·병합","온라인","SCR-04","레이어 토글·병합→규정 재검증(S7 조감)"),
|
||
("M03-DSN-01","부스 설계 3안 생성","API","SCR-06","레이아웃·파라메트릭 구조 3안(용도 다양화)"),
|
||
("M03-ONL-01","부스 설계 스튜디오","온라인","SCR-06","조립/독립 설계·3D 프리뷰·선택/병합"),
|
||
("M03-RULE-01","설계 규정 사전검증","API","SCR-09","높이5m·리깅·복층·방염·금지작업 플래깅"),
|
||
("M04-WIRE-01","배선 최단경로 산출 엔진","API","SCR-07","트렌치→분전반 통로횡단 최소 배선(PostGIS)"),
|
||
("M04-ONL-01","유틸리티 배선 뷰","온라인","SCR-07","전기/네트워크/급배수 배선·조명 배치"),
|
||
("M04-ONL-02","신청 요약·위치표시도","온라인","SCR-08","단말 클릭→위치표시도 자동생성·견적"),
|
||
("M05-API-01","RenderJob 큐 발행","API","SCR-12","씬 컴파일→스키마해시→Redis 큐 발행"),
|
||
("M05-WKR-01","나노바나나 렌더 워커","워커","SCR-12","Gemini image-to-image·방어·워터마크"),
|
||
("M05-ONL-01","시각화 갤러리","온라인","SCR-12","표준 샷 S1~S7·Before/After 슬라이더"),
|
||
], note="M2~M5 = 핵심 차별화 P0. 배치·배선은 결정적 알고리즘(솔버) 중심, LLM은 조건해석 보조.")
|
||
|
||
# 목록 4 — M6~M9, M15
|
||
list_slide("프로그램 목록 (4/6) — M6~M9 판매운영 · M15 옥션", [
|
||
("M06-ONL-01","서류·마일스톤 워크플로","온라인","SCR-22","D-150/30/25/7 마일스톤·서류 상태"),
|
||
("M06-DOC-01","신고서류 웹폼→HWP/PDF","API","SCR-23","7종 서식 자동생성·LLM 누락/불일치 검수"),
|
||
("M06-BAT-01","마일스톤 D-데이 알림","배치","SCR-40","역산 리마인더 스케줄 발행→알림센터"),
|
||
("M07-ONL-01","등록업체 매칭·RFQ","온라인","SCR-24","규모·업종·예산·지역 추천→RFQ 발송"),
|
||
("M07-BAT-01","등록업체 DB 주기 수집","배치","SCR-A7","14분류×739개 공개데이터 수집·정규화"),
|
||
("M08-ONL-01","반입/반출 슬롯 예약","온라인","SCR-25","하역장 슬롯·중량물 우선·통행증 QR"),
|
||
("M09-ONL-01","정산·결제 대시보드","온라인","SCR-21","납부 스케줄·PG 결제·예치금 정산 내역"),
|
||
("M09-BAT-01","납부 스케줄 자동 생성","배치","SCR-21","계약금20/중도금30+20/잔금30+예치금"),
|
||
("M15-ONL-01","옥션 목록·개설","온라인","SCR-26","유형·라운드·마감·낙찰기준·가중치 설정"),
|
||
("M15-ONL-02","옥션 상세·실시간 순위","온라인/WS","SCR-27","자료열람·응찰·실시간 순위 WebSocket"),
|
||
("M15-DOC-01","견적서 작성·PDF 산출","API","SCR-28","라인아이템·총액·부가세·버전·PDF"),
|
||
("M15-AWD-01","낙찰 비교·선정(Award)","API","SCR-29","최저가/종합점수(가격+평판+납기) 비교"),
|
||
], note="M15 = 폐루프 연결고리. M2~M5 산출물(물량서·시공 예상 이미지)이 응찰 근거 자료.")
|
||
|
||
# 목록 5 — M10~M14 관람·현장
|
||
list_slide("프로그램 목록 (5/6) — M10~M14 관람 · 참가 · 현장", [
|
||
("M10-ONL-01","관람객 등록 관리","온라인","SCR-30","유형별 사전등록 폼·현황 대시보드"),
|
||
("M10-BADGE-01","배지/QR 발급","API","SCR-P4","모바일 배지/QR 발급·배지 템플릿(M17)"),
|
||
("M10-ONL-02","현장 체크인","온라인","SCR-31","QR 체크인·즉석 배지·오프라인 폴백"),
|
||
("M10-LEAD-01","리드캡처·AI 스코어링","API","SCR-32","배지 QR 스캔→연락처·관심도·팔로업"),
|
||
("M11-MATCH-01","AI 미팅 추천","API","SCR-M8","프로필·intent 매칭→미팅 슬롯 예약"),
|
||
("M12-EDM-01","EDM·캠페인 관리","배치","SCR-33","세그먼트 캠페인·리마인더·수신동의"),
|
||
("M12-PUB-01","공개 홍보 사이트","온라인","SCR-P1","행사소개·일정·교통, SEO·다국어(한영중일)"),
|
||
("M12-ONL-01","스폰서십 패키지 판매","온라인","SCR-34","스폰서 등급·재고·판매 관리"),
|
||
("M13-NAV-01","실내 내비 경로 산출","API","SCR-M6","블루닷/존레벨 경로(부스·시설·비상구)"),
|
||
("M14-ONL-01","홀 현장운영 대시보드","온라인","SCR-14","입장·혼잡·전력부하·주차·안전 플래그"),
|
||
])
|
||
|
||
# 목록 6 — M16~M18 경영·콘텐츠·관리
|
||
list_slide("프로그램 목록 (6/6) — M16 BI · M17 CMS · M18 관리", [
|
||
("M16-ETL-01","BI 데이터마트 적재","배치","SCR-13","스타스키마 Fact/Dim 야간 적재(KpiSnapshot)"),
|
||
("M16-ONL-01","경영분석 대시보드","온라인","SCR-13","가동률·매출구성·P&L·리텐션·수요예측"),
|
||
("M17-ONL-01","CMS 콘텐츠·게시 워크플로","온라인","SCR-35","초안→검수→게시·버전·다국어"),
|
||
("M17-ONL-02","마이크로사이트 편집기","온라인","SCR-36","참가업체 부스소개·제품·예상샷 게시"),
|
||
("M17-ONL-03","다국어 콘텐츠 관리","온라인","SCR-37","한/영/중/일 콘텐츠·번역 검수"),
|
||
("M18-ONL-01","마스터데이터 관리","온라인","SCR-A7","홀·요율·유틸요금·부스표준·등록업체"),
|
||
("M18-ONL-02","규정 룰셋 버전 관리","온라인","SCR-A8","연 단위 요율·규정 개정 버전·이력"),
|
||
("M18-ONL-03","테넌트 관리·온보딩","온라인","SCR-A9","전시관(테넌트) 마스터·온보딩(플랫폼관)"),
|
||
("M18-ONL-04","AI 플랫폼 설정(AiConfig)","온라인","SCR-A10","Claude 기본·설정형 전환·폴백"),
|
||
], note="M18 = §5B-1 시스템관리 + 킨텍스 마스터데이터 확장. 테넌트/AiConfig는 v3.0·F100 반영.")
|
||
|
||
# ================================================================
|
||
# 5. 핵심 프로그램 상세 사양
|
||
# ================================================================
|
||
divider("03", "핵심 프로그램 상세 사양", "PROGRAM DETAIL SPEC",
|
||
["모듈 대표 20개 프로그램", "입력·출력·처리로직", "관련 테이블·예외 처리"])
|
||
|
||
def detail_slide(idx_total, d):
|
||
s = slide()
|
||
page_bg(s, BG)
|
||
rect(s, 0, 0, 13.333, 0.12, fill=BLUE)
|
||
cl = d.get("color", BLUE)
|
||
# PGM-ID chip
|
||
rect(s, 0.55, 0.42, 2.35, 0.62, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.55, 0.42, 2.35, 0.62, d["id"], size=15, color=WHITE, bold=True,
|
||
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 3.02, 0.4, 8.5, 0.34, d["name"], size=18, color=INK, bold=True)
|
||
text(s, 3.02, 0.78, 9.7, 0.26, d["module"], size=9.5, color=MUTED)
|
||
# 메타 칩 (유형·화면)
|
||
chip(s, 10.6, 0.42, 1.05, 0.3, d["type"], cl, WHITE, size=9)
|
||
chip(s, 11.72, 0.42, 1.06, 0.3, d["scr"], RGBColor(0x2B,0x4C,0x7E), WHITE, size=9)
|
||
text(s, 10.6, 0.78, 2.2, 0.24, f"상세사양 {idx_total}/20", size=8, color=MUTED, align=PP_ALIGN.RIGHT)
|
||
rect(s, 0.55, 1.18, 12.23, 0.02, fill=LINE)
|
||
text(s, 0.55, 7.06, 8, 0.3, FOOT, size=8.5, color=MUTED)
|
||
text(s, 11.3, 7.06, 1.48, 0.3, d["id"], size=8.5, color=MUTED, align=PP_ALIGN.RIGHT)
|
||
|
||
def block(x, y, w, h, title, tcol, items, numbered=False):
|
||
card(s, x, y, w, h)
|
||
rect(s, x, y+0.12, 0.09, h-0.24, fill=tcol, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x+0.26, y+0.13, w-0.4, 0.3, title, size=10.5, color=tcol, bold=True)
|
||
yy = y + 0.5
|
||
for i,it in enumerate(items):
|
||
if numbered:
|
||
rect(s, x+0.26, yy+0.015, 0.26, 0.22, fill=tcol, shape=MSO_SHAPE.OVAL)
|
||
text(s, x+0.26, yy, 0.26, 0.22, str(i+1), size=8, color=WHITE, bold=True,
|
||
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, x+0.62, yy-0.02, w-0.78, 0.44, it, size=8.6, color=INK, line_spacing=0.92)
|
||
else:
|
||
rect(s, x+0.28, yy+0.07, 0.08, 0.08, fill=tcol, shape=MSO_SHAPE.OVAL)
|
||
text(s, x+0.5, yy-0.01, w-0.66, 0.44, it, size=8.7, color=INK, line_spacing=0.92)
|
||
yy += d.get("lh", 0.315)
|
||
return
|
||
|
||
# 좌: 기능설명 / 입력 / 출력
|
||
block(0.55, 1.32, 6.0, 1.42, "기능 설명", cl, d["desc"])
|
||
block(0.55, 2.86, 2.9, 3.05, "입력 (Input)", BLUE_DK, d["input"])
|
||
block(3.65, 2.86, 2.9, 3.05, "출력 (Output)", BLUE_DK, d["output"])
|
||
# 우: 처리 로직 / 테이블 / 예외
|
||
block(6.72, 1.32, 6.06, 3.05, "처리 로직", PURPLE, d["logic"], numbered=True)
|
||
block(6.72, 4.49, 6.06, 1.42, "예외 처리", AMBER, d["exc"])
|
||
# 하단: 관련 테이블 스트립
|
||
rect(s, 0.55, 6.05, 6.0, 0.86, fill=HALL_BG, line=LINE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.75, 6.14, 5.7, 0.26, "관련 테이블 / 데이터", size=9.5, color=BLUE_DK, bold=True)
|
||
text(s, 0.75, 6.42, 5.7, 0.44, " · ".join(d["tables"]), size=8.6, color=INK, line_spacing=0.95)
|
||
# 하단 우: 연동
|
||
rect(s, 6.72, 6.05, 6.06, 0.86, fill=BLUE_LT, line=BLUE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 6.92, 6.14, 5.8, 0.26, "연동 / 비고", size=9.5, color=BLUE_DK, bold=True)
|
||
text(s, 6.92, 6.42, 5.8, 0.44, d["link"], size=8.6, color=INK, line_spacing=0.95)
|
||
|
||
DETAILS = [
|
||
{"id":"M01-QUOTE-01","name":"자동 견적 계산 엔진","type":"API","scr":"SCR-18","color":BLUE,
|
||
"module":"M1 행사·홀 배정 및 자동 견적","lh":0.30,
|
||
"desc":["규칙 엔진이 홀/반홀·기간·성수기 조건으로 임대 견적을 즉시 산정",
|
||
"HWP 신청→협의→견적(수일)을 즉시 자동 회신으로 전환"],
|
||
"input":["행사 기간(장치·전시·철거일)","홀/반홀 선택·면적(㎡)","부대(로비·옥외·회의실)","요율 룰셋 버전(테넌트)"],
|
||
"output":["임대료·관리비 예치금 소계","성수기/전시장 계수 반영 총액","견적 명세(항목별) JSON","킨텍스 제출 신청서식"],
|
||
"logic":["요율 마스터·성수기 규칙 로딩(tenant_id 스코프)",
|
||
"기본액 = 2,250원/㎡ × 면적 × 일수",
|
||
"계수 적용: 성수기 +10% / 비수기 -10% / 1전시장 +10%",
|
||
"초과시간(1일 임대료 1/10)·용도외 30% 할증 가산",
|
||
"예치금 15~20% 산출 → 견적 명세 조립·반환"],
|
||
"tables":["hall_master","rate_master","event","quotation_estimate"],
|
||
"exc":["요율 룰셋 미확정 → RULESET_NOT_FOUND 반환","면적/기간 유효성 위반 → VALIDATION 오류"],
|
||
"link":"연동 M9 정산·결제 · 행사일정 DB. 최종 배정 확정은 킨텍스 내부 의사결정(가견적까지)."},
|
||
|
||
{"id":"M02-BATCH-01","name":"부스 배치 3안 생성 엔진","type":"배치","scr":"SCR-03","color":PURPLE,
|
||
"module":"M2 플로어플랜 스튜디오 (P0)","lh":0.30,
|
||
"desc":["제약충족 솔버+휴리스틱으로 서로 다른 최적화 목표의 배치 정확히 3안 생성",
|
||
"생성형 LLM이 아닌 결정적 알고리즘 중심(LLM은 조건 해석 보조)"],
|
||
"input":["홀 선택·실측 지오메트리","목표 부스 수·프리미엄 비율","주출입구·무대·라운지 조건","통로폭·비상구·트렌치 제약"],
|
||
"output":["배치 3안(부스 폴리곤 집합)","안별 최적화 지표(부스수·동선·안전)","안별 규정검증 결과","S7 조감 시각화 입력"],
|
||
"logic":["홀 좌표계·제약(바닥하중·비상구·트렌치) 로딩",
|
||
"목표별 파라미터 분기(1안 부스수 / 2안 동선 / 3안 안전)",
|
||
"솔버로 부스 폴리곤 배치·통로 확보",
|
||
"각 안 M02-RULE-01 규정검증 호출",
|
||
"3안 + 지표·위반 요약 저장·반환"],
|
||
"tables":["hall_master","booth(PostGIS polygon)","design_plan(버전·출처)","trench_grid"],
|
||
"exc":["제약 충족 해 없음 → 부분안+완화 제안","트렌치 좌표 미확보 → 근사도면 경고 라벨"],
|
||
"link":"연동 M02-RULE-01·M03·M5·M6(제출 서류). 소방 최종 유권해석은 관할기관."},
|
||
|
||
{"id":"M02-RULE-01","name":"배치 규정 자동검증","type":"API","scr":"SCR-03·09","color":PURPLE,
|
||
"module":"M2 플로어플랜 스튜디오 (P0)","lh":0.30,
|
||
"desc":["배치안을 수치 규정에 대조해 위반을 차단(hard)·경고(soft)로 플래깅",
|
||
"병합/편집 결과는 재검증 필수(제약 파손 가능)"],
|
||
"input":["대상 배치안(부스 폴리곤)","홀 규정 룰셋(버전)","바닥하중·비상구·통로 기준","복층 가능 홀 매트릭스"],
|
||
"output":["위반 목록(항목·좌표·등급)","차단/경고 분류","검증 통과 여부(boolean)","검수 리포트(SCR-09)"],
|
||
"logic":["룰셋 로딩(tenant_id·버전 고정)",
|
||
"피난 통로폭·비상구 접근 거리 판정",
|
||
"홀별 바닥하중(2~5t/㎡)·복층 1/2 판정",
|
||
"이격(벽30cm·천장60cm)·소방 체크리스트 대조",
|
||
"위반 등급화 → hard=확정 차단, soft=경고"],
|
||
"tables":["ruleset_version","booth","hall_master","compliance_result"],
|
||
"exc":["룰셋 버전 불일치 → 최신 버전 강제·경고","좌표 결손 폴리곤 → 검증 제외·플래그"],
|
||
"link":"연동 M2·M3(SCR-09)·M10 승인. 최종 판정은 사람(홀매니저·구조기술사)."},
|
||
|
||
{"id":"M03-DSN-01","name":"부스 설계 3안 생성","type":"API","scr":"SCR-06","color":PURPLE,
|
||
"module":"M3 부스 설계 스튜디오 (P0)","lh":0.30,
|
||
"desc":["부스 크기·업종·예산 입력으로 레이아웃·파라메트릭 구조 초안 3안 생성",
|
||
"조립부스는 옵션 선택형, 독립부스는 AI 초안+장치업체 편집"],
|
||
"input":["부스 좌표·크기(M2)","업종·전시품·예산","용도존(시연/상담/창고)","조립/독립 유형"],
|
||
"output":["설계 3안(벽체·트러스·사인)","존 배치·집기 블록","M5 예상 사진 프롬프트 입력","규정 사전검증 플래그"],
|
||
"logic":["부스 골격·표준 자재 카탈로그 로딩",
|
||
"목표별 3안 분기(시연/상담/예산절감)",
|
||
"파라메트릭 구조·존 레이아웃 생성",
|
||
"M03-RULE-01 사전검증 호출(높이·방염)",
|
||
"3안 저장 → 선택/병합 대기"],
|
||
"tables":["booth","design_plan","material_catalog","design_zone"],
|
||
"exc":["예산/크기 비현실 → 경고·근사안","업로드 도면 인식 저정확 → 참고용 라벨"],
|
||
"link":"연동 M7 매칭·M5 시각화·kxwp 제출. 시공 상세도·구조판정은 장치업체 책임."},
|
||
|
||
{"id":"M04-WIRE-01","name":"배선 최단경로 산출 엔진","type":"API","scr":"SCR-07","color":PURPLE,
|
||
"module":"M4 유틸리티 설계 (P0)","lh":0.30,
|
||
"desc":["단말 위치에서 최근접 트렌치까지 통로 횡단 최소 배선 경로를 PostGIS로 산출",
|
||
"위치표시도 수기 작도를 자동 생성으로 대체"],
|
||
"input":["부스 내 단말 좌표(전기·LAN·급배수)","홀 트렌치 그리드 좌표","기기 목록·kW/회선","유틸리티 요금 마스터"],
|
||
"output":["배선 LineString(전기적/망청/급배수녹)","분전반 용량·수량","위치표시도·자동 견적","S6 오버레이 입력"],
|
||
"logic":["트렌치 그리드·공급 매트릭스 로딩",
|
||
"단말→최근접 트렌치 매칭(ST_ClosestPoint)",
|
||
"통로 횡단 최소 경로 산출(ST_ShortestLine)",
|
||
"kW 합산→분전반 용량·수량 산정",
|
||
"요금 견적·위치표시도 조립·반환"],
|
||
"tables":["trench_grid","utility_order(LineString)","rate_utility","booth"],
|
||
"exc":["트렌치 좌표 미확보 → 산출 보류·근사 경고","인터넷 마감(D-25) 초과 → 신청 차단 안내"],
|
||
"link":"연동 M9 결제·M5(S6 오버레이는 백엔드 래스터 우선)·kxwp/사무국 릴레이."},
|
||
|
||
{"id":"M05-WKR-01","name":"나노바나나 렌더 워커","type":"워커","scr":"SCR-12","color":PURPLE,
|
||
"module":"M5 나노바나나 시각화 (P0·핵심 차별화)","lh":0.30,
|
||
"desc":["Redis 큐의 RenderJob을 소비해 Gemini image-to-image로 시공 후 예상 사진 생성",
|
||
"ReRoomAI 방어 로직 이식 + 워터마크·고지 강제"],
|
||
"input":["RenderJob(씬 스키마·샷 프리셋)","참조 이미지(간이 렌더·홀 사진)","프롬프트 조각(유형·스타일·레이어)","행사별 렌더 쿼터"],
|
||
"output":["생성 이미지(오브젝트 스토리지)","워터마크·메타데이터 임베드","WebSocket 완료 푸시","스키마 해시 캐시"],
|
||
"logic":["큐 소비 → 크기 가드(8MB·base64 재검증)",
|
||
"참조 이미지 1024px 정규화·mimeType 감지",
|
||
"Gemini generateContent 호출(inlineData+text)",
|
||
"SAFETY/quota 에러 분기→친화 메시지",
|
||
"성공 시에만 쿼터 차감·워터마크→푸시"],
|
||
"tables":["render_job(상태·이미지)","render_quota","object_storage(메타)"],
|
||
"exc":["finishReason=SAFETY → 차단 에러","RESOURCE_EXHAUSTED/429 → 재시도·상한","한글 간판 렌더 불안정 → 후처리 합성"],
|
||
"link":"연동 M2~M4(자료)·WebSocket 푸시·M15 응찰 근거. API키는 서버 env(GEMINI_API_KEY)."},
|
||
|
||
{"id":"M06-DOC-01","name":"신고서류 웹폼→HWP/PDF","type":"API","scr":"SCR-23","color":BLUE,
|
||
"module":"M6 서류·마일스톤 워크플로","lh":0.30,
|
||
"desc":["7종 신고서류를 웹폼 입력으로 킨텍스 제출 형식(HWP/PDF) 자동 생성",
|
||
"LLM이 누락 항목·배치도-운영계획서 불일치 검수"],
|
||
"input":["서식 유형·행사·부스 데이터","배치도(M2)·정산(M9) 참조","작성자 입력 필드","서식 템플릿(마스터)"],
|
||
"output":["HWP/PDF 제출 파일","검수 요약 리포트(누락·불일치)","마일스톤 제출 상태","업로드 안내 릴레이"],
|
||
"logic":["서식 템플릿·행사 데이터 병합",
|
||
"필수 항목 누락 검출",
|
||
"배치도-운영계획서 부스수 등 교차 검증",
|
||
"HWP/PDF 렌더링·서명 필드 생성",
|
||
"검수 리포트→홀매니저 전달"],
|
||
"tables":["document","doc_template","milestone","event"],
|
||
"exc":["필수 필드 누락 → 생성 차단·목록 반환","AI 생성 이미지 포함 시 → 서류에서 자동 배제"],
|
||
"link":"연동 kxwp(제출 릴레이·API 미공개)·M10 승인. 계약·심사 서류엔 도면만 유효."},
|
||
|
||
{"id":"M07-ONL-01","name":"등록업체 매칭·RFQ","type":"온라인","scr":"SCR-24","color":BLUE,
|
||
"module":"M7 등록업체 매칭·견적","lh":0.30,
|
||
"desc":["14분류×739개 등록업체를 조건 기반 추천, 설계안 첨부 RFQ 발송·비교",
|
||
"미등록 업체 시공 엄금 규정을 시스템이 강제"],
|
||
"input":["부스 규모·업종·예산·지역","M3 설계안 첨부","공종 분류 필터","등록업체 DB(검증)"],
|
||
"output":["추천 업체 목록·점수","RFQ 발송·수신 현황","견적 비교표","옥션 초대 자격(적격)"],
|
||
"logic":["등록업체 DB 조회·자격 검증",
|
||
"조건 매칭 점수화·정렬",
|
||
"RFQ에 설계안·물량 첨부 발송",
|
||
"회신 견적 취합·비교표 구성",
|
||
"적격 업체만 M15 옥션 초대 허용"],
|
||
"tables":["company(등록업체)","rfq","rfq_quote","booth"],
|
||
"exc":["미등록 업체 초대 시도 → 원천 차단","응답 없음 → 리마인더·대체 추천"],
|
||
"link":"연동 M3(설계)·M15(옥션 자격 게이트)·CCPY 공개 DB 수집(M07-BAT-01)."},
|
||
|
||
{"id":"M09-BAT-01","name":"납부 스케줄 자동 생성","type":"배치","scr":"SCR-21","color":BLUE,
|
||
"module":"M9 정산·결제","lh":0.30,
|
||
"desc":["계약 확정 시 단계별 납부 스케줄을 자동 생성하고 역산 알림을 발행",
|
||
"예치금 대비 실사용 정산 내역 투명화"],
|
||
"input":["계약 총액·확정일","납부 규칙(20/30+20/30)","예치금 비율(15~20%)","행사 일정"],
|
||
"output":["납부 스케줄(회차·금액·기일)","알림센터 리마인더 이벤트","예치금 정산 기준","결제 대기 항목"],
|
||
"logic":["계약 총액·규칙 로딩",
|
||
"계약금20→중도금30+20→잔금30 분할",
|
||
"예치금 15~20% 별도 스케줄",
|
||
"기일 역산 알림 이벤트 발행",
|
||
"PG 결제 대기 항목 생성"],
|
||
"tables":["payment","payment_schedule","event","rate_master"],
|
||
"exc":["계약 미확정 → 스케줄 생성 보류","PG 실패 → 재시도·수동 처리 플래그"],
|
||
"link":"연동 M1 견적·PG 결제·CMN-ALRM 알림. 세금계산서 등 재무연동 협의."},
|
||
|
||
{"id":"M15-ONL-02","name":"옥션 상세·실시간 순위·응찰","type":"온라인/WS","scr":"SCR-27","color":BLUE,
|
||
"module":"M15 공사/장치 옥션 (핵심 플로우)","lh":0.30,
|
||
"desc":["적격 등록업체가 AI 자료 열람 후 견적서로 응찰, 실시간 순위를 WebSocket으로 노출",
|
||
"역경매(라운드 내 인하)·마감 자동 처리"],
|
||
"input":["옥션(유형·라운드·마감·기준)","M2~M5 자료·물량서","응찰 견적서(총액·납기)","업체 자격(M7 검증)"],
|
||
"output":["실시간 순위(현재/최저/내 위치)","응찰 이력(버전)","라운드 마감 상태","비교표 입력(Award)"],
|
||
"logic":["응찰 자격 검증(등록업체만)",
|
||
"AI 자료 열람 권한 부여",
|
||
"견적서 제출→종합점수 산정",
|
||
"실시간 순위 WebSocket 브로드캐스트",
|
||
"마감 도달→자동 마감·집계"],
|
||
"tables":["auction","quotation(=bid·버전)","company","render_job"],
|
||
"exc":["미등록 응찰 → 차단","마감 후 제출 → 거부","동점 → 납기·평판 tie-break"],
|
||
"link":"연동 M2~M5·M7·M15-AWD(낙찰)·M6/M9. 익명 순위 옵션 지원."},
|
||
|
||
{"id":"M15-AWD-01","name":"낙찰 비교·선정(Award)","type":"API","scr":"SCR-29","color":BLUE,
|
||
"module":"M15 공사/장치 옥션 (핵심 플로우)","lh":0.30,
|
||
"desc":["최저가 또는 종합평가(가격+평판+납기) 가중 스코어로 견적서를 비교·낙찰",
|
||
"낙찰 견적서를 계약/발주 문서로 전환"],
|
||
"input":["옥션 응찰 견적서 집합","낙찰기준·가중치","평판 데이터(규정준수·평점)","납기 조건"],
|
||
"output":["항목별 비교표·순위","낙찰(Award) 결과·사유","계약/발주 링크(M6/M9)","시공 일정 연계(M8)"],
|
||
"logic":["응찰 견적서 정규화(사양 정렬)",
|
||
"가격/평판/납기 점수화·가중합",
|
||
"최저가 or 종합점수 순위 산정",
|
||
"전시업체 낙찰 선정(사유 기록)",
|
||
"낙찰서→계약/발주 전환·감사기록"],
|
||
"tables":["auction","quotation","award","company_reputation"],
|
||
"exc":["유효 응찰 0건 → 유찰·재개설","가중치 합≠100% → 검증 오류"],
|
||
"link":"연동 M6 계약·M9 발주정산·M8 시공일정·SYS-AUDIT(낙찰 전수기록)."},
|
||
|
||
{"id":"M10-BADGE-01","name":"배지/QR 발급","type":"API","scr":"SCR-P4","color":BLUE,
|
||
"module":"M10 관람객 등록·배지·체크인","lh":0.30,
|
||
"desc":["사전등록 관람객에게 모바일 배지/QR을 발급, 현장 즉석 배지 인쇄 대비",
|
||
"배지 템플릿은 M17 CMS 관리"],
|
||
"input":["관람객 등록 정보(유형)","배지 템플릿(M17)","행사·부스 매핑","개인정보 동의 상태"],
|
||
"output":["모바일 배지/QR(서명)","배지 인쇄 데이터","체크인 검증 토큰","리드 스캔 대상 매핑"],
|
||
"logic":["등록·동의 상태 검증",
|
||
"배지 템플릿 병합·QR 토큰 생성",
|
||
"서명·만료 정책 적용",
|
||
"모바일 발급·인쇄 데이터 산출",
|
||
"체크인/리드 스캔 매핑 등록"],
|
||
"tables":["visitor","registration","badge","badge_template"],
|
||
"exc":["개인정보 미동의 → 발급 보류","중복 등록 → 기존 배지 재사용"],
|
||
"link":"연동 M10 체크인·M10-LEAD·M13 wayfinding. PII 동의·보존정책 필수(R10)."},
|
||
|
||
{"id":"M10-LEAD-01","name":"리드캡처·AI 스코어링","type":"API","scr":"SCR-32","color":BLUE,
|
||
"module":"M10 관람객 (참가업체 리드)","lh":0.30,
|
||
"desc":["참가업체가 배지 QR을 스캔해 연락처·관심도·메모를 저장하고 AI로 스코어링",
|
||
"팔로업 EDM(M12)·ROI 측정(M16)으로 연계"],
|
||
"input":["스캔 배지 QR 토큰","관심도 평점·메모","부스·참가업체 컨텍스트","동의 범위"],
|
||
"output":["리드 레코드(연락처·점수)","AI 리드 스코어","팔로업 세그먼트","리드 접근 감사로그"],
|
||
"logic":["QR 토큰 검증·관람객 해소",
|
||
"동의 범위 내 연락처 취득",
|
||
"관심도·행동신호 AI 스코어링",
|
||
"리드 저장·세그먼트 분류",
|
||
"리드 접근 감사로그 기록"],
|
||
"tables":["lead","visitor","booth","audit_log"],
|
||
"exc":["동의 범위 밖 필드 → 마스킹","오프라인 스캔 → 큐 저장 후 동기화"],
|
||
"link":"연동 M12 EDM·M16 BI. 리드 접근은 개인정보 전수 감사(§5B-1)."},
|
||
|
||
{"id":"M11-MATCH-01","name":"AI 미팅 추천","type":"API","scr":"SCR-M8","color":BLUE,
|
||
"module":"M11 비즈니스 매칭","lh":0.30,
|
||
"desc":["프로필·관심업종·의향(intent) 기반으로 미팅을 추천하고 슬롯 예약",
|
||
"부스 위치(M2)·wayfinding(M13) 길안내 연계"],
|
||
"input":["관람/참가 프로필","관심 업종·intent 신호","부스 업종 태그(M3)","가용 미팅 슬롯"],
|
||
"output":["매칭 추천 목록·점수","미팅 슬롯 예약","길안내 경로(M13)","매칭 성과(M16)"],
|
||
"logic":["프로필·행동신호 수집",
|
||
"관심·의향 임베딩 유사도 산정",
|
||
"상호 의향 매칭 랭킹",
|
||
"미팅 슬롯 예약·충돌 해소",
|
||
"부스 위치·경로 연계 반환"],
|
||
"tables":["visitor","match_recommendation","meeting_slot","booth"],
|
||
"exc":["프로필 결손 → 저신뢰 라벨","외부 모델 게이트 위반 → 승인 모델만"],
|
||
"link":"연동 M10·M13·M16. AI 추천은 온프레미스/승인 모델 범위(R12)."},
|
||
|
||
{"id":"M16-ETL-01","name":"BI 데이터마트 적재","type":"배치","scr":"SCR-13","color":PURPLE,
|
||
"module":"M16 경영분석 BI (P1 승격)","lh":0.30,
|
||
"desc":["운영 DB를 스타 스키마 데이터마트로 야간 집계해 운영 부하 없이 지표 제공",
|
||
"운영사 관점·참가사 관점 지표 분리"],
|
||
"input":["M1 배정·M9 정산 이력","M10 관람·M15 옥션","행사일정·비용 마스터","전 적재 스냅샷"],
|
||
"output":["Fact/Dim 테이블 적재","KpiSnapshot(가동률·매출·P&L)","수요예측·수율 입력","이상탐지 대상"],
|
||
"logic":["증분 소스 추출(읽기 복제)",
|
||
"DimHall/Event/Date/Exhibitor 갱신",
|
||
"FactBooking/Settlement/Utility/Auction/Visitor 적재",
|
||
"KPI 집계·스냅샷 생성",
|
||
"품질 검증→커밋(멱등)"],
|
||
"tables":["fact_booking","fact_settlement","dim_hall","kpi_snapshot"],
|
||
"exc":["소스 정합 결손 → 부분적재·경고","적재 실패 → 재시도·이전 스냅샷 유지"],
|
||
"link":"연동 전 모듈(소비)·M18 권한. 크로스테넌트 집계는 플랫폼 슈퍼관리자 전용."},
|
||
|
||
{"id":"M17-ONL-01","name":"CMS 콘텐츠·게시 워크플로","type":"온라인","scr":"SCR-35","color":PURPLE,
|
||
"module":"M17 CMS","lh":0.30,
|
||
"desc":["전시 콘텐츠·공지를 초안→검수→게시 워크플로와 버전관리로 발행",
|
||
"다국어(한/영/중/일)·사이니지 연계"],
|
||
"input":["콘텐츠 초안·미디어","게시 대상·채널","다국어 번역","승인자·게시 일정"],
|
||
"output":["게시 콘텐츠(버전)","예약 게시 큐","다국어 렌더","사이니지 배포 콘텐츠"],
|
||
"logic":["콘텐츠 작성·미디어 첨부",
|
||
"검수 요청→승인/반려",
|
||
"다국어 번역 검수",
|
||
"게시/예약 게시 처리",
|
||
"사이니지·공개사이트 배포"],
|
||
"tables":["content(버전)","content_publish","content_i18n","microsite"],
|
||
"exc":["승인 없이 게시 → 차단","번역 누락 → 기본 로케일 폴백"],
|
||
"link":"연동 M12 마케팅·M13 wayfinding·M10 배지. 공지 채널은 CMN-NOTI와 정합."},
|
||
|
||
{"id":"M18-ONL-02","name":"규정 룰셋 버전 관리","type":"온라인","scr":"SCR-A8","color":PURPLE,
|
||
"module":"M18 관리자 백오피스","lh":0.30,
|
||
"desc":["요율·규정 룰셋을 버전으로 관리해 연 단위 개정을 안전하게 반영",
|
||
"룰 엔진(M1·M2 검증)이 버전 고정으로 참조"],
|
||
"input":["룰셋 초안(요율·규정 값)","적용 시작일·테넌트","개정 사유·근거","승인자"],
|
||
"output":["룰셋 버전(불변 스냅샷)","활성 버전 포인터","개정 이력·감사로그","엔진 참조 캐시 무효화"],
|
||
"logic":["룰셋 초안 작성·검증",
|
||
"버전 스냅샷 생성(불변)",
|
||
"승인→활성 버전 전환(시작일)",
|
||
"엔진 캐시 무효화·재로딩",
|
||
"개정 감사로그 기록"],
|
||
"tables":["ruleset_version","rate_master","audit_log","tenant"],
|
||
"exc":["활성 버전 삭제 시도 → 차단","시작일 소급 → 경고·승인 필요"],
|
||
"link":"연동 M1 견적·M2 검증·전 모듈. 테넌트 스코프 격리(§1A)."},
|
||
|
||
{"id":"SYS-AUTH-02","name":"2차 인증 OTP(TOTP) 검증","type":"API","scr":"SCR-51","color":BLUE_DK,
|
||
"module":"§5B-3 인증 (UIWS 표준 이식)","lh":0.30,
|
||
"desc":["TOTP(RFC6238)로 로그인 2단계 인증을 수행하고 QR 등록·재설정을 제공",
|
||
"관리자·홀매니저·주최자·업체 필수, 관람객 선택"],
|
||
"input":["1차 인증 통과 컨텍스트","OTP 6자리 코드","OTP 시크릿(등록 시)","서버 시간"],
|
||
"output":["OTP 검증 결과","QR 등록 데이터(최초)","실패 카운트·잠금 상태","인증 완료 토큰"],
|
||
"logic":["사용자 OTP 시크릿 조회",
|
||
"SHA1·30s·±1 윈도우 코드 산정",
|
||
"입력 코드 대조",
|
||
"실패 시 카운트 증가·잠금 판정",
|
||
"성공→인증 완료·감사기록"],
|
||
"tables":["user(otp_secret)","login_attempt","audit_log"],
|
||
"exc":["코드 불일치 → 실패 카운트·잠금","관리자 초기화 → otp_secret=NULL 재등록"],
|
||
"link":"연동 SYS-AUTH-01·SYS-AUTH-03. 시크릿·비번은 응답/로그 미노출."},
|
||
|
||
{"id":"SYS-RBAC-01","name":"역할·권한(RBAC) 관리","type":"온라인","scr":"SCR-A2","color":BLUE_DK,
|
||
"module":"§5B-1 시스템관리 / M18","lh":0.30,
|
||
"desc":["플랫폼·행사 이중 평가 권한 모델을 관리(테넌트 격리 ⊃ 행사 RBAC)",
|
||
"역할별 포털 IA·메뉴 접근을 구동"],
|
||
"input":["역할 정의·계층","권한(리소스·액션) 매핑","테넌트·행사 스코프","사용자-역할 배정"],
|
||
"output":["역할-권한 매트릭스","메뉴·API 게이트 규칙","이중 평가 정책","권한 변경 감사로그"],
|
||
"logic":["역할 계층 로딩(플랫폼/테넌트/행사)",
|
||
"권한 매핑 편집·검증",
|
||
"테넌트 격리 제약 적용",
|
||
"메뉴·API 게이트 규칙 생성",
|
||
"변경 감사로그 기록"],
|
||
"tables":["role","permission","user_role","menu"],
|
||
"exc":["테넌트 경계 위반 배정 → 차단","슈퍼관리자 외 크로스테넌트 → 거부"],
|
||
"link":"연동 SYS-MENU·전 포털. 행사 RBAC는 테넌트 내부로 스코프(§1A-4)."},
|
||
|
||
{"id":"CMN-ALRM-01","name":"알림센터(notification)","type":"온라인/WS","scr":"SCR-47","color":BLUE,
|
||
"module":"§5B-2 공통 업무 기능","lh":0.30,
|
||
"desc":["마감(D-데이)·낙찰·승인·결제 등 이벤트를 단일 채널로 수집·푸시",
|
||
"M10·M12·M15가 이벤트를 발행, 발송 채널은 공통 단일화"],
|
||
"input":["이벤트(유형·대상·페이로드)","수신자·역할","알림 설정·구독","WebSocket 세션"],
|
||
"output":["알림 레코드(읽음 상태)","실시간 WebSocket 푸시","배지 카운트","이메일/모바일 팬아웃"],
|
||
"logic":["이벤트 수신·정규화",
|
||
"수신자 해소(역할·구독)",
|
||
"알림 저장·중복 억제",
|
||
"WebSocket 실시간 푸시",
|
||
"미접속 시 이메일/푸시 팬아웃"],
|
||
"tables":["notification","notification_pref","event_outbox"],
|
||
"exc":["WebSocket 미연결 → 오프라인 큐","발송 실패 → 재시도·데드레터"],
|
||
"link":"연동 M6 마일스톤·M15 낙찰·M9 결제. 마케팅 발송은 M12(수신동의)."},
|
||
]
|
||
for i, d in enumerate(DETAILS):
|
||
detail_slide(i+1, d)
|
||
|
||
# ================================================================
|
||
# 6. 핵심 업무 순서도 (도형 작도 8종)
|
||
# ================================================================
|
||
divider("04", "핵심 업무 순서도", "KEY WORKFLOW FLOWCHARTS",
|
||
["8종 핵심 업무 흐름", "python-pptx 도형 직접 작도", "단자·처리·판단·커넥터"])
|
||
|
||
# --- 순서도 배치 헬퍼 ---
|
||
def after(nodes, h, gap=0.28, cx=None):
|
||
n = nodes[-1]
|
||
return (cx if cx is not None else n["cx"], n["b"] + gap + h/2)
|
||
|
||
# --- FC1: 부스 배치 3안 생성 → 선택/병합 ---
|
||
s = slide()
|
||
content_header(s, "04 · 핵심 업무 순서도 (1/8)", "부스 배치 3안 AI 생성 → 선택 / 병합", 4, chip_txt="F1", chip_col=PURPLE)
|
||
fc_legend(s)
|
||
sp = vchain(s, 2.7, 1.55, [
|
||
(["설계 조건 입력", "(홀·부스수·프리미엄)"], "term"),
|
||
(["제약 로딩(통로·비상구", "·트렌치·바닥하중)"], "proc"),
|
||
(["솔버+휴리스틱 3안 생성", "(목표 다양화)"], "proc"),
|
||
(["3안 규정검증·시각화(S7)"], "io"),
|
||
], w=3.4, h=0.56, gap=0.22)
|
||
dcx, dcy = after(sp, 0.86, gap=0.26)
|
||
dec = fnode(s, dcx, dcy, 2.2, 0.86, ["선택 or", "병합?"], "dec")
|
||
arrow(s, sp[-1]["cx"], sp[-1]["b"], dec["cx"], dec["t"])
|
||
sel = fnode(s, 6.6, dcy-1.45, 3.0, 0.62, ["단일 안 선택"], "proc")
|
||
mrg = fnode(s, 6.6, dcy, 3.0, 0.62, ["레이어 병합 편집", "(안별 블록 조합)"], "proc")
|
||
rechk = fnode(s, 10.4, dcy-0.72, 2.8, 0.66, ["병합/선택안", "규정 재검증"], "proc")
|
||
fin = fnode(s, 7.6, 6.4, 7.4, 0.5, ["최종안 확정·버전기록 → 참가업체 초대링크(M3/M4)"], "proc_ok")
|
||
arrow(s, dec["r"], dec["cy"], sel["l"], sel["cy"], label="선택")
|
||
arrow(s, dec["r"], dec["cy"], mrg["l"], mrg["cy"], label="병합")
|
||
arrow(s, sel["r"], sel["cy"], rechk["l"], rechk["cy"])
|
||
arrow(s, mrg["r"], mrg["cy"], rechk["l"], rechk["cy"])
|
||
arrow(s, rechk["cx"], rechk["b"], rechk["cx"], fin["t"])
|
||
text(s, 0.55, 6.66, 12, 0.3, "메모 규정 재검증에서 위반 발견 시 병합 편집으로 되돌려 재수정 후 재검증(hard 위반은 확정 차단).", size=9, color=MUTED)
|
||
|
||
# --- FC2: 규정 자동검증 ---
|
||
s = slide()
|
||
content_header(s, "04 · 핵심 업무 순서도 (2/8)", "규정 자동검증 (위반 차단 / 경고)", 4, chip_txt="F2", chip_col=PURPLE)
|
||
fc_legend(s)
|
||
sp = vchain(s, 2.9, 1.6, [
|
||
(["배치/설계안 제출"], "term"),
|
||
(["룰셋 버전 로딩", "(tenant·버전 고정)"], "proc"),
|
||
(["규정 항목 순차 판정", "(통로·하중·높이·방염·이격)"], "proc"),
|
||
], w=3.5, h=0.6, gap=0.3)
|
||
dcx, dcy = after(sp, 0.94, gap=0.28)
|
||
dec = fnode(s, 2.9, dcy, 2.4, 0.94, ["위반", "등급?"], "dec")
|
||
arrow(s, sp[-1]["cx"], sp[-1]["b"], dec["cx"], dec["t"])
|
||
hard = fnode(s, 6.95, dcy-1.3, 3.1, 0.66, ["HARD 위반", "→ 제출 차단"], "proc_bad")
|
||
soft = fnode(s, 6.95, dcy, 3.1, 0.66, ["SOFT 위반", "→ 경고(진행 가능)"], "proc")
|
||
none = fnode(s, 6.95, dcy+1.3, 3.1, 0.6, ["위반 없음 → 통과·확정"], "proc_ok")
|
||
rep = fnode(s, 11.45, dcy, 1.5, 2.0, ["검증", "리포트", "(SCR-09)", "위반좌표", "오버레이"], "io")
|
||
arrow(s, dec["r"], dec["cy"], hard["l"], hard["cy"], label="차단")
|
||
arrow(s, dec["r"], dec["cy"], soft["l"], soft["cy"], label="경고")
|
||
arrow(s, dec["r"], dec["cy"], none["l"], none["cy"], label="정상")
|
||
arrow(s, hard["r"], hard["cy"], rep["l"], rep["cy"]-0.6)
|
||
arrow(s, soft["r"], soft["cy"], rep["l"], rep["cy"])
|
||
arrow(s, none["r"], none["cy"], rep["l"], rep["cy"]+0.6)
|
||
text(s, 0.55, 6.66, 12, 0.3, "원칙 hard=확정 차단, soft=경고 후 진행 가능. 최종 판정은 홀매니저·구조기술사(사람).", size=9, color=MUTED)
|
||
|
||
# --- FC3: 배선 최단경로 산출 (단일 수직 흐름) ---
|
||
s = slide()
|
||
content_header(s, "04 · 핵심 업무 순서도 (3/8)", "유틸리티 배선 최단경로 산출 (PostGIS)", 4, chip_txt="F3", chip_col=PURPLE)
|
||
fc_legend(s)
|
||
vchain(s, 5.6, 1.66, [
|
||
(["단말 위치 클릭(전기·LAN·급배수)"], "term"),
|
||
(["기기 목록·kW/회선 입력"], "io"),
|
||
(["트렌치 그리드·공급 매트릭스 로딩"], "proc"),
|
||
(["최근접 트렌치 매칭(ST_ClosestPoint)"], "proc"),
|
||
(["통로 횡단 최소 경로(ST_ShortestLine)"], "proc"),
|
||
(["kW 합산 → 분전반 용량·수량 + 요금 견적"], "proc"),
|
||
(["배선 LineString 저장·위치표시도 자동생성"], "proc_ok"),
|
||
(["S6 오버레이(백엔드 래스터 합성)"], "term"),
|
||
], w=5.4, h=0.48, gap=0.14)
|
||
text(s, 0.6, 6.72, 12, 0.3, "예외 트렌치 좌표 미확보 → 산출 보류·근사도면 경고. 인터넷 마감(D-25) 초과 → 신청 차단. S6는 생성모델 아닌 결정적 래스터 합성 우선.", size=8.6, color=MUTED)
|
||
|
||
# --- FC4: 나노바나나 RenderJob ---
|
||
s = slide()
|
||
content_header(s, "04 · 핵심 업무 순서도 (4/8)", "나노바나나 RenderJob (큐→워커→워터마크→푸시)", 4, chip_txt="F4", chip_col=PURPLE)
|
||
fc_legend(s)
|
||
sp = vchain(s, 3.0, 1.6, [
|
||
(["시각화 요청(M2~M4)"], "term"),
|
||
(["씬 컴파일·스키마 해시"], "proc"),
|
||
(["Redis 큐 발행(RenderJob)"], "io"),
|
||
], w=3.4, h=0.58, gap=0.26)
|
||
dcx, dcy = after(sp, 0.86, gap=0.28)
|
||
dec = fnode(s, 3.0, dcy, 2.3, 0.86, ["캐시", "HIT?"], "dec")
|
||
arrow(s, sp[-1]["cx"], sp[-1]["b"], dec["cx"], dec["t"])
|
||
gem = fnode(s, 8.0, 1.95, 3.3, 0.64, ["워커 소비·Gemini 호출", "(inlineData + text)"], "wkr")
|
||
derr = fnode(s, 8.0, 3.25, 3.3, 0.86, ["응답", "상태?"], "dec")
|
||
bad = fnode(s, 11.6, 3.25, 1.4, 0.86, ["SAFETY/429", "친화 에러", "쿼터 미차감"], "proc_bad")
|
||
o1 = fnode(s, 8.0, 4.55, 3.3, 0.56, ["워터마크·메타 임베드"], "proc")
|
||
o2 = fnode(s, 8.0, 5.55, 3.3, 0.56, ["스토리지 저장 + 성공 시 쿼터 차감"], "proc")
|
||
o3 = fnode(s, 8.0, 6.55, 3.3, 0.56, ["WebSocket 완료 푸시(S1~S7)"], "proc_ok")
|
||
arrow(s, dec["r"], dec["cy"], gem["l"], gem["cy"], label="MISS")
|
||
arrow(s, gem["cx"], gem["b"], derr["cx"], derr["t"])
|
||
arrow(s, derr["r"], derr["cy"], bad["l"], bad["cy"], label="실패")
|
||
arrow(s, derr["cx"], derr["b"], o1["cx"], o1["t"], label="성공")
|
||
arrow(s, o1["cx"], o1["b"], o2["cx"], o2["t"])
|
||
arrow(s, o2["cx"], o2["b"], o3["cx"], o3["t"])
|
||
arrow_elbow(s, dec["cx"], dec["b"], o3["l"], o3["cy"], first='v', label="HIT")
|
||
|
||
# --- FC5: 공사 옥션 ---
|
||
s = slide()
|
||
content_header(s, "04 · 핵심 업무 순서도 (5/8)", "공사 옥션 (견적 → 역경매 → 낙찰)", 4, chip_txt="F5", chip_col=BLUE)
|
||
fc_legend(s)
|
||
sp = vchain(s, 3.0, 1.5, [
|
||
(["옥션 개설(유형·라운드·마감)"], "term"),
|
||
(["M2~M5 자료·물량서 첨부"], "io"),
|
||
(["응찰 자격 검증(M7) 등록업체만"], "proc"),
|
||
(["적격 업체 자료 열람"], "proc"),
|
||
(["견적서 제출(응찰)"], "proc"),
|
||
], w=3.7, h=0.52, gap=0.18)
|
||
dcx, dcy = after(sp, 0.82, gap=0.24)
|
||
dec = fnode(s, 3.0, dcy, 2.2, 0.82, ["마감/라운드", "종료?"], "dec")
|
||
arrow(s, sp[-1]["cx"], sp[-1]["b"], dec["cx"], dec["t"])
|
||
rebid = fnode(s, 6.7, dcy-1.25, 3.0, 0.62, ["재응찰", "(라운드 내 인하)"], "proc")
|
||
arrow(s, dec["r"], dec["cy"], rebid["l"], rebid["cy"], label="No")
|
||
arrow_elbow(s, rebid["cx"], rebid["b"], dec["cx"]+1.1, dec["t"]+0.15, first='v')
|
||
yes = vchain(s, 9.4, 1.6, [
|
||
(["종합점수 산정(가격+평판+납기)"], "proc"),
|
||
(["견적서 비교표·순위"], "io"),
|
||
(["전시업체 낙찰(Award)"], "proc"),
|
||
(["계약/발주 전환(M6·M9)·감사기록"], "proc_ok"),
|
||
], w=3.5, h=0.58, gap=0.24)
|
||
arrow(s, dec["r"], dec["cy"]-0.1, yes[0]["l"], yes[0]["cy"], label="Yes")
|
||
|
||
# --- FC6: 관람객 등록 → 배지 → 체크인 ---
|
||
s = slide()
|
||
content_header(s, "04 · 핵심 업무 순서도 (6/8)", "관람객 등록 → 배지 → 체크인", 4, chip_txt="F6", chip_col=BLUE)
|
||
fc_legend(s)
|
||
sp = vchain(s, 3.0, 1.55, [
|
||
(["온라인 사전등록(유형별 폼)"], "term"),
|
||
(["개인정보 동의 확인"], "proc"),
|
||
(["배지/QR 발급(서명·만료)"], "proc"),
|
||
(["모바일 배지 수신"], "io"),
|
||
], w=3.4, h=0.54, gap=0.2)
|
||
dcx, dcy = after(sp, 0.86, gap=0.26)
|
||
dec = fnode(s, 3.0, dcy, 2.3, 0.86, ["현장", "네트워크?"], "dec")
|
||
arrow(s, sp[-1]["cx"], sp[-1]["b"], dec["cx"], dec["t"])
|
||
onl = fnode(s, 6.9, dcy, 3.0, 0.62, ["QR 온라인 체크인"], "proc")
|
||
off = fnode(s, 6.9, dcy-1.35, 3.0, 0.62, ["오프라인 폴백 큐"], "proc_bad")
|
||
arrow(s, dec["r"], dec["cy"], onl["l"], onl["cy"], label="정상")
|
||
arrow(s, dec["r"], dec["cy"], off["l"], off["cy"], label="불안정")
|
||
arrow(s, off["cx"], off["b"], onl["cx"], onl["t"], label="복구 동기화")
|
||
tail = vchain(s, 10.7, dcy-1.35, [
|
||
(["즉석 배지 인쇄·입장 집계"], "proc"),
|
||
(["리드캡처 매핑(M10-LEAD)"], "proc"),
|
||
(["체크인 데이터 → M16 BI"], "proc_ok"),
|
||
], w=3.1, h=0.6, gap=0.24)
|
||
arrow(s, onl["r"], onl["cy"], tail[0]["l"], tail[0]["cy"])
|
||
|
||
# --- FC7: 승인 워크플로 (2FA 포함) ---
|
||
s = slide()
|
||
content_header(s, "04 · 핵심 업무 순서도 (7/8)", "승인 워크플로 (2FA 포함)", 4, chip_txt="F7", chip_col=BLUE_DK)
|
||
fc_legend(s)
|
||
sp = vchain(s, 3.0, 1.55, [
|
||
(["로그인(ID/PW)"], "term"),
|
||
(["JWT 발급"], "proc"),
|
||
], w=3.2, h=0.56, gap=0.22)
|
||
dcx, dcy = after(sp, 0.86, gap=0.24)
|
||
dec = fnode(s, 3.0, dcy, 2.3, 0.86, ["2FA 대상", "역할?"], "dec")
|
||
arrow(s, sp[-1]["cx"], sp[-1]["b"], dec["cx"], dec["t"])
|
||
otp = fnode(s, 3.0, dcy+1.35, 3.1, 0.6, ["OTP(TOTP) 검증·실패 잠금"], "proc")
|
||
arrow(s, dec["cx"], dec["b"], otp["cx"], otp["t"], label="필수")
|
||
que = fnode(s, 8.3, 2.2, 3.2, 0.62, ["승인 큐 진입(AI 사전심사 플래그)"], "io")
|
||
rev = fnode(s, 8.3, 3.4, 3.2, 0.6, ["검수 상세(도면+위반 오버레이)"], "proc")
|
||
dap = fnode(s, 8.3, 4.65, 2.4, 0.86, ["승인", "판정?"], "dec")
|
||
rej = fnode(s, 11.6, 4.65, 1.4, 0.86, ["반려", "사유 통지", "재제출"], "proc_bad")
|
||
ap1 = fnode(s, 8.3, 5.85, 3.2, 0.56, ["승인 확정·상태 전이"], "proc")
|
||
ap2 = fnode(s, 8.3, 6.6, 3.2, 0.5, ["감사로그 기록 + 알림센터 통지(CMN-ALRM)"], "proc_ok")
|
||
arrow(s, dec["r"], dec["cy"], que["l"], que["cy"], label="선택/면제")
|
||
arrow(s, otp["r"], otp["cy"], que["l"], que["cy"]+0.1)
|
||
arrow(s, que["cx"], que["b"], rev["cx"], rev["t"])
|
||
arrow(s, rev["cx"], rev["b"], dap["cx"], dap["t"])
|
||
arrow(s, dap["r"], dap["cy"], rej["l"], rej["cy"], label="반려")
|
||
arrow(s, dap["cx"], dap["b"], ap1["cx"], ap1["t"], label="승인")
|
||
arrow(s, ap1["cx"], ap1["b"], ap2["cx"], ap2["t"])
|
||
|
||
# --- FC8: CI/CD 배포 ---
|
||
s = slide()
|
||
content_header(s, "04 · 핵심 업무 순서도 (8/8)", "CI/CD 배포 (빌드 → health → 롤백)", 4, chip_txt="F8", chip_col=BLUE_DK)
|
||
fc_legend(s)
|
||
sp = vchain(s, 3.0, 1.55, [
|
||
(["Gitea push(main)"], "term"),
|
||
(["webhook 수신(deploy 서버)"], "proc"),
|
||
(["백엔드 bootJar + 프론트 Vite 빌드"], "proc"),
|
||
(["나노바나나 워커 패키징"], "proc"),
|
||
(["배포·서비스 재기동"], "proc"),
|
||
], w=3.7, h=0.56, gap=0.22)
|
||
dcx, dcy = after(sp, 0.9, gap=0.24)
|
||
dec = fnode(s, 3.0, dcy, 2.3, 0.9, ["health", "check", "OK?"], "dec")
|
||
arrow(s, sp[-1]["cx"], sp[-1]["b"], dec["cx"], dec["t"])
|
||
okb = fnode(s, 7.3, dcy, 3.4, 0.62, ["트래픽 전환·배포 완료"], "proc_ok")
|
||
rb = fnode(s, 7.3, dcy-1.3, 3.4, 0.62, ["직전 버전 롤백"], "proc_bad")
|
||
arrow(s, dec["r"], dec["cy"], okb["l"], okb["cy"], label="UP")
|
||
arrow(s, dec["r"], dec["cy"], rb["l"], rb["cy"], label="FAIL")
|
||
arrow(s, rb["cx"], rb["b"], okb["cx"], okb["t"], label="복구")
|
||
fin8 = fnode(s, 7.3, dcy+1.1, 3.4, 0.56, ["배포 결과 알림 + 감사로그·배포 이력 기록"], "term")
|
||
arrow(s, okb["cx"], okb["b"], fin8["cx"], fin8["t"])
|
||
text(s, 0.55, 6.74, 12, 0.3, "보안 불변 API키·비밀번호·서버IP·SSH 계정은 코드·커밋·로그·응답에 미기재(서버 env 로드).", size=9, color=MUTED)
|
||
|
||
# ================================================================
|
||
# 7. 공통 규약
|
||
# ================================================================
|
||
divider("05", "공통 규약", "COMMON CONVENTIONS",
|
||
["API 응답 봉투(ApiResponse)", "에러 코드·처리", "감사 로그(@Audited)"])
|
||
|
||
s = slide()
|
||
content_header(s, "05 · 공통 규약", "API 응답 봉투 · 에러 처리 · 감사 로그", 5)
|
||
# 응답 봉투
|
||
card(s, 0.55, 1.5, 6.05, 2.55)
|
||
rect(s, 0.55, 1.5, 6.05, 0.46, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.55, 1.5, 6.05, 0.46, "API 응답 봉투 (ApiResponse<T>)", size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
text(s, 0.78, 2.05, 5.6, 0.28, "모든 REST 응답은 공통 봉투 사용(예외 없음)", size=9, color=MUTED)
|
||
rect(s, 0.78, 2.38, 5.6, 0.62, fill=RGBColor(0x0E,0x14,0x22), shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.95, 2.44, 5.3, 0.52, [
|
||
[{"t":'성공 ',"size":8.5,"color":GREEN,"bold":True},{"t":'{ "success": true, "data": {…}, "error": null }',"size":8.5,"color":RGBColor(0xD6,0xE2,0xF0)}],
|
||
[{"t":'실패 ',"size":8.5,"color":AMBER,"bold":True},{"t":'{ "success": false, "data": null, "error": {code,message} }',"size":8.5,"color":RGBColor(0xD6,0xE2,0xF0)}],
|
||
], line_spacing=1.1)
|
||
for i,t in enumerate([
|
||
"성공: 컨트롤러 ApiResponse.ok(dto)",
|
||
"실패: ApiException throw → GlobalExceptionHandler 봉투 변환",
|
||
"페이징: 공통 PageResponse<T>",
|
||
]):
|
||
y=3.12+i*0.3
|
||
rect(s, 0.82, y+0.06, 0.09, 0.09, fill=BLUE, shape=MSO_SHAPE.OVAL)
|
||
text(s, 1.02, y, 5.4, 0.28, t, size=9, color=INK)
|
||
# 에러 처리
|
||
card(s, 6.75, 1.5, 6.03, 2.55)
|
||
rect(s, 6.75, 1.5, 6.03, 0.46, fill=AMBER, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 6.75, 1.5, 6.03, 0.46, "에러 처리 (ErrorCode·ApiException)", size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
for i,(h,d) in enumerate([
|
||
("단일 매핑","ErrorCode(enum)가 코드↔HTTP 단일 매핑, 신규는 여기에만 추가"),
|
||
("3자 세트","ErrorCode → ApiException(코드+요약) → GlobalExceptionHandler"),
|
||
("스택 미노출","error.message는 요약만, 상세는 서버 로그(include-* : never)"),
|
||
("남용 금지","RuntimeException 남발 금지 — 최종 방어선만 INTERNAL"),
|
||
("보안","IP·SSH·비번·PII를 응답·에러에 미노출"),
|
||
]):
|
||
y=2.08+i*0.38
|
||
rect(s, 7.0, y+0.04, 0.11, 0.11, fill=AMBER, shape=MSO_SHAPE.OVAL)
|
||
text(s, 7.22, y, 5.4, 0.36, [[{"t":h+" — ","size":9.4,"color":INK,"bold":True},{"t":d,"size":8.8,"color":MUTED}]], line_spacing=0.92)
|
||
# 감사 로그
|
||
card(s, 0.55, 4.28, 12.23, 2.15)
|
||
rect(s, 0.55, 4.28, 12.23, 0.46, fill=BLUE_DK, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, 0.55, 4.28, 12.23, 0.46, "감사 로그 (@Audited AOP → TB_AUDIT_LOG)", size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||
audit = [
|
||
("기록 대상","상태 변경 API — 승인·낙찰(M15)·설계 변경·룰셋 개정·리드 접근(PII)"),
|
||
("기록 항목","액터 · 행사 · 대상 · before/after 요약 · 룰셋 버전 · 시각"),
|
||
("미기록","민감정보 · 비밀번호 · 스택트레이스 (보안 불변)"),
|
||
("스키마","WISE TB_AUDIT_LOG 이식 · 멱등 DDL(sql.init mode=always + continue-on-error)"),
|
||
]
|
||
for i,(h,d) in enumerate(audit):
|
||
x=0.75+(i%2)*6.1
|
||
y=4.9+(i//2)*0.72
|
||
rect(s, x, y, 5.85, 0.62, fill=HALL_BG, line=LINE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||
text(s, x+0.18, y+0.1, 5.55, 0.26, h, size=10, color=BLUE_DK, bold=True)
|
||
text(s, x+0.18, y+0.36, 5.55, 0.24, d, size=8.6, color=MUTED)
|
||
text(s, 0.55, 6.62, 12.2, 0.3,
|
||
"적용 원칙 ApiResponse·ErrorCode·감사로그는 전 프로그램 공통(architecture/app.md §5-2·5-3·7-1 정본). 신규 프로그램도 예외 없이 준수.",
|
||
size=9, color=MUTED)
|
||
|
||
# ================================================================
|
||
# 저장
|
||
# ================================================================
|
||
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))
|