harness/plugins/proposal-builder/skills/proposal-builder-orchestrator/scripts/build_deck.py
DESKTOP-TKLFCPR\ython 53f0dc5fa3 feat(proposal-builder): RFP+PPT템플릿 제안서 자동완성 플러그인 추가
- 전문가 7인: rfp-analyst·strategist·tech-advisor(아키텍처 총동원)·writer·visual-designer(선 아이콘 직접제작)·deck-designer·qa
- 오케스트레이터: '제안서 작성' 한마디로 전 파이프라인 총지휘 (Phase1~6)
- 커맨드 /proposal + 번들 스크립트(inspect_template·build_deck) + 설치/사용 가이드
- marketplace 등록 + plugin.json 동기화
- SI 구축·SM 유지보수 제안 주력

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:46:30 +09:00

92 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""
build_deck.py — 템플릿 스타일 + 제안서 콘텐츠(JSON) → proposal.pptx 생성.
deck-designer가 프로젝트별 콘텐츠 JSON만 주입해 재생성 가능한 골격 스크립트.
디자인 시스템(색/폰트)·선 아이콘(SVG는 사전 PNG/EMF 변환 또는 도형)·도식은 deck-designer가 채운다.
사용: python build_deck.py --content content.json [--template template.pptx] --out proposal.pptx
content.json 예:
{
"design": {"primary":"1F4E79","accent":"2E9BD6","text":"222222","font_ko":"맑은 고딕"},
"slides": [
{"layout":"title", "title":"○○시스템 구축 제안서", "subtitle":"제안사 / 2026"},
{"layout":"section", "title":"1. 사업 이해"},
{"layout":"content", "title":"추진 전략", "bullets":["...","..."]},
{"layout":"diagram", "title":"추진 체계도", "note":"도형은 deck-designer가 보강"}
]
}
의존: pip install python-pptx · 외부 호출 없음.
"""
import sys, json, argparse
def hex_color(s):
from pptx.dml.color import RGBColor
return RGBColor.from_string(s)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--content", required=True)
ap.add_argument("--template")
ap.add_argument("--out", default="proposal.pptx")
a = ap.parse_args()
try:
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
except ImportError:
print("python-pptx 미설치 → pip install python-pptx", file=sys.stderr); sys.exit(2)
with open(a.content, encoding="utf-8") as f:
data = json.load(f)
design = data.get("design", {})
primary = design.get("primary", "1F4E79")
accent = design.get("accent", "2E9BD6")
text_c = design.get("text", "222222")
font_ko = design.get("font_ko", "맑은 고딕")
prs = Presentation(a.template) if a.template else Presentation()
blank = prs.slide_layouts[6] if len(prs.slide_layouts) > 6 else prs.slide_layouts[-1]
W, H = prs.slide_width, prs.slide_height
def add_text(slide, txt, left, top, width, height, size, color, bold=False, align=PP_ALIGN.LEFT):
tb = slide.shapes.add_textbox(left, top, width, height)
tf = tb.text_frame; tf.word_wrap = True
p = tf.paragraphs[0]; p.alignment = align
r = p.add_run(); r.text = txt
r.font.size = Pt(size); r.font.bold = bold
r.font.name = font_ko; r.font.color.rgb = hex_color(color)
return tb
for s in data.get("slides", []):
slide = prs.slides.add_slide(blank)
layout = s.get("layout", "content")
title = s.get("title", "")
if layout == "title":
# 상단 컬러 바
bar = slide.shapes.add_shape(1, 0, 0, W, Inches(2.2))
bar.fill.solid(); bar.fill.fore_color.rgb = hex_color(primary); bar.line.fill.background()
add_text(slide, title, Inches(0.8), Inches(0.7), W - Inches(1.6), Inches(1.2), 36, "FFFFFF", True)
if s.get("subtitle"):
add_text(slide, s["subtitle"], Inches(0.8), Inches(2.5), W - Inches(1.6), Inches(0.8), 18, text_c)
elif layout == "section":
bar = slide.shapes.add_shape(1, 0, Inches(2.6), W, Inches(1.6))
bar.fill.solid(); bar.fill.fore_color.rgb = hex_color(accent); bar.line.fill.background()
add_text(slide, title, Inches(0.8), Inches(2.9), W - Inches(1.6), Inches(1.0), 30, "FFFFFF", True)
else:
# 제목 + 강조 underline
add_text(slide, title, Inches(0.7), Inches(0.5), W - Inches(1.4), Inches(0.9), 26, primary, True)
ul = slide.shapes.add_shape(1, Inches(0.7), Inches(1.35), Inches(2.2), Pt(3))
ul.fill.solid(); ul.fill.fore_color.rgb = hex_color(accent); ul.line.fill.background()
top = Inches(1.8)
for b in s.get("bullets", []):
add_text(slide, "" + b, Inches(0.9), top, W - Inches(1.8), Inches(0.6), 16, text_c)
top += Inches(0.55)
if s.get("note"):
add_text(slide, s["note"], Inches(0.9), H - Inches(0.8), W - Inches(1.8), Inches(0.5), 11, "888888")
prs.save(a.out)
print(f"생성: {a.out} (slides={len(data.get('slides', []))})")
if __name__ == "__main__":
main()