以下情境为教学假设,人物与数字不代表经核实的真实案例。
单位经济要回答的问题很具体:多服务一个客户,这门生意是赚钱,还是先烧钱再指望以后赚回来?它不看总收入,也不看估值,只看「一个单位」的收入、成本、获客花费和回收速度。单位算不过来,规模越大亏得越快;单位算得过来,扩张才有意义。
这一节只拆四个数:ARPA、单客户毛利、CAC、回本月数。LTV 和 LTV/CAC 是两个组合比率,会在这里出现,但三种算法的差异留到下一节展开。主案例是 LedgerLite;Tidecast 和 Nord Studio 各用两段做横向对照,说明同一组指标换一种收入形态就会变形。
四个数看起来简单,真正的难点在口径:ARPA 的分母是付费客户,CAC 的分母是新签客户,两者不是同一批人;毛利的分母是收入,回本的分母是月毛利。口径一混,结论就会从「2.2 个月回本」变成「一年半回本」。
四个数各自的定义
先把定义写死。下面每个公式都只使用案例事实表里的原始数字,派生值保留整数美元或一位小数。
| 指标 | 公式 | LedgerLite 代入 | 结果 |
|---|---|---|---|
| ARPA | MRR ÷ 付费客户数 | $20,020 ÷ 260 | $77.00/月 |
| 单客户月毛利 | ARPA × 毛利率 | $77.00 × 84.2% | $64.83/月 |
| CAC | 获客支出 ÷ 新签客户数 | $16,800 ÷ 120 | $140.00 |
| 回本期 | CAC ÷ 单客户月毛利 | $140.00 ÷ $64.83 | 2.2 个月 |
| LTV | 单客户月毛利 ÷ 月流失率 | $64.83 ÷ 1.5% | $4,322 |
| LTV/CAC | LTV ÷ CAC | $4,322 ÷ $140 | 30.9 |
这张表里有两个容易看漏的点。
第一,ARPA 用的是期末付费客户 260 家,CAC 用的是全年新签 120 家。前者是存量,后者是增量。把 120 家当成 ARPA 的分母,会得到 166.83 美元的假 ARPA;把 260 家当成 CAC 的分母,会得到 64.62 美元的假 CAC。两个错误方向相反,却都可能让单位经济「看起来还行」。
第二,回本期的分母是月毛利,不是月收入。用收入算,LedgerLite 的回本期是 140 ÷ 77 = 1.8 个月,比真实值快 0.4 个月。毛利率越低,这个偏差越大;到 50% 毛利时,用收入算会把回本期低估一半。
LTV 和 LTV/CAC 不是新的原始数,而是前四个数的组合。LTV 把「月毛利」和「流失率」乘成一个客户生命周期内的毛利总额;LTV/CAC 再把它和获客成本放在一起。组合比率方便比较,但也最容易掩盖口径差异,所以下一节会专门拆 LTV 的三种算法。
LedgerLite:把四个数算出来
LedgerLite 的收入侧很干净:260 家付费客户,ARPA 77 美元/月,MRR 20,020 美元,ARR 240,240 美元。月流失率 1.5%,年流失约 16.6%;GRR 83%,NRR 102%。年付客户 30%,月付 70%,年付折扣 15%。这些数字说明 ARPA 是一个混合平均值,不是价目表上的标价。
成本侧,年度变动成本 38,000 美元,其中云 18,000、支付 12,000、支持 8,000;毛利 202,240 美元,毛利率 84.2%。获客侧,年营销费用 16,800 美元,年新签 120 家,CAC 140 美元。把这三段接起来:
单客户月毛利 = $77.00 × 84.2% = $64.83
回本期 = $140.00 ÷ $64.83 = 2.16 → 2.2 个月
LTV = $64.83 ÷ 1.5% = $4,322.00
LTV/CAC = $4,322.00 ÷ $140.00 = 30.87 → 30.9
2.2 个月的回本期意味着:一个客户在第三个月初就已经把获客成本赚回来了。按年看,单客户年毛利是 64.83 × 12 = 777.96 美元,是 CAC 的 5.56 倍。即使这个客户只活一年,获客投入也有充足余量。
LTV 4,322 美元对应的是 1 ÷ 1.5% = 66.7 个月的期望寿命。这个数字看起来很长,但它建立在「月流失率长期保持 1.5%」的假设上;如果流失率升到 3%,LTV 会立刻掉到 2,161 美元,LTV/CAC 从 30.9 掉到 15.4。单位经济不是一张静态成绩单,而是一组对流失率高度敏感的估计。
这里还有一个舍入链要说明。案例事实表把单客户月毛利写成 64.8 美元,再用 64.8 ÷ 1.5% 得到 4,320 美元;代码保留整数美分,先得到 6,483 美分,再除以 1.5%,得到 4,322 美元。两者相差 2 美元,来自中间步骤的舍入,不是公式冲突。LTV/CAC 在两种口径下都四舍五入为 30.9,因为 4,320 ÷ 140 = 30.86,4,322 ÷ 140 = 30.87。
如果把视角换成「新增客户」而不是「平均客户」,还能看到另一个细节。MRR 桥里当月新签 10 家、新增 MRR 780 美元,平均新单价是 78 美元,略高于 77 美元的存量 ARPA。这不影响本节四个数的计算,但提醒我们:ARPA 是存量与增量的混合值,CAC 是增量口径,两者匹配时要意识到这个时间差。
三个案例的横向对照
把同一组公式套到三种收入形态上,会得到一张「看起来能比、其实要小心比」的表。
| 项 | LedgerLite | Tidecast | Nord Studio |
|---|---|---|---|
| 收入形态 | 订阅 | 买断 | 服务 + 订阅 |
| 单位 | 客户·月 | 份 | 客户·月(产品线) |
| ARPA / 单位净收入 | $77.00/月 | $14.39/份 | $39.00/月 |
| 毛利率口径 | 84.2% | 72.0%(净得率) | 84.4%(混合) |
| 单位毛利 | $64.83/月 | $14.39/份 | $32.92/月(近似) |
| CAC / 单位获客成本 | $140.00 | $1.25/份 | 不可复算 |
| 回本期 | 2.2 个月 | 成交时 | 不可复算 |
| LTV | $4,322 | $14.39(单次) | $1,097(产品线) |
| LTV/CAC | 30.9 | 11.5 | 不可复算 |
Tidecast 是买断制,单位不是「客户·月」而是「份」。9,600 份销量、138,171 美元净得,每份净收入 14.39 美元;营销 12,000 美元对应每份 1.25 美元,LTV/CAC 是 11.5。这里的「LTV」退化成单次净收入,因为没有续费;回本在成交时完成,平台月结 30 天只影响现金时点,不影响单位经济。它的 72.0% 是净得率,已经扣掉退款 4%、Steam 分成 30% 和官网支付费 5%,但没有扣营销、服务器和工具;若把后三项也扣掉,每份 SDE 是 121,171 ÷ 9,600 = 12.62 美元。拿 72.0% 去和 SaaS 的 84.2% 比,会把「平台分成」误读成「毛利薄」。
Nord Studio 是服务加产品的混合体。产品线 90 家、39 美元/月,ARPA 39 美元;公司整体毛利率 84.4%,用它近似产品线,单客户月毛利是 39 × 84.4% = 32.92 美元;产品月流失 3.0%,LTV 是 32.92 ÷ 3.0% = 1,097 美元。但案例事实表没有单列营销费用,直接成本只有外包 18,000 和工具 6,000,获客主要靠 55% 的转介绍和合伙人时间,所以 CAC 不可复算,回本与 LTV/CAC 也无法判定。这不是数据缺失的小问题,而是混合业务做单位经济的典型障碍:服务线的客户获取成本藏在人力里,产品线的获客又可能搭服务的便车。
三个案例放在一起,真正可比的只有「口径是否一致」,而不是「数值谁高谁低」。LedgerLite 的 30.9 和 Tidecast 的 11.5 都建立在「获客支出可以单独归集」上;Nord Studio 连这个前提都不满足。下一节会把 LTV 的三种算法拆开,说明为什么同一个 LedgerLite 可以算出 4,322、4,688 甚至 4,092 三个不同的 LTV。
四个数的边界
四个数能成立,依赖四个前提。任何一个不成立,数字就要打折扣。
ARPA 是平均值,不是价格。 LedgerLite 有 30% 年付客户享受 15% 折扣,70% 月付;ARPA 77 美元是两类客户的混合。如果年付比例从 30% 升到 50%,在标价不变的情况下 ARPA 会下降,因为年付折扣被更多客户使用。用 ARPA 做预测时,要同时跟踪客户结构。
毛利率的口径必须跟着业务走。 LedgerLite 的变动成本是云、支付、支持;Tidecast 的退款、平台分成、支付费是收入的直接扣减;Nord Studio 的直接成本是外包和工具。三者都叫「毛利」,但成本边界不同。跨案例比较毛利率,先问一句「哪些成本被算进了变动成本」。
CAC 只算现金获客支出,会低估一人公司的真实获客成本。 LedgerLite 的 140 美元只含 16,800 美元营销费用,不含创始人谈客户、写内容、做演示的时间。Tidecast 的 1.25 美元只含 12,000 美元营销,不含 14 个月开发换来的品牌与愿望单积累。时间成本不进 CAC,不代表它不存在;它只是没有出现在这张表里。
回本期是「毛利回收」口径,不是「现金回收」口径。 回本期回答「多久赚回获客成本」,不回答「账上什么时候有钱」。LedgerLite 平均账期 Net 15,Tidecast 平台月结 30 天,Nord Studio 账期 Net 30;这些时点差异会影响现金流,但不改变 2.2 个月这个单位经济结论。现金流口径要等第 3 章和第 8 章。
把这四个前提写进模型,比记住四个数字更重要。获客如何归因、渠道成本如何分摊,见 获客归因与复盘 ;收入结构如何组合,见 收入模型组合 。本节只负责把单位算清楚。
把四个数写成代码
下面的 economics.ts 把四个数和两个比率固化成函数。金额统一用整数美分,比率统一用基点(1% = 100 bps),每个函数返回 Result,输入不合法时给出明确错误,而不是返回 NaN。buildUnitEconomics 一次跑完整条链,适合直接喂给案例数据。
// file: src/unit/economics.ts
// tier: L1
// verify: node --test test/unit/economics.test.ts
import { type Cents } from "../core/money.ts";
import { type Bps } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";
export type UnitEconomicsInput = {
mrrCents: Cents;
customers: number;
grossMarginBps: Bps;
marketingCents: Cents;
newCustomers: number;
monthlyChurnBps: Bps;
};
export type UnitEconomics = {
arpaCents: Cents;
grossProfitPerCustomerCents: Cents;
cacCents: Cents;
paybackMonths: number;
ltvCents: Cents;
ltvCacRatioBps: Bps;
};
function isCents(value: number): boolean {
return Number.isInteger(value) && value >= 0;
}
function isBps(value: number): boolean {
return Number.isInteger(value) && value >= 0 && value <= 10000;
}
/** Monthly average revenue per account, in cents. */
export function arpa(mrrCents: Cents, customers: number): Result<Cents> {
if (!isCents(mrrCents)) {
return fail("MRR must be a non-negative integer number of cents");
}
if (!Number.isInteger(customers) || customers <= 0) {
return fail("customers must be a positive integer");
}
return ok(Math.round(mrrCents / customers));
}
/** Gross profit contributed by one customer in one month, in cents. */
export function grossProfitPerCustomer(
arpaCents: Cents,
grossMarginBps: Bps,
): Result<Cents> {
if (!isCents(arpaCents)) {
return fail("ARPA must be a non-negative integer number of cents");
}
if (!isBps(grossMarginBps)) {
return fail("gross margin must be an integer between 0 and 10000 bps");
}
return ok(Math.round((arpaCents * grossMarginBps) / 10000));
}
/** Customer acquisition cost: acquisition spend divided by new customers. */
export function cac(
marketingCents: Cents,
newCustomers: number,
): Result<Cents> {
if (!isCents(marketingCents)) {
return fail("marketing spend must be a non-negative integer number of cents");
}
if (!Number.isInteger(newCustomers) || newCustomers <= 0) {
return fail("new customers must be a positive integer");
}
return ok(Math.round(marketingCents / newCustomers));
}
/**
* Months of gross profit needed to recover CAC, rounded to one decimal.
* Returns 0 when CAC is zero.
*/
export function paybackMonths(
cacCents: Cents,
monthlyGrossProfitCents: Cents,
): Result<number> {
if (!isCents(cacCents)) {
return fail("CAC must be a non-negative integer number of cents");
}
if (!isCents(monthlyGrossProfitCents)) {
return fail("monthly gross profit must be a non-negative integer number of cents");
}
if (cacCents === 0) {
return ok(0);
}
if (monthlyGrossProfitCents === 0) {
return fail("monthly gross profit must be positive when CAC is positive");
}
return ok(Math.round((cacCents / monthlyGrossProfitCents) * 10) / 10);
}
/**
* Gross-profit LTV under a constant monthly churn rate.
* The formula is monthly gross profit divided by monthly churn.
*/
export function ltvFromGrossProfit(
monthlyGrossProfitCents: Cents,
monthlyChurnBps: Bps,
): Result<Cents> {
if (!isCents(monthlyGrossProfitCents)) {
return fail("monthly gross profit must be a non-negative integer number of cents");
}
if (!Number.isInteger(monthlyChurnBps) || monthlyChurnBps <= 0 || monthlyChurnBps > 10000) {
return fail("monthly churn must be an integer between 1 and 10000 bps");
}
return ok(
Math.round((monthlyGrossProfitCents * 10000) / monthlyChurnBps),
);
}
/** LTV divided by CAC, expressed in basis points, so 3.0x is 30000 bps. */
export function ltvCacRatio(ltvCents: Cents, cacCents: Cents): Result<Bps> {
if (!isCents(ltvCents)) {
return fail("LTV must be a non-negative integer number of cents");
}
if (!isCents(cacCents) || cacCents === 0) {
return fail("CAC must be a positive integer number of cents");
}
return ok(Math.round((ltvCents * 10000) / cacCents));
}
/** Compute the four numbers and the two headline ratios in one pass. */
export function buildUnitEconomics(
input: UnitEconomicsInput,
): Result<UnitEconomics> {
const arpaResult = arpa(input.mrrCents, input.customers);
if (!arpaResult.ok) {
return arpaResult;
}
const grossProfitResult = grossProfitPerCustomer(
arpaResult.value,
input.grossMarginBps,
);
if (!grossProfitResult.ok) {
return grossProfitResult;
}
const cacResult = cac(input.marketingCents, input.newCustomers);
if (!cacResult.ok) {
return cacResult;
}
const paybackResult = paybackMonths(cacResult.value, grossProfitResult.value);
if (!paybackResult.ok) {
return paybackResult;
}
const ltvResult = ltvFromGrossProfit(
grossProfitResult.value,
input.monthlyChurnBps,
);
if (!ltvResult.ok) {
return ltvResult;
}
const ratioResult = ltvCacRatio(ltvResult.value, cacResult.value);
if (!ratioResult.ok) {
return ratioResult;
}
return ok({
arpaCents: arpaResult.value,
grossProfitPerCustomerCents: grossProfitResult.value,
cacCents: cacResult.value,
paybackMonths: paybackResult.value,
ltvCents: ltvResult.value,
ltvCacRatioBps: ratioResult.value,
});
}
代码里有三个设计选择值得说明。
第一,金额用整数美分,除法只在最后一步舍入。grossProfitPerCustomer 先把 7,700 × 8,420 ÷ 10,000 舍入成 6,483 美分,再由它算回本和 LTV;这样每个中间值都能和案例事实表对账,不会出现「不同函数给出不同小数」的情况。
第二,paybackMonths 返回一位小数,ltvCacRatio 返回基点。前者适合写「2.2 个月」,后者适合做阈值判断(2.3 节会用到 3.0x = 30,000 bps)。单位不同是刻意的:回本期是时间,比率是倍数,混在一起容易读错。
第三,buildUnitEconomics 在零流失率时返回失败,而不是返回 Infinity。零流失意味着 LTV 没有有限值,继续算下去只会得到一串无意义的数字;显式失败比隐式无穷更适合放进报表。输入校验也让「CAC 为 0」「客户数为 0」这类边界在测试里可复现。
小结
- 单位经济的四个原始数是 ARPA、单客户月毛利、CAC、回本期;LTV 和 LTV/CAC 是组合比率。
- LedgerLite:ARPA 77 美元/月,毛利率 84.2%,单客户月毛利 64.83 美元,CAC 140 美元,回本 2.2 个月,LTV 4,322 美元,LTV/CAC 30.9。
- ARPA 的分母是 260 家付费客户,CAC 的分母是 120 家年新签;混用分母会得到假数。
- 回本期要用月毛利做分母;用月收入会把 2.2 个月算成 1.8 个月。
- Tidecast 的每份净收入是 14.39 美元,每份获客成本 1.25 美元,LTV/CAC 11.5;买断制没有续费,LTV 退化成单次净收入。
- Nord Studio 的产品线 ARPA 39 美元、LTV 约 1,097 美元,但案例没有单列营销费用,CAC 与回本不可复算。
- 四个数依赖四个前提:ARPA 是混合值、毛利率口径随业务变、CAC 不含创始人时间、回本期不等于现金回收期。
- LTV 对流失率高度敏感:LedgerLite 的月流失从 1.5% 升到 3.0%,LTV 从 4,322 掉到 2,161。
阅读导航:上一节:1.3 生命周期转型:从服务到产品、从买断到订阅 · 下一节:2.2 LTV 的三种算法与适用边界 。
常见错误与边界
- 用同一批客户做 ARPA 和 CAC 的分母。 ARPA 用期末 260 家,CAC 用年新签 120 家;混用会同时算错两个数。
- 用收入代替毛利算回本期。 140 ÷ 77 = 1.8 个月是收入口径;140 ÷ 64.83 = 2.2 个月才是毛利口径。
- 把 ARPA 当成价目表价格。 77 美元混合了 70% 月付和 30% 年付(年付折扣 15%),客户结构变化会改变 ARPA。
- 跨案例直接比毛利率。 LedgerLite 的 84.2%、Tidecast 的 72.0%、Nord Studio 的 84.4% 成本边界不同,不能放在一条横线上排名。
- 忘记 CAC 不含创始人时间。 一人公司的营销费用只是获客成本的一部分;用 140 美元做决策时,要意识到真实投入更高。
- 把回本期当成现金回收期。 回本期是毛利口径;Net 15、平台月结 30 天、Net 30 都会让现金晚到。
- 用单月数据外推全年。 MRR 桥只描述当月;CAC 的 120 家是全年新签,两个窗口不同。
- 把 LTV 当成承诺值。 LTV 4,322 美元依赖 1.5% 的月流失率;流失率翻倍,LTV 减半。
- 在买断制里硬套订阅公式。 Tidecast 没有月流失,LTV 只能退化成单次净收入 14.39 美元;硬套「毛利 ÷ 流失率」会除以零或编造流失率。
- 在混合业务里假设 CAC 可单独归集。 Nord Studio 的获客靠 55% 转介绍和合伙人时间,营销费用没有单列;此时「不可复算」比拍一个数更诚实。
四个数是单位经济的地基,但不是全部。下一节会把 LTV 拆成三种算法,说明同一组原始数为什么会算出不同的生命周期价值;再下一节会把四个数变成五个失效信号,用来判断一组单位经济到底该继续、观察还是停下。第 3 章会把它们接进利润表,看单位经济如何变成年度利润。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。