Refactor financial chart implementation

This commit is contained in:
R524809
2026-08-28 16:32:10 +08:00
parent 5ecc554aaa
commit 7aab3d7134
73 changed files with 2340 additions and 594 deletions
+156 -312
View File
@@ -1,353 +1,197 @@
# 智能财报分析平台 · 方案设计
> 版本:v0.2(MVP 草案)
> 状态:待评审,尚未动代码
> 范围:本仓库 `smart-fin-chart` 只负责「规范化 CSV → ECharts 展示」这一层。
> 原始财报 CSV 由使用者自行提供(外部数据处理流程不在本仓库范围内)。
> 版本:v0.3(P0 已落地)
>
> 状态:与当前代码同步
> 范围:本仓库负责「规范化 CSV → ECharts 图表展示」。原始财报的抽取、整理与复核在仓库外完成。
---
## 1. 目标与愿景
## 1. 当前目标
把当前「单公司、硬编码」的财报图表项目,升级成**多公司、可声明式扩展**的个人财报分析平台。第一版以“够用、易维护”为目标,不预建复杂的数据平台:
这是一个以“够用、易维护”为优先级的多公司财报图表工具,而不是一个预先建设完整数据平台的项目。
1. **多公司**:左栏是公司列表,选中后展示该公司的图表体系。
2. **通用图表 + 专属图表**:
- 通用图表(资产负债堆积、营收/净利增速、费用率、人效、现金流…)用**模板**,任何公司都能套。
- 专属图表(泡泡玛特各 IP 收入、茅台基酒产能、分渠道销售…)**一图一文件**,逐步沟通搭建。
3. **AI 协作**:继续以 Claude Code 对话方式协作,AI 负责「把原始 CSV 整理成规范化 CSV」+「偶发生成新图代码」,不内嵌运行期聊天机器人。
4. **数据更新闭环**:图表体系搭好后,已有图表的数据更新只需替换 CSV;只有新增专属图才需要新增图表代码。
1. **多公司浏览**:左侧选择公司,中间按分析主题查看图表,右侧可跳转当前栏目的图表。
2. **概览优先**:概览只展示最值得先看的跨主题图表;它是精选入口,不是新的数据或图表副本。
3. **确定性渲染**:CSV 相同,图表结果相同;AI 只协助将原始财报整理成 CSV,或一次性生成经 review 后固化的新增图表代码。
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 分层
## 2. 当前架构
```
原始财报 CSV(用户提供)
│ AI / 脚本:抽取 + 归一化 + 口径对齐
原始财报 / PDF / XLSX(仓库外保存)
│ 人工、AI 或脚本:抽取、归一化、口径确认
▼
data/{company}/*.csv ← 图表使用的规范化数据(唯一运行时事实来源;是否入 git 按数据策略决定)
│ P0:读取失败直接提示;P3:再加列名/类型/单位/勾稽/期间校验
data/{company}/*.csv ← 运行时数据
▼
渲染层(确定性,无 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:抽取 + 归一化 + 口径对齐
manifest.json ← 展示编排与图表配置的唯一事实来源
├─ chartCatalog ← 每张图只定义一次
└─ tabs[].chartRefs ← 各栏目仅引用图表并决定顺序
▼
data/{company}/*.csv ← AI 的输出,也是唯一与 AI 耦合的点
│ 校验器
▼ ✅ 通过 → 图表自动渲染;❌ 不通过 → 拦下人工 review
图表渲染(确定性模板,无 AI 参与)
js/charts/index.js ← chart key → ECharts option 构建函数
js/main.js + js/ui.js ← 解析引用、渲染三栏、导出和刷新
```
- **AI 不直接生成 ECharts option**。option 由确定性的图表代码生成,保证「同一份 CSV → 同一张图」,可回放、可回归、可 diff。
- **校验器是 P3 的命门**,把 AI 幻觉挡在数据层之外。届时至少校验:列名符合契约、数值列无 NaN、单位一致、勾稽关系(营收 = 分部加总、资产 = 负债 + 权益)、累计/单期口径与期数连续性。
- **PDF 比 CSV 难一个量级**。优先人工/半自动把 PDF 落成结构化 CSV 再进管道;AI 从 PDF 出的只能是「初稿 CSV」,必须过校验 + 人工确认,不指望零人工全自动。
- **第一版不内嵌运行期聊天机器人**。「对话式开发 → 落到文件和 CSV → git 可追溯」已经是最稳的 AI 形态。等模板 + 契约成熟后再考虑轻量「对话入口」只做「触发数据更新」。
### 2.1 图表定义与页面编排分离
---
## 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 schema(JSON,纯数据,不引用函数)
每家公司拥有一份 `chartCatalog`。一张图的 `key`、CSV、标题、高度与导出名只在这里维护一次;Tab 只使用 `chartRefs` 引用它。
```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": "ppmt",
"chartCatalog": {
"ppmt-growth": {
"key": "ppmt-growth",
"title": "营收到利润的增速对比",
"csv": "data/ppmt/ppmt_growth.csv",
"height": 700,
"filename": "泡泡玛特营收到利润增速对比"
}
},
"tabs": [
{ "id": "overview", "name": "概览", "chartRefs": ["ppmt-growth"] },
{ "id": "profitability", "name": "盈利能力", "chartRefs": ["ppmt-growth"] }
]
}
```
`id` 是卡片的唯一标识,供 DOM、锚点、URL 和 ECharts 实例使用;`key` 只做 `chart key → build 函数` 映射(`'ppmt-profit': profitChart, …`)。`csv` 用于单视图图表,`views` 用于可切换周期的图表;两者二选一。JSON 不引用函数、JS 不描述布局,各管一半。
这保证概览与原分类使用**同一数据源、同一图表构建函数、同一份展示配置**。修改图表只需改 `chartCatalog` 一处。
### 9.4 现有图表迁移表(tab 映射)
页面在每次展示时生成 `公司 + Tab + 图表 ID` 的实例 ID,供 DOM 和 ECharts 使用。因此,同一图既能出现在概览,也能出现在原分类,不会发生画布或导出实例冲突。URL 中仍使用稳定的图表 ID,例如:`#/ppmt/profitability/ppmt-growth`。
| 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 |
### 2.2 图表代码职责
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 不改变既有图表函数。
- `manifest.json`:图表定义在哪里展示、读哪个 CSV、卡片尺寸和导出名。
- `js/charts/index.js`:注册 `key → build 函数`。
- `js/charts/*.js`:只处理该图的业务字段与 ECharts 表达。
- `js/theme.js`:字体、配色、图例、坐标轴、脚注和水印等统一视觉规范。
- `data/{company}/`:图表需要的规范化 CSV。
---
## 10. 风险与注意事项
## 3. 页面与交互
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 中人工确认优先于自动化。
```
┌──────────┬────────────────────────────────────┬──────────┐
│ 公司列表 │ [概览][盈利能力][费用率][资产负债]… │ 当前图表 │
│ 泡泡玛特 │ ┌────────────────────────────────┐ │ · 图表 A │
│ 示例 │ │ 图表卡片:标题、数据路径、导出 │ │ · 图表 B │
│ │ └────────────────────────────────┘ │ │
└──────────┴────────────────────────────────────┴──────────┘
```
- 左栏切换公司;顶部 Tab 切换该公司的分析栏目;仅渲染当前栏目,切走后释放 ECharts 实例。
- 右栏显示当前栏目锚点,点击后平滑定位到相应图表。
- 右上角固定刷新按钮重新读取 manifest 与 CSV;导出按钮按当前卡片导出 PNG。
- 宽度 **≤1040px** 时隐藏右侧锚点;宽度 **≤880px** 时公司栏改为页面顶部横向菜单,图表卡片允许横向滚动以保持可读性。
- Vite 开发环境中,JS/CSS 修改热更新;`manifest.json` 或 CSV 修改会自动整页刷新。
---
## 11. 后续路线
## 4. 泡泡玛特当前栏目设计
| 阶段 | 内容 |
|---|---|
| P0 | 可用展示层:manifest + 三栏 + tab + 锚点 + 回归重写;以一张图验证年度/半年/季度 CSV 切换 |
| P1 | 接入第二家公司,验证 manifest、目录和周期切换是否够用;只在重复处抽少量模板 |
| P2 | 通用模板:现金流/费用率/人效/偿债,先回填已有公司 |
| P3 | AI 数据管道 + schema/勾稽校验:原始财报 → 规范化 CSV → 校验 → 渲染 |
### 4.1 概览:精选入口
概览用四张图快速回答“公司是否仍在增长、增长是否转化为利润、增长来自哪里、海外/渠道结构如何”。它们同时保留在原分析栏目中。
1. **营收到利润的增速对比**:经营增长和利润兑现。
2. **盈利能力**:主要利润率趋势。
3. **主要 IP 收入占比**:IP 集中度与核心 IP 贡献。
4. **区域渠道分布**:区域、渠道的收入结构。
### 4.2 详细栏目
| Tab | 当前内容 | 设计意图 |
|---|---|---|
| 概览 | 上述 4 张精选图 | 进入公司后的快速判断 |
| 盈利能力 | 营收到利润的增速对比、核心利润及同比、核心利润率、净利率同比拆解、利润流向、经营现金流与净利润(年度)、三大现金流与现金净增加(年度) | 将“增长是否兑现为利润”与利润质量放在同一叙事中 |
| 费用率 | 利润率与费用率拆解、费用结构与费用率(年度) | 从费率和金额两个角度拆解利润率变化 |
| 资产负债 | 资产结构演变、现金、负债与净营运资本、营运资本效率 | 观察资本与资产结构、流动性、资金占用和现金转换周期 |
| IP 与品类 | IP 收入趋势/占比、非 THE MONSTERS IP、品类结构 | 观察 IP 集中度与品类演变 |
| 区域渠道 | 区域渠道分布、同比增速、同比增速趋势、渠道收入结构与增速 | 观察全球化与渠道驱动 |
**成长性不是泡泡玛特的独立 Tab。** 目前它只有“营收到利润的增速对比”一张图,且目标是解释利润增长,因此归入“盈利能力”。未来若有多张独立增长主题图(如收入、门店、海外、分部增速),再恢复单独的“成长性”Tab。
“利润流向”是单期的利润结构细节,放在“盈利能力”而非概览。
---
## 12. 待决策项
## 5. 数据与时间维度
1. **示例图表处理**:inventory/margin/twoline/growth/fx 收进「示例」公司(默认),或删掉只留泡泡玛特。
2. **泡泡玛特 tab 映射**:见 9.4,是否认可,不认可直接改归类。
3. **周期试点图**:默认以“净利润与同比”验证年度 / 半年 / 季度切换;每个视图由使用者提供对应 CSV。
4. **`data/` 恢复来源与提交策略**:当前 checkout 里 `data/` 为空(gitignored),19 个既有图表 CSV 及周期试点 CSV 需由使用者提供;确认哪些可入 git。
5. **是否开工 P0**。
### 5.1 CSV 约定
- 每图一份 CSV;同一图的年度、半年、季度视图使用各自 CSV。
- 一份 CSV 只表达一种期间口径,禁止混合累计和单期含义。
- CSV 保留四位年份;显示层可简写。`H2` 的含义必须以数据口径为准:若为下半年单期,不得显示成全年累计。
- 同比、环比、核心利润等需要计算的指标,应在 CSV 整理阶段明确生成;前端不自动从累计值推导单季或下半年。
- CSV 至少包含 `period` 和该图需要的指标列;单位、口径与重要计算方式通过图表脚注说明。
### 5.2 可选周期切换
需要切换年度、半年、季度的图,可在 `chartCatalog` 中使用 `views` 替代单个 `csv`:
```json
{
"key": "some-chart",
"title": "净利润与同比",
"defaultView": "annual",
"views": {
"annual": "data/company/net_profit_annual.csv",
"halfyear": "data/company/net_profit_halfyear.csv",
"quarter": "data/company/net_profit_quarter.csv"
}
}
```
页面仅在声明多个视图时显示切换按钮,并将选择写入 URL query。当前泡泡玛特图表没有强制启用这一功能;基础能力已保留,等真实数据需要时再登记。
---
## 6. 视觉一致性与新增图表
新增图表必须复用 `theme.js` 的 `FONT`、`PALETTE`、`LAYOUT`、`periodAxis()`、`standardLegend()`、`lineSeries()`、`barSeries()`、`footnote()` 与 `watermarks()`,而不是在单个图表中重复手写基础样式。
- 财务含义相同的系列应使用固定语义色;IP、区域、品类等多分类数据使用统一分类色板。
- 新图的业务差异由 chart 文件表达;统一视觉由主题文件表达;展示位置与 CSV 由 manifest 表达。
- 新增或调整图表后,必须检查标签、图例、脚注和控制器是否重叠,以及颜色与文字是否有足够对比度。
通用图表模板仍然后置。只有至少两家公司需要相同图型、字段映射与口径已稳定时,才抽取 `template(rows, meta, { width, height }) => option`;在此之前,保留一图一文件更低风险。
---
## 7. 校验、发布与维护
### 7.1 当前校验
`pnpm test` 会遍历每家公司的 `chartCatalog`,以 ECharts SSR 渲染所有图表并检查:
- 图表 key 已在 registry 注册;
- CSV 可读取;
- Tab 的 `chartRefs` 都存在,且同一 Tab 不重复;
- 图例、网格、折线圆点等共享视觉规范;
- SVG 导出没有字体引号导致的非法属性。
截至当前,仓库登记 **27 张**图表(泡泡玛特 22 张、示例 5 张)。概览中的重复展示不增加图表定义或测试数量。
### 7.2 日常工作流
1. 更新已有数据:替换对应 CSV,检查口径与脚注,运行 `pnpm test`。
2. 新增图表:新增 CSV → 新增图表构建文件 → 在 registry 注册 key → 在公司的 `chartCatalog` 登记一次 → 在需要的 Tab 增加 `chartRefs`。
3. 调整首页:只调整 `overview.chartRefs` 的选择与顺序,不复制任何图表配置。
4. 发布静态文件:运行 `pnpm build`;产物只包含运行需要的 manifest 和 CSV,不包含 `data/*/raw/` 中的原始文件。
---
## 8. 明确后置的能力
- 原始 PDF 自动抽取与全自动财务勾稽;
- 自动将累计半年/季度数据拆分为单期;
- 全公司统一指标库与 schema 强校验;
- 现金流、人效、偿债等通用模板;
- 截图像素级视觉回归。
这些能力并非不需要,而是在当前“先把现有公司稳定用起来”的阶段不应抢占开发成本。出现第二家公司、稳定重复的数据与图型后,再按实际痛点逐项引入。