每一份现金流量表都依赖于一个在每个账户上做出的默默决定:这个账户是你所解释的现金的一部分,还是属于经营、投资或融资活动?做对了这个决定,报表就能告诉你钱实际去了哪里。做错了,报表就会自信地误导你。
到目前为止,Beancount.io 的现金流量表是通过启发式规则来替你做这个决定的 —— 基于账户的根类型,再加上一个根据名称猜测哪些资产属于现金的方法。对于大多数账本来说,这个猜测大多数时候是对的,但其余时候则是默默出错。一个命名不当的经纪账户会被归入错误的分类。一个在实际意义上就是现金的货币市场基金,却因为名称没有说明而被放在投资活动中。
从今天开始,你可以明确地进行分类。只需在账户的 open 指令上添加一行标准的 Beancount 元数据:
2000-01-01 open Assets:US:Brokerage
cash-flow-role: "investing"
2000-01-01 open Assets:US:Marcus:Savings
cash-flow-role: "cash"一个键,四个值 —— cash、operating、investing、financing —— 这就是全部配置。没有设置页面,没有 JSON 文件,没有按报告区分的选项对话框。下一次现金流量表加载时,经纪账户的流量会出现在投资活动下,储蓄账户会加入现金池(报表解释其变动),而其他所有账户则像以前一样正常工作。

每个角色的含义
现金流量表将每个账户归入两类工作之一,而 cash-flow-role 用一个词就回答了两个问题:
"cash"—— 该账户属于现金及等价物池。现金账户永远不会作为报表行项目出现;报表解释的是其合并余额的 变动,并且两个现金账户之间的转账会相互抵消,就像你将钱从支票账户转到储蓄账户一样。"operating"/"investing"/"financing"—— 该账户不属于现金,其期间变动会作为对应活动部分下的一行出现。
默认情况下,报表会将名称中包含 Cash、Checking、Savings 或 Bank 的资产账户视为现金等价物。声明角色可以在两个方向上覆盖此默认行为。cash-flow-role: "cash" 可以将名称规则遗漏的账户(如货币市场基金、稳定币钱包)纳入现金。而 cash-flow-role: "investing" 放在 Assets:US:Bank:CD 上则一举两得:它将定期存款从现金中 排除,并 同时 将其归入投资活动 —— 即使其名称本会匹配现金规则。
我们刻意使用了一个键而不是两个。"这个账户是现金吗?" 和 "它属于哪个活动部分?" 从来都不是独立的问题 —— 一个账户要么属于现金池,要么恰好属于一个活动部分。使用两个键本会允许无意义的组合状态,比如一个具有经营活动角色的现金账户。一个键使得非法状态无法表示,整个规范也因此可以浓缩成一条推文。

你声明的分类就是每个视图使用的分类。"按活动" 图表按月份堆叠显示经营、投资和融资流量;移动一个账户的角色,它的流量就会在图表中移动到正确的层级,在报表中移动到正确的部分,在导出中移动到正确的行 —— 全部同步完成。
声明优于猜测 —— 报表会展示其依据
分类现在按照严格的顺序解析:你的元数据优先,内置的启发式规则其次。而且报表会诚实地说明它做了什么。报表末尾会有一个 "Cash & cash equivalents in this report"(本报表中的现金及等价物)面板,精确列出它在构建报表时视为现金的账户,然后通过核对进行调节:期初现金、期末现金,以及活动部分解释的净变动。

报表还会区分你声明的分类和它推断出的分类,这种区别会带入 CSV 和 Markdown 导出:一份完全由声明角色构建的报表,不再带有 "分类为推断所得" 的披露说明 —— 因为那时分类已不是推断,而是你账本的一部分。
打字错误会被妥善处理 —— 就像纯文本工具应有的样子
cash-flow-role: "invsting" 这样的拼写错误不会被静默接受,也不会被静默忽略:账户会回退到默认的启发式规则,同时状态面板会标记它 —— "未知的 cash-flow-role 值,正在使用默认值" —— 这样错误就在你能看到的地方显现,而不是埋在日志里。
如果你不改变任何东西,一切都不会改变
未标注的账户使用与之前相同的已发布默认值:收入和支出映射到经营活动,非现金资产映射到投资活动,负债和权益映射到融资活动,名称匹配的现金等价物构成现金池。现有账本在此版本发布前后渲染效果完全相同。大多数用户只会标注少量账户 —— 那些默认规则判断错误的 —— 其余的则无需操心。
可移植性也完全不受影响。open 指令上的元数据是 Beancount 的核心语法,每个 v2/v3 工具都能解析它,而任何不查找此特定键的工具只会忽略它。bean-check 依然通过,Fava 也无动于衷,你的账本在 Beancount.io 之外依然完全可用。
每个视图对你现金的看法都一致
一个共享的解析器为每个账户生成最终角色,每个消费者都从中读取:
- 现金流量表 从解析出的角色构建其活动部分及其底线 —— 现金及等价物的净变动。
- CSV、Markdown 和打印导出 携带相同的数字,并且仅在确实使用了启发式规则时才包含 "分类为推断所得" 的披露。
- 概览桑基图 读取相同的声明,因此你标记为现金的账户不会作为流量节点出现,你声明的活动角色对于非收入类账户也会得到尊重。
- 账户状态面板 不再将已声明角色的账户列为 "未分类资产"。

报表和图表之间不可能对你的现金产生分歧,因为答案只有一个来源。
为什么选择账本,而不是设置页面
这是我们对此次发布感触最深的部分。我们考虑过设置界面,但出于三个原因拒绝了:
- 账本是唯一事实来源。 存在于其他任何地方的分类都可能与它所描述的账本产生分歧。而存在于
open指令上的分类会随账户一起传播 —— 经历重命名、仓库迁移,以及每一个读取该文件的客户端。 - 纯文本是可区分、可搜索、可审查的。 如果你将账本保存在 git 中 —— 而如果你使用 Beancount.io,你确实如此 —— 分类规则现在可以
grep、diff和blame,并且审查者可以在拉取请求中看到它。设置页面不具备这些特性中的任何一项。 - 它离线且在任何地方都能工作。 任何编辑器都可以声明角色。任何未来读取账本的客户端都能免费获得分类,无需同步任何按客户端设置的选项。
出于同样的原因,分类刻意不按日期生效。元数据描述的是账户的性质,它很少改变;当它改变时,你编辑 open 指令,你的 git 历史会记录发生了什么以及何时发生的。那段历史 就是 审计追踪。
在你的账本上试试
该功能现已向所有账本推出。声明角色只需要一个文本编辑器:
- 打开包含该账户
open指令的文件。 - 在其下方添加缩进的元数据行
cash-flow-role: "cash"(或"operating"、"investing"、"financing")。 - 重新加载现金流量表。你的声明会立即胜过启发式规则。
从默认猜测出错的账户开始 —— 并非真正现金的定期存款、实际是现金的货币市场基金、以及你希望干净地归入投资活动下的经纪账户。其余的保持不变。完整规范 —— 接受值、优先级、默认分类以及无效值的处理方式 —— 位于 现金流角色参考。
现金流量表是每个 Beancount.io 账本的一部分,与损益表和资产负债表并列。如果你想了解今年夏天还发布了什么 —— 更智能的导入、支持操作的人工智能助手,以及重建的移动应用 —— 3.6 版本发布说明 涵盖了其余内容。
你的账本早已知道哪些账户是投资、哪些是日常支出。现在,现金流量表也知道了。
