File size: 4,433 Bytes
efc6dd5
a5ec4d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
efc6dd5
a5ec4d8
 
 
c204318
 
a5ec4d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c204318
a5ec4d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c204318
 
a5ec4d8
 
efc6dd5
c204318
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# 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)