22 KiB
智能财报分析平台 · 方案设计
版本:v0.2(MVP 草案) 状态:待评审,尚未动代码 范围:本仓库
smart-fin-chart只负责「规范化 CSV → ECharts 展示」这一层。 原始财报 CSV 由使用者自行提供(外部数据处理流程不在本仓库范围内)。
1. 目标与愿景
把当前「单公司、硬编码」的财报图表项目,升级成多公司、可声明式扩展的个人财报分析平台。第一版以“够用、易维护”为目标,不预建复杂的数据平台:
- 多公司:左栏是公司列表,选中后展示该公司的图表体系。
- 通用图表 + 专属图表:
- 通用图表(资产负债堆积、营收/净利增速、费用率、人效、现金流…)用模板,任何公司都能套。
- 专属图表(泡泡玛特各 IP 收入、茅台基酒产能、分渠道销售…)一图一文件,逐步沟通搭建。
- AI 协作:继续以 Claude Code 对话方式协作,AI 负责「把原始 CSV 整理成规范化 CSV」+「偶发生成新图代码」,不内嵌运行期聊天机器人。
- 数据更新闭环:图表体系搭好后,已有图表的数据更新只需替换 CSV;只有新增专属图才需要新增图表代码。
2. 总体判断(这个想法是否成立)
结论:方向正确、可行,且单公司路径已经走通 60%。
现有项目就是「单公司完整实现」:theme.js 统一视觉 + 一文件一图 + csv.js 解析 + ECharts 渲染 + 导出 PNG + SSR 回归测试,这套已经成熟。要做的不是推倒重来,而是把「单公司、硬编码」抽象成「多公司、可扩展」。
真正的风险不在图表,而在三件事:
- 数据契约与口径治理 —— 跨多家公司后,单位、累计/单期、非 GAAP、衍生指标这些坑会被放大。
- AI 出数的可靠性 —— AI 生成的 CSV 必须可校验、可 diff、可回放,否则幻觉直接污染所有图表。
- 通用模板的边界 —— 别把「通用」抽象过头,模板要能容忍字段缺失、要按公司可配置。
一个关键认知:AI 的职责是「把原始财报整理成图表可用的 CSV」,而不是「绘制图表」。图表由确定性代码渲染,AI 只在需要全新图型时一次性生成 chart 代码(人工 review 后固化)。这样数据更新 = 只改 CSV,图表结果可回放、可回归测试。
MVP 不先建设通用指标库、自动累计转单季引擎或完整财务勾稽系统。先用清晰的 CSV 口径和脚注保证正确性;出现重复劳动、且第二家公司接入后,再升级数据层。
3. 架构设计
3.1 分层
原始财报 CSV(用户提供)
│ AI / 脚本:抽取 + 归一化 + 口径对齐
▼
data/{company}/*.csv ← 图表使用的规范化数据(唯一运行时事实来源;是否入 git 按数据策略决定)
│ P0:读取失败直接提示;P3:再加列名/类型/单位/勾稽/期间校验
▼
渲染层(确定性,无 AI 参与)
├─ manifest.json ← 公司 × tab × 图表 的注册表(唯一配置事实来源)
├─ js/charts/index.js ← chart key → build 函数的映射
├─ js/charts/ ← P0 保持现有图表文件;后续再按公司/模板整理
└─ js/main.js + js/ui.js ← 三栏壳:读 manifest,懒加载渲染
3.2 核心原则
- 单一事实来源:
manifest.json是「公司 / tab / 图表 / CSV / 高度 / 导出名」的唯一真相。左栏公司、顶部 tab、中间卡片、右侧锚点、回归测试全部由它生成,消除现在index.html/main.js CHARTS/regress.mjs all三处手工同步的维护债。 - 先清晰、后自动化:MVP 的 CSV 只要求列名和图表口径明确;缺列或读取失败时显示卡片错误。完整 schema、勾稽和数值校验留到后续有真实需要时再做。
- 图表注册与实例分离:每张卡片有全局唯一的
id(DOM、URL、ECharts 实例使用);key只表示图表构建函数。相同key可以在不同 tab 或周期视图复用。 - 模板渲染后置:专属图延续一文件一图。确认跨公司重复出现的图型后,再抽取纯函数模板。
3.3 AI 管道边界(最重要的决策)
原始财报 CSV
│ AI:抽取 + 归一化 + 口径对齐
▼
data/{company}/*.csv ← AI 的输出,也是唯一与 AI 耦合的点
│ 校验器
▼ ✅ 通过 → 图表自动渲染;❌ 不通过 → 拦下人工 review
图表渲染(确定性模板,无 AI 参与)
- AI 不直接生成 ECharts option。option 由确定性的图表代码生成,保证「同一份 CSV → 同一张图」,可回放、可回归、可 diff。
- 校验器是 P3 的命门,把 AI 幻觉挡在数据层之外。届时至少校验:列名符合契约、数值列无 NaN、单位一致、勾稽关系(营收 = 分部加总、资产 = 负债 + 权益)、累计/单期口径与期数连续性。
- PDF 比 CSV 难一个量级。优先人工/半自动把 PDF 落成结构化 CSV 再进管道;AI 从 PDF 出的只能是「初稿 CSV」,必须过校验 + 人工确认,不指望零人工全自动。
- 第一版不内嵌运行期聊天机器人。「对话式开发 → 落到文件和 CSV → git 可追溯」已经是最稳的 AI 形态。等模板 + 契约成熟后再考虑轻量「对话入口」只做「触发数据更新」。
4. 前端三栏布局与交互
┌──────────┬───────────────────────────────┬──────────┐
│ 公司列表 │ [概览][成长性][盈利能力][…] │ 图表锚点 │
│ 泡泡玛特 │ ┌────────────────────────┐ │ · 盈利能力 │
│ 贵州茅台 │ │ 图表卡片(标题+导出) │ │ · 利润流向 │
│ … │ └────────────────────────┘ │ … │
└──────────┴───────────────────────────────┴──────────┘
- 左栏:公司列表,点击选中 → 渲染该公司第一个 tab。
- 中上:tab 栏(财务类型),点击 → 只渲染该 tab 的图表卡片。
- 中主:图表卡片堆叠(标题 + csv 路径 + 导出按钮 + canvas),纵向滚动。
- 右栏:当前 tab 的图表锚点列表,点击
scrollIntoView({behavior:'smooth'})滑动定位。 - 懒加载:只渲染「当前公司 + 当前 tab」的图,切走
dispose(),避免多公司全量 canvas 拖垮性能。 - 状态:URL hash
#/ppmt/growth保留公司 + tab(锚点可选#/ppmt/growth/ppmt-bridge),刷新可还原。 - 保留:全局「重新加载数据」、每卡「导出 PNG」、resize 重绘。
4.1 时间维度切换(MVP)
部分图表(如“净利润与同比”)可在卡片标题区显示 [年度] [半年] [季度]。用户切换时,页面读取该视图对应的 CSV,并复用同一个 chart build 函数重新渲染。
- 只有 manifest 声明了多个
views的图表才显示切换按钮;没有季度数据就不显示“季度”。 - 每个视图文件已包含图表需要的数值和同比,不在前端自动计算同比,也不从累计值推导单季/下半年。
- URL hash 可选记录视图,例如
#/ppmt/profitability/ppmt-net-profit?view=quarter;未声明或无效时回退到默认视图。 - 若未来多个公司反复需要同一套转换逻辑,再将这一层抽成通用数据处理;MVP 不预建。
5. 数据层:目录与契约
5.1 目录
data/
├── {company}/ # 每公司一个目录,company 名 = manifest 里的 id
│ ├── {chart}_annual.csv # 年度视图(仅在该图需要时提供)
│ ├── {chart}_halfyear.csv # 半年视图(仅在该图需要时提供)
│ └── {chart}_quarter.csv # 季度视图(仅在该图需要时提供)
MVP 继续使用“每图一份 CSV”;同一图有多个时间维度时,用多份 CSV 表达,不在浏览器内自动把累计值拆成单季/下半年。原始财报可由使用者在仓库外保存;是否将原始文件或规范化 CSV 纳入 git,按数据来源、授权和体积另行决定。
5.2 CSV 契约(口径约定)
这些是从现有项目踩坑经验里提炼的硬约束,跨公司时必须显式标注,不能靠人肉记忆。
| 项 | 约定 |
|---|---|
| 期间格式 | CSV 存四位年份。2026FY = 全年,2026H1 / 2026H2 = 上/下半年单期,2026Q1…2026Q4 = 单季;简写只在显示层做 |
| 一份文件一种口径 | 年度、半年、季度分别存文件;同一份 CSV 不混写累计和单期含义。若源数据为累计值,先在整理 CSV 时人工确认并转换 |
| 必需列 | 每个 CSV 至少有 period 和该图需要的指标列;含同比的图直接提供 yoy 列,不在第一版临时计算 |
| 单位 | 每个 CSV 在图表脚注注明单位(常用“亿元”);百分比、天数、人数等按自身量纲标注,禁止隐式混用 |
| 数值精度 | 默认保留一位小数;确需更高精度(如归一化占比)须在脚注说明 |
| 衍生指标 | 非 GAAP、核心利润率等「算出来的」行,必须标注计算公式与替代来源 |
5.3 校验(按需后置)
P0 只做 manifest 完整性检查:图表 id 唯一、key 在 registry 中存在、CSV 能读取。财务 schema(列名、类型、单位、累计口径、期数连续性)和勾稽校验留到 P3;届时再把 AI 出数错误挡在数据层之外。
6. 通用财务分析类型清单
按分析视角分 9 类,作为「通用骨架」:
- 成长性:营收及增速(同比/环比)、净利/归母净利及增速、毛利及增速、分部/业务线增速、量价拆分。
- 盈利能力:毛利率、净利率(含非 GAAP)、核心利润率、EBITDA margin、ROE / ROIC / ROA、杜邦分解、利润率同比桥。
- 费用率:销售/管理/研发/财务费用率、期间费用率合计、费用结构占比。
- 营运能力:存货周转天数/率、应收/应付周转天数、现金转化周期 CCC、总资产/固定资产周转率、存货结构与库龄。
- 偿债与资本结构:资产负债率、流动/速动比率、有息负债、净负债、负债期限结构、资产结构堆积。
- 现金流:经营/投资/筹资净现金流、自由现金流 FCF、净现比、收现比、现金流组合形态。
- 人效:人均营收、人均利润、人均薪酬/人力成本、人均创收/创利增速、人效 vs 薪酬剪刀差。
- 结构/分部:收入结构(产品/渠道/区域/客户/IP)、分部毛利率、收入占比变迁、分部利润贡献。
- 估值/市场(可选):股息回购、股本变化、资本开支 vs 折旧、PE/PS(需股价,可后置)。
7. 财务 Tab 分类
两级:通用 Tab(骨架,所有公司尽量覆盖)+ 专属 Tab(manifest 里按公司声明)。
7.1 通用 Tab(推荐 9 个,顺序即 manifest 顺序)
| Tab | 内容 |
|---|---|
| 概览 | 落地页:核心指标卡片 + 最关键的几张图(营收净利、ROE/杜邦、现金流) |
| 成长性 | 营收/净利/毛利增速、分部增速 |
| 盈利能力 | 毛利率、净利率、核心利润率、ROE/杜邦、利润桥、利润流向桑基 |
| 费用率 | 销售/管理/研发/财务费用率 |
| 营运能力 | 存货/应收/应付周转、现金周期 |
| 现金流 | 三流、自由现金流、净现比 |
| 资产负债 | 资产结构堆积、资产负债率、流动/速动、负债结构 |
| 人效 | 人均营收/利润/薪酬 |
| 分部结构 | 收入结构、分部毛利率、占比变迁 |
7.2 专属 Tab(示例)
- 泡泡玛特:IP 收入 / 品类结构 / 区域渠道
- 贵州茅台:基酒产能 / 渠道(直销 vs 批发)/ 库存与投放
7.3 设计原则
- Tab 由 manifest 声明,顺序 = manifest 顺序;通用 tab 用固定 id(
growth/profitability/expense/ …),专属 tab 自由命名。 - 公司没数据 → 该通用 tab 隐藏或置灰,不硬凑。
- 「概览」承担汇总,放跨 tab 的核心指标。
- Tab 总数控制在 7~10 个,多了靠「概览 + 专属」消化。
8. 通用模板设计(后续,非 P0 前置条件)
- 模板 = 纯函数
template(rows, meta, {width, height}) => option,meta携带{title, subtext, fields, unit, ...}。 - 字段映射显式化:manifest 里写「模板字段 ↔ CSV 列名」的映射,列名不写死在模板里。
- 容错:字段缺失 = 跳过该图并提示,而不是报错。
- 触发条件:至少两家公司出现相同图型,并且字段映射与口径已稳定;在此之前宁可保留专属图,避免“为了通用而通用”。
- 第一批候选模板:现金流、费用率、人效、偿债(当前泡泡玛特缺的四块)。
8.1 视觉一致性(P0 保持,轻量治理)
现有 theme.js、CLAUDE.md 和 SSR 样式回归已经形成成熟视觉语言。新增公司或图表时,目标是复用它,而不是另建设计系统。
- 主题组件是硬约束:新图必须从
theme.js使用lineSeries()、barSeries()、standardLegend()、periodAxis()、footnote()、watermarks()、LAYOUT、FONT与PALETTE。图表文件不重复手写等价的字体、网格、图例位置、水印和基础折线/柱状样式。 - 颜色分两类集中管理:跨公司仍有相同财务含义的系列使用语义色,例如营收、毛利、净利润、现金流、费用各自固定颜色;IP、区域、产品等大量分类项使用统一分类色板。若确有专属业务色板,在
theme.js以命名色板新增,不在单个图表中散落十六进制颜色值。 - 职责边界明确:manifest 只定义“图表在哪里展示、读取哪个 CSV”;
theme.js定义“所有图怎样长得一致”;图表文件只处理业务字段和该图独有的表达。 - 加图最小检查清单:使用主题组件 → 运行该图的单图 SSR 回归 → 在浏览器人工检查标签重叠、颜色可辨性和脚注 → 再登记进 manifest。无需在 P0 引入成本更高的截图像素基线。
语义色可在后续按需要以类似 SERIES_COLORS.revenue、SERIES_COLORS.netProfit 的命名映射集中补入 theme.js;P0 不要求为现有图表批量重构。
9. P0 实施计划(本次:展示层壳)
9.1 目标与验收
- 用现有图表时,加一家公司 = 改
manifest.json+ 放 CSV,不碰 HTML;新增专属图时才增加一个图表文件和 registry 项。 - 三栏布局跑通:左公司列表、中「顶部 tab + 图表卡片」、右锚点导航。
- 现有 19 张图(5 示例 + 14 泡泡玛特)原样迁移,视觉、导出 PNG、回归测试全部保持。
npm test用相对路径 + 遍历 manifest,不再硬编码。- 用一张图验证年度 / 半年 / 季度 CSV 切换;不做自动累计拆分、指标平台和财务勾稽。
9.2 目标文件结构
smart-fin-chart/
├── index.html # 改成空壳:三栏骨架 + CDN echarts + <script type=module>
├── manifest.json # ★ 唯一事实来源:公司 × tab × 图表
├── data/ # 使用者提供的图表 CSV;是否入 git 按数据策略决定
│ ├── ppmt/
│ └── demo/
├── js/
│ ├── main.js # 编排:读 manifest → 渲染三栏 → 懒加载 → 导出/刷新/resize
│ ├── ui.js # DOM 构建:左栏 / tab / 卡片 / 右锚点(新增)
│ ├── csv.js # 不变
│ ├── theme.js # 不变
│ └── charts/
│ ├── index.js # ★ 注册表:chart key → build 函数
│ └── *.js # P0 保持现有平铺文件,避免只为整理目录批量改 import
├── test/regress.mjs # 改:相对路径 + 遍历 manifest
└── css/style.css # 增:三栏布局样式
9.3 manifest schema(JSON,纯数据,不引用函数)
{
"companies": [
{
"id": "ppmt", "name": "泡泡玛特",
"tabs": [
{
"id": "overview", "name": "概览",
"charts": [
{
"id": "ppmt-net-profit",
"key": "ppmt-profit",
"title": "净利润与同比",
"height": 680,
"filename": "泡泡玛特净利润",
"defaultView": "annual",
"views": {
"annual": "data/ppmt/net_profit_annual.csv",
"halfyear": "data/ppmt/net_profit_halfyear.csv",
"quarter": "data/ppmt/net_profit_quarter.csv"
}
},
{ "id": "ppmt-sankey", "key": "ppmt-sankey", "title": "利润流向", "csv": "data/ppmt/ppmt_sankey_2026h1.csv", "height": 800, "filename": "泡泡玛特利润流向2026H1" }
]
}
]
}
]
}
id 是卡片的唯一标识,供 DOM、锚点、URL 和 ECharts 实例使用;key 只做 chart key → build 函数 映射('ppmt-profit': profitChart, …)。csv 用于单视图图表,views 用于可切换周期的图表;两者二选一。JSON 不引用函数、JS 不描述布局,各管一半。
9.4 现有图表迁移表(tab 映射)
| Tab | 图表(chart key) |
|---|---|
| 概览 | ppmt-profit、ppmt-sankey |
| 成长性 | ppmt-growth |
| 盈利能力 | ppmt-core-profit、ppmt-core-margin、ppmt-bridge |
| 费用率 | ppmt-margins |
| 资产负债 | ppmt-assets |
| IP 与品类(专属) | ppmt-ip、ppmt-ip-share、ppmt-ip-share-history、ppmt-ip-ex-monsters、ppmt-category |
| 区域渠道(专属) | ppmt-region-yoy |
demo(示例)公司:demo-inventory(营运)、demo-margin/demo-twoline(盈利)、demo-growth(成长)、demo-fx(汇率)。
9.5 regress.mjs 重写
- 去掉硬编码绝对路径
/Users/joey/sites/vest-tools/echart-demo/(已过期),改process.cwd()/import.meta.url相对定位。 - 固定
all数组 → 读 manifest + 遍历 registry;有views的卡片逐个视图测试。 - 保留按 key 只测单图(
node test/regress.mjs ppmt-assets)与 STYLE_RULES 断言(含已知的右侧纵向图例例外);新增图还须按 §8.1 做一次浏览器视觉检查。
9.6 .gitignore 变更
- 本次不强制变更。恢复数据时再决定:若 CSV 可公开且体积合适,按公司目录精确取消忽略并纳入 git;否则保留忽略,并提供不含敏感数据的测试 fixture。不要不加区分地把原始财报全部提交。
9.7 实施步骤(顺序,每步可验证)
- 恢复
data/目录(由使用者提供图表 CSV,本仓库不负责生成),安装依赖,先让现有图表回归可运行,建立迁移基线。 - 新建
manifest.json+js/charts/index.js(注册表),保持现有 19 个图表文件路径不变。 - 重写
index.html(空壳三栏)+ 补css/style.css布局 + 新增js/ui.js。 - 重写
js/main.js为 manifest 驱动的懒加载渲染,支持单视图csv和可选views。 - 用净利润图接入年度 / 半年 / 季度三份 CSV,验证切换、导出、hash 恢复。
- 重写
regress.mjs,跑对应图表回归,浏览器手测三栏交互 + 导出 PNG。 - 最后再按数据来源决定
.gitignore与数据提交策略;无关的文件整理和删除死文件独立处理,不混入 P0。
9.8 明确不变的部分
theme.js、csv.js 核心解析、全部 19 个 chart 构建函数、PNG 导出逻辑、水印/脚注/配色规范、CLAUDE.md 全部约定——一概不动。P0 不为目录美观迁移图表文件;新增 registry 不改变既有图表函数。
10. 风险与注意事项
- 周期口径必须在文件层隔离:
FY、H1/H2、Q1-Q4分文件;不让一列或一个标签同时承担“累计”和“单期”两种意思。复杂的自动转换后置。 - 单位和衍生指标必须写进脚注:第一版无需全库换算,但每张图要说明单位、非 GAAP 替代和计算口径。
- 不要过早抽模板:一个公司的一张图先写专属实现;第二家公司复用且字段稳定,才抽取模板。
- 性能与状态:只渲染当前公司 + tab;切换时 dispose 并删除实例记录。刷新、切换和 resize 的异步请求不能让旧结果覆盖新视图。
- 三处手工同步合并成 manifest,否则每加公司都要同步改 HTML / CHARTS / regress,迟早漏。
- 硬编码路径清零(
regress.mjs的绝对路径已过期),全相对路径;有多周期视图时,每个视图都要能被回归测试读取。 - 数据纳入版本控制前先确认边界:规范化 CSV 有利于 diff 和复现,但原始文件可能涉及授权、体积或敏感性,不能默认全部提交。
- PDF 抽取是最大不确定性:优先半自动落 CSV 再进管道;MVP 中人工确认优先于自动化。
11. 后续路线
| 阶段 | 内容 |
|---|---|
| P0 | 可用展示层:manifest + 三栏 + tab + 锚点 + 回归重写;以一张图验证年度/半年/季度 CSV 切换 |
| P1 | 接入第二家公司,验证 manifest、目录和周期切换是否够用;只在重复处抽少量模板 |
| P2 | 通用模板:现金流/费用率/人效/偿债,先回填已有公司 |
| P3 | AI 数据管道 + schema/勾稽校验:原始财报 → 规范化 CSV → 校验 → 渲染 |
12. 待决策项
- 示例图表处理:inventory/margin/twoline/growth/fx 收进「示例」公司(默认),或删掉只留泡泡玛特。
- 泡泡玛特 tab 映射:见 9.4,是否认可,不认可直接改归类。
- 周期试点图:默认以“净利润与同比”验证年度 / 半年 / 季度切换;每个视图由使用者提供对应 CSV。
data/恢复来源与提交策略:当前 checkout 里data/为空(gitignored),19 个既有图表 CSV 及周期试点 CSV 需由使用者提供;确认哪些可入 git。- 是否开工 P0。