--- 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个) | 新布局设计规格 | 实现新布局时 |