---
name: ppt-station
description: "PowerPoint 数据报告自动化工具包。从结构化数据（CSV/Excel/Tushare/MCP）生成带图表的专业金融报告 PPT。支持 12 种页面布局、10 个主题（含研究级 morningstar/macro_research）、10 种图表配色、6 种日期轴预设、4:3/16:9 双比例。内置投行级设计规范、ChartJunkCleaner 自动清洗图表样式、投行级表格（无垂直边框/数值右对齐）、DeckLinter 合规审计、结论先行 insight 插槽。当用户需要创建、修改、分析 PowerPoint 演示文稿，或需要从数据生成图表报告时触发。Do NOT trigger for: 非 PPT 格式（PDF、Word）、纯数据分析不涉及 PPT 输出。"
---

# PPT-Station

项目根: `./`（即 `skill/` 自身；`skill/ppt_station` 是 symlink → `../ppt_station`，始终与主代码同步）

## 工作流路由

| 需求 | Flow | 入口 |
|------|------|------|
| 模板+数据→PPT | A | `scripts/generate_ppt.py` — [workflow-template](reference/workflow-template.md) |
| Job JSON 声明式编排（模板） | B | `scripts/run_job.py` — [workflow-engine](reference/workflow-engine.md) |
| 解析→编辑→重建已有PPT | C | `scripts/parse_ppt.py` + `rebuild_ppt.py` — [workflow-parse-rebuild](reference/workflow-parse-rebuild.md) |
| 数据+布局声明→PPT | D (推荐) | `scripts/render_ppt.py` + Composer Job JSON |

原子工具:
- `scripts/fetch_data.py` — 数据获取（datasources JSON → CSV）
- `scripts/validate_job.py` — Job JSON 校验（schema + 布局 + datasource/source 引用）
- `scripts/render_ppt.py` — PPT 渲染（Job JSON → PPT）
- `scripts/validate_ppt.py` — PPT 合规审计（DeckLinter CLI 入口）

通用工具: `scripts/parse_template.py`, `scripts/describe_chart.py`, `scripts/list_presets.py`, `scripts/add_placeholders.py`

---

## Flow D 快速示例（Composer Job JSON）

```json
{
  "mode": "composer",
  "theme": "jp_finance",
  "aspect_ratio": "16:9",
  "default_layout_config": {
    "legend_config": {"font_size_pt": 9, "font_name": "黑体"},
    "value_axis_config": {"font_size_pt": 9, "font_name": "黑体"}
  },
  "datasources": {
    "revenue": {"type": "csv", "path": "data/revenue.csv"}
  },
  "pages": [
    {
      "layout": "title_dark",
      "data": {"title": "安克创新竞争壁垒分析", "date": "2026-02"}
    },
    {
      "layout": "kpi_cards",
      "data": {
        "title": "核心指标",
        "footnote": "来源: Wind, 公司年报",
        "cards": [
          {"label": "营收", "value": "247亿", "change": "+41%"},
          {"label": "净利润", "value": "32亿", "change": "+55%"}
        ]
      }
    },
    {
      "layout": "chart_full",
      "data": {
        "title": "营收与利润趋势",
        "source": "revenue",
        "categories_col": "年度",
        "series_config": [
          {"key": "营收", "name": "营收(亿元)", "type": "bar", "axis": "primary"},
          {"key": "净利润", "name": "净利润(亿元)", "type": "line", "axis": "secondary"}
        ],
        "footnote": "来源: Wind, 公司年报",
        "layout_config": {
          "value_axis_config": {"number_format": "#,##0"}
        }
      }
    }
  ],
  "output": {"path": "output/anker_moat.pptx"}
}
```

**关键字段说明**：
- `aspect_ratio`：幻灯片比例 `"16:9"`（默认）或 `"4:3"`，影响所有布局的宽高计算
- `default_layout_config`：Job 级图表布局默认配置，所有图表页继承，per-page `layout_config` 只写差异项（浅合并覆盖）
- `footnote`：页脚来源文本，所有非封面/结论布局均自动渲染为页脚
- `source`：在图表页中作为 datasource 引用（值匹配已知数据源时），在非图表页中保留为脚注文本（兼容回退）
- `datasource`（新 canonical key）：明确的 datasource 引用，推荐在 left/right 嵌套中使用
- `style_config` 不再必需 — 使用 `jp_finance` 主题时，图表自动使用 `jp_finance` 配色方案

**原子工具工作流**：

```bash
# 1. 获取数据
python scripts/fetch_data.py --config datasources.json --output data/

# 2. 校验 Job JSON
python scripts/validate_job.py job.json

# 3. 渲染 PPT
python scripts/render_ppt.py job.json
```

**仍需手写 Python 的场景**（add_custom_page）：自定义布局不在 LAYOUT_REGISTRY 中时

---

## API 合约（编码前必读）

**完整函数签名、参数类型、所有布局数据接口**：[reference/api-contracts.md](reference/api-contracts.md)

关键要点速查：

```
create_combo_chart(slide, df, categories_col, series_config,
                   position=(left, top), size=(width, height), ...)

series_config = [{"key": "列名", "name": "显示名", "type": "bar|line|area", "axis": "primary|secondary"}]

ChartLayoutConfig(..., date_axis_config=MONTHLY_TICKS)   ← 日期轴预设在这里
add_custom_page(render_fn)  → render_fn(slide, theme)    ← 只有 2 个参数
composer.save("output.pptx", lint=True)                  ← 保存后自动 DeckLinter 审计
```

### 布局数据接口速查

| 布局 | 必填字段 | 可选字段 |
|------|---------|---------|
| `title_dark` | `title` | `subtitle`, `date`, `author` |
| `title_light` | `title` | `subtitle`, `date` |
| `kpi_cards` | `title`, `cards: [{label, value}]` | `subtitle`, `footnote`; card 可选: `change`, `note` |
| `chart_full` | `title`, `source`/`datasource`, `categories_col`, `series_config` | `subtitle`, `insight`, `footnote`, `style_config`, `layout_config` |
| `two_charts` | `title`, `left: {source/datasource, ...}`, `right: {...}` | `left_title`, `right_title`, `insight`, `footnote` |
| `chart_text` | `title`, `source`/`datasource`, `categories_col`, `series_config` | `text_title`, `text_body`, `text_bullets`, `insight`, `footnote` |
| `two_charts_vertical` | `title`, `top: {source/datasource, ...}`, `bottom: {...}` | `top_title`, `bottom_title`, `insight`, `footnote` |
| `chart_table` | `title`, `source`/`datasource`, `categories_col`, `series_config`, `headers`, `rows` | `insight`, `chart_ratio` (默认 0.62), `table_df`, `footnote` |
| `bullet_points` | `title`, `items: [{icon, label, desc}]` | `subtitle`, `footnote` |
| `comparison_table` | `title`, `headers`, `rows` | `highlight_row`, `highlight_cols`, `footnote` |
| `section_divider` | `title`, `number` | `subtitle` |
| `conclusion_dark` | `verdict` | `title`, `score`, `items`, `conclusions`, `risk`, `footnote` |

**`footnote` 字段**：所有非封面/结论布局均支持 `footnote` 字段，自动渲染为页脚来源脚注（8pt，左下角）。`source` 作为兼容回退（`footnote` 优先）。

**`source` vs `datasource` 字段**：
- `source: "revenue"` — 值匹配已知 datasource 时解析为 DataFrame；不匹配时保留为脚注文本（兼容回退）
- `datasource: "revenue"` — 新 canonical key，始终视为 datasource 引用，推荐在 left/right 嵌套中使用

**`default_layout_config` 字段**（Job 级）：全局图表布局默认配置，所有含 `df` 的图表页自动继承。per-page `layout_config` 浅合并覆盖。典型用于统一字体/字号，per-page 只写 `number_format` 等差异项。

---

## 自动化功能（新增）

### ChartJunkCleaner — 图表自动清洗

`ChartBuilder.build()` 完成后自动执行，无需手动调用：
- 移除图表外边框、plotArea 边框
- Y 轴网格线 → 极浅灰色虚线 (`#E8E8E8`, 0.5pt, dot style)
- 坐标轴刻度线 → none
- 图例框边框/填充 → 移除

手动使用：
```python
from ppt_station.chart_builder.cleaner import clean_chart
clean_chart(chart)  # chart = python-pptx Chart 对象
```

### 投行级表格 (IB Style)

`add_table()` 默认启用 `ib_style=True`：
- **无垂直边框** — 仅保留表头上下 + 表格底部水平线
- **数值列自动右对齐** — 检测数值型单元格，自动右对齐（首列左对齐）
- **斑马纹** — 使用 theme token `table_zebra_even` / `table_zebra_odd`
- **列高亮** — `highlight_cols=[2, 3]` 高亮指定列

### 自动页眉/页脚

`PageComposer.add_page()` 自动注入（封面/结论/分隔页除外）：
- **页眉**：16pt 标题 + 全宽 primary 色分隔线（使用 `add_page_header()`）
- **页脚**：左侧来源 + 右侧页码（使用 `add_page_footer()`，由 `data["footnote"]` 传入，兼容回退 `data["source"]`）

不再需要手动在每个布局里写标题+分隔线。

### DeckLinter — PPT 合规审计

`composer.save()` 默认 `lint=True`，自动输出审计报告。

CLI 使用：
```bash
python scripts/validate_ppt.py output.pptx          # 终端摘要
python scripts/validate_ppt.py output.pptx --json    # JSON 报告
python scripts/validate_ppt.py output.pptx --write-notes  # 写入 slide notes
```

检查规则：

| 级别 | 规则 | 说明 |
|------|------|------|
| ERROR | `header_missing` | 非封面页必须有 y < 0.6" 的文本框 |
| ERROR | `footer_missing` | 非封面页必须有 y > 6.8" 的文本框 |
| ERROR | `bounds_overflow` | 元素越界（动态阈值：从 PPT 实际尺寸计算，适配 4:3/16:9） |
| WARN | `title_size_overflow` | 内容页标题 > 18pt |
| WARN | `chart_misaligned` | 同页多图 top 偏差 > 0.05" |
| WARN | `insight_missing` | 图表页无结论先行文本（推荐 2-3 行摘要） |

### Design Tokens（主题结构化）

所有主题现在包含完整的结构 token（由 `_LAYOUT_DEFAULTS` 兜底）：

| Token | 默认值 | 用途 |
|-------|--------|------|
| `slide_w` | 13.333 | 幻灯片宽度（16:9），4:3 时自动设为 10.0 |
| `slide_h` | 7.5 | 幻灯片高度（固定） |
| `cover_title_size` | 36 | 封面标题字号 |
| `page_title_size` | 16 | 内容页标题字号 |
| `chart_subtitle_size` | 11 | 图表副标题字号 |
| `footer_size` | 8 | 页脚字号 |
| `header_y` | 0.25 | 页眉 y 坐标 |
| `divider_y` | 0.80 | 分隔线 y 坐标 |
| `content_y` | 1.00 | 内容区起始 y |
| `footer_y` | 7.10 | 页脚 y 坐标 |
| `content_w` | 自动派生 | 内容区宽度 = `slide_w - 2 * margin`（16:9 下为 12.133） |
| `table_header_bg` | 跟随 primary | 表头背景色 |
| `chart_default_scheme` | 跟随主题名 | 图表默认配色方案 |

图表配色现在自动关联主题：使用 `jp_finance` 主题时，图表自动使用 `jp_finance` 配色，无需手动指定 `style_config`。

---

## 常见陷阱

1. series_config 用 `key`/`name`/`type`/`axis` — 不是 `column`/`chart_type`
2. `position=(Inches(x), Inches(y))` 元组 — 不是 `left=`/`top=` 关键字
3. 日期轴预设传 `ChartLayoutConfig(date_axis_config=MONTHLY_TICKS)` — 不是 `CategoryAxisConfig(date_axis=...)`
4. `add_custom_page(fn)` 的 fn 签名是 `(slide, theme)` — 没有 `prs` 参数
5. kpi_cards 用 `label`/`value` — 不是 `title`/`unit`
6. bullet_points 用 `label`/`desc` — 不是 `title`/`body`
7. conclusion_dark 用 `verdict`/`items` — 不是 `summary`/`cards`
8. `ChartLayoutConfig` 没有 `has_gridlines` 参数（网格线由 ChartJunkCleaner 自动处理）
9. `composer.prs` — 不是 `composer._prs`
10. 不要手写 `add_text(标题) + add_rect(分隔线)` — 使用 `add_page_header(slide, title, theme)` 自动处理
11. 不要手写页脚来源脚注 — 在 data 中传 `"footnote": "来源: Wind, 公司年报"` 即可自动渲染（`source` 兼容回退，但推荐 `footnote`）
12. conclusion_dark 新增 `conclusions` 列表（`[{title, desc}]`）用于左侧要点 — 不是 `items`（`items` 在右侧指标卡片）
13. `comparison_table` 新增 `highlight_cols` 列索引列表 — 不是 `highlight_col`（单数）
14. `composer.save()` 默认 `lint=True` — 禁用审计传 `lint=False`
15. `source` 在顶层图表页中会被 pop 为 DataFrame — 脚注文本请用 `footnote`，不要用 `source`
16. `default_layout_config` 是浅合并 — per-page `layout_config` 的整个子 key 覆盖默认（如 `legend_config` 整体替换，不是字段级合并）
17. 图表页推荐提供 `insight` 字段（2-3行结论文本）— DeckLinter 会 WARN `insight_missing`
18. `two_charts_vertical` 用 `top`/`bottom`，不是 `left`/`right`
19. `chart_table` 的 `rows` 可省略 — 如果有 `table_df` 或 `df` 会自动转换为 headers/rows

---

## 设计规则

**金融报告级 PPT 标准。完整规则**：[reference/design-rules.md](reference/design-rules.md)

核心规则：

1. **50/50 双图是默认** — 相关图表并排在一页（`two_charts` 布局），不要一图一页
2. **禁止 emoji** — 用编号（"01"）或色块代替。金融报告零 emoji
3. **标题 16pt** — 不超过 18pt。投行级标准
4. **纯白背景** — 内容页用 `FFFFFF`，不用浅色系 bg_light
5. **每页有来源** — 底部 8pt 灰色脚注："来源：Wind, 公司年报"
6. **全宽分隔线** — 标题下方 12.133" 宽 × 0.015" 高 `text_dark` 色细线（不是 2" 短装饰线）
7. **最多 3 色** — 每张图表不超过 3 种颜色
8. **少用 section_divider** — 10-15 页报告不需要分隔页，用标题前缀区分章节

---

## NEVER / ALWAYS

1. **NEVER** 使用绝对路径。脚本用 `Path(__file__).resolve().parent.parent` 定位 `skill/` 根目录（即 `ppt_station` 包所在目录）
2. **NEVER** 在 Job JSON 中用 `merge` 变换——未实现。用 `from: [src1, src2]` concat 替代
3. **NEVER** 依赖 PptEngine 图表分支传递完整参数——复杂图表用 Flow D
4. **NEVER** 跳过 QA——首次渲染必有问题，至少一次检查-修复循环
5. **NEVER** 在金融报告中使用 emoji
6. **NEVER** 手写 `gen_xxx.py` 脚本——用 Composer Job JSON 替代
7. **NEVER** 手写标题+分隔线——用 `add_page_header()` 自动处理
8. **NEVER** 手写页脚来源脚注——用 `data["footnote"]` 自动渲染（兼容 `data["source"]`）
9. **NEVER** 在每个图表页重复 `layout_config` 字体/字号——用 Job 级 `default_layout_config`，per-page 只写差异
10. **NEVER** 硬编码幻灯片宽度 `13.333` — 使用 `theme["slide_w"]`，4:3 时自动适配
11. **ALWAYS** 先读 [api-contracts.md](reference/api-contracts.md) 再编码（特别是上下文压缩后）
12. **ALWAYS** 使用主题同名的 color_scheme（现已自动关联，无需手动指定 `style_config`）
13. **ALWAYS** 通过 `data["footnote"]` 传入数据来源（自动渲染为页脚），不要用 `source`（`source` 会被引擎尝试解析为 datasource）
14. **ALWAYS** 优先使用 `two_charts` 布局组合相关图表
15. **ALWAYS** 优先用 Composer Job JSON（Flow D），不要写一次性 Python 脚本
16. **ALWAYS** 先调 `validate_job.py` 校验再渲染
17. **ALWAYS** 检查 DeckLinter 输出——`save()` 默认运行，ERROR 级问题必须修复
18. **ALWAYS** 在图表页提供 `insight` 文本（DeckLinter 会 WARN `insight_missing`）

---

## 主题选择

| 场景 | 主题 |
|------|------|
| 金融/银行 | `jp_finance` |
| 养老金/保险 | `pension_warm` |
| 科技/互联网 | `tech_blue` |
| 政府/国企 | `state_red` |
| ESG/绿色 | `esg_green` |
| 路演/投屏 | `dark_pro` |
| 研究/晨星风格 | `morningstar`（深石板灰+暖橙，body_size=11，kpi_size=36） |
| 宏观研究 | `macro_research`（深藏蓝灰+净蓝，低调中性灰 accent） |
| 通用商务 | `midnight` / `charcoal` |

完整色值: [reference/design-themes.md](reference/design-themes.md)

---

## QA 流程（必须执行）

### Step 0: DeckLinter（自动）

`composer.save()` 默认运行 DeckLinter，输出到 stderr。ERROR 级问题必须修复。

手动运行：
```bash
python scripts/validate_ppt.py output.pptx           # 终端摘要
python scripts/validate_ppt.py output.pptx --json     # JSON 报告
python scripts/validate_ppt.py output.pptx --write-notes  # 写入 slide notes
```

### Step 1: 内容

```bash
python -m markitdown output.pptx     # 或 scripts/parse_ppt.py output.pptx
```

检查内容遗漏、错别字、残留占位符。

### Step 2: 结构

```bash
python scripts/describe_chart.py output.pptx
```

检查系列数量、类型、轴分配。

### Step 3: 视觉

方式一（推荐）：`soffice` → PDF → 图片 → subagent 视觉检查
方式二（fallback）：python-pptx 直接检查形状位置和尺寸

### Step 4: 修复→再验证

至少一次修复-验证循环。一个修复往往引入另一个问题。

---

## 参考文档

| 文档 | 用途 | 何时读取 |
|------|------|---------|
| [api-contracts.md](reference/api-contracts.md) | 函数签名、数据接口、新增 Helper API | **每次编码前** |
| [design-rules.md](reference/design-rules.md) | 投行级设计标准 + 表格/图表自动化规范 | **每次设计报告前** |
| [design-themes.md](reference/design-themes.md) | 10 主题完整色值 + Design Tokens + 比例预设 | 选择/自定义主题时 |
| [chart-config.md](reference/chart-config.md) | 图表配置完整字段 | 复杂图表配置时 |
| [institutional-design-analysis.md](reference/institutional-design-analysis.md) | 77页逐页设计分析 | 需要高级图表/布局灵感时 |
| [placeholder-syntax.md](reference/placeholder-syntax.md) | 占位符语法 | Flow A 模板替换时 |
| [job-schema.md](reference/job-schema.md) | Job JSON Schema | Flow B 编排时 |
| [connectors-transforms.md](reference/connectors-transforms.md) | 数据源+变换 | 数据处理时 |
| layout-*.md (12个) | 新布局设计规格 | 实现新布局时 |
