# 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_config(Job 级) ```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_charts(50/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 ```