Files
smart-fin-chart/docs/方案设计.md
T
2026-08-27 17:21:48 +08:00

354 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 智能财报分析平台 · 方案设计
> 版本:v0.2MVP 草案)
> 状态:待评审,尚未动代码
> 范围:本仓库 `smart-fin-chart` 只负责「规范化 CSV → ECharts 展示」这一层。
> 原始财报 CSV 由使用者自行提供(外部数据处理流程不在本仓库范围内)。
---
## 1. 目标与愿景
把当前「单公司、硬编码」的财报图表项目,升级成**多公司、可声明式扩展**的个人财报分析平台。第一版以“够用、易维护”为目标,不预建复杂的数据平台:
1. **多公司**:左栏是公司列表,选中后展示该公司的图表体系。
2. **通用图表 + 专属图表**
- 通用图表(资产负债堆积、营收/净利增速、费用率、人效、现金流…)用**模板**,任何公司都能套。
- 专属图表(泡泡玛特各 IP 收入、茅台基酒产能、分渠道销售…)**一图一文件**,逐步沟通搭建。
3. **AI 协作**:继续以 Claude Code 对话方式协作,AI 负责「把原始 CSV 整理成规范化 CSV」+「偶发生成新图代码」,不内嵌运行期聊天机器人。
4. **数据更新闭环**:图表体系搭好后,已有图表的数据更新只需替换 CSV;只有新增专属图才需要新增图表代码。
---
## 2. 总体判断(这个想法是否成立)
**结论:方向正确、可行,且单公司路径已经走通 60%。**
现有项目就是「单公司完整实现」:`theme.js` 统一视觉 + 一文件一图 + `csv.js` 解析 + ECharts 渲染 + 导出 PNG + SSR 回归测试,这套已经成熟。要做的不是推倒重来,而是把「单公司、硬编码」抽象成「多公司、可扩展」。
真正的风险不在图表,而在三件事:
1. **数据契约与口径治理** —— 跨多家公司后,单位、累计/单期、非 GAAP、衍生指标这些坑会被放大。
2. **AI 出数的可靠性** —— AI 生成的 CSV 必须可校验、可 diff、可回放,否则幻觉直接污染所有图表。
3. **通用模板的边界** —— 别把「通用」抽象过头,模板要能容忍字段缺失、要按公司可配置。
**一个关键认知**: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 核心原则
1. **单一事实来源**`manifest.json` 是「公司 / tab / 图表 / CSV / 高度 / 导出名」的唯一真相。左栏公司、顶部 tab、中间卡片、右侧锚点、回归测试**全部由它生成**,消除现在 `index.html` / `main.js CHARTS` / `regress.mjs all` 三处手工同步的维护债。
2. **先清晰、后自动化**:MVP 的 CSV 只要求列名和图表口径明确;缺列或读取失败时显示卡片错误。完整 schema、勾稽和数值校验留到后续有真实需要时再做。
3. **图表注册与实例分离**:每张卡片有全局唯一的 `id`DOM、URL、ECharts 实例使用);`key` 只表示图表构建函数。相同 `key` 可以在不同 tab 或周期视图复用。
4. **模板渲染后置**:专属图延续一文件一图。确认跨公司重复出现的图型后,再抽取纯函数模板。
### 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 类,作为「通用骨架」:
1. **成长性**:营收及增速(同比/环比)、净利/归母净利及增速、毛利及增速、分部/业务线增速、量价拆分。
2. **盈利能力**:毛利率、净利率(含非 GAAP)、核心利润率、EBITDA margin、ROE / ROIC / ROA、杜邦分解、利润率同比桥。
3. **费用率**:销售/管理/研发/财务费用率、期间费用率合计、费用结构占比。
4. **营运能力**:存货周转天数/率、应收/应付周转天数、现金转化周期 CCC、总资产/固定资产周转率、存货结构与库龄。
5. **偿债与资本结构**:资产负债率、流动/速动比率、有息负债、净负债、负债期限结构、资产结构堆积。
6. **现金流**:经营/投资/筹资净现金流、自由现金流 FCF、净现比、收现比、现金流组合形态。
7. **人效**:人均营收、人均利润、人均薪酬/人力成本、人均创收/创利增速、人效 vs 薪酬剪刀差。
8. **结构/分部**:收入结构(产品/渠道/区域/客户/IP)、分部毛利率、收入占比变迁、分部利润贡献。
9. **估值/市场(可选)**:股息回购、股本变化、资本开支 vs 折旧、PE/PS(需股价,可后置)。
---
## 7. 财务 Tab 分类
**两级:通用 Tab(骨架,所有公司尽量覆盖)+ 专属 Tab(manifest 里按公司声明)。**
### 7.1 通用 Tab(推荐 9 个,顺序即 manifest 顺序)
| Tab | 内容 |
|---|---|
| 概览 | 落地页:核心指标卡片 + 最关键的几张图(营收净利、ROE/杜邦、现金流) |
| 成长性 | 营收/净利/毛利增速、分部增速 |
| 盈利能力 | 毛利率、净利率、核心利润率、ROE/杜邦、利润桥、利润流向桑基 |
| 费用率 | 销售/管理/研发/财务费用率 |
| 营运能力 | 存货/应收/应付周转、现金周期 |
| 现金流 | 三流、自由现金流、净现比 |
| 资产负债 | 资产结构堆积、资产负债率、流动/速动、负债结构 |
| 人效 | 人均营收/利润/薪酬 |
| 分部结构 | 收入结构、分部毛利率、占比变迁 |
### 7.2 专属 Tab(示例)
- 泡泡玛特:IP 收入 / 品类结构 / 区域渠道
- 贵州茅台:基酒产能 / 渠道(直销 vs 批发)/ 库存与投放
### 7.3 设计原则
1. Tab 由 manifest 声明,顺序 = manifest 顺序;通用 tab 用固定 id(`growth` / `profitability` / `expense` / …),专属 tab 自由命名。
2. 公司没数据 → 该通用 tab 隐藏或置灰,不硬凑。
3. 「概览」承担汇总,放跨 tab 的核心指标。
4. 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 样式回归已经形成成熟视觉语言。新增公司或图表时,目标是复用它,而不是另建设计系统。
1. **主题组件是硬约束**:新图必须从 `theme.js` 使用 `lineSeries()``barSeries()``standardLegend()``periodAxis()``footnote()``watermarks()``LAYOUT``FONT``PALETTE`。图表文件不重复手写等价的字体、网格、图例位置、水印和基础折线/柱状样式。
2. **颜色分两类集中管理**:跨公司仍有相同财务含义的系列使用语义色,例如营收、毛利、净利润、现金流、费用各自固定颜色;IP、区域、产品等大量分类项使用统一分类色板。若确有专属业务色板,在 `theme.js` 以命名色板新增,不在单个图表中散落十六进制颜色值。
3. **职责边界明确**:manifest 只定义“图表在哪里展示、读取哪个 CSV”;`theme.js` 定义“所有图怎样长得一致”;图表文件只处理业务字段和该图独有的表达。
4. **加图最小检查清单**:使用主题组件 → 运行该图的单图 SSR 回归 → 在浏览器人工检查标签重叠、颜色可辨性和脚注 → 再登记进 manifest。无需在 P0 引入成本更高的截图像素基线。
语义色可在后续按需要以类似 `SERIES_COLORS.revenue``SERIES_COLORS.netProfit` 的命名映射集中补入 `theme.js`P0 不要求为现有图表批量重构。
---
## 9. P0 实施计划(本次:展示层壳)
### 9.1 目标与验收
1. 用现有图表时,加一家公司 = 改 `manifest.json` + 放 CSV,**不碰 HTML**;新增专属图时才增加一个图表文件和 registry 项。
2. 三栏布局跑通:左公司列表、中「顶部 tab + 图表卡片」、右锚点导航。
3. 现有 19 张图(5 示例 + 14 泡泡玛特)原样迁移,视觉、导出 PNG、回归测试全部保持。
4. `npm test` 用相对路径 + 遍历 manifest,不再硬编码。
5. 用一张图验证年度 / 半年 / 季度 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 schemaJSON,纯数据,不引用函数)
```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 实施步骤(顺序,每步可验证)
1. 恢复 `data/` 目录(由使用者提供图表 CSV,本仓库不负责生成),安装依赖,先让现有图表回归可运行,建立迁移基线。
2. 新建 `manifest.json` + `js/charts/index.js`(注册表),保持现有 19 个图表文件路径不变。
3. 重写 `index.html`(空壳三栏)+ 补 `css/style.css` 布局 + 新增 `js/ui.js`
4. 重写 `js/main.js` 为 manifest 驱动的懒加载渲染,支持单视图 `csv` 和可选 `views`
5. 用净利润图接入年度 / 半年 / 季度三份 CSV,验证切换、导出、hash 恢复。
6. 重写 `regress.mjs`,跑对应图表回归,浏览器手测三栏交互 + 导出 PNG。
7. 最后再按数据来源决定 `.gitignore` 与数据提交策略;无关的文件整理和删除死文件独立处理,不混入 P0。
### 9.8 明确不变的部分
`theme.js``csv.js` 核心解析、全部 19 个 chart 构建函数、PNG 导出逻辑、水印/脚注/配色规范、`CLAUDE.md` 全部约定——**一概不动**。P0 不为目录美观迁移图表文件;新增 registry 不改变既有图表函数。
---
## 10. 风险与注意事项
1. **周期口径必须在文件层隔离**`FY``H1/H2``Q1-Q4` 分文件;不让一列或一个标签同时承担“累计”和“单期”两种意思。复杂的自动转换后置。
2. **单位和衍生指标必须写进脚注**:第一版无需全库换算,但每张图要说明单位、非 GAAP 替代和计算口径。
3. **不要过早抽模板**:一个公司的一张图先写专属实现;第二家公司复用且字段稳定,才抽取模板。
4. **性能与状态**:只渲染当前公司 + tab;切换时 dispose 并删除实例记录。刷新、切换和 resize 的异步请求不能让旧结果覆盖新视图。
5. **三处手工同步合并成 manifest**,否则每加公司都要同步改 HTML / CHARTS / regress,迟早漏。
6. **硬编码路径清零**`regress.mjs` 的绝对路径已过期),全相对路径;有多周期视图时,每个视图都要能被回归测试读取。
7. **数据纳入版本控制前先确认边界**:规范化 CSV 有利于 diff 和复现,但原始文件可能涉及授权、体积或敏感性,不能默认全部提交。
8. **PDF 抽取是最大不确定性**:优先半自动落 CSV 再进管道;MVP 中人工确认优先于自动化。
---
## 11. 后续路线
| 阶段 | 内容 |
|---|---|
| P0 | 可用展示层:manifest + 三栏 + tab + 锚点 + 回归重写;以一张图验证年度/半年/季度 CSV 切换 |
| P1 | 接入第二家公司,验证 manifest、目录和周期切换是否够用;只在重复处抽少量模板 |
| P2 | 通用模板:现金流/费用率/人效/偿债,先回填已有公司 |
| P3 | AI 数据管道 + schema/勾稽校验:原始财报 → 规范化 CSV → 校验 → 渲染 |
---
## 12. 待决策项
1. **示例图表处理**inventory/margin/twoline/growth/fx 收进「示例」公司(默认),或删掉只留泡泡玛特。
2. **泡泡玛特 tab 映射**:见 9.4,是否认可,不认可直接改归类。
3. **周期试点图**:默认以“净利润与同比”验证年度 / 半年 / 季度切换;每个视图由使用者提供对应 CSV。
4. **`data/` 恢复来源与提交策略**:当前 checkout 里 `data/` 为空(gitignored),19 个既有图表 CSV 及周期试点 CSV 需由使用者提供;确认哪些可入 git。
5. **是否开工 P0**