# ADR-022: PPT 簡報系統 v3 — 暖紙風 + matplotlib 專業圖表 + 模板版本快取

- **Status**: Accepted
- **Date**: 2026-05-02 / 03
- **Deciders**: 統帥
- **Related**: 取代 ADR-014 中的 PPT 視覺/結構描述（V2 → V3）；不影響 V2 既有「6 種報告 vs 9 種報告」校正（growth/vendor/bcg 仍未落地）
- **Affects**: `services/ppt_generator.py`、`routes/openclaw_bot_routes.py`、`Dockerfile`、`scripts/ppt_cleanup.sh` 等
- **Commit chain**: `38967ce` → `3b0b4b3` → `52c06f6` → `1c81866` → `b5a2b09` → `c7b7cee` → `92b8035` → `5a7012f`

## Context

2026-05-02 統帥檢視月報（V2）後指出五項視覺品質缺陷：

1. 圖表醜（python-pptx 原生柱狀圖無資料標註、無顏色分級）
2. 熱銷商品只 10 品（用戶要求 50 品）
3. 專案 RAG 情報內容太陽春（單一 textbox 顯示靜態節日字串）
4. 排版亂（封面大面積暖墨黑、AI 頁深色 + 白字過於濃重）
5. 字體未對齊設計系統（中英混排撞 Courier New）

並要求對齊「市場上專業日報/週報/月報」的標準（McKinsey/BCG 等級顧問報告）。

第一輪重做後又抓出三類延伸需求：
- 同份 PPT 可秒回（cache）；但模板升版時舊 cache 必須自動失效
- AI 分析內容要詳盡且專業，能直接幫銷售/行銷做決策參考
- 容器 CJK 字型必須真正能渲染中文

## Decision

### A. 視覺重做 — 暖紙風（取代暖墨黑）

**封面**：`_BG_PAPER (#F3EEE2)` 米紙底取代 `_BG_DARK (#2A2520)`，焦糖橘左寬條 + 暖墨色標題。

**KPI 卡（v2）**：value 大字含右下角 △% 徽章（綠↑紅↓），含 `delta_label="vs 上月"` / inverse 反轉模式（用於成本類指標）。

**字型分軌**：透過 lxml 直寫 `<a:latin>`+`<a:ea>` (`_set_run_fonts`)，數字/英文走 Consolas（點陣等寬感），中文走 Microsoft JhengHei。`_insert_rpr_child` 依 ECMA-376 §21.1.2.3 順序插入子元素（避免 LibreOffice/Keynote 拒絕讀取）。

### B. 圖表升級 — matplotlib 暖色系（取代 python-pptx 原生）

| Helper | 用於 | 特色 |
|---|---|---|
| `_mpl_horiz_bar_png` | 月報品類橫條 | TOP3 焦糖橘 / 4-6 蜂蜜金 / 7+ 焦土，條右標 NT$X.X萬+佔比% |
| `_mpl_line_chart_png` | 日/週/月趨勢 | 本月實線+上月虛線+日均水平線+高低點標註，N≤2 sparse 防呆 |
| `_mpl_pareto_chart_png` | 月報品類雙視圖 | 業績條（80% 主力焦糖橘）+ 累計%曲線+80% 主力線 |

容器 CJK fallback：`fonts-noto-cjk` 套件 ttc 檔在 matplotlib `font_manager.ttflist` 只認 `Noto Sans CJK JP` 變體，但 ttc 內漢字字型表共用，可正常渲染中文。`_mpl_setup` fallback 列表必須包含 `Noto Sans CJK JP`。

### C. 模板版本快取（template_version cache invalidation）

```python
# services/ppt_generator.py
TEMPLATE_VERSIONS = {
    'monthly': 'v3.1.3',
    'daily':   'v3.0.2',
    ...
}

# routes/openclaw_bot_routes.py
def _normalize_ppt_parameters(parameters):
    """自動把 tpl_ver 注入 cache key"""
    params['tpl_ver'] = get_template_version(report_type)
    return json.dumps(params, sort_keys=True)
```

**Bump 規則**：major 設計改版 +0.1（如 v3.0 → v3.1）；補丁修正 +0.01（v3.1.1 → v3.1.2）。bump 即觸發舊快取自動 miss。

**緊急清空**：`_invalidate_ppt_cache(report_type=None)` Python helper / `/cache flush` Telegram 指令（admin only）。

**磁碟清理**：`cleanup_expired_ppt_cache(days_old=7, dry_run=True)` — 安全預設 dry_run=True，呼叫方明確傳 False 才實刪。launchd plist 排程每日 03:15 跑（macOS 開發機）。

### D. AI prompt 升級到顧問深度

**月報 prompt 升級**（`_ppt_ai_analysis`）：
- 角色：BCG/麥肯錫策略顧問 + momo 行銷主管 + 品類採購（三合一）
- 必含「市場趨勢脈絡」段：當期檔期 + 2026 熱門賽道（永續美妝/母嬰高端/敏弱肌/銀髮保健等）+ 平台競爭（蝦皮/PChome/酷澎）
- 行動建議走 SMART 框架（Specific/Measurable/Achievable/Relevant/Time-bound），分本週/本月/下月三層
- 禁用「可能/也許/建議考慮」等模糊用詞
- 字數 900-1200，max_tokens 2400

**AI 段落 parser**（`_parse_ai_sections`）：丟掉空 body 段（避免顯示「本段無內容」）；上限 6→10 容納 SMART 9 段。

### E. 安全機制 — admin 白名單 + dry_run 預設

- **`OPENCLAW_ADMIN_USER_IDS` 環境變數**：`/cache flush` 與 `/cache cleanup confirm` 限管理員執行；未設時退回 ALLOWED_USERS（向後兼容 fail-closed）
- **`_CURRENT_USER_ID_CTX` ContextVar**：webhook 入口（msg + callback）set；handle_cmd 讀取，避免改 30+ 處呼叫端簽名
- **cleanup days<1 強制乾跑**（防呆）

## Consequences

### Positive

1. **視覺對齊市場專業標準**：與 McKinsey/BCG 月度報告一致的暖紙風＋圖表標註＋帕雷托雙視圖
2. **模板升版零阻力**：bump TEMPLATE_VERSIONS 即生效，無需手動清快取
3. **記憶體穩定**：matplotlib helpers 全部 try/finally 包好，渲染失敗不洩漏 figure
4. **AI 內容可實戰**：每段含具體商品名+量化目標+期限，BU 主管可直接做決策
5. **容器化部署**：Dockerfile 加 `fonts-noto-cjk`，CI/CD 自動帶字型

### Negative

1. **月報 cache miss 路徑變慢**：cache miss 時連續查 current/prev_month/prev_year 三次（12 SQL），延遲 3×。命中後正常（同份秒回）。
2. **PPT 檔變大**：matplotlib PNG 取代原生 chart，月報 130KB → 326KB（含 3 張高品質圖）
3. **`fonts-noto-cjk-extra` 增加 ~100MB image 大小**：可瘦身但 trade-off 字型完整度

### Risks Mitigated

- **figure 洩漏 → OOM**：try/finally 修復（critic HIGH-1）
- **靜默實刪災難**：cleanup 預設 dry_run=True（critic HIGH-2）
- **群組成員誤觸破壞性指令**：admin 白名單（critic Medium-3）
- **未來改讀模板 .pptx schema 違反**：`_insert_rpr_child` 維護順序（critic Medium-1）

## Alternatives Considered

1. **保留 python-pptx 原生 chart**：拒絕 — 原生不支援資料標註、顏色分級無法做到 BCG 級觀感
2. **用 hash(ppt_generator.py 內容)[:8] 自動 cache key**：拒絕 — 改個 typo 也 invalidate 全部，過度敏感
3. **改 handle_cmd 簽名加 user_id**：拒絕 — 30+ 處呼叫端要動，風險大；用 ContextVar 更乾淨

## References

- Critic 完整審查報告：見 commit `38967ce` 之後的 critic agent run
- 實戰驗證：在 188 prod 容器內生成 monthly v3.1.3，AI 內容 3 頁全 SMART 行動建議
- 原 ADR-014（V2 PPT）：未廢止，但視覺/結構/cache 部分由本 ADR 取代
