Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import Optional
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 ← from money.amount ^1.0.0 · built alongside by fune
from .subscriptions_churn_types import ChurnMetrics
def _rate(numerator: int, denominator: int) -> Optional[int]:
return None if denominator == 0 else round_div(numerator * 10000, denominator, "half-up")
def _is_count(value: object) -> bool:
return not isinstance(value, bool) and isinstance(value, int) and value >= 0
def churn_metrics(
customers_at_start: int,
customers_lost: int,
mrr_at_start: Money,
expansion_mrr: Money,
contraction_mrr: Money,
churned_mrr: Money,
) -> ChurnMetrics:
"""Logo churn, gross and net revenue churn, and gross and net revenue
retention for one period, each as basis points of the starting cohort.
"""
if not _is_count(customers_at_start) or not _is_count(customers_lost):
raise ValueError(
"customer counts must be whole numbers of 0 or more, received %r and %r"
% (customers_at_start, customers_lost)
)
if customers_lost > customers_at_start:
raise ValueError(
"customers lost must not exceed customers at start, received %d of %d"
% (customers_lost, customers_at_start)
)
for amount in (expansion_mrr, contraction_mrr, churned_mrr):
assert_same_currency(mrr_at_start, amount)
start = mrr_at_start.minor
expansion = expansion_mrr.minor
lost = contraction_mrr.minor + churned_mrr.minor
if start < 0 or expansion < 0 or contraction_mrr.minor < 0 or churned_mrr.minor < 0:
raise ValueError("MRR amounts must not be negative")
if lost > start:
raise ValueError(
"contraction and churned MRR must not exceed MRR at start, received %d of %d" % (lost, start)
)
return ChurnMetrics(
logo_churn_basis_points=_rate(customers_lost, customers_at_start),
gross_revenue_churn_basis_points=_rate(lost, start),
net_revenue_churn_basis_points=_rate(lost - expansion, start),
gross_revenue_retention_basis_points=_rate(start - lost, start),
net_revenue_retention_basis_points=_rate(start + expansion - lost, start),
)