Files
ewoooc/services/ppt_generator.py
ogt 1b4f3a7bbe
Some checks failed
CD Pipeline / deploy (push) Failing after 59s
feat: EwoooC 初始化 — 完整專案推版至 Gitea
- 建立 Gitea Actions CD pipeline (.gitea/workflows/cd.yaml)
- 部署模式: rsync Python 檔案至 188 → docker restart (volume mount)
- Dockerfile/requirements 變動時自動重建 Docker image
- 部署通知: Telegram (開始/成功/失敗)
- 健康檢查: https://mo.wooo.work/health (最多 5 次重試)
- 同步最新 CLAUDE.md / ADR-008 / memory (2026-04-19)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 01:21:13 +08:00

163 lines
4.6 KiB
Python

"""
services/ppt_generator.py
緊急復原版 (2026-04-18) — 原始檔案 4/17 間遺失
依 openclaw_bot_routes.py 呼叫約定提供 7 個 function:
check_pptx_available, generate_daily_ppt, generate_weekly_ppt,
generate_monthly_ppt, generate_strategy_ppt, generate_competitor_ppt,
generate_promo_ppt
每個 generate_* 回傳 pptx 檔案路徑 (str)。
"""
import os
import uuid
from datetime import datetime
from pathlib import Path
REPORTS_DIR = Path(os.environ.get("REPORTS_DIR", "/app/data/reports"))
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
def check_pptx_available() -> bool:
try:
import pptx # noqa: F401
return True
except ImportError:
return False
def _new_path(kind: str) -> str:
rid = uuid.uuid4().hex[:8]
return str(REPORTS_DIR / f"ocbot_{kind}_{rid}.pptx")
def _format_data(data) -> str:
if data is None:
return "(無資料)"
if isinstance(data, dict):
if not data:
return "(空 dict)"
lines = []
for k, v in list(data.items())[:30]:
line = f"{k}: {v}"
lines.append(line[:200])
return "\n".join(lines)
if isinstance(data, list):
if not data:
return "(空 list)"
lines = []
for item in data[:20]:
lines.append(f"{str(item)[:200]}")
return "\n".join(lines)
return str(data)[:2000]
def _build_ppt(filename_kind: str, title: str, subtitle: str, sections: list) -> str:
"""sections = [(heading, body), ...]"""
from pptx import Presentation
from pptx.util import Pt
prs = Presentation()
# 標題頁
s = prs.slides.add_slide(prs.slide_layouts[0])
if s.shapes.title is not None:
s.shapes.title.text = title
if len(s.placeholders) > 1:
s.placeholders[1].text = subtitle
# 內容頁
for heading, body in sections:
s = prs.slides.add_slide(prs.slide_layouts[1])
if s.shapes.title is not None:
s.shapes.title.text = heading
body_text = body if isinstance(body, str) else _format_data(body)
body_text = body_text[:3500]
if len(s.placeholders) > 1:
tf = s.placeholders[1].text_frame
tf.text = body_text
for p in tf.paragraphs:
for r in p.runs:
r.font.size = Pt(14)
# 產生路徑並存檔
path = _new_path(filename_kind)
prs.save(path)
return path
def generate_daily_ppt(date_str: str, db_data, ai_text: str) -> str:
sections = [
("本日銷售概況", _format_data(db_data)),
("AI 洞察", ai_text or "(暫無 AI 分析)"),
]
return _build_ppt(
"daily_daily",
f"日報 {date_str}",
f"生成時間 {datetime.now().strftime('%Y-%m-%d %H:%M')}",
sections,
)
def generate_weekly_ppt(db_data, ai_text: str) -> str:
sections = [
("本週銷售概況", _format_data(db_data)),
("AI 洞察", ai_text or "(暫無 AI 分析)"),
]
return _build_ppt(
"weekly",
"週報",
f"生成時間 {datetime.now().strftime('%Y-%m-%d %H:%M')}",
sections,
)
def generate_monthly_ppt(yr, mo, db_data, ai_text: str) -> str:
sections = [
(f"{yr}/{mo} 月度概況", _format_data(db_data)),
("AI 洞察", ai_text or "(暫無 AI 分析)"),
]
return _build_ppt(
"monthly",
f"月報 {yr}/{mo}",
f"生成時間 {datetime.now().strftime('%Y-%m-%d %H:%M')}",
sections,
)
def generate_strategy_ppt(date_str: str, db_data, ai_text: str) -> str:
sections = [
("策略資料", _format_data(db_data)),
("AI 洞察", ai_text or "(暫無 AI 分析)"),
]
return _build_ppt(
"strategy",
f"策略報告 {date_str}",
f"生成時間 {datetime.now().strftime('%Y-%m-%d %H:%M')}",
sections,
)
def generate_competitor_ppt(period_label: str, db_data, ai_text: str) -> str:
sections = [
(f"競品資料 ({period_label})", _format_data(db_data)),
("AI 洞察", ai_text or "(暫無 AI 分析)"),
]
return _build_ppt(
"competitor",
f"競品分析 {period_label}",
f"生成時間 {datetime.now().strftime('%Y-%m-%d %H:%M')}",
sections,
)
def generate_promo_ppt(promo_label: str, data, ai_text: str) -> str:
sections = [
(f"促銷資料 ({promo_label})", _format_data(data)),
("AI 洞察", ai_text or "(暫無 AI 分析)"),
]
return _build_ppt(
"promo",
f"促銷報告 {promo_label}",
f"生成時間 {datetime.now().strftime('%Y-%m-%d %H:%M')}",
sections,
)