docs(deliverables): add PoC 추진계획서 PPTX (25 slides) + generator
- 5-stage PoC plan structure: overview / goals·criteria / method / schedule·org / risk·value - reuses proven design system from POC결과보고서 generator (palette·master·helpers·charts) - future-tense plan framing: KPI 8 targets, success definition (3-grade), gates G1~G3 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
83486d5737
commit
2bd827310a
852
docs/deliverables/POC계획보고서/_gen/gen_poc_plan.py
Normal file
852
docs/deliverables/POC계획보고서/_gen/gen_poc_plan.py
Normal file
@ -0,0 +1,852 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
킨텍스(KINTEX) AI 시스템 — PoC(개념증명) 추진계획서 PPTX 생성기
|
||||
- 목차: PoC 계획보고서 표준 5단계(추진개요 / 검증목표·평가기준 / 수행방안·방법 / 추진일정·조직 / 리스크·기대효과)
|
||||
- 결과보고서와 달리 "무엇을·어떻게·언제 검증할 것인가"(계획·미래시제) 관점으로 구성
|
||||
- 디자인: KINTEX Blue #0066B3 / AI Purple #6D4AFF / 맑은 고딕 / 16:9 · 슬라이드 마스터 사용
|
||||
재실행: python gen_poc_plan.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.oxml.ns import qn
|
||||
from pptx.chart.data import CategoryChartData
|
||||
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION, XL_LABEL_POSITION
|
||||
from pptx.shapes.autoshape import Shape, AutoShapeType
|
||||
|
||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
OUT = os.path.join(os.path.dirname(BASE), "킨텍스_AI_POC계획보고서.pptx")
|
||||
|
||||
# ---------------- 팔레트 ----------------
|
||||
BLUE = RGBColor(0x00, 0x66, 0xB3)
|
||||
BLUE_DK = RGBColor(0x00, 0x44, 0x7A)
|
||||
BLUE_LT = RGBColor(0xE1, 0xEF, 0xF9)
|
||||
PURPLE = RGBColor(0x6D, 0x4A, 0xFF)
|
||||
PURPLE_DK = RGBColor(0x4A, 0x2F, 0xCC)
|
||||
PURPLE_LT = RGBColor(0xEC, 0xE8, 0xFF)
|
||||
INK = RGBColor(0x10, 0x18, 0x28)
|
||||
MUTED = RGBColor(0x66, 0x70, 0x85)
|
||||
SLATE = RGBColor(0x98, 0xA2, 0xB3)
|
||||
LINE = RGBColor(0xE4, 0xE7, 0xEC)
|
||||
BG = RGBColor(0xF9, 0xFA, 0xFB)
|
||||
PANEL = RGBColor(0xF2, 0xF4, 0xF7)
|
||||
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
|
||||
CARD = WHITE
|
||||
GREEN = RGBColor(0x12, 0x9E, 0x63)
|
||||
GREEN_LT = RGBColor(0xE3, 0xF5, 0xEC)
|
||||
AMBER = RGBColor(0xE0, 0x8A, 0x00)
|
||||
AMBER_LT = RGBColor(0xFC, 0xF1, 0xDD)
|
||||
RED = RGBColor(0xD9, 0x2D, 0x20)
|
||||
RED_LT = RGBColor(0xFC, 0xE9, 0xE7)
|
||||
CYAN = RGBColor(0x0B, 0xA5, 0xEC)
|
||||
TEAL = RGBColor(0x0E, 0x93, 0x84)
|
||||
NAVY = RGBColor(0x2B, 0x4C, 0x7E)
|
||||
ZEBRA = RGBColor(0xFC, 0xFC, 0xFD)
|
||||
SUBLT = RGBColor(0xC7, 0xDD, 0xF2)
|
||||
FONT = "맑은 고딕"
|
||||
|
||||
EMU_W = 12192000; EMU_H = 6858000
|
||||
IN = 914400
|
||||
def inch(v): return int(v * IN)
|
||||
|
||||
prs = Presentation()
|
||||
prs.slide_width = EMU_W; prs.slide_height = EMU_H
|
||||
BLANK = prs.slide_layouts[6]
|
||||
|
||||
# ================================================================
|
||||
# 슬라이드 마스터 — 공통 배경·상단 액센트바·푸터
|
||||
# ================================================================
|
||||
def _set_fill(sp, color):
|
||||
if color is None: sp.fill.background()
|
||||
else: sp.fill.solid(); sp.fill.fore_color.rgb = color
|
||||
|
||||
def _no_line(sp): sp.line.fill.background()
|
||||
|
||||
_mid = [900]
|
||||
def _mnext(): _mid[0] += 1; return _mid[0]
|
||||
|
||||
def _master_shape(shapes, shape_type, x, y, w, h, fill, line=None):
|
||||
ast = AutoShapeType(shape_type); id_ = _mnext()
|
||||
sp = shapes._spTree.add_autoshape(id_, '%s %d' % (ast.basename, id_), ast.prst,
|
||||
inch(x), inch(y), inch(w), inch(h))
|
||||
shp = Shape(sp, shapes)
|
||||
_set_fill(shp, fill)
|
||||
if line is None: _no_line(shp)
|
||||
else: shp.line.color.rgb = line; shp.line.width = Pt(1.0)
|
||||
shp.shadow.inherit = False
|
||||
return shp
|
||||
|
||||
def _master_text(shapes, x, y, w, h, txt, size, color, bold=False, align=PP_ALIGN.LEFT):
|
||||
id_ = _mnext()
|
||||
sp = shapes._spTree.add_textbox(id_, 'TextBox %d' % id_, inch(x), inch(y), inch(w), inch(h))
|
||||
tb = Shape(sp, shapes)
|
||||
tf = tb.text_frame; tf.word_wrap = True
|
||||
tf.margin_left = 0; tf.margin_right = 0; tf.margin_top = 0; tf.margin_bottom = 0
|
||||
p = tf.paragraphs[0]; p.alignment = align
|
||||
r = p.add_run(); r.text = txt
|
||||
r.font.name = FONT; r.font.size = Pt(size); r.font.bold = bold; r.font.color.rgb = color
|
||||
rPr = r._r.get_or_add_rPr(); rPr.append(rPr.makeelement(qn('a:ea'), {'typeface': FONT}))
|
||||
return tb
|
||||
|
||||
def setup_master():
|
||||
master = prs.slide_masters[0]
|
||||
shapes = master.shapes
|
||||
bg = _master_shape(shapes, MSO_SHAPE.RECTANGLE, -0.15, -0.15, 13.63, 7.8, BG)
|
||||
spTree = shapes._spTree
|
||||
spTree.remove(bg._element); spTree.insert(2, bg._element)
|
||||
_master_shape(shapes, MSO_SHAPE.RECTANGLE, -0.15, 0.0, 13.63, 0.11, BLUE)
|
||||
_master_shape(shapes, MSO_SHAPE.RECTANGLE, -0.15, 0.0, 4.4, 0.11, PURPLE)
|
||||
_master_text(shapes, 0.55, 7.02, 8.6, 0.3,
|
||||
"킨텍스(KINTEX) AI 시스템 · PoC(개념증명) 추진계획서", 8.5, MUTED)
|
||||
_master_text(shapes, 9.4, 7.02, 3.38, 0.3, "2026-08-11 · CONFIDENTIAL", 8.5, MUTED, align=PP_ALIGN.RIGHT)
|
||||
|
||||
setup_master()
|
||||
|
||||
def slide(): return prs.slides.add_slide(BLANK)
|
||||
|
||||
# ================================================================
|
||||
# 공통 드로잉 헬퍼
|
||||
# ================================================================
|
||||
def rect(s, x, y, w, h, fill=None, line=None, line_w=1.0, shape=MSO_SHAPE.RECTANGLE, shadow=False, dash=None):
|
||||
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)
|
||||
if dash:
|
||||
ln = sp.line._get_or_add_ln(); ln.append(ln.makeelement(qn('a:prstDash'), {'val': dash}))
|
||||
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'})
|
||||
clr.append(clr.makeelement(qn('a:alpha'), {'val':'15000'}))
|
||||
sh.append(clr); ef.append(sh)
|
||||
return sp
|
||||
|
||||
def line_conn(s, x1, y1, x2, y2, color=SLATE, w=1.0, dash=None):
|
||||
ln = s.shapes.add_connector(2, inch(x1), inch(y1), inch(x2), inch(y2))
|
||||
ln.line.color.rgb = color; ln.line.width = Pt(w); ln.shadow.inherit = False
|
||||
if dash:
|
||||
el = ln.line._get_or_add_ln(); el.append(el.makeelement(qn('a:prstDash'), {'val': dash}))
|
||||
return ln
|
||||
|
||||
def text(s, x, y, w, h, content, size=11, 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(); rPr.append(rPr.makeelement(qn('a:ea'), {'typeface':FONT}))
|
||||
return tb
|
||||
|
||||
def chip(s, x, y, w, h, label, fill, tc=WHITE, size=9.5, bold=True, line=None):
|
||||
rect(s, x, y, w, h, fill=fill, line=line, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x, y, w, h, label, size=size, color=tc, bold=bold, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
|
||||
def card(s, x, y, w, h, fill=CARD, line=LINE, shadow=True):
|
||||
return rect(s, x, y, w, h, fill=fill, line=line, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE, shadow=shadow)
|
||||
|
||||
def bullets(s, x, y, w, items, dot=BLUE, size=10.5, gap=0.44, lh=1.0, dotsize=0.1, tcolor=INK):
|
||||
for i, it in enumerate(items):
|
||||
yy = y + i*gap
|
||||
rect(s, x, yy+0.07, dotsize, dotsize, fill=dot, shape=MSO_SHAPE.OVAL)
|
||||
segs = it if isinstance(it, list) else [{"t": it}]
|
||||
for sg in segs: sg.setdefault("size", size); sg.setdefault("color", tcolor)
|
||||
text(s, x+0.24, yy, w-0.24, gap, [segs], line_spacing=lh)
|
||||
|
||||
def lead(s, x, y, w, h, txt, fill=BLUE, tc=WHITE, size=14):
|
||||
rect(s, x, y, w, h, fill=fill, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.25, y, w-0.4, h, txt, size=size, color=tc, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||||
|
||||
def titlecard(s, x, y, w, h, title, tcolor, items, headfill=None, bodysize=9.5, hsize=12, igap=None):
|
||||
card(s, x, y, w, h)
|
||||
if headfill:
|
||||
rect(s, x, y, w, 0.46, fill=headfill, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x, y, w, 0.46, title, size=hsize, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
y0 = y+0.62
|
||||
else:
|
||||
rect(s, x, y, 0.09, h, fill=tcolor, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.24, y+0.13, w-0.4, 0.32, title, size=hsize, color=tcolor, bold=True)
|
||||
y0 = y+0.55
|
||||
g = igap if igap else min(0.4, (h-(y0-y)-0.1)/max(1,len(items)))
|
||||
for i, it in enumerate(items):
|
||||
yy = y0 + i*g
|
||||
rect(s, x+0.22, yy+0.06, 0.09, 0.09, fill=tcolor, shape=MSO_SHAPE.OVAL)
|
||||
segs = it if isinstance(it, list) else [{"t": it}]
|
||||
for sg in segs: sg.setdefault("size", bodysize); sg.setdefault("color", INK)
|
||||
text(s, x+0.42, yy, w-0.6, g, [segs], line_spacing=0.94)
|
||||
|
||||
def table(s, x, y, w, cols, rows, colw, headfill=BLUE, rowh=0.4, hsize=9.5, bsize=9,
|
||||
hl_rows=None, hl_fill=BLUE_LT):
|
||||
hl_rows = hl_rows or set()
|
||||
rect(s, x, y, w, rowh, fill=headfill, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
cx = x
|
||||
for j, c in enumerate(cols):
|
||||
text(s, cx+0.1, y, colw[j]-0.12, rowh, c, size=hsize, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE,
|
||||
align=PP_ALIGN.CENTER if j>0 else PP_ALIGN.LEFT)
|
||||
cx += colw[j]
|
||||
for i, row in enumerate(rows):
|
||||
ry = y + rowh + i*rowh
|
||||
if i in hl_rows: rect(s, x, ry, w, rowh, fill=hl_fill)
|
||||
elif i % 2 == 1: rect(s, x, ry, w, rowh, fill=ZEBRA)
|
||||
rect(s, x, ry, w, 0.008, fill=LINE)
|
||||
cx = x
|
||||
for j, cell in enumerate(row):
|
||||
col = INK if j==0 else MUTED
|
||||
if isinstance(cell, tuple): txt, col = cell
|
||||
else: txt = cell
|
||||
text(s, cx+0.1, ry, colw[j]-0.12, rowh, txt, size=bsize,
|
||||
color=(INK if i in hl_rows else col), bold=(i in hl_rows),
|
||||
anchor=MSO_ANCHOR.MIDDLE, align=PP_ALIGN.CENTER if j>0 else PP_ALIGN.LEFT)
|
||||
cx += colw[j]
|
||||
|
||||
def content_header(s, kicker, title, idx):
|
||||
rect(s, 0.55, 0.42, 0.62, 0.62, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 0.55, 0.42, 0.62, 0.62, idx, size=(20 if len(idx)<3 else 12), color=WHITE, bold=True,
|
||||
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
text(s, 1.32, 0.42, 11.3, 0.28, kicker, size=11, color=PURPLE, bold=True)
|
||||
text(s, 1.32, 0.66, 11.3, 0.5, title, size=18.5, color=INK, bold=True, line_spacing=0.96)
|
||||
rect(s, 0.55, 1.26, 12.23, 0.02, fill=LINE)
|
||||
|
||||
def caption(s, txt, y=6.62):
|
||||
text(s, 0.55, y, 12.23, 0.3, txt, size=9.3, color=MUTED)
|
||||
|
||||
def kpi(s, x, y, w, h, big, unit, label, sub, cl):
|
||||
card(s, x, y, w, h)
|
||||
rect(s, x, y, w, 0.08, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.2, y+0.28, w-0.4, 0.62, [[{"t":big,"size":30,"color":cl,"bold":True},
|
||||
{"t":" "+unit,"size":12,"color":cl,"bold":True}]], anchor=MSO_ANCHOR.MIDDLE)
|
||||
text(s, x+0.2, y+0.96, w-0.4, 0.3, label, size=11, color=INK, bold=True)
|
||||
text(s, x+0.2, y+1.26, w-0.4, 0.5, sub, size=8.6, color=MUTED, line_spacing=0.95)
|
||||
|
||||
def timeline(s, x, y, w, steps, cl=BLUE):
|
||||
n = len(steps); seg = w/n
|
||||
rect(s, x, y+0.5, w, 0.05, fill=LINE)
|
||||
rect(s, x, y+0.5, w*0.999, 0.05, fill=cl)
|
||||
for i,(date,title,desc) in enumerate(steps):
|
||||
cx = x + seg*i + seg/2
|
||||
rect(s, cx-0.11, y+0.4, 0.22, 0.22, fill=cl, shape=MSO_SHAPE.OVAL, line=WHITE, line_w=2.0)
|
||||
text(s, cx-seg/2+0.1, y+0.0, seg-0.2, 0.32, date, size=9.5, color=cl, bold=True, align=PP_ALIGN.CENTER)
|
||||
card(s, cx-seg/2+0.14, y+0.78, seg-0.28, 1.5)
|
||||
text(s, cx-seg/2+0.24, y+0.9, seg-0.48, 0.5, title, size=10, color=INK, bold=True,
|
||||
align=PP_ALIGN.CENTER, line_spacing=0.92)
|
||||
text(s, cx-seg/2+0.24, y+1.42, seg-0.48, 0.78, desc, size=8.3, color=MUTED,
|
||||
align=PP_ALIGN.CENTER, line_spacing=0.95)
|
||||
|
||||
def divider(no, kr, en, points):
|
||||
s = slide()
|
||||
rect(s, -0.15, -0.15, 13.63, 7.8, 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, 1.9, 5, 2.2, no, size=150, color=RGBColor(0x2C,0x4E,0x82), bold=True)
|
||||
rect(s, 1.15, 3.78, 0.9, 0.09, fill=PURPLE)
|
||||
text(s, 1.15, 3.95, 7, 0.8, kr, size=33, color=WHITE, bold=True)
|
||||
text(s, 1.18, 4.82, 7, 0.4, en, size=13, color=RGBColor(0x9C,0xB4,0xD8), bold=True)
|
||||
for i, p in enumerate(points):
|
||||
yy = 2.4 + i*0.62
|
||||
rect(s, 7.55, yy+0.07, 0.16, 0.16, fill=PURPLE, shape=MSO_SHAPE.OVAL)
|
||||
text(s, 7.85, yy, 4.7, 0.5, p, size=12.5, color=WHITE, anchor=MSO_ANCHOR.MIDDLE)
|
||||
return s
|
||||
|
||||
def add_bar(s, x, y, w, h, cats, series, title=None, colors=None, ctype=XL_CHART_TYPE.COLUMN_CLUSTERED,
|
||||
maxv=None, show_val=True):
|
||||
cd = CategoryChartData(); cd.categories = cats
|
||||
for nm, vals in series: cd.add_series(nm, vals)
|
||||
gf = s.shapes.add_chart(ctype, inch(x), inch(y), inch(w), inch(h), cd)
|
||||
ch = gf.chart
|
||||
ch.has_title = bool(title)
|
||||
if title:
|
||||
ch.chart_title.text_frame.text = title
|
||||
r = ch.chart_title.text_frame.paragraphs[0].runs[0]
|
||||
r.font.size = Pt(11); r.font.bold = True; r.font.name = FONT; r.font.color.rgb = INK
|
||||
ch.has_legend = len(series) > 1
|
||||
if ch.has_legend:
|
||||
ch.legend.position = XL_LEGEND_POSITION.TOP; ch.legend.include_in_layout = False
|
||||
ch.legend.font.size = Pt(9); ch.legend.font.name = FONT
|
||||
plot = ch.plots[0]; plot.gap_width = 60
|
||||
if colors and len(series)==1:
|
||||
for i, pt in enumerate(plot.series[0].points):
|
||||
pt.format.fill.solid(); pt.format.fill.fore_color.rgb = colors[i % len(colors)]
|
||||
elif colors:
|
||||
for si, sr in enumerate(plot.series):
|
||||
sr.format.fill.solid(); sr.format.fill.fore_color.rgb = colors[si % len(colors)]
|
||||
if show_val:
|
||||
plot.has_data_labels = True
|
||||
dl = plot.data_labels; dl.font.size = Pt(8.5); dl.font.name = FONT; dl.font.bold = True
|
||||
dl.number_format = '0'; dl.number_format_is_linked = False
|
||||
try:
|
||||
ca = ch.value_axis; ca.has_major_gridlines = True
|
||||
ca.tick_labels.font.size = Pt(8.5); ca.tick_labels.font.name = FONT
|
||||
if maxv: ca.maximum_scale = maxv; ca.minimum_scale = 0
|
||||
except Exception: pass
|
||||
ch.category_axis.tick_labels.font.size = Pt(8.5); ch.category_axis.tick_labels.font.name = FONT
|
||||
return ch
|
||||
|
||||
def add_doughnut(s, x, y, w, h, cats, vals, colors, title=None):
|
||||
cd = CategoryChartData(); cd.categories = cats; cd.add_series('s', vals)
|
||||
gf = s.shapes.add_chart(XL_CHART_TYPE.DOUGHNUT, inch(x), inch(y), inch(w), inch(h), cd)
|
||||
ch = gf.chart
|
||||
ch.has_title = bool(title)
|
||||
if title:
|
||||
ch.chart_title.text_frame.text = title
|
||||
r = ch.chart_title.text_frame.paragraphs[0].runs[0]
|
||||
r.font.size = Pt(10.5); r.font.bold = True; r.font.name = FONT; r.font.color.rgb = INK
|
||||
ch.has_legend = True; ch.legend.position = XL_LEGEND_POSITION.RIGHT
|
||||
ch.legend.include_in_layout = False; ch.legend.font.size = Pt(9); ch.legend.font.name = FONT
|
||||
pts = ch.plots[0].series[0].points
|
||||
for i, pt in enumerate(pts):
|
||||
pt.format.fill.solid(); pt.format.fill.fore_color.rgb = colors[i % len(colors)]
|
||||
return ch
|
||||
|
||||
# ================================================================
|
||||
# S01 · 표지
|
||||
# ================================================================
|
||||
def cover():
|
||||
s = slide()
|
||||
rect(s, -0.15, -0.15, 13.63, 7.8, 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 · AI EXHIBITION SYSTEM", size=14, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||||
chip(s, 0.85, 2.15, 2.3, 0.44, "PoC 추진계획서", PURPLE, WHITE, size=13)
|
||||
chip(s, 3.27, 2.15, 2.9, 0.44, "Proof of Concept Plan", NAVY, WHITE, size=10.5)
|
||||
text(s, 0.85, 2.78, 11.5, 1.9, [
|
||||
[{"t":"킨텍스 AI 전시관리 시스템","size":42,"color":WHITE,"bold":True}],
|
||||
[{"t":"개념증명(PoC) 추진계획서","size":42,"color":WHITE,"bold":True}],
|
||||
], line_spacing=1.03)
|
||||
rect(s, 0.85, 4.62, 11.5, 0.66, fill=RGBColor(0x14,0x35,0x63), shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
rect(s, 0.85, 4.62, 0.1, 0.66, fill=PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 1.15, 4.62, 11.0, 0.66, "무엇을·어떻게·언제 검증할 것인가 — 실증으로 도입 타당성을 증명하기 위한 PoC 실행 계획.",
|
||||
size=14.5, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||||
rect(s, 0.85, 5.95, 11.63, 0.02, fill=RGBColor(0x3A,0x5A,0x8C))
|
||||
text(s, 0.85, 6.15, 8.8, 0.4, [[{"t":"검증 대상 ","size":10.5,"color":RGBColor(0x90,0xA6,0xC9)},
|
||||
{"t":"부스 배치·설계·배선 자동화 + 나노바나나 시공 예측 + 공사 옥션 폐루프","size":10.5,"color":WHITE,"bold":True}]])
|
||||
text(s, 0.85, 6.52, 9.4, 0.4, [[{"t":"기술스택 ","size":10.5,"color":RGBColor(0x90,0xA6,0xC9)},
|
||||
{"t":"React(Vite)+TS · Spring Boot 3.x(Java17)+MyBatis · PostgreSQL(PostGIS) · Redis · 나노바나나 워커 · Claude AI","size":9,"color":WHITE}]])
|
||||
text(s, 10.0, 6.15, 2.5, 0.4, "작성일 2026-08-11", size=11, color=WHITE, bold=True, align=PP_ALIGN.RIGHT)
|
||||
text(s, 10.0, 6.52, 2.5, 0.4, "수행 · GUARDiA/WISE 팀", size=9, color=RGBColor(0x90,0xA6,0xC9), align=PP_ALIGN.RIGHT)
|
||||
|
||||
# ================================================================
|
||||
# S02 · 목차
|
||||
# ================================================================
|
||||
def toc():
|
||||
s = slide(); content_header(s, "CONTENTS", "목차 — PoC 추진계획서 표준 5단계", "―")
|
||||
items=[("1","추진 개요","배경·목적 / 검증 대상 솔루션·기술 / 검증 범위·대상",BLUE),
|
||||
("2","검증 목표 및 평가 기준","핵심 검증 항목(KPI) / 성공·실패 기준 / 테스트 환경·아키텍처",PURPLE),
|
||||
("3","PoC 수행 방안 및 방법","기능·성능 시나리오 / 데이터 준비·적용범위 / 시각화 검증 방법",TEAL),
|
||||
("4","추진 일정 및 수행 조직","WBS·마일스톤 / 수행조직·R&R / 산출물·형상관리",BLUE),
|
||||
("5","리스크 관리 및 기대효과","리스크·대응 / 보안·품질 관리 / 기대효과·본사업 전환",PURPLE),
|
||||
("부록","APPENDIX","기술스택 상세 · 게이트 정의 · 체크리스트",NAVY)]
|
||||
for i,(no,t,d,cl) in enumerate(items):
|
||||
col=i//3; row=i%3
|
||||
x=0.6+col*6.23; y=1.5+row*1.68
|
||||
rect(s, x, y, 5.95, 1.5, fill=CARD, line=LINE, shape=MSO_SHAPE.ROUNDED_RECTANGLE, shadow=True)
|
||||
rect(s, x, y, 0.1, 1.5, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.3, y, 1.4, 1.5, no, size=(30 if no!="부록" else 15), color=cl, bold=True,
|
||||
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
text(s, x+1.75, y+0.3, 4.0, 0.5, t, size=15, color=INK, bold=True)
|
||||
text(s, x+1.75, y+0.82, 4.0, 0.6, d, size=9.3, color=MUTED, line_spacing=1.0)
|
||||
caption(s, "본 계획서는 PoC 표준 목차(추진개요·검증목표·수행방안·일정조직·리스크)에 킨텍스 자동전시시스템 실증 계획을 정렬하여 구성.", y=6.7)
|
||||
|
||||
# ================================================================
|
||||
# S03 · Executive Summary
|
||||
# ================================================================
|
||||
def exec_summary():
|
||||
s = slide(); content_header(s, "EXECUTIVE SUMMARY", "핵심 요약 — 14일 집중 PoC로 자동전시 파이프라인의 실현 가능성을 실증한다", "요약")
|
||||
kpi(s, 0.55, 1.45, 2.95, 1.85, "3", "대 핵심가설", "검증 초점 3결합", "나노바나나 시공예측 · PostGIS 공간데이터 · 공사 옥션 폐루프", PURPLE)
|
||||
kpi(s, 3.63, 1.45, 2.95, 1.85, "14", "일 집중검증", "2026-07-11~07-24", "기획·아키텍처 → 코어 → AI/시각화 → 시연·QA, 6세션 구성", BLUE)
|
||||
kpi(s, 6.71, 1.45, 2.95, 1.85, "8", "개 KPI", "정량 성공 지표", "모듈·화면·시각화·배치·옥션·데이터·배포·모바일 목표 정의", TEAL)
|
||||
kpi(s, 9.79, 1.45, 2.99, 1.85, "6", "역할 포털", "웹·모바일 분리 검증", "주최자·참가사·공사사·관람객·관리자·운영, RBAC(JWT)+2FA", GREEN)
|
||||
card(s, 0.55, 3.55, 7.55, 2.75)
|
||||
rect(s, 0.55, 3.55, 7.55, 0.46, fill=INK, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 0.55, 3.55, 7.55, 0.46, "PoC 추진 방향", size=12.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
bullets(s, 0.78, 4.18, 7.1, [
|
||||
[{"t":"검증 가설 — ","bold":True,"color":PURPLE},{"t":"“신청하는 순간 시공 후 사진을 먼저 본다” — 설계·시각화·발주·운영·분석을 한 흐름으로 자동화 가능한가."}],
|
||||
[{"t":"실증 방식 — ","bold":True,"color":BLUE},{"t":"개발서버(kintex.zioinfo.co.kr:8021)에서 E2E 라이브로 기능·성능·데이터 파이프라인을 실측 검증."}],
|
||||
[{"t":"평가 기준 — ","bold":True,"color":TEAL},{"t":"KPI 8종을 3등급(성공/조건부/실패)으로 판정, health 게이트·보안 불변을 통과 조건화."}],
|
||||
[{"t":"기반 재사용 — ","bold":True,"color":GREEN},{"t":"WISE 표준 프레임워크를 재사용하여 착수 공수를 절감하고 본사업 전환 타당성을 확인."}],
|
||||
], gap=0.56, size=9.6, lh=0.98)
|
||||
titlecard(s, 8.28, 3.55, 4.5, 2.75, "성공의 정의", GREEN, [
|
||||
[{"t":"기능 검증 ","bold":True},{"t":"— 핵심 3결합 E2E 성립","color":GREEN,"bold":True}],
|
||||
[{"t":"성능/안정성 ","bold":True},{"t":"— 개발서버 기준 충족","color":GREEN,"bold":True}],
|
||||
[{"t":"데이터 파이프라인 ","bold":True},{"t":"— 크롤·시드 폐루프 구동","color":GREEN,"bold":True}],
|
||||
[{"t":"운영 성숙도 ","bold":True},{"t":"— 본사업 전환 시 보완","color":AMBER,"bold":True}],
|
||||
"→ KPI 8종 달성 + 게이트 통과 시 '본사업 전환 권고'로 결론",
|
||||
], headfill=GREEN, bodysize=9.3, igap=0.5)
|
||||
caption(s, "상세 목표는 §2 검증 목표·평가 기준, 실행 방법은 §3 수행 방안, 일정은 §4 추진 일정을 참조.", y=6.62)
|
||||
|
||||
# ================================================================
|
||||
# Ⅰ. 추진 개요
|
||||
# ================================================================
|
||||
def sec1_divider():
|
||||
divider("1", "추진 개요", "PROJECT OVERVIEW",
|
||||
["PoC 추진 배경 및 목적", "검증 대상 솔루션·기술 개요", "검증 범위 및 대상(Scope)"])
|
||||
|
||||
def s_background():
|
||||
s = slide(); content_header(s, "1. 추진 개요", "1-1. PoC 추진 배경 및 목적", "1")
|
||||
probs = [("문제 ① 설계-발주 단절","주최·참가사가 도면을 읽기 어려워 시공 결과를 사전에 확신하지 못함 — 재작업·분쟁 발생", RED),
|
||||
("문제 ② 공간데이터 분산","부스 배치·트렌치·배선 정보가 도구별로 흩어져 검증·정산·분석이 수작업", AMBER),
|
||||
("문제 ③ 공사 발주 비효율","AI 설계자료와 응찰·낙찰·발주가 단절 — 견적 비교·계약 전환에 시간 소요", PURPLE)]
|
||||
for i,(t,d,cl) in enumerate(probs):
|
||||
x=0.55+i*4.12
|
||||
card(s, x, 1.45, 3.95, 1.7)
|
||||
rect(s, x, 1.45, 3.95, 0.08, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.24, 1.66, 3.5, 0.4, t, size=11.5, color=cl, bold=True)
|
||||
text(s, x+0.24, 2.12, 3.5, 0.9, d, size=9.5, color=INK, line_spacing=1.05)
|
||||
lead(s, 0.55, 3.4, 12.23, 0.6, "PoC 목적 — “신청서를 내는 순간, 시공 후 사진을 먼저 본다”: 설계·시각화·발주·운영·분석을 한 흐름으로 자동화할 수 있음을 실증한다.", fill=BLUE, size=13)
|
||||
titlecard(s, 0.55, 4.2, 6.0, 2.1, "추진 목적(검증 가설)", BLUE, [
|
||||
"AI가 부스 배치·설계·배선을 자동 생성하고, 나노바나나가 시공 후 사진을 예측할 수 있는가",
|
||||
"PostGIS 단일 공간원천으로 검증·정산·BI가 일관되게 도는가",
|
||||
"AI 설계자료→응찰→낙찰→발주 폐루프가 성립하는가",
|
||||
], headfill=BLUE, bodysize=9.6, igap=0.55)
|
||||
titlecard(s, 6.73, 4.2, 6.05, 2.1, "기대 효과(가설이 참이면)", PURPLE, [
|
||||
[{"t":"의사결정 가속 ","bold":True},{"t":"— 도면 없이 사진으로 즉시 컨펌"}],
|
||||
[{"t":"데이터 일관성 ","bold":True},{"t":"— 공간 단일원천으로 재작업·오류 감소"}],
|
||||
[{"t":"발주 자동화 ","bold":True},{"t":"— 옥션 폐루프로 계약 리드타임 단축"}],
|
||||
], headfill=PURPLE, bodysize=9.6, igap=0.55)
|
||||
caption(s, "배경·목적은 docs/PLANNING.md 및 OWNER_FEEDBACK.md(소유자 지시 로그)를 근거로 정리.")
|
||||
|
||||
def s_solution():
|
||||
s = slide(); content_header(s, "1. 추진 개요", "1-2. 검증 대상 솔루션 및 기술 개요", "1")
|
||||
text(s, 0.55, 1.4, 12.2, 0.3, "검증 대상 = 킨텍스 자동전시시스템의 핵심 3결합(해자) + 이를 떠받치는 확정 기술스택", size=11, color=INK, bold=True)
|
||||
assets = [("나노바나나 시공 예측","도면을 못 읽어도 신청 시점에 '시공 후 사진'으로 의사결정 (image-to-image·구조보존)", PURPLE),
|
||||
("PostGIS 실측 공간데이터","부스 폴리곤·트렌치 포인트·배선 LineString을 단일 원천에서 검증·정산·BI", BLUE),
|
||||
("공사 옥션 폐루프","AI 설계자료 → 응찰 → 낙찰 → 발주 자동전환, 한 흐름에서 종결", TEAL)]
|
||||
for i,(t,d,cl) in enumerate(assets):
|
||||
x=0.55+i*4.12
|
||||
card(s, x, 1.78, 3.95, 1.6)
|
||||
rect(s, x, 1.78, 0.09, 1.6, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.28, 1.94, 3.5, 0.4, t, size=12, color=cl, bold=True)
|
||||
text(s, x+0.28, 2.42, 3.55, 0.9, d, size=9.4, color=INK, line_spacing=1.05)
|
||||
stack = [("프론트엔드","React 18/19 + Vite + TypeScript","반응형 — 데스크톱=설계/에디터, 모바일=조회·승인·현장"),
|
||||
("백엔드","Spring Boot 3.x (Java 17) + MyBatis","REST + WebSocket(STOMP), 룰 엔진·배치/배선 엔진"),
|
||||
("데이터베이스","PostgreSQL + PostGIS","부스 폴리곤·트렌치 포인트·배선 LineString 공간 데이터"),
|
||||
("비동기","Redis 작업 큐","RenderJob·서류·알림 비동기 처리"),
|
||||
("이미지 생성","나노바나나 Python 워커(google-genai)","gemini-3.1-flash-image-preview, 오브젝트 스토리지+WS 완료 푸시"),
|
||||
("AI","Claude API 기본 + 설정형 전환","AiTextRouter/AiConfig — 조건해석·검수·추천·조회·번역"),
|
||||
("인증·보안","JWT + 2FA(TOTP RFC6238) · SSO(Keycloak OIDC)","6역할 RBAC·테넌트 격리, PostGIS 공간검증")]
|
||||
table(s, 0.55, 3.6, 12.23, ["레이어","기술","검증 관점"], stack, [2.2, 4.7, 5.33], rowh=0.4, hsize=9.5, bsize=8.5)
|
||||
caption(s, "확정 스택(2026-07-11, 소유자 지정) — GUARDiA 표준 프레임워크(Spring Boot 3.5 + React 19 + MyBatis + PostgreSQL)와 정렬.", y=6.68)
|
||||
|
||||
def s_scope():
|
||||
s = slide(); content_header(s, "1. 추진 개요", "1-3. 검증 범위 및 대상 (Scope)", "1")
|
||||
# In-scope 카드
|
||||
card(s, 0.55, 1.45, 6.05, 3.55)
|
||||
rect(s, 0.55, 1.45, 6.05, 0.46, fill=GREEN, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 0.55, 1.45, 6.05, 0.46, "검증 범위 (In-Scope)", size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
bullets(s, 0.78, 2.08, 5.6, [
|
||||
[{"t":"부스 코어(M2~M5) ","bold":True},{"t":"— 배치·설계·배선·시각화 자동화"}],
|
||||
[{"t":"공사 옥션 폐루프 ","bold":True},{"t":"— 응찰→낙찰→발주 전환"}],
|
||||
[{"t":"6역할 포털 ","bold":True},{"t":"— 주최자·참가사·공사사·관람객·관리자·운영"}],
|
||||
[{"t":"공통·시스템관리 ","bold":True},{"t":"— WISE 이식(인증 2FA·코드·메뉴·감사)"}],
|
||||
[{"t":"데이터 파이프라인 ","bold":True},{"t":"— 크롤 적재 + 데모 시드 폐루프"}],
|
||||
[{"t":"배포 자동화 ","bold":True},{"t":"— CI/CD + health 게이트 실증"}],
|
||||
], gap=0.48, size=9.5, dot=GREEN)
|
||||
# Out-of-scope 카드
|
||||
card(s, 6.73, 1.45, 6.05, 3.55)
|
||||
rect(s, 6.73, 1.45, 6.05, 0.46, fill=SLATE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 6.73, 1.45, 6.05, 0.46, "검증 제외 (Out-of-Scope · 본사업 이관)", size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
bullets(s, 6.96, 2.08, 5.6, [
|
||||
[{"t":"운영 HA/이중화 ","bold":True},{"t":"— 개발서버 단일 노드 범위"}],
|
||||
[{"t":"대규모 부하테스트 ","bold":True},{"t":"— 성능 SLA 튜닝은 본사업"}],
|
||||
[{"t":"외부 연동 실계약 ","bold":True},{"t":"— PG 정산·전자계약 실서비스"}],
|
||||
[{"t":"보안 인증·취약점 진단 ","bold":True},{"t":"— 정식 모의해킹 별도"}],
|
||||
[{"t":"스토어 정식 배포 ","bold":True},{"t":"— 모바일은 EAS 내부빌드까지"}],
|
||||
[{"t":"실 운영 데이터 이행 ","bold":True},{"t":"— 마이그레이션은 본사업"}],
|
||||
], gap=0.48, size=9.5, dot=SLATE)
|
||||
lead(s, 0.55, 5.25, 12.23, 0.55, "대상 환경 — 개발서버 kintex.zioinfo.co.kr:8021 (PostGIS+Redis+Flyway) · 운영 kintex.wise.ai.kr 은 본사업 전환 후 구성", fill=NAVY, size=12)
|
||||
caption(s, "PoC는 '실현 가능성 실증'에 집중 — 비기능(HA·부하·보안점검)은 조건부 항목으로 본사업 전환 전제.", y=6.7)
|
||||
|
||||
# ================================================================
|
||||
# Ⅱ. 검증 목표 및 평가 기준
|
||||
# ================================================================
|
||||
def sec2_divider():
|
||||
divider("2", "검증 목표 및 평가 기준", "GOALS & CRITERIA",
|
||||
["핵심 검증 항목(KPI·요구사항)", "성공/실패 판단 기준", "테스트 환경 및 구성(Architecture)"])
|
||||
|
||||
def s_kpi():
|
||||
s = slide(); content_header(s, "2. 검증 목표 및 평가 기준", "2-1. 핵심 검증 항목 (KPI 및 목표치)", "2")
|
||||
rows = [("KPI-01","모듈 구현","부스 코어 M2~M5 100% + 도메인","코어 완결 · 도메인 모듈 실동작", ("정량",BLUE)),
|
||||
("KPI-02","화면 구현","설계 89화면 + 트랙 셸","전 화면 라우팅·데이터 배선", ("정량",BLUE)),
|
||||
("KPI-03","나노바나나 시각화","시공 예측 E2E 라이브","렌더 완료·구조보존·워터마크(목표 ≤60초)", ("정량",PURPLE)),
|
||||
("KPI-04","AI 자동배치·규정","자연어 해석·규정 플래깅","3안 생성 + 규정(5m·방염·리깅) 자동 검출", ("정량",PURPLE)),
|
||||
("KPI-05","공사 옥션 폐루프","응찰→낙찰→발주 전이","역경매 스코어링·상태 무결 전이", ("정량",TEAL)),
|
||||
("KPI-06","데이터 시드·크롤","실측 데이터 반영","다수 테이블 시드 + 포스터·행사 크롤 적재", ("정량",TEAL)),
|
||||
("KPI-07","배포 자동화","CI/CD health 게이트","무중단 배포 + 공개 API 스모크 통과", ("정량",GREEN)),
|
||||
("KPI-08","모바일 앱 빌드","EAS APK 산출","현장·관람 앱 내부빌드 QR 배포", ("정량",GREEN))]
|
||||
table(s, 0.55, 1.45, 12.23, ["ID","검증 항목","검증 목표","성공 판정 기준(목표치)","구분"],
|
||||
rows, [1.1, 2.5, 2.6, 4.73, 1.3], rowh=0.53, hsize=9.5, bsize=8.6)
|
||||
caption(s, "핵심 검증 항목은 PLANNING §8 아키텍처·design.md·백로그에서 도출. 목표치는 개발서버(kintex.zioinfo.co.kr:8021) 기준.", y=6.72)
|
||||
|
||||
def s_criteria():
|
||||
s = slide(); content_header(s, "2. 검증 목표 및 평가 기준", "2-2. 성공/실패 판단 기준 정의", "2")
|
||||
grades = [("성공 (Pass)","목표치 100% 달성 · 라이브 실증", GREEN, GREEN_LT),
|
||||
("조건부 (Conditional)","핵심 달성 · 운영/비기능 일부 보완 필요", AMBER, AMBER_LT),
|
||||
("실패 (Fail)","핵심 가설 미검증 · 재설계 필요", RED, RED_LT)]
|
||||
for i,(t,d,cl,bg) in enumerate(grades):
|
||||
x=0.55+i*4.12
|
||||
rect(s, x, 1.45, 3.95, 1.15, fill=bg, line=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
rect(s, x, 1.45, 0.09, 1.15, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.28, 1.62, 3.5, 0.4, t, size=12.5, color=cl, bold=True)
|
||||
text(s, x+0.28, 2.08, 3.55, 0.5, d, size=9.4, color=INK, line_spacing=1.02)
|
||||
rows = [("배치 자동생성","규정(높이 5m·방염·리깅 6.5~8.5m) 위반 자동 플래깅","솔버 미수렴·규정 위반 미검출"),
|
||||
("나노바나나 예측","렌더 완료·구조보존·워터마크 강제 부착","타임아웃 / 원본 구조 붕괴 / 워터마크 누락"),
|
||||
("공간 정합","ST_Area/ST_Intersects/ST_Length 결과 일치","공간 연산 오차·좌표계 불일치"),
|
||||
("옥션 폐루프","응찰→낙찰→발주 상태 무결 전이","상태 전이 누락·데이터 불일치"),
|
||||
("보안 불변","자격증명·PII·스택트레이스 미노출·2FA 통과","민감정보 노출 / 권한 우회 발생"),
|
||||
("배포 게이트","health 200 + 공개 API 스모크 통과","health 실패 / 스모크 미달 / 롤백 미작동")]
|
||||
table(s, 0.55, 2.85, 12.23, ["검증 항목","성공 기준 (Pass)","실패 기준 (Fail)"],
|
||||
rows, [2.6, 5.2, 4.43], rowh=0.44, hsize=9.5, bsize=8.8, headfill=INK)
|
||||
caption(s, "판정은 3등급(성공/조건부/실패). 비기능(HA·부하·보안점검)은 개발서버 PoC 범위 밖 → 조건부 항목으로 분류.", y=6.66)
|
||||
|
||||
def s_architecture():
|
||||
s = slide(); content_header(s, "2. 검증 목표 및 평가 기준", "2-3. 테스트 환경 및 구성 (Architecture)", "2")
|
||||
def box(x,y,w,h,t,sub,fill,tc,ln=None):
|
||||
rect(s,x,y,w,h,fill=fill,line=(ln or fill),line_w=1.2,shape=MSO_SHAPE.ROUNDED_RECTANGLE,shadow=True)
|
||||
text(s,x,y+0.1,w,0.3,t,size=10,color=tc,bold=True,align=PP_ALIGN.CENTER)
|
||||
text(s,x,y+0.42,w,h-0.46,sub,size=7.8,color=(WHITE if fill!=WHITE else MUTED),align=PP_ALIGN.CENTER,line_spacing=0.9)
|
||||
rect(s,0.55,1.45,12.23,1.0,fill=BLUE_LT,line=BLUE,shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s,0.7,1.5,2.4,0.3,"클라이언트 계층",size=9.5,color=BLUE_DK,bold=True)
|
||||
for i,(t,sub) in enumerate([("주최자 웹","설계·운영"),("참가사 웹","신청·승인"),("공사사 포털","응찰·발주"),("관람객 웹/앱","등록·현장"),("관리자","백오피스"),("모바일","Expo/EAS")]):
|
||||
box(0.75+i*1.99,1.82,1.82,0.52,t,sub,WHITE,INK,BLUE)
|
||||
rect(s,0.55,2.62,12.23,1.55,fill=PURPLE_LT,line=PURPLE,shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s,0.7,2.67,6.0,0.3,"애플리케이션 계층 (Spring Boot 3.x · Java 17 · MyBatis)",size=9.5,color=PURPLE_DK,bold=True)
|
||||
for i,(t,sub) in enumerate([("REST API","6포털 서비스"),("WebSocket(STOMP)","렌더·옥션 실시간"),("룰 엔진","규정·요율"),("배치/배선 엔진","PostGIS 연산"),("AiTextRouter","Claude 기본")]):
|
||||
box(0.75+i*2.4,3.0,2.25,0.98,t,sub,WHITE,INK,PURPLE)
|
||||
rect(s,0.55,4.32,12.23,1.55,fill=RGBColor(0xEC,0xF6,0xF3),line=TEAL,shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s,0.7,4.37,6.0,0.3,"데이터 · 워커 · 인프라 계층",size=9.5,color=TEAL,bold=True)
|
||||
for i,(t,sub,cl) in enumerate([("PostgreSQL+PostGIS","공간 단일원천",BLUE),("Redis","작업 큐",RED),("나노바나나 워커","Gemini 이미지",PURPLE),("오브젝트 스토리지","렌더 결과",NAVY),("Gitea CI/CD","자동배포·health",GREEN)]):
|
||||
box(0.75+i*2.4,4.72,2.25,0.98,t,sub,WHITE,INK,cl)
|
||||
lead(s, 0.55, 6.02, 12.23, 0.5, "PoC 서버 — kintex.zioinfo.co.kr:8021 (개발) · Flyway 마이그레이션 라이브 dry-run(BEGIN…ROLLBACK) 후 반영 · 시크릿 fail-fast 프로파일", fill=NAVY, size=11.5)
|
||||
|
||||
# ================================================================
|
||||
# Ⅲ. PoC 수행 방안 및 방법
|
||||
# ================================================================
|
||||
def sec3_divider():
|
||||
divider("3", "PoC 수행 방안 및 방법", "EXECUTION METHOD",
|
||||
["기능·성능 검증 시나리오", "데이터 준비 및 적용 범위", "나노바나나 시각화 검증 방법"])
|
||||
|
||||
def s_scenario():
|
||||
s = slide(); content_header(s, "3. PoC 수행 방안 및 방법", "3-1. 기능·성능 검증 시나리오", "3")
|
||||
text(s, 0.55, 1.4, 12.2, 0.3, "핵심 사용자 여정을 E2E 시나리오로 정의하고, 각 단계의 통과 조건을 사전 명세 — 개발서버에서 라이브 실행", size=10.5, color=INK, bold=True)
|
||||
steps = [("S-1 신청·설계","참가사 신청 → AI 부스 배치 3안 생성 → 규정 자동 플래깅 → 배선/물량 산출", BLUE),
|
||||
("S-2 시각화","확정 배치 → 나노바나나 시공 후 사진 렌더 → Before/After 비교 → 워터마크 확인", PURPLE),
|
||||
("S-3 발주 옥션","AI 설계자료 공개 → 공사사 응찰 → 역경매 스코어링 → 낙찰·발주 전환", TEAL),
|
||||
("S-4 운영·분석","현장 체크인·리드 캡처 → 정산 집계 → BI 대시보드 → KPI 리포트", GREEN)]
|
||||
for i,(t,d,cl) in enumerate(steps):
|
||||
y=1.85+i*0.82
|
||||
rect(s, 0.55, y, 0.5, 0.7, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 0.55, y, 0.5, 0.7, "S%d"%(i+1), size=13, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
card(s, 1.2, y, 11.58, 0.7)
|
||||
text(s, 1.42, y+0.09, 3.0, 0.5, t, size=11, color=cl, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||||
text(s, 4.4, y+0.09, 8.2, 0.5, d, size=9.4, color=INK, anchor=MSO_ANCHOR.MIDDLE, line_spacing=0.95)
|
||||
# 성능 관점
|
||||
titlecard(s, 0.55, 5.28, 6.05, 1.1, "성능 검증 관점", NAVY, [
|
||||
[{"t":"렌더 응답 ","bold":True},{"t":"— 나노바나나 렌더 목표 ≤60초, WS 완료 푸시"}],
|
||||
[{"t":"공간 연산 ","bold":True},{"t":"— PostGIS ST_* 쿼리 정확·응답성"}],
|
||||
], headfill=NAVY, bodysize=9.2, igap=0.32)
|
||||
titlecard(s, 6.73, 5.28, 6.05, 1.1, "검증 방법론", BLUE, [
|
||||
[{"t":"점진 QA ","bold":True},{"t":"— 각 모듈 완성 직후 경계면(shape) 교차검증"}],
|
||||
[{"t":"게이트 ","bold":True},{"t":"— Phase별 QA PASS 후 다음 단계 진행"}],
|
||||
], headfill=BLUE, bodysize=9.2, igap=0.32)
|
||||
caption(s, "시나리오·통과조건은 design.md 화면흐름 + 백로그 인수기준에 정렬. 실패 시 원인 분석 → 보완 → 재검증 루프.", y=6.5)
|
||||
|
||||
def s_data():
|
||||
s = slide(); content_header(s, "3. PoC 수행 방안 및 방법", "3-2. 데이터 준비 및 적용 범위", "3")
|
||||
text(s, 0.55, 1.4, 12.2, 0.3, "실감형 실증을 위해 실측 자산 크롤 + 멱등 데모 시드(Flyway)로 전 모듈 데이터를 사전 구축", size=10.5, color=INK, bold=True)
|
||||
# 데이터 소스 3분류
|
||||
cats = [("실측 자산 (크롤)","kintex.com 행사·포스터·관람안내·교통 정보를 크롤 → 멱등 시드로 적재", PURPLE, PURPLE_LT),
|
||||
("도면·공간 데이터","제1전시장 평면도 JPG 15장 + CAD(DWG, 트렌치 포함) → PostGIS 폴리곤·포인트·라인", BLUE, BLUE_LT),
|
||||
("데모 트랜잭션 시드","행사·부스·참가사·옥션·리드·정산·결재·공지·알림을 폐루프로 연결", TEAL, RGBColor(0xE9,0xF6,0xF4))]
|
||||
for i,(t,d,cl,bg) in enumerate(cats):
|
||||
x=0.55+i*4.12
|
||||
rect(s, x, 1.82, 3.95, 1.5, fill=bg, line=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
rect(s, x, 1.82, 0.09, 1.5, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.28, 1.98, 3.55, 0.4, t, size=11, color=cl, bold=True)
|
||||
text(s, x+0.28, 2.42, 3.55, 0.85, d, size=9.2, color=INK, line_spacing=1.05)
|
||||
# 데이터 준비 원칙 표
|
||||
rows = [("멱등성","Flyway 마이그레이션으로 재실행 안전 — 라이브 dry-run(BEGIN…ROLLBACK) 후 반영"),
|
||||
("행사 스코프 정합","데모 해소행사에 시드를 묶어 '화면이 비어 보이는' 오탐 방지"),
|
||||
("데이터 표준","공통코드·view/mview·배치 집계 — FK 최소화로 이식성 확보"),
|
||||
("PII·보안","테스트 데이터는 가명 처리, 민감정보 미노출 불변 준수"),
|
||||
("공간 검증","좌표계(SRID) 일관 · ST_Area/ST_Length로 물량·정산 교차검증")]
|
||||
table(s, 0.55, 3.55, 12.23, ["준비 원칙","내용"], rows, [2.6, 9.63], rowh=0.46, hsize=9.5, bsize=9, headfill=NAVY)
|
||||
caption(s, "확보 자산: docs/assets/floorplans/ 홀별 평면도 15장 + CAD. 데이터 표준은 data.md, 크롤은 kintex-crawler 트랙.", y=6.5)
|
||||
|
||||
def s_nanobanana():
|
||||
s = slide(); content_header(s, "3. PoC 수행 방안 및 방법", "3-3. 나노바나나 시각화 검증 방법", "3")
|
||||
text(s, 0.55, 1.4, 12.2, 0.3, "PoC 최대 차별점 — '시공 후 사진 예측'을 image-to-image 파이프라인으로 검증한다", size=11, color=INK, bold=True)
|
||||
# 파이프라인 단계
|
||||
flow = [("설계 입력","배치·부스 도면",BLUE),("프롬프트 생성","보존/교체 규칙",PURPLE),("Gemini 렌더","워커 큐 처리",TEAL),
|
||||
("구조 검증","원본 보존 확인",NAVY),("워터마크·저장","오브젝트 스토리지",GREEN)]
|
||||
n=len(flow); seg=12.23/n
|
||||
for i,(t,d,cl) in enumerate(flow):
|
||||
x=0.55+i*seg
|
||||
card(s, x+0.06, 1.85, seg-0.4, 1.1)
|
||||
rect(s, x+0.06, 1.85, seg-0.4, 0.08, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.06, 2.02, seg-0.4, 0.4, t, size=10.5, color=cl, bold=True, align=PP_ALIGN.CENTER)
|
||||
text(s, x+0.06, 2.44, seg-0.4, 0.45, d, size=8.6, color=MUTED, align=PP_ALIGN.CENTER, line_spacing=0.95)
|
||||
if i<n-1:
|
||||
text(s, x+seg-0.36, 1.85, 0.34, 1.1, "▶", size=13, color=SLATE, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
titlecard(s, 0.55, 3.25, 6.05, 3.0, "검증 항목 (Acceptance)", PURPLE, [
|
||||
[{"t":"라이브 렌더 ","bold":True},{"t":"— Gemini 유료전환·실제 이미지 생성(모의 아님)"}],
|
||||
[{"t":"구조 보존 ","bold":True},{"t":"— 부스 형태·배치 유지, 마감/조명만 변화"}],
|
||||
[{"t":"응답 시간 ","bold":True},{"t":"— 렌더 완료 목표 ≤60초, WS 완료 푸시 수신"}],
|
||||
[{"t":"워터마크 강제 ","bold":True},{"t":"— AI 생성 이미지 식별 표식 필수 부착"}],
|
||||
[{"t":"Before/After ","bold":True},{"t":"— 슬라이더 비교 UI로 시각 검수"}],
|
||||
], headfill=PURPLE, bodysize=9.4, igap=0.46)
|
||||
titlecard(s, 6.73, 3.25, 6.05, 3.0, "검증 방법·안전장치", NAVY, [
|
||||
[{"t":"참조 방식 ","bold":True},{"t":"— ReRoomAI image-to-image 보존/교체 프롬프트 차용"}],
|
||||
[{"t":"모델 ","bold":True},{"t":"— gemini-3.1-flash-image-preview"}],
|
||||
[{"t":"키 관리 ","bold":True},{"t":"— GEMINI_API_KEY 환경변수·마스킹·미커밋"}],
|
||||
[{"t":"라이브 게이트 ","bold":True},{"t":"— NANOBANANA_LIVE=1 로 실호출 활성"}],
|
||||
[{"t":"실패 대응 ","bold":True},{"t":"— 타임아웃·재시도, 큐 기반 비동기 격리"}],
|
||||
], headfill=NAVY, bodysize=9.4, igap=0.46)
|
||||
caption(s, "호출은 tools/nanobanana 모듈만 경유(규칙). 선행 게이트 G1(나노바나나 라이브)이 착수 전제.", y=6.5)
|
||||
|
||||
# ================================================================
|
||||
# Ⅳ. 추진 일정 및 수행 조직
|
||||
# ================================================================
|
||||
def sec4_divider():
|
||||
divider("4", "추진 일정 및 수행 조직", "SCHEDULE & TEAM",
|
||||
["추진 일정 (WBS · 마일스톤)", "수행 조직 및 R&R", "산출물 및 형상관리 계획"])
|
||||
|
||||
def s_schedule():
|
||||
s = slide(); content_header(s, "4. 추진 일정 및 수행 조직", "4-1. 추진 일정 (WBS · 마일스톤)", "4")
|
||||
text(s, 0.55, 1.38, 12.2, 0.3, "PoC 집중 기간 2026-07-11 ~ 07-24 (14일·6세션) · 결과보고 08-11 (시간 진행 순)", size=11, color=INK, bold=True)
|
||||
timeline(s, 0.55, 1.72, 12.23, [
|
||||
("07-11","기획·아키텍처","스택 확정, 하네스(전문 에이전트 12팀) 구성, PLANNING·design·백로그"),
|
||||
("07-12~13","공통·시드·UI","대량 시드, 공개사이트·AI 관람도우미, Nifty 정렬, 신규 도메인"),
|
||||
("07-14~15","AI·나노바나나 E2E","자연어 배치 해석, ReRoom 스타일, Gemini 유료전환, DWG·트렌치 캘리브"),
|
||||
("07-23~24","시연·QA 종결","공개사이트·멀티테넌시, 마이그레이션 반영, QA PASS · 결과보고 08-11"),
|
||||
])
|
||||
# 게이트
|
||||
text(s, 0.55, 4.35, 12.2, 0.3, "선행 게이트 (착수 전제) · Phase 게이트", size=11, color=INK, bold=True)
|
||||
gates = [("G1 나노바나나 라이브","Gemini 실렌더(API키·LIVE=1)·워터마크·미커밋", PURPLE),
|
||||
("G2 개발 배포서버","kintex.zioinfo.co.kr:8021 · PostGIS+Redis+Flyway", BLUE),
|
||||
("G3 모바일 EAS 빌드","외부 클라우드 빌드 — 소유자 승인 후 실행", TEAL),
|
||||
("Phase 게이트","각 단계 QA PASS 후 다음 단계 진행", GREEN)]
|
||||
for i,(t,d,cl) in enumerate(gates):
|
||||
x=0.55+i*3.08
|
||||
card(s, x, 4.72, 2.92, 1.35)
|
||||
rect(s, x, 4.72, 2.92, 0.08, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.2, 4.9, 2.55, 0.5, t, size=10, color=cl, bold=True, line_spacing=0.95)
|
||||
text(s, x+0.2, 5.42, 2.55, 0.6, d, size=8.6, color=MUTED, line_spacing=0.98)
|
||||
caption(s, "일정은 6세션 단위 스프린트. 배포 전 필수: Flyway 라이브 dry-run·시크릿 확인·파일단위 커밋·health 게이트.", y=6.5)
|
||||
|
||||
def s_org():
|
||||
s = slide(); content_header(s, "4. 추진 일정 및 수행 조직", "4-2. 수행 조직 및 R&R", "4")
|
||||
text(s, 0.55, 1.4, 12.2, 0.3, "수행 조직 — 거버넌스 · 아키텍트 · 구현 · 품질/배포 (전문 에이전트 12팀 + 범용)", size=11, color=INK, bold=True)
|
||||
rows = [("거버넌스","kintex-pm · dev-pm · pmo","범위·일정·리스크·게이트(G1/G2)·품질 거버넌스", NAVY),
|
||||
("아키텍트","AA · SA · TA · DA · NA","앱·시스템·기술·데이터·네트워크 아키텍처 표준", BLUE),
|
||||
("공통·코어","common-dev · backend · frontend · db-engineer","WISE 공통레이어 이식 + 부스코어(M2~M5)", PURPLE),
|
||||
("도메인","bidding · visitor · cms · bi · admin","옥션·관람·CMS·경영분석·관리자 백오피스", TEAL),
|
||||
("AI·품질·배포","ai-dev · visualizer · qa · devops","Claude AI·나노바나나·점진 QA·CI/CD", GREEN)]
|
||||
y=1.85
|
||||
for i,(cat,mem,desc,cl) in enumerate(rows):
|
||||
yy=y+i*0.62
|
||||
rect(s, 0.55, yy, 2.5, 0.52, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 0.7, yy, 2.3, 0.52, cat, size=10.5, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||||
rect(s, 3.2, yy, 5.1, 0.52, fill=PANEL, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 3.35, yy, 4.95, 0.52, mem, size=9.3, color=INK, bold=True, anchor=MSO_ANCHOR.MIDDLE)
|
||||
rect(s, 8.45, yy, 4.33, 0.52, fill=CARD, line=LINE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 8.6, yy, 4.05, 0.52, desc, size=8.8, color=MUTED, anchor=MSO_ANCHOR.MIDDLE, line_spacing=0.9)
|
||||
lead(s, 0.55, 5.15, 12.23, 0.55, "실행 모드 — 하이브리드(전문가 풀 + 감독자): 다중 도메인은 팀, 단일·분석·점검은 서브 에이전트", fill=BLUE, size=12)
|
||||
caption(s, "선행 게이트(G1·G2) + Phase 게이트 QA 통과로 진행. 실명 대신 직군·역할로 표기.", y=6.5)
|
||||
|
||||
def s_deliverables():
|
||||
s = slide(); content_header(s, "4. 추진 일정 및 수행 조직", "4-3. 산출물 및 형상관리 계획", "4")
|
||||
text(s, 0.55, 1.4, 12.2, 0.3, "PoC 산출물은 문서·코드·데이터·보고로 구분하고, Gitea 저장소에서 형상관리한다", size=11, color=INK, bold=True)
|
||||
rows = [("기획","PLANNING.md · IMPLEMENTATION_BACKLOG.md · OWNER_FEEDBACK.md","planner","docs/"),
|
||||
("설계","design.md(화면 스펙·Stitch 프롬프트) · data.md · 아키텍처 문서","designer·아키텍트","docs/"),
|
||||
("소스","src/backend(Spring) · src/frontend(React) · mobile(Expo) · tools/nanobanana","구현팀","src/·mobile/"),
|
||||
("데이터","Flyway 마이그레이션(스키마·인덱스·시드) · 크롤 적재 스크립트","db-engineer·crawler","db/migration"),
|
||||
("품질","QA 리포트 · GAP_AUDIT · 보안 체크리스트","qa","docs/"),
|
||||
("보고","PoC 추진계획서 · 결과보고서(PPTX) · 완료보고","pmo","docs/deliverables/")]
|
||||
table(s, 0.55, 1.85, 12.23, ["구분","주요 산출물","담당","위치"], rows, [1.5, 6.6, 2.3, 1.83], rowh=0.5, hsize=9.5, bsize=8.7, headfill=NAVY)
|
||||
titlecard(s, 0.55, 5.15, 6.05, 1.15, "형상관리 원칙", BLUE, [
|
||||
[{"t":"저장소 ","bold":True},{"t":"— Gitea(zio/kintex), origin SSH, webhook 자동배포"}],
|
||||
[{"t":"커밋 ","bold":True},{"t":"— 파일단위 커밋으로 공유 트리 교차 방지"}],
|
||||
], headfill=BLUE, bodysize=9.2, igap=0.34)
|
||||
titlecard(s, 6.73, 5.15, 6.05, 1.15, "품질 게이트", GREEN, [
|
||||
[{"t":"배포 전 ","bold":True},{"t":"— Flyway dry-run·시크릿 확인·health 200"}],
|
||||
[{"t":"검수 ","bold":True},{"t":"— Phase별 QA PASS + 소유자 피드백 반영"}],
|
||||
], headfill=GREEN, bodysize=9.2, igap=0.34)
|
||||
caption(s, "디자인은 항상 Google Stitch 경유(design.md → Stitch → React 이식). 문서는 한국어, 코드/커밋은 영어.", y=6.5)
|
||||
|
||||
# ================================================================
|
||||
# Ⅴ. 리스크 관리 및 기대효과
|
||||
# ================================================================
|
||||
def sec5_divider():
|
||||
divider("5", "리스크 관리 및 기대효과", "RISK & EXPECTED VALUE",
|
||||
["리스크 식별 및 대응 계획", "보안 및 품질 관리", "기대효과 및 본사업 전환 계획"])
|
||||
|
||||
def s_risk():
|
||||
s = slide(); content_header(s, "5. 리스크 관리 및 기대효과", "5-1. 리스크 식별 및 대응 계획", "5")
|
||||
rows = [("R1","나노바나나 렌더 품질·지연","高","프롬프트 보존/교체 튜닝 · 큐 비동기 · 타임아웃·재시도, 목표 ≤60초", ("대응",PURPLE)),
|
||||
("R2","공간데이터 좌표계·정합 오류","中","SRID 표준화 · ST_* 교차검증 · CAD/트렌치 캘리브레이션", ("대응",BLUE)),
|
||||
("R3","Stitch 디자인 도구 불안정","中","design.md 스펙 직접 구현 폴백(문서화) · Nifty 시스템 준수", ("완화",AMBER)),
|
||||
("R4","배포 롤백·캐시·교차커밋","中","Flyway dry-run · 파일단위 커밋 · health 게이트 · 롤백 절차", ("대응",GREEN)),
|
||||
("R5","AI 토큰 비용·응답 편차","低","AiTextRouter 설정형 전환 · 토큰 최소화 기획 · 캐싱", ("완화",TEAL)),
|
||||
("R6","일정 압축(14일)·범위 초과","中","코어 우선(M2~M5) · Out-of-scope 명확화 · 세션 게이트", ("대응",NAVY)),
|
||||
("R7","보안(자격증명·PII 노출)","高","불변식 강제 · 2FA · 스택트레이스 미노출 · 키 마스킹·미커밋", ("대응",RED))]
|
||||
table(s, 0.55, 1.45, 12.23, ["ID","리스크","심각도","대응 방안","구분"],
|
||||
rows, [0.9, 3.6, 1.3, 5.13, 1.3], rowh=0.6, hsize=9.5, bsize=8.6, headfill=INK)
|
||||
caption(s, "리스크는 세션 게이트마다 재점검. 高 리스크(R1·R7)는 선행 게이트·불변식으로 사전 통제.", y=6.62)
|
||||
|
||||
def s_security():
|
||||
s = slide(); content_header(s, "5. 리스크 관리 및 기대효과", "5-2. 보안 및 품질 관리", "5")
|
||||
# 보안 불변식
|
||||
card(s, 0.55, 1.45, 6.05, 3.2)
|
||||
rect(s, 0.55, 1.45, 6.05, 0.46, fill=RED, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 0.55, 1.45, 6.05, 0.46, "보안 불변식 (통과 조건)", size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
bullets(s, 0.78, 2.08, 5.6, [
|
||||
[{"t":"인증 ","bold":True},{"t":"— JWT + 2FA(TOTP RFC6238), 6역할 RBAC"}],
|
||||
[{"t":"격리 ","bold":True},{"t":"— 멀티테넌시 테넌트 격리, 권한 우회 차단"}],
|
||||
[{"t":"노출 금지 ","bold":True},{"t":"— 자격증명·PII·스택트레이스 미노출"}],
|
||||
[{"t":"AI 표식 ","bold":True},{"t":"— 나노바나나 생성 이미지 워터마크 강제"}],
|
||||
[{"t":"키 관리 ","bold":True},{"t":"— 시크릿 fail-fast·마스킹·미커밋"}],
|
||||
[{"t":"모바일 ","bold":True},{"t":"— 루트/탈옥 탐지·무결성·화면캡처 방지"}],
|
||||
], gap=0.44, size=9.4, dot=RED)
|
||||
# 품질 관리
|
||||
card(s, 6.73, 1.45, 6.05, 3.2)
|
||||
rect(s, 6.73, 1.45, 6.05, 0.46, fill=GREEN, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 6.73, 1.45, 6.05, 0.46, "품질 관리 계획", size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
bullets(s, 6.96, 2.08, 5.6, [
|
||||
[{"t":"점진 QA ","bold":True},{"t":"— 모듈 완성 직후 경계면(shape) 교차검증"}],
|
||||
[{"t":"공간 정합 ","bold":True},{"t":"— 배치·배선·규정 검증 로직 정합 확인"}],
|
||||
[{"t":"미개발 감사 ","bold":True},{"t":"— 메뉴↔라우트↔API 4자 체인 GAP 감사"}],
|
||||
[{"t":"빌드 회귀 ","bold":True},{"t":"— 각 배포 전 빌드·스모크 회귀 점검"}],
|
||||
[{"t":"게이트 ","bold":True},{"t":"— Phase별 QA PASS 후 진행"}],
|
||||
[{"t":"피드백 루프 ","bold":True},{"t":"— 소유자 지시 즉시 반영·재검증"}],
|
||||
], gap=0.44, size=9.4, dot=GREEN)
|
||||
lead(s, 0.55, 4.9, 12.23, 0.55, "보안 점검(모의해킹)·부하테스트는 PoC 범위 밖 → 본사업 전환 전 별도 수행(조건부 항목)", fill=NAVY, size=12)
|
||||
caption(s, "보안 체크리스트: docs/security/mobile-security-*.md. QA는 kintex-qa 점진 검증 트랙.", y=6.5)
|
||||
|
||||
def s_expected():
|
||||
s = slide(); content_header(s, "5. 리스크 관리 및 기대효과", "5-3. 기대효과 및 본사업 전환 계획", "5")
|
||||
# 기대효과 3
|
||||
effs = [("정성적 가치","도면 없이 사진으로 즉시 의사결정 · 데이터 일관성 · 발주 자동화", PURPLE),
|
||||
("정량적 목표","KPI 8종 달성 · 렌더 ≤60초 · 무중단 배포 · 크롤·시드 폐루프", BLUE),
|
||||
("사업적 효과","WISE 재사용으로 착수 공수 절감 · 본사업 전환 타당성 근거 확보", TEAL)]
|
||||
for i,(t,d,cl) in enumerate(effs):
|
||||
x=0.55+i*4.12
|
||||
card(s, x, 1.45, 3.95, 1.45)
|
||||
rect(s, x, 1.45, 0.09, 1.45, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, x+0.28, 1.6, 3.55, 0.4, t, size=11.5, color=cl, bold=True)
|
||||
text(s, x+0.28, 2.05, 3.55, 0.8, d, size=9.3, color=INK, line_spacing=1.05)
|
||||
# 본사업 전환 로드맵
|
||||
text(s, 0.55, 3.1, 12.2, 0.3, "본사업 전환 로드맵 (PoC 성공 판정 시)", size=11, color=INK, bold=True)
|
||||
timeline(s, 0.55, 3.42, 12.23, [
|
||||
("전환 판정","도입 권고","PoC 결과보고 → 도입 타당성 승인 → 본사업 착수"),
|
||||
("운영 고도화","HA·부하·보안","이중화·부하테스트·모의해킹·성능 SLA 튜닝"),
|
||||
("연동 정식화","외부 실계약","PG 정산·전자계약·SSO/HR 실연동, 데이터 이행"),
|
||||
("정식 배포","운영 오픈","kintex.wise.ai.kr 운영 · 모바일 스토어 배포"),
|
||||
], cl=PURPLE)
|
||||
caption(s, "PoC는 '전환 타당성 근거'를 만드는 단계 — 조건부 항목(운영·비기능)은 본사업 초기 스프린트에서 해소.", y=6.5)
|
||||
|
||||
def s_closing():
|
||||
s = slide()
|
||||
rect(s, -0.15, -0.15, 13.63, 7.8, fill=BLUE_DK)
|
||||
rect(s, 7.5, -1.5, 9, 11, fill=BLUE, shape=MSO_SHAPE.PARALLELOGRAM)
|
||||
rect(s, 10.2, -2.0, 7, 12, fill=PURPLE, shape=MSO_SHAPE.PARALLELOGRAM)
|
||||
text(s, 0.9, 1.35, 11, 0.5, "PoC 추진계획 — 종합", size=15, color=RGBColor(0x9C,0xB4,0xD8), bold=True)
|
||||
text(s, 0.9, 1.9, 11.5, 1.7, [
|
||||
[{"t":"“말이 아니라, 이미 도는 것으로 증명한다.”","size":32,"color":WHITE,"bold":True}],
|
||||
], line_spacing=1.05)
|
||||
rect(s, 0.95, 3.35, 0.9, 0.09, fill=PURPLE)
|
||||
bullets(s, 0.95, 3.7, 7.6, [
|
||||
[{"t":"14일 집중 PoC로 핵심 3결합(나노바나나·PostGIS·옥션 폐루프)을 개발서버에서 라이브 실증","color":WHITE}],
|
||||
[{"t":"KPI 8종 + 게이트 통과를 성공의 정의로 삼아 3등급 판정","color":WHITE}],
|
||||
[{"t":"WISE 표준 재사용으로 공수 절감, 본사업 전환 타당성 근거 확보","color":WHITE}],
|
||||
], gap=0.6, size=12, dot=PURPLE, tcolor=WHITE)
|
||||
rect(s, 0.9, 5.95, 11.5, 0.02, fill=RGBColor(0x3A,0x5A,0x8C))
|
||||
text(s, 0.9, 6.15, 8, 0.4, "킨텍스(KINTEX) AI 전시관리 시스템 · PoC 추진계획서", size=11, color=RGBColor(0x90,0xA6,0xC9), bold=True)
|
||||
text(s, 8.9, 6.15, 3.5, 0.4, "2026-08-11 · GUARDiA/WISE 팀", size=11, color=RGBColor(0x90,0xA6,0xC9), bold=True, align=PP_ALIGN.RIGHT)
|
||||
|
||||
# ================================================================
|
||||
# 부록
|
||||
# ================================================================
|
||||
def appendix():
|
||||
s = slide(); content_header(s, "APPENDIX", "부록 — PoC 통과 체크리스트 및 참조", "부록")
|
||||
# 체크리스트
|
||||
card(s, 0.55, 1.45, 6.05, 4.5)
|
||||
rect(s, 0.55, 1.45, 6.05, 0.46, fill=INK, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 0.55, 1.45, 6.05, 0.46, "PoC 통과 체크리스트", size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
checks = ["부스 배치 3안 생성 + 규정 자동 플래깅","나노바나나 라이브 렌더·구조보존·워터마크",
|
||||
"PostGIS ST_* 공간연산 정합","옥션 응찰→낙찰→발주 무결 전이",
|
||||
"6역할 RBAC + 2FA 인증 통과","크롤 적재 + 데모 시드 폐루프 구동",
|
||||
"CI/CD 무중단 배포 + health 게이트 200","모바일 EAS APK 내부빌드",
|
||||
"보안 불변식(자격증명·PII·스택트레이스) 준수"]
|
||||
for i,c in enumerate(checks):
|
||||
yy=2.02+i*0.42
|
||||
rect(s, 0.78, yy+0.03, 0.24, 0.24, fill=GREEN_LT, line=GREEN, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 0.78, yy+0.01, 0.24, 0.24, "✓", size=10, color=GREEN, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
text(s, 1.14, yy, 5.4, 0.4, c, size=9.4, color=INK, anchor=MSO_ANCHOR.MIDDLE)
|
||||
# 참조 자산
|
||||
card(s, 6.73, 1.45, 6.05, 4.5)
|
||||
rect(s, 6.73, 1.45, 6.05, 0.46, fill=NAVY, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 6.73, 1.45, 6.05, 0.46, "참조 자산 · 게이트 정의", size=12, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
|
||||
refs = [("docs/PLANNING.md","시스템 기획서 — 모듈·아키텍처·나노바나나 파이프라인"),
|
||||
("docs/design.md","UI 디자인 스펙 — 화면 Stitch 프롬프트"),
|
||||
("docs/IMPLEMENTATION_BACKLOG.md","구현 백로그 — Phase·모듈·의존"),
|
||||
("docs/analysis/reroomai-source.md","ReRoomAI 소스 — image-to-image 차용"),
|
||||
("G1 나노바나나","GEMINI_API_KEY·NANOBANANA_LIVE=1·워터마크"),
|
||||
("G2 개발서버","kintex.zioinfo.co.kr:8021·PostGIS+Redis+Flyway"),
|
||||
("G3 EAS 빌드","외부 클라우드 빌드 — 소유자 승인 후")]
|
||||
for i,(t,d) in enumerate(refs):
|
||||
yy=2.05+i*0.55
|
||||
rect(s, 6.95, yy+0.05, 0.09, 0.3, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
|
||||
text(s, 7.15, yy, 5.5, 0.3, t, size=9.4, color=BLUE_DK, bold=True)
|
||||
text(s, 7.15, yy+0.28, 5.5, 0.28, d, size=8.4, color=MUTED)
|
||||
caption(s, "본 계획서 승인 후 착수 — 선행 게이트(G1·G2) 해소를 전제로 6세션 스프린트 진행.", y=6.5)
|
||||
|
||||
# ================================================================
|
||||
# BUILD
|
||||
# ================================================================
|
||||
cover() # 1
|
||||
toc() # 2
|
||||
exec_summary() # 3
|
||||
sec1_divider() # 4
|
||||
s_background() # 5
|
||||
s_solution() # 6
|
||||
s_scope() # 7
|
||||
sec2_divider() # 8
|
||||
s_kpi() # 9
|
||||
s_criteria() # 10
|
||||
s_architecture() # 11
|
||||
sec3_divider() # 12
|
||||
s_scenario() # 13
|
||||
s_data() # 14
|
||||
s_nanobanana() # 15
|
||||
sec4_divider() # 16
|
||||
s_schedule() # 17
|
||||
s_org() # 18
|
||||
s_deliverables() # 19
|
||||
sec5_divider() # 20
|
||||
s_risk() # 21
|
||||
s_security() # 22
|
||||
s_expected() # 23
|
||||
s_closing() # 24
|
||||
appendix() # 25
|
||||
|
||||
prs.save(OUT)
|
||||
print(f"saved: {OUT} ({len(prs.slides.__iter__.__self__._sldIdLst)} slides)")
|
||||
BIN
docs/deliverables/POC계획보고서/킨텍스_AI_POC계획보고서.pptx
Normal file
BIN
docs/deliverables/POC계획보고서/킨텍스_AI_POC계획보고서.pptx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user