以下情境为教学假设,人物与数字不代表经核实的真实案例。
卷一写了 13 章,从三种生意的收入差异一路写到尽调准备。这一节做收口:用附录 C 的清单给三个案例打分,看分数落在哪一档,再把最弱的组映射到卷二的具体章节。收口不是总结陈词,而是给下一卷留一张任务清单。
六组三十八项
附录 C 把评审分成六组,共 38 项,每项 0 到 2 分,满分 76 分。六组分别是收入质量、毛利与成本、单位经济、现金与流动性、集中度与依赖、报表与合规。
| 组 | 项数 | 满分 | 这一组在问什么 |
|---|---|---|---|
| 收入质量 | 8 | 16 | 收入是不是真的、稳的、能复算的 |
| 毛利与成本 | 6 | 12 | 成本分类是否一致,毛利率是否稳定 |
| 单位经济 | 5 | 10 | 一个客户赚不赚钱,多久回本 |
| 现金与流动性 | 7 | 14 | 未来十三周会不会断粮 |
| 集中度与依赖 | 6 | 12 | 收入集中在谁身上,人走了还剩多少 |
| 报表与合规 | 6 | 12 | 三张表能不能勾稽,主体与申报是否一致 |
评分规则只有一条:拿不出证据的项按 0 分。1 分留给「有证据但不完整」或「口径中途变过」,不留给「感觉还行」。
三案例的评分
把三案例代入附录 C 的标准,得到下表。分数是教学示例,用于演示评分方法,不是对案例的正式评价。
| 组 | 满分 | LedgerLite | Tidecast | Nord Studio |
|---|---|---|---|---|
| 收入质量 | 16 | 15 | 9 | 8 |
| 毛利与成本 | 12 | 10 | 12 | 8 |
| 单位经济 | 10 | 9 | 5 | 6 |
| 现金与流动性 | 14 | 12 | 10 | 8 |
| 集中度与依赖 | 12 | 10 | 6 | 4 |
| 报表与合规 | 12 | 11 | 9 | 7 |
| 合计 | 76 | 67 | 51 | 41 |
| 得分率 | 88.2% | 67.1% | 53.9% | |
| 评级 | 健康 | 需关注 | 高风险 |
LedgerLite 88.2%,落在健康档。它的失分不在结构,而在细节:退款与拒付没有独立科目,前五大客户占 31% 略高于内部警戒线。这两项买方会追问,但不会因此推翻交易。
Tidecast 67.1%,落在需关注档。它的毛利与成本拿满分,因为成本结构极简;失分集中在收入质量与集中度:没有经常性收入、Steam 占销量 80%、单位经济无法套用订阅口径。这三项都不是「做错了」,而是「生意形态决定的」。
Nord Studio 53.9%,落在高风险档。它的每一组都偏低,最弱的是集中度与依赖:服务收入占 72.7%、前三大客户 48%、交付依赖两位合伙人、没有书面退出条款。这组分数直接解释了它为什么只能按 3.0 倍 SDE 成交。
分数之间的差距不是经营水平的简单排序。Tidecast 的分数低于 LedgerLite,但它的 SDE 只比 LedgerLite 少 9,069 美元;Nord 的分数最低,但它的两位合伙人可能过得很舒服。清单衡量的是「账能不能被第三方复核」,不是「生意好不好」。
最弱的组指向卷二哪一章
卷一只解决「账算不算得清」,卷二解决「钱收不收得回、条款扛不扛得住」。把每组最弱的项映射到卷二,就得到一张进入下一卷的任务表。
| 最弱的组 | 具体缺口 | 卷二对应主题 |
|---|---|---|
| 收入质量 | 收入确认与合同条款不一致、退款科目缺失 | 合同结构、收入确认条款 |
| 毛利与成本 | 供应商成本无合同、价格调整无机制 | 供应商依赖、成本转嫁条款 |
| 单位经济 | 客户分层不清、续约与扩张无法归因 | 客户集中、续约与终止 |
| 现金与流动性 | 账期长、逾期无催收记录 | 账期设计、应收账龄、坏账 |
| 集中度与依赖 | 单一客户或平台占比过高、创始人不可替代 | 责任上限、业务连续性 |
| 报表与合规 | 数据责任不清、保险缺口 | 数据责任、保险、争议解决 |
三案例的映射结果不同。LedgerLite 的缺口主要在「报表与合规」的细节,卷二会帮它把 DPA、责任上限与 SLA 抵扣补上。Tidecast 的缺口在「集中度与依赖」,卷二会讨论平台协议、IP 授权与业务连续性。Nord 的缺口几乎覆盖全部六组,卷二对它是「补合同、补账期、补退出条款」三件事同时进行。
进入卷二前的三个动作
不管读者做的是哪门生意,进入卷二前先完成三个动作。
第一,把卷一的三张表补齐到能勾稽。 利润表、现金流量表、资产负债表三张都要有,且资产等于负债加权益。缺一张,卷二讨论合同与账期时就没有基数。
第二,把五处口径差异写进自己的对照表。 收入是标价还是净得、SDE 是否扣所有者薪酬、毛利率的分母是什么、现金流是经营现金流还是可支配现金、资产负债表是否完整。五条写清楚,跨期比较才不会反复出错。
第三,用附录 C 打一次分,记下最弱的两组。 不要试图一次修完 38 项,先修最弱的两组。分数低不可怕,可怕的是不知道自己低在哪。
三个动作做完,卷二的第一节会从合同结构讲起,读者能把合同条款直接放回自己的三张表里,看它影响哪一行。
这张基线怎么继续用
卷一的数字不会到此结束。卷二讨论账期时,会用 LedgerLite 的 Net 15、Tidecast 的平台月结 30 天、Nord 的 Net 30 去算应收与坏账;讨论责任上限时,会用 12 个月费用、合同金额 100% 去算最坏损失;讨论 SLA 抵扣时,会用 99.5% 与 10%、99.0% 与 5% 去算抵扣金额。
卷三讨论交易时,会用 3.2 倍 ARR、1.8 倍收入、4.5 倍 SDE 去算 headline 价格,再用 earn-out、托管与营运资金调整折算净得。这些条款在卷一都已经出现过,只是当时只做了一行注记。
读者自己的基线也一样。把三张表、五处分歧、六组分数存成一个文件,每季度更新一次。卷二的合同清单、卷三的交易清单,都会在这份基线之上继续加减。
读者自己的收口表
把三案例换成自己的生意,收口只需要一页纸。格式如下:
一、三张表(最近 12 个月)
收入:____ 毛利:____ 毛利率:____
SDE:____ SDE 率:____
净利润:____ 经营现金流:____ 转换率:____
资产:____ 负债:____ 权益:____
二、五处口径
1. 收入是标价、净得还是确认收入:
2. SDE 是否扣除所有者薪酬:
3. 毛利率的分母:
4. 现金流是经营现金流还是可支配现金:
5. 资产负债表是否完整:
三、六组评分(附录 C)
收入质量:__/16 毛利与成本:__/12
单位经济:__/10 现金与流动性:__/14
集中度与依赖:__/12 报表与合规:__/12
合计:__/76 评级:____
四、最弱的两组与对应卷二章节:
五、进入卷二前要完成的三个动作:
一页纸的价值在于可重复。每季度重填一次,把上一季度的表放在旁边对比,就能看出哪些比率在改善、哪些口径在漂移。不要每次换模板,模板一换,历史数据就不可比。
如果某一格填不出来,那一格就是缺口。不要用「大概」填,也不要从别的表里借一个相近的数字。填不出来本身就是结论:这项证据缺失,附录 C 会给它 0 分。
三个交接问题
从卷一进入卷二,有三个问题必须先回答。它们不属于财务计算,但决定了卷二哪些章节对你最重要。
第一,你的收入是「签了合同就稳」还是「每月都要重新赢」? 前者的问题在合同条款与责任上限,后者的问题在续约、流失与客户集中。LedgerLite 属于前者,Tidecast 属于后者,Nord 两条线各占一半。
第二,你的钱是「先收后干」还是「先干后收」? 年付订阅是先收后干,现金充裕但形成递延收入;咨询与平台结算常是先干后收,利润好看但应收占用现金。两种节奏对应卷二的账期、应收与坏账三章。
第三,如果明天你不再参与,收入还能保留多少? 这个比例可以粗略用「不依赖创始人的客户收入 ÷ 总收入」估计。LedgerLite 偏高,Tidecast 中等(IP 可授权但续作依赖个人),Nord 偏低。这个比例直接决定卷三的估值折价与交易结构。
三个问题答完,卷二的阅读顺序也就定了。收入稳、先收后干、依赖低的读者,可以从责任上限与业务连续性读起;收入要靠每月重新赢、先干后收、依赖高的读者,应先读账期、应收与客户集中。
小结
卷一的收口用三件事完成:三张表能勾稽、五处口径写清楚、六组分数打分并找到最弱的两组。LedgerLite 的 88.2% 说明「账可以被复核」,Tidecast 的 67.1% 说明「结构决定分数」,Nord 的 53.9% 说明「可转移性是最贵的短板」。
volume-review.ts 把评分与映射写成了函数:reviewVolume 按固定阈值给出健康、需关注、高风险三档,nextVolumeFocus 把最弱的组映射到卷二主题。它的一个明确拒绝是:空清单直接报错,而不是返回零分——没有数据不等于零分,而是一个需要补证的空缺。
阅读导航:上一节:13.2 单位经济与估值区间 · 下一节:目录与学习路径 。
常见错误与边界
- 把分数当估值。 88.2% 不代表能按 3.2 倍 ARR 成交。评分衡量可复核性,估值还看增长、市场与买方战略。
- 只修高分项。 高分项再修也加不了几分,低分项才是折价来源。
- 空清单给零分。 没有数据是缺口,不是零分。按 0 分记会让总分虚低,掩盖真正的问题。
- 把「不适用」当满分。 买断制没有 NRR,不等于收入质量满分。没有替代证据就给 0 或 1。
- 一次修完所有项。 38 项同时动,流程会乱。先修最弱的两组,稳定后再扩。
- 分数不动就以为没问题。 分数不动但结构在恶化(经常性收入占比下降、集中度上升),同样要处理。
边界:清单是教学工具,不是审计准则。正式交易中的财务尽调由买方或其会计师执行,标准通常更细。清单的价值是让经营者提前看到买方会问什么。
// file: src/value/volume-review.ts
// tier: L1
// verify: node --test test/value/volume-review.test.ts
import { type Bps, divide } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";
export type ReviewGroup = {
key: string;
label: string;
maxScore: number;
score: number;
};
export type ReviewResult = {
totalScore: number;
maxScore: number;
scoreBps: Bps;
grade: "healthy" | "watch" | "high-risk";
gaps: ReviewGroup[];
weakestGroup: ReviewGroup | null;
};
/**
* Grades are fixed thresholds, not relative ranks: 70% and 55% are the two
* lines used throughout volume one. A missing group is a data gap, so the
* function rejects the whole review instead of silently scoring it as zero.
*/
export function reviewVolume(groups: ReviewGroup[]): Result<ReviewResult> {
if (groups.length === 0) {
return fail("at least one review group is required");
}
let totalScore = 0;
let maxScore = 0;
for (const group of groups) {
if (!Number.isInteger(group.maxScore) || group.maxScore <= 0) {
return fail(`group ${group.key} must have a positive integer max score`);
}
if (
!Number.isInteger(group.score) ||
group.score < 0 ||
group.score > group.maxScore
) {
return fail(`group ${group.key} score must be between 0 and its max score`);
}
totalScore += group.score;
maxScore += group.maxScore;
}
const scoreBps = divide(totalScore, maxScore);
let grade: ReviewResult["grade"] = "healthy";
if (scoreBps < 5500) {
grade = "high-risk";
} else if (scoreBps < 7000) {
grade = "watch";
}
const gaps = groups
.filter((group) => group.score < group.maxScore)
.sort((a, b) => a.score / a.maxScore - b.score / b.maxScore);
return ok({
totalScore,
maxScore,
scoreBps,
grade,
gaps,
weakestGroup: gaps.length > 0 ? gaps[0] : null,
});
}
/**
* A gap is only actionable if it points at a chapter. This maps the six review
* groups to the volume-two themes that continue the work, so the closing
* chapter can hand the reader a concrete next step.
*/
export function nextVolumeFocus(groupKey: string): string | null {
const map: Record<string, string> = {
revenue: "合同与收入确认",
cost: "供应商与成本条款",
unit: "客户集中与续约",
cash: "账期、应收与催收",
concentration: "责任上限与业务连续性",
compliance: "数据责任与保险",
};
return map[groupKey] ?? null;
}
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。