Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from .math_round_div import round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, assert_same_currency, money ← from money.amount ^1.0.0 · built alongside by fune
from .subscriptions_ltv_cac_types import LtvCac
MAX_EXACT = 9007199254740991
def _check_rate(name: str, value: int) -> None:
if isinstance(value, bool) or not isinstance(value, int) or value < 0 or value > 10000:
raise ValueError("%s must be between 0 and 10000, received %r" % (name, value))
def ltv_cac(arpa: Money, gross_margin_basis_points: int, monthly_churn_basis_points: int, cac: Money) -> LtvCac:
"""LTV, LTV:CAC and CAC payback, each computed as one exact division from
the inputs so no figure inherits another's rounding.
"""
assert_same_currency(arpa, cac)
if arpa.minor < 0 or cac.minor < 0:
raise ValueError("ARPA and CAC must not be negative, received %d and %d" % (arpa.minor, cac.minor))
_check_rate("gross margin basis points", gross_margin_basis_points)
_check_rate("monthly churn basis points", monthly_churn_basis_points)
# Monthly margin per account, in minor units x 10000.
margin = arpa.minor * gross_margin_basis_points
churn = monthly_churn_basis_points
if margin * 10000 > MAX_EXACT or cac.minor * 10000 > MAX_EXACT or churn * cac.minor > MAX_EXACT:
# Python would carry on exactly; TypeScript could not, and the three must agree.
raise ValueError("amounts too large for exact arithmetic (2^53 - 1)")
return LtvCac(
ltv=None if churn == 0 else money(round_div(margin, churn, "half-up"), arpa.currency),
ltv_to_cac_basis_points=None
if churn == 0 or cac.minor == 0
else round_div(margin * 10000, churn * cac.minor, "half-up"),
payback_months=None if margin == 0 else round_div(cac.minor * 10000, margin, "up"),
)