Spaces:
Sleeping
Sleeping
| # 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) | |