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

22 KiB
Raw Blame History

智能财报分析平台 · 方案设计

版本:v0.2(MVP 草案) 状态:待评审,尚未动代码 范围:本仓库 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. 图表注册与实例分离:每张卡片有全局唯一的 idDOM、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 = 上/下半年单期,2026Q12026Q4 = 单季;简写只在显示层做
一份文件一种口径 年度、半年、季度分别存文件;同一份 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}) => optionmeta 携带 {title, subtext, fields, unit, ...}
  • 字段映射显式化:manifest 里写「模板字段 ↔ CSV 列名」的映射,列名不写死在模板里。
  • 容错:字段缺失 = 跳过该图并提示,而不是报错。
  • 触发条件:至少两家公司出现相同图型,并且字段映射与口径已稳定;在此之前宁可保留专属图,避免“为了通用而通用”。
  • 第一批候选模板:现金流、费用率、人效、偿债(当前泡泡玛特缺的四块)。

8.1 视觉一致性(P0 保持,轻量治理)

现有 theme.jsCLAUDE.md 和 SSR 样式回归已经形成成熟视觉语言。新增公司或图表时,目标是复用它,而不是另建设计系统。

  1. 主题组件是硬约束:新图必须从 theme.js 使用 lineSeries()barSeries()standardLegend()periodAxis()footnote()watermarks()LAYOUTFONTPALETTE。图表文件不重复手写等价的字体、网格、图例位置、水印和基础折线/柱状样式。
  2. 颜色分两类集中管理:跨公司仍有相同财务含义的系列使用语义色,例如营收、毛利、净利润、现金流、费用各自固定颜色;IP、区域、产品等大量分类项使用统一分类色板。若确有专属业务色板,在 theme.js 以命名色板新增,不在单个图表中散落十六进制颜色值。
  3. 职责边界明确:manifest 只定义“图表在哪里展示、读取哪个 CSV”;theme.js 定义“所有图怎样长得一致”;图表文件只处理业务字段和该图独有的表达。
  4. 加图最小检查清单:使用主题组件 → 运行该图的单图 SSR 回归 → 在浏览器人工检查标签重叠、颜色可辨性和脚注 → 再登记进 manifest。无需在 P0 引入成本更高的截图像素基线。

语义色可在后续按需要以类似 SERIES_COLORS.revenueSERIES_COLORS.netProfit 的命名映射集中补入 theme.jsP0 不要求为现有图表批量重构。


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,纯数据,不引用函数)

{
  "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.jscsv.js 核心解析、全部 19 个 chart 构建函数、PNG 导出逻辑、水印/脚注/配色规范、CLAUDE.md 全部约定——一概不动。P0 不为目录美观迁移图表文件;新增 registry 不改变既有图表函数。


10. 风险与注意事项

  1. 周期口径必须在文件层隔离FYH1/H2Q1-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