Corin1998's picture
Update export/ppt.py
a5ec4d8 verified
# export/ppt.py
from __future__ import annotations
from typing import Dict, List, Tuple
from datetime import datetime
try:
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
from pptx.dml.color import RGBColor
except ModuleNotFoundError as e:
# 呼び出し側でこのエラーをユーザに分かりやすく返すため、敢えてそのまま投げる
raise
MAX_BULLETS = 10
def _add_title(prs: Presentation, title: str, subtitle: str | None = None):
slide_layout = prs.slide_layouts[0] # Title Slide
slide = prs.slides.add_slide(slide_layout)
slide.shapes.title.text = title
if subtitle is not None:
slide.placeholders[1].text = subtitle
def _add_title_and_bullets(prs: Presentation, title: str, bullets: List[str]):
slide_layout = prs.slide_layouts[1] # Title and Content
slide = prs.slides.add_slide(slide_layout)
slide.shapes.title.text = title
tx = slide.shapes.placeholders[1].text_frame
tx.clear()
# 先頭段落
if not bullets:
bullets = ["(該当なし)"]
bullets = bullets[:MAX_BULLETS]
p = tx.paragraphs[0]
p.text = bullets[0]
p.level = 0
for b in bullets[1:]:
rp = tx.add_paragraph()
rp.text = b
rp.level = 0
def _add_sources(prs: Presentation, links: List[str]):
slide_layout = prs.slide_layouts[5] # Title Only
slide = prs.slides.add_slide(slide_layout)
slide.shapes.title.text = "根拠リンク / 参考資料"
left = Inches(0.8); top = Inches(1.8); width = Inches(9); height = Inches(5)
box = slide.shapes.add_textbox(left, top, width, height)
tf = box.text_frame
tf.word_wrap = True
if not links:
tf.text = "(なし)"
return
tf.clear()
for i, url in enumerate(links, 1):
p = tf.add_paragraph() if i > 1 else tf.paragraphs[0]
p.text = f"{i}. {url}"
p.level = 0
def _split_lines(s: str, sep: str = "\n") -> List[str]:
s = (s or "").strip()
if not s:
return []
out = [ln.strip(" \t\u3000") for ln in s.split(sep)]
return [ln for ln in out if ln]
def build_deck(sections: Dict[str, str | List[str]], links: List[str] | None = None) -> Presentation:
"""
sections 例:
{
"highlights": "• 売上+10%\n• 営業益+5%\n...",
"outlook": "...",
"segments": "...",
"finance": "...",
"shareholder": "...",
"esg": "...",
"risks": "..."
}
"""
prs = Presentation()
# 1) タイトル
today = datetime.now().strftime("%Y-%m-%d")
_add_title(prs, "IR/PR Co-Pilot Pro 自動生成スライド", f"作成日: {today}")
# 2) 目次
toc = [
"業績ハイライト",
"見通し",
"セグメント",
"財務",
"株主還元",
"ESG",
"リスク",
"想定Q&A(抜粋)",
"参考リンク",
]
_add_title_and_bullets(prs, "目次", toc)
# 3) 各セクション
def bullets_of(key: str) -> List[str]:
v = sections.get(key, "") if sections else ""
if isinstance(v, list):
return [str(x) for x in v if str(x).strip()]
return _split_lines(str(v or ""))
_add_title_and_bullets(prs, "業績ハイライト", bullets_of("highlights"))
_add_title_and_bullets(prs, "見通し", bullets_of("outlook"))
_add_title_and_bullets(prs, "セグメント", bullets_of("segments"))
_add_title_and_bullets(prs, "財務", bullets_of("finance"))
_add_title_and_bullets(prs, "株主還元", bullets_of("shareholder"))
_add_title_and_bullets(prs, "ESG", bullets_of("esg"))
_add_title_and_bullets(prs, "リスク", bullets_of("risks"))
# 4) 想定Q&A(別モジュール側で上位N件だけ詰めて来てもOK)
qas = sections.get("qa_top", [])
if isinstance(qas, str):
qas = _split_lines(qas)
qa_bullets = []
if qas:
# "Q: ... / A: ..." 形式を1行ずつ箇条書き
for qa in qas[:MAX_BULLETS]:
qa_bullets.append(qa)
_add_title_and_bullets(prs, "想定Q&A(抜粋)", qa_bullets or ["(別添CSV参照)"])
# 5) 参考リンク
_add_sources(prs, list(dict.fromkeys(links or [])))
return prs
def save_pptx(prs: Presentation, path: str) -> None:
import os
os.makedirs(os.path.dirname(path), exist_ok=True)
prs.save(path)