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>
This commit is contained in:
ythong 2026-07-22 17:43:12 +09:00
parent 02c6b8ddee
commit 397b5de985
2 changed files with 323 additions and 0 deletions

Binary file not shown.

323
scripts/gen_ai_demo_ppt.py Normal file
View File

@ -0,0 +1,323 @@
# -*- 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()