Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_amount::{assert_same_currency, money_from_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
fn rate(numerator: i64, denominator: i64) -> Option<i64> {
if denominator == 0 {
None
} else {
Some(round_div(numerator * 10000, denominator, "half-up"))
}
}
/// Logo churn, gross and net revenue churn, and gross and net revenue
/// retention for one period, each as basis points of the starting cohort.
///
/// # Panics
/// Panics on negative counts or amounts, more lost than there was, or mixed
/// currencies.
pub fn churn_metrics(
customers_at_start: i64,
customers_lost: i64,
mrr_at_start: &Money,
expansion_mrr: &Money,
contraction_mrr: &Money,
churned_mrr: &Money,
) -> ChurnMetrics {
if customers_at_start < 0 || customers_lost < 0 {
panic!(
"customer counts must be whole numbers of 0 or more, received {} and {}",
customers_at_start, customers_lost
);
}
if customers_lost > customers_at_start {
panic!(
"customers lost must not exceed customers at start, received {} of {}",
customers_lost, customers_at_start
);
}
for amount in [expansion_mrr, contraction_mrr, churned_mrr] {
assert_same_currency(mrr_at_start, amount);
}
let start = mrr_at_start.minor;
let expansion = expansion_mrr.minor;
let lost = contraction_mrr.minor + churned_mrr.minor;
if start < 0 || expansion < 0 || contraction_mrr.minor < 0 || churned_mrr.minor < 0 {
panic!("MRR amounts must not be negative");
}
if lost > start {
panic!(
"contraction and churned MRR must not exceed MRR at start, received {} of {}",
lost, start
);
}
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),
}
}
fn opt(v: Option<i64>) -> Value {
match v {
Some(n) => Value::Int(n),
None => Value::Null,
}
}
pub fn churn_metrics_to_value(m: &ChurnMetrics) -> Value {
Value::obj(vec![
("logoChurnBasisPoints", opt(m.logo_churn_basis_points)),
("grossRevenueChurnBasisPoints", opt(m.gross_revenue_churn_basis_points)),
("netRevenueChurnBasisPoints", opt(m.net_revenue_churn_basis_points)),
("grossRevenueRetentionBasisPoints", opt(m.gross_revenue_retention_basis_points)),
("netRevenueRetentionBasisPoints", opt(m.net_revenue_retention_basis_points)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
churn_metrics_to_value(&churn_metrics(
args[0].as_i64(),
args[1].as_i64(),
&money_from_value(&args[2]),
&money_from_value(&args[3]),
&money_from_value(&args[4]),
&money_from_value(&args[5]),
))
}