Corin1998 commited on
Commit
a5ec4d8
·
verified ·
1 Parent(s): 2223c9a

Update export/ppt.py

Browse files
Files changed (1) hide show
  1. export/ppt.py +123 -34
export/ppt.py CHANGED
@@ -1,43 +1,132 @@
1
  # export/ppt.py
2
- from pptx import Presentation
3
- from typing import Dict, List
4
- import os
5
-
6
- def _add_bullets(prs: Presentation, title: str, body: str):
7
- layout = prs.slide_layouts[1] # Title and Content
8
- s = prs.slides.add_slide(layout)
9
- s.shapes.title.text = title
10
- tf = s.placeholders[1].text_frame
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  tf.clear()
12
- lines = [l.strip("•-  ") for l in (body or "").splitlines() if l.strip()]
13
- if not lines: lines = ["(なし)"]
14
- first = True
15
- for line in lines:
16
- if first:
17
- p = tf.paragraphs[0]; first = False
18
- else:
19
- p = tf.add_paragraph()
20
- p.text = line
21
  p.level = 0
22
 
23
- def build_deck(sections: Dict[str, str], links: List[str]) -> Presentation:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  prs = Presentation()
25
- title_slide_layout = prs.slide_layouts[0]
26
- s0 = prs.slides.add_slide(title_slide_layout)
27
- s0.shapes.title.text = "IR/PR サマリー"
28
- s0.placeholders[1].text = "自動生成(RAG + OpenAI)"
29
-
30
- _add_bullets(prs, "業績ハイライト", sections.get("highlights",""))
31
- _add_bullets(prs, "見通し", sections.get("outlook",""))
32
- _add_bullets(prs, "セグメント", sections.get("segments",""))
33
- _add_bullets(prs, "財務", sections.get("finance",""))
34
- _add_bullets(prs, "株主還元", sections.get("shareholder",""))
35
- _add_bullets(prs, "ESG", sections.get("esg",""))
36
- _add_bullets(prs, "リスク", sections.get("risks",""))
37
- _add_bullets(prs, "参考リンク", "\n".join(links or []))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  return prs
39
 
40
- def save_pptx(prs: Presentation, path: str) -> str:
 
41
  os.makedirs(os.path.dirname(path), exist_ok=True)
42
  prs.save(path)
43
- return path
 
1
  # export/ppt.py
2
+ from __future__ import annotations
3
+ from typing import Dict, List, Tuple
4
+ from datetime import datetime
5
+
6
+ try:
7
+ from pptx import Presentation
8
+ from pptx.util import Inches, Pt
9
+ from pptx.enum.text import PP_ALIGN
10
+ from pptx.dml.color import RGBColor
11
+ except ModuleNotFoundError as e:
12
+ # 呼び出し側でこのエラーをユーザに分かりやすく返すため、敢えてそのまま投げる
13
+ raise
14
+
15
+ MAX_BULLETS = 10
16
+
17
+ def _add_title(prs: Presentation, title: str, subtitle: str | None = None):
18
+ slide_layout = prs.slide_layouts[0] # Title Slide
19
+ slide = prs.slides.add_slide(slide_layout)
20
+ slide.shapes.title.text = title
21
+ if subtitle is not None:
22
+ slide.placeholders[1].text = subtitle
23
+
24
+ def _add_title_and_bullets(prs: Presentation, title: str, bullets: List[str]):
25
+ slide_layout = prs.slide_layouts[1] # Title and Content
26
+ slide = prs.slides.add_slide(slide_layout)
27
+ slide.shapes.title.text = title
28
+ tx = slide.shapes.placeholders[1].text_frame
29
+ tx.clear()
30
+ # 先頭段落
31
+ if not bullets:
32
+ bullets = ["(該当なし)"]
33
+ bullets = bullets[:MAX_BULLETS]
34
+ p = tx.paragraphs[0]
35
+ p.text = bullets[0]
36
+ p.level = 0
37
+ for b in bullets[1:]:
38
+ rp = tx.add_paragraph()
39
+ rp.text = b
40
+ rp.level = 0
41
+
42
+ def _add_sources(prs: Presentation, links: List[str]):
43
+ slide_layout = prs.slide_layouts[5] # Title Only
44
+ slide = prs.slides.add_slide(slide_layout)
45
+ slide.shapes.title.text = "根拠リンク / 参考資料"
46
+ left = Inches(0.8); top = Inches(1.8); width = Inches(9); height = Inches(5)
47
+ box = slide.shapes.add_textbox(left, top, width, height)
48
+ tf = box.text_frame
49
+ tf.word_wrap = True
50
+ if not links:
51
+ tf.text = "(なし)"
52
+ return
53
  tf.clear()
54
+ for i, url in enumerate(links, 1):
55
+ p = tf.add_paragraph() if i > 1 else tf.paragraphs[0]
56
+ p.text = f"{i}. {url}"
 
 
 
 
 
 
57
  p.level = 0
58
 
59
+ def _split_lines(s: str, sep: str = "\n") -> List[str]:
60
+ s = (s or "").strip()
61
+ if not s:
62
+ return []
63
+ out = [ln.strip(" \t\u3000") for ln in s.split(sep)]
64
+ return [ln for ln in out if ln]
65
+
66
+ def build_deck(sections: Dict[str, str | List[str]], links: List[str] | None = None) -> Presentation:
67
+ """
68
+ sections 例:
69
+ {
70
+ "highlights": "• 売上+10%\n• 営業益+5%\n...",
71
+ "outlook": "...",
72
+ "segments": "...",
73
+ "finance": "...",
74
+ "shareholder": "...",
75
+ "esg": "...",
76
+ "risks": "..."
77
+ }
78
+ """
79
  prs = Presentation()
80
+
81
+ # 1) タイトル
82
+ today = datetime.now().strftime("%Y-%m-%d")
83
+ _add_title(prs, "IR/PR Co-Pilot Pro 自動生成スライド", f"作成日: {today}")
84
+
85
+ # 2) 目次
86
+ toc = [
87
+ "業績ハイライト",
88
+ "見通し",
89
+ "セグメント",
90
+ "財務",
91
+ "株主還元",
92
+ "ESG",
93
+ "リスク",
94
+ "想定Q&A(抜粋)",
95
+ "参考リンク",
96
+ ]
97
+ _add_title_and_bullets(prs, "目次", toc)
98
+
99
+ # 3) 各セクション
100
+ def bullets_of(key: str) -> List[str]:
101
+ v = sections.get(key, "") if sections else ""
102
+ if isinstance(v, list):
103
+ return [str(x) for x in v if str(x).strip()]
104
+ return _split_lines(str(v or ""))
105
+
106
+ _add_title_and_bullets(prs, "業績ハイライト", bullets_of("highlights"))
107
+ _add_title_and_bullets(prs, "見通し", bullets_of("outlook"))
108
+ _add_title_and_bullets(prs, "セグメント", bullets_of("segments"))
109
+ _add_title_and_bullets(prs, "財務", bullets_of("finance"))
110
+ _add_title_and_bullets(prs, "株主還元", bullets_of("shareholder"))
111
+ _add_title_and_bullets(prs, "ESG", bullets_of("esg"))
112
+ _add_title_and_bullets(prs, "リスク", bullets_of("risks"))
113
+
114
+ # 4) 想定Q&A(別モジュール側で上位N件だけ詰めて来てもOK)
115
+ qas = sections.get("qa_top", [])
116
+ if isinstance(qas, str):
117
+ qas = _split_lines(qas)
118
+ qa_bullets = []
119
+ if qas:
120
+ # "Q: ... / A: ..." 形式を1行ずつ箇条書き
121
+ for qa in qas[:MAX_BULLETS]:
122
+ qa_bullets.append(qa)
123
+ _add_title_and_bullets(prs, "想定Q&A(抜粋)", qa_bullets or ["(別添CSV参照)"])
124
+
125
+ # 5) 参考リンク
126
+ _add_sources(prs, list(dict.fromkeys(links or [])))
127
  return prs
128
 
129
+ def save_pptx(prs: Presentation, path: str) -> None:
130
+ import os
131
  os.makedirs(os.path.dirname(path), exist_ok=True)
132
  prs.save(path)