Functional Weave
Code in Python

subscriptions.ltv-cac@1.0.0

impl/python.py

1,771 bytes · the Python 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.

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"),
    )