kintex/scripts/gen_ai_demo_ppt.py
ythong 397b5de985 docs(demo): customer-facing AI demo deck in scenario order
12-slide PPTX (cover, overview, agenda, divider+flow per demo 1-4,
closing) generated by scripts/gen_ai_demo_ppt.py. Content sourced from
internal AI_DEMO_SCRIPT.pptx with internal-only info (accounts,
checklist, troubleshooting) removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 18:46:05 +09:00

324 lines
17 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""고객 시연용 AI 파트 발표 PPT 생성 — 시연 시나리오 순 구성.
근거: docs/demo/AI_DEMO_SCRIPT.pptx(내부 절차서)의 4개 데모 흐름.
고객용이므로 계정·체크리스트·트러블슈팅 등 내부 정보는 제외.
출력: docs/demo/AI_시연_시나리오.pptx
"""
import os
BASE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "docs", "demo")
KOR_FONT = "맑은 고딕"
from pptx import Presentation
from pptx.util import Inches, 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
P_BLUE = RGBColor(0x00, 0x66, 0xB3)
P_DARK = RGBColor(0x00, 0x4C, 0x86)
P_AI = RGBColor(0x6D, 0x4A, 0xFF)
P_GREY = RGBColor(0x34, 0x40, 0x54)
P_LIGHT = RGBColor(0xEF, 0xF6, 0xFC)
P_WHITE = RGBColor(0xFF, 0xFF, 0xFF)
P_SOFT = RGBColor(0xD9, 0xEA, 0xF7)
P_FOOT = RGBColor(0x66, 0x70, 0x85)
P_INK = RGBColor(0x10, 0x18, 0x28)
TITLE = "KINTEX AI 전시·행사시스템"
def pset(run, size, bold=False, color=P_GREY):
f = run.font
f.size = Pt(size)
f.bold = bold
f.color.rgb = color
f.name = KOR_FONT
rPr = run._r.get_or_add_rPr()
for tag in ("a:ea", "a:cs"):
el = rPr.find(qn(tag))
if el is None:
el = rPr.makeelement(qn(tag), {})
rPr.append(el)
el.set("typeface", KOR_FONT)
def add_box(slide, x, y, w, h):
tb = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
tb.text_frame.word_wrap = True
return tb.text_frame
def add_rect(slide, x, y, w, h, fill, line=False):
sh = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h))
sh.fill.solid()
sh.fill.fore_color.rgb = fill
if not line:
sh.line.fill.background()
sh.shadow.inherit = False
return sh
def footer(slide, idx, total):
tf = add_box(slide, 0.5, 7.05, 12.3, 0.35)
r = tf.paragraphs[0].add_run()
r.text = f"{TITLE} · AI 기능 고객 시연"
pset(r, 9, color=P_FOOT)
tf2 = add_box(slide, 12.3, 7.05, 0.7, 0.35)
p2 = tf2.paragraphs[0]
p2.alignment = PP_ALIGN.RIGHT
r2 = p2.add_run()
r2.text = f"{idx}/{total}"
pset(r2, 9, color=P_FOOT)
def blank(prs):
return prs.slides.add_slide(prs.slide_layouts[6])
def head(slide, kicker, title):
add_rect(slide, 0, 0, 13.333, 0.18, P_BLUE)
tf = add_box(slide, 0.55, 0.35, 12.2, 1.15)
r = tf.paragraphs[0].add_run(); r.text = kicker; pset(r, 12, True, P_AI)
p2 = tf.add_paragraph()
r = p2.add_run(); r.text = title; pset(r, 25, True, P_DARK)
add_rect(slide, 0.6, 1.62, 12.1, 0.03, P_SOFT)
def add_table(slide, x, y, w, header, rows, col_w=None, hdr_size=11, size=10.5, row_h=0.42):
shp = slide.shapes.add_table(1 + len(rows), len(header), Inches(x), Inches(y),
Inches(w), Inches(row_h * (1 + len(rows))))
t = shp.table
if col_w:
for j, cw in enumerate(col_w):
t.columns[j].width = Inches(cw)
for j, htxt in enumerate(header):
c = t.cell(0, j)
c.fill.solid(); c.fill.fore_color.rgb = P_DARK
c.vertical_anchor = MSO_ANCHOR.MIDDLE
pa = c.text_frame.paragraphs[0]; pa.alignment = PP_ALIGN.CENTER
r = pa.add_run(); r.text = htxt; pset(r, hdr_size, True, P_WHITE)
for i, row in enumerate(rows):
for j, val in enumerate(row):
c = t.cell(i + 1, j)
c.fill.solid()
c.fill.fore_color.rgb = P_LIGHT if i % 2 == 0 else P_WHITE
c.vertical_anchor = MSO_ANCHOR.MIDDLE
c.margin_top = Pt(3); c.margin_bottom = Pt(3)
lines = str(val).split("\n")
for k, ln in enumerate(lines):
pa = c.text_frame.paragraphs[0] if k == 0 else c.text_frame.add_paragraph()
r = pa.add_run(); r.text = ln
pset(r, size, j == 0, P_INK if j != 0 else P_DARK)
return t
def note_bar(slide, y, text, h=0.55):
add_rect(slide, 0.6, y, 12.1, h, P_LIGHT)
tf = add_box(slide, 0.8, y + 0.06, 11.8, h - 0.1)
r = tf.paragraphs[0].add_run(); r.text = text; pset(r, 12, True, P_DARK)
# ══════════════════════════════ 슬라이드 정의 ══════════════════════════════
DEMOS = [
dict(
no="DEMO 1", tag="공개 홈 · 로그인 불필요", time="5분",
title="AI 관람 도우미",
sub="자연어로 묻고, 실제 행사 데이터만 근거로 답한다",
divider_points=[
"관람객이 홈페이지에서 말하듯 질문 — 행사·일정·교통을 즉시 안내",
"답변 근거는 실제 적재된 행사 DB — 근거가 없으면 \"모른다\"고 답변(환각 차단)",
],
steps=[
("1", "공개 홈 접속 → 관람객 트랙 진입", "-", "관람객 서브홈 · AI 도우미 노출"),
("2", "AI 도우미에 질문 ①", "\"이번 달 킨텍스에서 볼만한 전시 알려줘\"", "행사명·기간·홀을 DB 근거로 요약 답변"),
("3", "질문 ② — 구체 카테고리", "\"유아용품 관련 박람회 언제 해?\"", "해당 카테고리 행사 목록 + 근거 표기"),
("4", "질문 ③ — 교통·편의", "\"대중교통으로 가는 방법 알려줘\"", "교통 가이드 데이터 기반 안내"),
("5", "환각 차단 확인 — 근거 없는 질문", "\"킨텍스에서 BTS 콘서트 언제 해?\"", "근거 없음 → 모른다고 답변 (추측 없음)"),
],
point="핵심 포인트 — 모든 답변은 실제 행사 데이터 근거. 근거가 없으면 지어내지 않습니다.",
),
dict(
no="DEMO 2", tag="플로어플랜 스튜디오 · ★핵심", time="12분",
title="자연어 AI 자동배치",
sub="말로 시키면 배치가 나온다 — 자연어 → 3안 생성 → AI 평가 → 조감 렌더",
divider_points=[
"\"부스 120개, 프리미엄 3할…\" 한 문장 → AI가 조건 해석 → 배치안 3안 자동 생성",
"배치안마다 활용률·통로폭 실계산 지표 근거의 AI 평가 요약",
"선택한 배치안은 AI가 홀 전경(조감) 이미지로 렌더링",
],
steps=[
("1", "시연 행사 → 부스 배치(플로어플랜 스튜디오) 진입", "-", "홀 평면 캔버스 표시"),
("2", "[AI 자동배치] → 자연어로 조건 입력 → [AI 해석]", "\"부스 120개, 프리미엄 3할, 무대 1개,\n입구 2개로 3안 뽑아줘\"", "AI가 조건 해석 → 폼 자동 채움 + 해석 근거 표시"),
("3", "해석된 값 확인(수정 가능) → [생성]", "목표 부스 수만 150으로 수정해 보기", "배치안 A/B/C 3안 생성 (제약 엔진)"),
("4", "배치안별 AI 평가 요약 확인", "-", "활용률·통로폭 등 실계산 지표 근거 장단점 요약"),
("5", "홀 전경(조감) 렌더 → 마음에 드는 안 [적용]", "(재시도) \"소형 부스 위주 200개, 라운지 2개\"", "AI 조감 이미지 생성 · 선택안 캔버스 반영"),
],
point="핵심 포인트 — 수일 걸리던 CAD 배치 작업이 자연어 한 문장으로. AI 해석 결과는 폼에서 수정 가능해 사람이 최종 결정합니다.",
),
dict(
no="DEMO 3", tag="부스설계 스튜디오 · ★핵심", time="10분",
title="부스 사진 시안 (AI)",
sub="사진 한 장이 시공 시안이 된다 — 업로드 → 스타일 선택 → 시공 후 사진",
divider_points=[
"부스·공간 사진 한 장 업로드 → 스타일 선택 → AI가 '시공 후 결과 사진' 생성",
"구조 보존 — 벽·바닥·기둥·구도는 그대로, 인테리어·조명·사이니지만 교체",
"Before/After 슬라이더로 비교 — 장치공사 발주(견적) 자료로 바로 연결",
],
steps=[
("1", "부스설계 스튜디오 → [사진 시안 (AI)] 섹션", "-", "01 업로드 → 02 스타일 → 03 지시 3단 UI"),
("2", "01 부스/공간 사진 업로드", "샘플 부스 사진 (드래그앤드롭)", "미리보기 표시"),
("3", "02 스타일 선택 + 03 자연어 지시(선택)", "스타일: \"테크\"\n지시: \"로고 월과 LED 사이니지, 우드 톤 바닥\"", "[사진 시안 생성] 활성화"),
("4", "[사진 시안 생성] 클릭", "-", "생성 진행 표시 (약 30~60초)"),
("5", "Before/After 슬라이더로 비교", "(재생성) 스타일 \"럭셔리\"로 비교", "구조·구도 보존, 인테리어만 교체된 시안 + AI 워터마크"),
],
point="핵심 포인트 — 참가업체가 현장 사진만 보내면 공사 발주 전에 시공 결과를 미리 봅니다. 이 시안이 그대로 견적(옥션) 근거자료가 됩니다.",
),
dict(
no="DEMO 4", tag="경영분석 대시보드 · AI 콘솔", time="8분",
title="자연어 데이터 조회 + 규정 챗",
sub="데이터와 규정에 말로 묻는다 — 조회는 DB 실집계, 규정은 룰셋 근거",
divider_points=[
"\"이번 행사 부스 판매율은?\" — 자연어 질문을 DB 실조회로 답변",
"\"천장 높이 제한은?\" — 등록된 규정 룰셋 버전을 근거 조항과 함께 답변",
],
steps=[
("1", "좌측 메뉴 → 경영분석 → 대시보드 하단 AI 콘솔", "-", "NL Query / 규정 챗 탭"),
("2", "자연어 조회 ①", "\"이번 행사 부스 판매율 알려줘\"", "DB 실조회 수치·표 응답"),
("3", "자연어 조회 ②", "\"홀별 배정 현황 요약해줘\"", "홀 H1~H5 배정 집계 응답"),
("4", "규정 챗 ①", "\"부스 천장 높이 제한이 몇 미터야?\"", "룰셋(규정 버전) 근거 답변 + 근거 조항"),
("5", "규정 챗 ②", "\"전기 증설 신청은 언제까지 해야 해?\"", "규정·마일스톤 근거 답변"),
],
point="핵심 포인트 — 답변 근거인 규정 룰셋 버전을 관리 화면에서 함께 확인할 수 있어, AI 답변의 출처가 투명합니다.",
),
]
def build():
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
total = 4 + len(DEMOS) * 2 # 표지·개요·아젠다·클로징 + (간지+시나리오)×4
idx = 1
# ── 1. 표지 ──────────────────────────────────────────────
s = blank(prs)
add_rect(s, 0, 0, 13.333, 7.5, P_DARK)
add_rect(s, 0, 4.9, 13.333, 0.06, P_AI)
tf = add_box(s, 1.0, 2.0, 11.3, 2.6)
r = tf.paragraphs[0].add_run(); r.text = TITLE; pset(r, 40, True, P_WHITE)
p2 = tf.add_paragraph()
r = p2.add_run(); r.text = "AI 기능 고객 시연"; pset(r, 26, True, P_SOFT)
tf2 = add_box(s, 1.0, 5.1, 11.3, 1.6)
r = tf2.paragraphs[0].add_run()
r.text = "AI 관람 도우미 · 자연어 자동배치 · 부스 사진 시안 · 자연어 조회/규정 챗"
pset(r, 15, True, P_WHITE)
p2 = tf2.add_paragraph()
r = p2.add_run(); r.text = "\"신청서를 내는 시간, 시공 후 사진을 먼저 본다\" · 2026. 7."
pset(r, 13, False, P_SOFT)
idx += 1
# ── 2. 시연 개요 ─────────────────────────────────────────
s = blank(prs)
head(s, "OVERVIEW", "오늘 시연에서 보실 것")
add_table(s, 0.6, 2.0, 12.1,
["구분", "무엇을 보여드리나", "핵심 가치"],
[
("데모 1", "AI 관람 도우미 — 자연어 질문 → 행사·교통 안내", "근거 기반 답변 · 환각 차단"),
("데모 2 ★", "자연어 자동배치 — 한 문장 → 배치안 3안 + AI 평가 + 조감 렌더", "수일의 CAD 작업 → 수분"),
("데모 3 ★", "부스 사진 시안 — 사진 한 장 → 시공 후 결과 사진", "발주 전 결과 확인 · 견적 근거"),
("데모 4", "AI 콘솔 — 자연어 데이터 조회 + 규정 챗", "데이터·규정에 말로 질문"),
],
col_w=[1.5, 7.3, 3.3], row_h=0.62)
note_bar(s, 5.6,
"3가지 핵심 메시지 — ① 말로 시키면 배치가 나온다 ② 사진 한 장이 시공 시안이 된다 ③ 모든 답변은 근거 기반",
h=0.55)
tf = add_box(s, 0.6, 6.35, 12.1, 0.5)
r = tf.paragraphs[0].add_run()
r.text = "시연 환경: 라이브 시스템(실서버·실데이터) — 화면 캡처가 아닌 실제 동작을 시연합니다."
pset(r, 12, False, P_GREY)
footer(s, idx, total); idx += 1
# ── 3. 시연 순서 ─────────────────────────────────────────
s = blank(prs)
head(s, "AGENDA", "시연 순서 (약 40분)")
add_table(s, 0.6, 2.0, 12.1,
["순서", "내용", "시간", "위치"],
[
("데모 1", "AI 관람 도우미 — 근거 기반 자연어 응답 (로그인 불필요)", "5분", "공개 홈"),
("데모 2 ★", "자연어 AI 자동배치 → 3안 생성 → AI 평가 → 조감 렌더", "12분", "플로어플랜 스튜디오"),
("데모 3 ★", "부스 사진 시안 — 사진 업로드 → 시공 후 사진 + Before/After", "10분", "부스설계 스튜디오"),
("데모 4", "자연어 데이터 조회(NL Query) + 규정 챗", "8분", "경영분석 대시보드"),
("마무리", "질의응답 · 안전 설계(폴백) 소개", "5분", "-"),
],
col_w=[1.5, 7.3, 1.2, 2.1], row_h=0.6)
note_bar(s, 5.75, "각 데모는 실제 화면에서 라이브로 진행하며, 본 자료는 데모 사이 브리지(안내) 용도입니다.", h=0.55)
footer(s, idx, total); idx += 1
# ── 4~11. 데모별 간지 + 시나리오 ────────────────────────
for d in DEMOS:
# 간지 (라이브 시연 직전 브리지)
s = blank(prs)
add_rect(s, 0, 0, 13.333, 7.5, P_BLUE)
add_rect(s, 0, 5.9, 13.333, 0.05, P_AI)
tf = add_box(s, 1.2, 1.6, 11.0, 3.6)
r = tf.paragraphs[0].add_run()
r.text = f"{d['no']} · {d['tag']} · {d['time']}"
pset(r, 18, True, P_SOFT)
p2 = tf.add_paragraph()
r = p2.add_run(); r.text = d["title"]; pset(r, 40, True, P_WHITE)
p3 = tf.add_paragraph()
r = p3.add_run(); r.text = d["sub"]; pset(r, 17, False, P_SOFT)
tfp = add_box(s, 1.25, 4.6, 10.9, 1.9)
for k, pt in enumerate(d["divider_points"]):
pa = tfp.paragraphs[0] if k == 0 else tfp.add_paragraph()
pa.space_after = Pt(6)
r = pa.add_run(); r.text = f"{pt}"; pset(r, 14, False, P_WHITE)
footer(s, idx, total); idx += 1
# 시나리오 흐름 (진행자용 겸 고객 안내)
s = blank(prs)
head(s, f"{d['no']} · 시나리오", f"{d['title']} — 진행 흐름")
add_table(s, 0.6, 1.95, 12.1,
["단계", "진행", "입력(자연어) 예시", "보실 결과"],
d["steps"],
col_w=[0.7, 4.0, 3.9, 3.5], row_h=0.62, size=10)
note_bar(s, 6.1, d["point"], h=0.6)
footer(s, idx, total); idx += 1
# ── 12. 클로징 ──────────────────────────────────────────
s = blank(prs)
add_rect(s, 0, 0, 13.333, 7.5, P_DARK)
add_rect(s, 0, 1.9, 13.333, 0.05, P_AI)
tf = add_box(s, 1.2, 1.0, 11.0, 0.8)
r = tf.paragraphs[0].add_run(); r.text = "시연을 마치며 — 3가지 핵심 메시지"; pset(r, 28, True, P_WHITE)
msgs = [
("① 말로 시키면 배치가 나온다", "자연어 → 제약 엔진 → 배치안 3안 + 실계산 지표 근거 AI 평가"),
("② 사진 한 장이 시공 시안이 된다", "구조 보존 image-to-image — 발주 전 결과 확인, 견적(옥션) 근거자료로 연결"),
("③ 모든 답변은 근거 기반", "행사 DB·규정 룰셋만 근거로 답변 — 근거가 없으면 모른다고 답한다(환각 차단)"),
]
y = 2.5
for t1, t2 in msgs:
add_rect(s, 1.2, y, 10.9, 1.05, RGBColor(0x0A, 0x3A, 0x63))
tf = add_box(s, 1.5, y + 0.12, 10.4, 0.9)
r = tf.paragraphs[0].add_run(); r.text = t1; pset(r, 17, True, P_WHITE)
p2 = tf.add_paragraph()
r = p2.add_run(); r.text = t2; pset(r, 12.5, False, P_SOFT)
y += 1.25
tf = add_box(s, 1.2, 6.35, 10.9, 0.6)
r = tf.paragraphs[0].add_run()
r.text = "AI가 실패해도 화면은 멈추지 않습니다 — 모든 AI 기능은 수동 폴백을 갖춘 안전 설계 · AI 생성물 라벨·워터마크 표기"
pset(r, 12, False, P_SOFT)
idx += 1
out = os.path.join(BASE, "AI_시연_시나리오.pptx")
prs.save(out)
print("saved:", out, "slides:", len(prs.slides))
if __name__ == "__main__":
build()