Functional Weave
Code in Python

subscriptions.ltv-cac@1.0.0

impl/typescript.ts

1,679 bytes · the TypeScript implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { type Money, assertSameCurrency, money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { type LtvCac } from "./subscriptions_ltv_cac_types.ts";

const MAX_EXACT = 9007199254740991;

function checkRate(name: string, value: number): void {
  if (!Number.isInteger(value) || value < 0 || value > 10000) {
    throw new RangeError(`${name} must be between 0 and 10000, received ${value}`);
  }
}

/**
 * LTV, LTV:CAC and CAC payback, each computed as one exact division from the
 * inputs so no figure inherits another's rounding.
 */
export function ltvCac(arpa: Money, grossMarginBasisPoints: number, monthlyChurnBasisPoints: number, cac: Money): LtvCac {
  assertSameCurrency(arpa, cac);
  if (arpa.minor < 0 || cac.minor < 0) {
    throw new RangeError(`ARPA and CAC must not be negative, received ${arpa.minor} and ${cac.minor}`);
  }
  checkRate("gross margin basis points", grossMarginBasisPoints);
  checkRate("monthly churn basis points", monthlyChurnBasisPoints);
  // Monthly margin per account, in minor units x 10000.
  const margin = arpa.minor * grossMarginBasisPoints;
  if (margin * 10000 > MAX_EXACT || cac.minor * 10000 > MAX_EXACT || monthlyChurnBasisPoints * cac.minor > MAX_EXACT) {
    throw new RangeError("amounts too large for exact arithmetic (2^53 - 1)");
  }
  const churn = monthlyChurnBasisPoints;
  return {
    ltv: churn === 0 ? null : money(roundDiv(margin, churn, "half-up"), arpa.currency),
    ltvToCacBasisPoints: churn === 0 || cac.minor === 0 ? null : roundDiv(margin * 10000, churn * cac.minor, "half-up"),
    paybackMonths: margin === 0 ? null : roundDiv(cac.minor * 10000, margin, "up"),
  };
}