skills/ppt-station-skill/reference/api-contracts.md

445 lines
15 KiB
Markdown
Raw Permalink Normal View History

# API Contracts — 函数签名与数据接口
> 本文档是 ppt-station 所有公开 API 的精确合约。编码前必读。
---
## 1. create_combo_chart()
```python
from ppt_station.chart_builder.api import create_combo_chart
create_combo_chart(
slide, # pptx Slide 对象
df, # pandas DataFrame
categories_col, # str — X轴列名
series_config, # List[Dict] — 见下方格式
position=(Inches(1), Inches(2)), # (left, top) 元组
size=(Inches(8), Inches(4.5)), # (width, height) 元组
style_config=None, # StyleConfig 实例
layout_config=None, # ChartLayoutConfig 实例
)
```
### series_config 格式4个字段全部必需
```python
series_config = [
{"key": "营收(亿)", "name": "营收(亿)", "type": "bar", "axis": "primary"},
{"key": "增长率", "name": "增长率", "type": "line", "axis": "secondary"},
]
```
| 字段 | 说明 | 可选值 |
|------|------|--------|
| `key` | DataFrame 列名 | 必须与 df 列名完全一致 |
| `name` | 图表图例显示名 | 任意字符串 |
| `type` | 图表类型 | `"bar"`, `"line"`, `"area"` |
| `axis` | 轴分配 | `"primary"` (左轴), `"secondary"` (右轴) |
**常见错误**:用 `column` 代替 `key`、用 `chart_type` 代替 `type`、缺少 `name` 字段。
---
## 2. StyleConfig
```python
from ppt_station.chart_builder.styles import StyleConfig
StyleConfig(
color_scheme="tech_blue", # 配色方案名(见 COLOR_SCHEMES
line_width_pt=2.0, # 线宽 pt: 0.5/0.75/1.0/1.5/2.0/2.25/3.0
marker_style="none", # "none"/"circle"/"square"/"diamond"/"triangle"
marker_size=5, # 标记点大小 pt
)
```
---
## 3. ChartLayoutConfig
```python
from ppt_station.chart_builder.layout import (
ChartLayoutConfig, LegendConfig, ValueAxisConfig, CategoryAxisConfig,
)
from ppt_station.chart_builder.date_axis import MONTHLY_TICKS # DateAxisConfig 预设
ChartLayoutConfig(
title=None, # str — 图表内标题(通常 None标题放页面上
legend_config=LegendConfig(...), # 图例
category_axis_config=CategoryAxisConfig(...), # 横轴
value_axis_config=ValueAxisConfig(...), # 主值轴(左)
secondary_value_axis_config=ValueAxisConfig(...), # 次值轴(右)
date_axis_config=MONTHLY_TICKS, # DateAxisConfig 日期轴(优先于 category_axis_config
)
```
**关键:`date_axis_config` 在 ChartLayoutConfig 上,不在 CategoryAxisConfig 上。**
### LegendConfig
```python
LegendConfig(
position=LegendConfig.TOP, # TOP/BOTTOM/LEFT/RIGHT/CORNER
font_size_pt=9,
font_name="黑体",
)
```
### CategoryAxisConfig
```python
CategoryAxisConfig(
is_date_axis=False, # 是否日期轴
major_unit_days=None, # 主刻度天数
font_name="黑体",
font_size_pt=10,
number_format=None, # 如 "yyyy-mm-dd"
)
```
**没有 `date_axis` 参数。** 日期轴预设传入 `ChartLayoutConfig(date_axis_config=...)`
### ValueAxisConfig
```python
ValueAxisConfig(
number_format="#,##0", # 数字格式
font_name="黑体",
font_size_pt=9,
)
```
### 日期轴预设
| 预设 | 间隔 | 适用 |
|------|------|------|
| `DAILY_TICKS` | 1天 | 1-30天 |
| `WEEKLY_TICKS` | 7天 | 1-6月 |
| `BIWEEKLY_TICKS` | 14天 | 季度 |
| `MONTHLY_TICKS` | 1月 | 年度 |
| `QUARTERLY_TICKS` | 3月 | 多年 |
| `YEARLY_TICKS` | 1年 | 长期 |
---
## 4. PageComposer
```python
from ppt_station.composer import PageComposer
composer = PageComposer(theme="jp_finance") # 主题名(从 theme 自动读取 slide_w/slide_h
# 方式一:注册布局
composer.add_page("layout_name", data_dict)
# 自动注入页眉add_page_header和页脚add_page_footer
# 封面/结论/分隔页除外title_dark, title_light, conclusion_dark, section_divider
# 页脚来源从 data["footnote"] 获取(兼容回退 data["source"]),页码自动计算
# 方式二:自定义渲染(无 data
composer.add_custom_page(render_fn)
# render_fn 签名: def render_fn(slide, theme) → None ← 只有 2 个参数
# 方式三:自定义渲染(带 data
composer.add_custom_page(render_fn, data=my_dict)
# render_fn 签名: def render_fn(slide, data, theme) → None ← 3 个参数
composer.save("output.pptx") # 自动运行 DeckLinter
composer.save("output.pptx", lint=False) # 跳过审计
```
**常见错误**`render_fn(slide, prs, theme)` — 没有 `prs` 参数。
---
## 4.1 ChartJunkCleaner自动无需手动调用
`ChartBuilder.build()` 完成后自动执行清洗:
- 移除图表外边框、plotArea 边框
- Y 轴网格线 → 极浅灰色虚线 (`#E8E8E8`, 0.5pt, dot)
- 坐标轴刻度线 → none
- 图例框边框/填充 → 移除
手动使用:
```python
from ppt_station.chart_builder.cleaner import clean_chart, ChartJunkCleaner
clean_chart(chart) # 便捷函数
# 或
ChartJunkCleaner(chart).clean() # 完整控制
```
---
## 4.2 DeckLinter保存时自动运行
```python
from ppt_station.qa.deck_linter import DeckLinter
linter = DeckLinter("output.pptx")
report = linter.run()
linter.print_summary(report) # 终端摘要
linter.to_json(report) # JSON 字符串
linter.write_to_notes(report) # 写入 PPT slide notes
```
检查规则:`header_missing`, `footer_missing`, `bounds_overflow`(动态阈值,适配 4:3/16:9, `title_size_overflow`, `chart_misaligned`, `insight_missing`(图表页无结论先行文本)
---
## 4.3 页眉/页脚 Helpers
```python
from ppt_station.composer.helpers import add_page_header, add_page_footer
add_page_header(slide, "页面标题", theme)
# 输出: 16pt 标题 + 全宽 primary 色分隔线 (使用 theme tokens: page_title_size, header_y, divider_y)
add_page_footer(slide, theme, source="Wind, 公司年报", page_num=3, brand="PPT Station")
# 输出: 左侧来源 + 右侧页码/品牌 (使用 theme tokens: footer_size, footer_y)
```
**注意**注册布局自动调用custom_page 不自动调用(需手动使用)。
---
## 4.4 PptEngine — Job JSON 渲染
### source / datasource / footnote 语义
| 字段 | 位置 | 语义 |
|------|------|------|
| `datasource` | 顶层或 left/right/top/bottom 嵌套 | **新 canonical key**,始终视为 datasource 引用 |
| `source` | 顶层或 left/right/top/bottom 嵌套 | 值匹配已知 datasource 时解析为 DataFrame 并 pop不匹配时保留为脚注文本兼容回退 |
| `footnote` | 顶层 | 页脚来源文本auto-footer 优先读取 |
**推荐**:顶层脚注用 `footnote`,嵌套 datasource 引用用 `source``datasource`
### default_layout_configJob 级)
```json
{
"default_layout_config": {
"legend_config": {"font_size_pt": 8, "font_name": "黑体"},
"value_axis_config": {"font_size_pt": 8, "font_name": "黑体"},
"secondary_value_axis_config": {"font_size_pt": 8}
}
}
```
- **作用**:所有含 `df` 的图表页(包括 left/right 嵌套)自动继承此配置
- **合并策略**浅合并per-page `layout_config` 的 key 覆盖默认 key整个子对象替换非字段级合并
- **无 `df` 的页面**kpi_cards, bullet_points 等)不注入
- **Optional**:不提供时不注入,完全兼容旧 JSON
---
## 5. 布局数据接口
### title_dark / title_light
```python
{"title": "标题", "subtitle": "副标题"} # subtitle 可选
```
### kpi_cards
```python
{
"title": "页面标题",
"footnote": "来源: Wind, 公司年报", # 可选auto-footer 渲染
"cards": [
{"label": "指标名", "value": "数值", "change": "+5.9%"}, # change/note 可选
]
}
```
**常见错误**:用 `title` 代替 `label`,用 `unit` 作为单独字段(应把单位写进 `value`)。
### bullet_points
```python
{
"title": "页面标题",
"footnote": "来源: 行业报告", # 可选auto-footer 渲染
"items": [
{"icon": "01", "label": "要点标题", "desc": "要点描述"},
]
}
```
**常见错误**:用 `title` 代替 `label`,用 `body` 代替 `desc`
### chart_full / chart_text
```python
{
"title": "页面标题",
"df": df, # pandas DataFrame引擎从 source/datasource 解析)
"categories_col": "日期", # X轴列名
"series_config": [...], # 同 create_combo_chart 的 series_config
"insight": "结论先行文本...", # 可选2-3行结论摘要header 和图表之间)
"footnote": "来源: Wind", # 可选auto-footer 渲染
"style_config": StyleConfig(...), # 可选
"layout_config": ChartLayoutConfig(...), # 可选(可由 default_layout_config 继承)
}
```
**Job JSON 中**`df` 由 `source`/`datasource` 字段解析,不直接传 DataFrame。
### two_charts50/50 双图)
```python
{
"title": "页面标题",
"left_title": "左图标题", # 可选
"right_title": "右图标题", # 可选
"insight": "结论先行文本...", # 可选header 和图表之间
"footnote": "来源: Wind", # 可选auto-footer 渲染
"left": {
"df": df1, "categories_col": "日期", "series_config": [...],
"style_config": ..., "layout_config": ...,
},
"right": {
"df": df2, "categories_col": "日期", "series_config": [...],
"style_config": ..., "layout_config": ...,
},
}
```
**Job JSON 中**left/right 内用 `source`/`datasource` 指向数据源,`footnote` 放顶层。
### two_charts_vertical上下双图
```python
{
"title": "页面标题",
"top_title": "上图标题", # 可选
"bottom_title": "下图标题", # 可选
"insight": "结论先行文本...", # 可选header 和上图之间
"footnote": "来源: Wind", # 可选auto-footer 渲染
"top": {
"df": df1, "categories_col": "日期", "series_config": [...],
"style_config": ..., "layout_config": ...,
},
"bottom": {
"df": df2, "categories_col": "日期", "series_config": [...],
"style_config": ..., "layout_config": ...,
},
}
```
**设计特点**:两个图表均使用全宽(最大化时间轴跨度),上下等高,中间 0.15" 间隔。适合共享日期轴的趋势对比。
**Job JSON 中**top/bottom 内用 `source`/`datasource` 指向数据源。
### chart_table左图右表
```python
{
"title": "页面标题",
"df": df, # 图表数据
"categories_col": "日期",
"series_config": [...],
"headers": ["指标", "值"], # 表格表头(可省略,从 table_df/df 自动提取)
"rows": [["A", "1"], ...], # 表格数据行(可省略,从 table_df/df 自动转换)
"table_df": table_df, # 可选,表格数据 DataFrame自动转为 headers/rows
"insight": "结论先行文本...", # 可选
"chart_ratio": 0.62, # 可选,图表宽度占比(默认 0.62
"footnote": "来源: Wind", # 可选
}
```
**设计特点**:左图表 (62%) + 右表格 (38%)表格使用投行级样式ib_style。适合趋势可视化 + 截面数据快照组合。
**Job JSON 中**:图表数据用 `source`/`datasource`,表格数据用 `table_df` 对应的 datasource 或直接传 `headers`/`rows`。
### comparison_table
```python
{
"title": "标题",
"headers": ["指标", "公司A", "公司B"],
"rows": [["营收", "100亿", "80亿"], ...],
"highlight_row": 0, # 可选,高亮行索引
"highlight_cols": [1, 2], # 可选,高亮列索引列表
"footnote": "来源: Wind, 公司年报", # 可选auto-footer 渲染
}
```
表格默认启用投行级样式(`ib_style=True`):无垂直边框、数值列自动右对齐、斑马纹。
**注意**footnote 统一由 auto-footer 渲染(不再在表格下方手动添加)。
### section_divider
```python
{"title": "章节名", "number": "01"}
```
### conclusion_dark左右分栏布局
```python
{
"verdict": "核心结论(必填)",
"title": "结论", # 可选,默认"结论"
"score": "8/10", # 可选,左侧顶部大字
"conclusions": [ # 可选左侧要点列表最多3条
{"title": "高壁垒", "desc": "品牌+渠道双重优势"},
{"title": "稳增长", "desc": "营收年化增速 20%+"},
],
"items": [{"label": "评分", "value": "8/10"}], # 可选,右侧指标卡片
"risk": "风险提示文字", # 可选,右侧底部
"footnote": "脚注", # 可选
}
```
布局结构:左侧 55%score + verdict + conclusions| 右侧 45%items 卡片 + risk
**常见错误**:用 `summary` 代替 `verdict`,用 `cards` 代替 `items`。`conclusions`(左侧要点)和 `items`(右侧指标)是不同字段。
---
## 6. 完整示例:双轴组合图(自定义页面)
```python
from pptx.util import Inches
from ppt_station.composer import PageComposer
from ppt_station.chart_builder.api import create_combo_chart
from ppt_station.chart_builder.styles import StyleConfig
from ppt_station.chart_builder.layout import (
ChartLayoutConfig, LegendConfig, ValueAxisConfig, CategoryAxisConfig,
)
from ppt_station.chart_builder.date_axis import MONTHLY_TICKS
def render_my_chart(slide, theme):
# 页眉(自动: 16pt 标题 + 全宽分隔线)
from ppt_station.composer.helpers import add_page_header, add_page_footer
add_page_header(slide, "股价走势2024-2026", theme)
# 图表 — style_config 可省略,自动使用主题配色
m = theme["margin"]
content_y = theme.get("content_y", 1.00)
footer_y = theme.get("footer_y", 7.10)
create_combo_chart(
slide, price_df, categories_col="日期",
series_config=[
{"key": "成交量", "name": "成交量", "type": "bar", "axis": "secondary"},
{"key": "收盘价", "name": "收盘价", "type": "line", "axis": "primary"},
],
position=(Inches(m), Inches(content_y)),
size=(Inches(12.133), Inches(footer_y - content_y - 0.3)),
# style_config 省略 — 自动使用 jp_finance 配色
layout_config=ChartLayoutConfig(
legend_config=LegendConfig(position=LegendConfig.TOP, font_size_pt=9, font_name="黑体"),
value_axis_config=ValueAxisConfig(number_format="#,##0.00", font_name="黑体", font_size_pt=9),
secondary_value_axis_config=ValueAxisConfig(number_format="#,##0", font_name="黑体", font_size_pt=9),
date_axis_config=MONTHLY_TICKS,
),
)
# 页脚(来源 + 页码)
add_page_footer(slide, theme, source="Tushare, Wind")
composer = PageComposer(theme="jp_finance")
composer.add_custom_page(render_my_chart)
composer.save("output.pptx") # 自动运行 DeckLinter
```