预算沙盘(budget-sandbox)v3 · 模型驱动 · 预算+实际双轨
这个 skill 最重要的一句话
不要拿到 Excel 就生成沙盘。 先把这张表背后的业务模型问出来、跟用户确认,再生成。 沙盘是模型的可视化;没有模型,再好看的沙盘也只是一张贴着数字的壳。
⚡ 最短路径(照着做,不要自己发挥)
不确定该怎么办的时候,就执行这一段。 每一步都以「机器说什么」为准,不靠你自己判断。 带 ⛔ 的是确认点:没拿到用户的答案,不许往下走。
bashcd <skill目录>/scripts # ⓪ 新电脑先体检(仓库自带纯 Python 依赖,零 pip 也能跑) python3 doctor.py
输入是多维表格(飞书/Lark Base)? 两条路,产物完全同构、后面链路一样:
- 主路(默认):让用户手工导出——浏览器打开 Base → 右上角「···」→ 导出 → Excel(.xlsx)
- 辅路(本机有 lark-cli 登录态):
python3 ingest_base.py <base_token> <table_id> -o 表.xlsx(退出码 3 = 没有 lark-cli,不是失败,回主路)
bash# ① 探查:机器读表,给你反问清单 python3 analyze_budget.py <预算.xlsx> --json /tmp/an.json
→ 看到 🔴 A0_uncached_formula 立刻停,让用户用 Excel/WPS 另存一次再来。
→ ⛔ 确认点 1 · 口径反问:照 questions 里 blocking: true 的题目问用户,
一次问 2–4 个,把 options 当选项给他。探测到实际数(报告里有【实际数候选】)时必问:
实际数截至第几个月?哪些月是完整月?口径与预算一致吗?
bash# ② 挑模板:不要从零写 spec ls ../templates/
→ 按用户的生意挑一个最像的:
| 用户的生意像这样 | 用这个模板 |
|---|---|
| 有门店/网点,靠开店增长 | storefront-连锁门店 |
| 靠投放买流量卖货 | ecommerce-电商DTC |
| 按月订阅、有续费和流失 | subscription-订阅SaaS |
| 接项目、按工期交付、分期回款 | project-项目制 |
| 有产线/车队,产能是天花板 | capacity-产能型 |
| 先收钱后服务(课程/储值卡/年费) | prepaid-预收型 |
| 没有营收(市场部/HR/IT 预算) | costcenter-纯成本中心 |
| 靠人头数决定成本 | headcount-人头驱动 |
| 靠会员/老客复购 | clientpool-客户池驱动 |
bashcp ../templates/<选中的>.json /tmp/spec.json
→ 打开 /tmp/spec.json,照 _todo 字段逐条把数字换成用户的。
→ 只换数字和 provenance,不要改结构。改完把 _todo 删掉。
→ meta 的 name / industry / purpose 三件套必填——它们就是页面刊头
(名称大标、行业眉题、「这个沙盘要回答什么」主旨句),构建会检查。
bash# ③ 核对有没有漏掉表里的科目(照模板填最容易犯的错) python3 coverage_check.py /tmp/spec.json --against /tmp/an.json
→ 它会列出「表里有、模型里找不到对应项」的科目。逐条看:名字不同但已建模的忽略;
真漏了的补进模型,或在 assumptions 里写明为什么不要它。
(实测:一个执行者照模板做完,build / audit / 结构检查三道全过,
却整条漏掉了「其他门店费用」——因为那三道只管「是不是模型」和「内部闭不闭合」,
从不核对有没有覆盖用户的表。)
有实际数(双轨)再做下面两步,没有就直接跳到模型卡:
bashcp ../templates/actuals-模板.json /tmp/actuals.json # 照 _todo 逐条填 python3 actuals_check.py /tmp/actuals.json --spec /tmp/spec.json --xlsx <实际数源表.xlsx> --report
→ ⛔ 确认点 2 · 映射对照:把 --report 打印的对照表逐条讲给用户
(表里哪个科目对哪条模型序列、哪些序列没有实际数),等他确认。回放检查(数字必须
能在源表里找到)是不可豁免的——报错就是抄错位或编了数字,修 actuals.json,不许改检查。
→ 想让滚动预测按达成率重锚定:在 spec 对应序列上加
"reforecast": {"method": "runrate", "window": 3}。默认 hold_plan(按原计划),零魔法系数。
→ ⛔ 确认点 3 · 模型卡:用人话把模型讲回给用户(照下方完整版③的样子),双轨时补一句
「实际数覆盖到第 X 月、对上了 Y 个科目、Z 没有实际数」。用户没点头不许生成。
→ ⛔ 确认点 4 · 分析口径(双轨时):三件事拿到明确答案——
① 差异按「实际−预算」报(费用超支显示为正数、标红)可以吗;
② 哪些序列按达成率重锚定(就是上面的 reforecast);
③ 要不要配决策卡:spec.decisions 节写「动作=改哪几个参数、改成多少」,后果全部引擎算。
bash# ④ 生成(机器会自己体检;有实际数就带上——差异桥/滚动预测/敏感性/决策卡随沙盘发布) python3 build_v2.py /tmp/spec.json [--actuals /tmp/actuals.json --xlsx <实际数源表>] -o /tmp/沙盘.html
→ 退出码 0 才算过。非 0 时读它打印的 → 开头的那几行,那就是修法,改完重跑。
→ ⛔ 确认点 5 · 要点编辑:CLI 会打印 findings(固定规则产出的要点)。把全量 findings
列给用户,让他挑哪几条上「本期要点」。有明确选择就写进 spec:
"meta": {"headline_findings": ["runway", "variance_top"]} 然后重跑 ④;
没意见就默认前 5 条。不替汇报人做编辑决定。
bash# ⑤ 审计(双轨页多跑 G 组三探针:桥随实际动 / 实际线锁定 / 龙卷风排序现算一致) python3 audit_sandbox.py /tmp/沙盘.html
→ 没装 playwright 就加 --no-browser,并在交付时如实说少验了页面那两项。
→ 不通过就按提示改,不要交付没过审计的沙盘。
四条绝对不许做的事:
- ❌ 把 Excel 里的逐月数字抄进
path当收入 —— 那不是模型,是把表塑封了一遍。机器会判死(structure_check),改分类也绕不过去。 - ❌ 用户还没确认模型卡就生成 —— 生成前必须用人话把模型讲一遍给用户听,等他说对。
- ❌ 审计不过就交付 —— 不通过的项如实告诉用户,别粉饰。
- ❌ 对
coverage_check报出来的科目视而不见 —— 每一条都要有交代: 要么在模型里,要么在assumptions里写明为什么不要。
下面是完整版说明;上面这段跑通了就够了。
v1 的教训:它内置了一套写死的「MRR 复利」模型,任何行业的预算都被硬塞进去,算得自洽但未必为真。
十个行业的对照实测(references/why-v2.md)显示,这种简化不是精度问题,是方向性错误:
连锁餐饮激进扩张情景下期末现金高估 4.5 倍;物流阶梯成本单月误差 -37%;
教育忽略预收递延使收入虚高 10.1%;即使在最有利的 SaaS,复利拟合 24 个月末也高估 MRR 22.6%~28.9%。
两条铁律
- 引擎是固定资产,生成模型永不重写公式。 (2026-07-23 aily 把月净燃烧算成 −30.9 亿换来的教训)
- 模型不写死在引擎里。
模型是声明式规格
model-spec.json,由你从 Excel 推导 + 向用户反问确认后产出。
调和这两条的方式:
引擎 = 通用「驱动树求解器」(scripts/solver.py,代码,你不许碰) 模型 = 声明式规格(model-spec.json,数据,你负责产出) 你的职责 = 识别结构 → 提出问题 → 产出规格。**永远不写公式。**
工作流(五步 × 五个确认点)
① 探查 ──► ② 反问 ──► ③ 模型卡确认 ──► ④ 生成 ──► ⑤ 审计 analyze AskUserQuestion 用户点头 build_v2 audit_sandbox ↑ 机器做 ↑ 你做 ↑ 必须 ↑ 机器做 ↑ 机器做 确认点(⛔ 没拿到答案不许往下):1 口径反问(含实际数截至月)→ 2 实际数映射对照 → 3 模型卡 → 4 分析口径(差异符号/重锚定/决策卡)→ 5 要点编辑(headline_findings) 双轨细节见「⚡ 最短路径」;实际数契约见 references/model-spec.md 第 7 节。
① 探查:先让机器把表读明白
bashpython3 scripts/analyze_budget.py <预算.xlsx> --json /tmp/analysis.json
输出:表结构地图(含转置表 / 参数表 / 合并单元格 / 万元口径)、科目归类、五类专业体检 (口径完整性 / 结构闭合 / 时间形态 / 驱动可得性 / 参数表成本性质),以及一份按影响排序的反问清单。
必看的红灯:
| 代码 | 含义 | 不处理会怎样 |
|---|---|---|
A0_uncached_formula | 公式没有缓存值,读进来是空的 | 后面所有分析都建立在空表上。先让用户用 Excel/WPS 另存一次 |
A1_no_cogs | 有收入没成本 | 隐含毛利率 100%,现金高估 40%~70% |
A2_no_cash_terms | 没有任何账期线索 | 现金曲线形状会错,不是差一点 |
E2_no_driver | 只有金额没有业务量 | 沙盘回答不了「多开一家店会怎样」 |
F2_nature_vague | 标了「随规模」但没说随什么 | v1 最典型的假联动:拖营收滑块成本纹丝不动 |
② 反问:只问会改变结论的问题
用 AskUserQuestion 按 analysis.json 里 questions 的优先级顺序问,blocking: true 的必须问到答案。
问法要求:
- 一次问 2–4 个,别一口气甩十几个问题。
- 让用户明白不回答会错多少(analysis 已给
why_it_matters,直接用)。 - 给候选答案(analysis 已给
options),挑比填空容易得多。 - 用户说「我不知道」时:给出建议默认值 并明确告诉他这会被标成「工具假设」,绝不偷偷填上。
典型必答题:
| 编号 | 问什么 | 不问会怎样 |
|---|---|---|
| Q-COGS | 有没有商品/材料/履约成本?成本率多少? | 隐含毛利率 100% |
| Q-CASH | 钱什么时候真的到账/付出去?账期、预收、备货 | 「接单越多现金越紧」完全看不出来 |
| Q-DRIVER | 收入本质由什么决定? | 沙盘退化成费用表的皮肤 |
| Q-NATURE | 标了「随规模」的成本随的是哪个量? | 拖营收滑块成本纹丝不动 = 假联动 |
| Q-STEP | 有没有「每 N 个业务量要加一个资源」的阶梯? | 加车 / 开班 / 上产线的临界点看不见 |
| Q-CAPEX | 大额支出是资本开支还是一次性费用? | 利润和现金同时算错 |
| Q-DECISION | 这个沙盘最终要回答哪一个问题? | KPI 和图表选不对 |
③ 模型卡:用人话复述一遍,等用户点头
产出 model-spec.json(契约见 references/model-spec.md),然后把它讲回给用户:
我理解你的生意是这样的: 收入 = 等效成熟店数 × 单店月营业额;新店按 50%/70%/90%/100% 四个月爬坡 成本 = 食材 35%(随营收,供应商月结滞后 1 个月);人工与房租随门店数;总部费用固定 现金 = 装修 85 万/店当月付、分 5 年折旧;外卖平台佣金滞后 1 个月结算 我替你假设的(表里没有):折旧年限 5 年、所得税 25% 这个沙盘要回答:不跌破 500 万现金安全垫的前提下,每月最多能开几家店?
对吗?哪里不对告诉我。
用户没有点头之前,不要生成沙盘。 这是硬闸——v1 就是跳过了这一步。
④ 生成
bashpython3 scripts/build_v2.py <model-spec.json> [--actuals actuals.json --xlsx 实际数源表.xlsx] -o <沙盘.html>
内置:全情景求解 + 恒等式自检 + 滑块体检(推到底 KPI 不动的装饰性滑块直接报错)+ 假设溯源统计
- 分析层(情景对比/敏感性/盈亏平衡/决策卡/findings 恒跑;带
--actuals再加差异桥与滚动预测, 实际数先过不可豁免的契约+回放闸)。退出码非 0 就是没过,别交付。
⑤ 审计:证明它不是壳
bashpython3 scripts/audit_sandbox.py <沙盘.html> --json /tmp/audit.json
六维审计(需要 playwright;没有就加 --no-browser,但要如实告诉用户少验了哪两项):
| 维度 | 问的问题 |
|---|---|
| A | 页面上的数 = 模型算出来的数吗?(真实浏览器求解 vs Python 逐月对数) |
| B | 拖滑块页面真的重算并重绘了吗? |
| C | 联动方向和量级对吗?变动成本随驱动动?COGS 与营收挂钩?毛利率有没有无解释漂移? |
| D | 有装饰性滑块吗? |
| E | 恒等式成立吗? |
| F | 有多少数字是工具自己编的?页面上披露了吗? |
| G | 汇报页三探针:篡改实际数桥会不会精确跟着变?拖滑块页面画的「实际」动没动(数据级锁定)?龙卷风条序与现算一致吗?(无实际数的页跳过并声明——跳过≠通过) |
| H | 界面语言:用户可见文本里有没有内部措辞/过程语言/单元格引用?(黑话清单在 scripts/ui_language_check.py,界面是给看汇报的人的,不是给开发者的) |
审计不过就不要交付。 把不通过的项如实告诉用户,别粉饰。
什么情况下绝对不许生成沙盘
- 探查报到
A0_uncached_formula(表是空的)而用户还没重新提供数据 blocking: true的问题还没有答案- 用户还没确认模型卡
build_v2.py或audit_sandbox.py退出码非 0- 你想用「先做一版给你看看」绕过上面任何一条 —— 这正是 v1 的做法
特殊场景
纯成本中心(市场部 / HR / IT 预算,没有营收)
不要硬造一个「营收」。role 里不放 revenue,KPI 自动切换成:全期支出 / 峰值月 / 期末现金 / 业务指标。
决策问题换成「砍 20% 预算会损失多少产出」「哪个月现金压力最大」。
用户没有预算表
python3 scripts/new_budget_template.py 生成一张合规空白预算让业务方填。
用户坚持要发给 aily / 豆包自助生成
如实说明:让通用生成模型重写引擎不可靠(就是本 case 翻车的原因)。可靠做法是把本 skill 产出的
完整可运行 HTML 给它,并要求「逐字保留 <script> 内的引擎与 SPEC,只允许改文案/配色」——
让它复制而不是重写。
关键文件
| 文件 | 作用 |
|---|---|
scripts/analyze_budget.py | ① 结构探查 + 专业体检 + 反问清单(入口) |
references/model-spec.md | ③ 模型规格契约 + 9 个行业模型族速查(产出 spec 前必读) |
references/why-v2.md | v1 为什么是「形而非实」+十行业对照实测证据 |
scripts/solver.py | 固定引擎:通用驱动树求解器(不许改) |
assets/engine.js | 引擎的 JS 孪生(浏览器实时求解,改一侧必须同步另一侧) |
scripts/parity_check.py | Python ↔ JS 随机扰动逐月对数(改过引擎必跑) |
scripts/build_v2.py | ④ spec → 沙盘 HTML |
scripts/audit_sandbox.py | ⑤ 六维审计 |
examples/spec-chain-restaurant.json | 完整范例 spec(含爬坡 / 阶梯 / capex / 账期) |
scripts/new_budget_template.py | 没有预算表时生成合规空白预算 |
分发形态(三种,都是 v2、单源同步)
| 形态 | 怎么来 | 给谁 |
|---|---|---|
| 仓库 | git clone 到 ~/.claude/skills/budget-sandbox | Claude 系 agent;唯一能跑完整六维审计的形态(有 playwright/node 时) |
| 单文件版 | python3 scripts/make_standalone.py -o 预算沙盘_单文件版.py | 任何能跑代码的环境,只依赖 openpyxl |
| 对话技能卡 | python3 scripts/make_skill_card.py -o 预算沙盘.skill.md | 粘给任意 agent,对话即装 |
后两者把源文件原样打进 zip 再 base64,不是代码副本——这个项目最贵的教训就是 "两份实现必然悄悄分叉",所以分发形态坚决不复制代码。 它们跑不了浏览器审计(A/B 两维),会在输出里自己声明,不会默认报通过。
改了引擎或脚本后,记得重新生成 dist/:
bashpython3 scripts/make_standalone.py -o dist/budget_sandbox.py python3 scripts/make_skill_card.py -o dist/skill-card.md
交付
- 本地:直接把 HTML 发给用户 / 双击打开(单文件,无外部依赖)。
- 发布到妙搭:
magic-builder page publish <html> --title "预算沙盘·<名称>"。 - 嵌入飞书文档:
magic-builder doc create/append成 HTML Box。
交付时必须一并说明三件事:模型是什么、哪些数字是工具假设的、审计结果。 只发个链接不说这三样,等于把 v1 的问题原样带回来。


