DocsFinance metrics

Finance metrics

Reference for the 15 metrics surfaced across the Finance domain: definitions, formulas, and source tables.

Getting started

Four ideas to anchor before diving in

The four pillars that structure every card in the dashboard.

Revenue
MRR / ARR

Recurring monthly revenue normalized per month, projected to a year for ARR.

Retention
Client & MRR churn

Client churn counts leaving accounts; MRR churn counts lost euros. Keep them distinct.

Unit economics
LTV / CAC

LTV observed per cohort vs acquisition cost. > 3× healthy, < 1× alarming.

Balance
Rule of 40

Growth + margin. A classic SaaS indicator of balance between growth and profitability.

Conventions: recurring MRR excludes one-shot founder deals. Metrics live in lib/analytics/finance/metrics.ts. Definitions stay in finance.glossary so they can be reused in tooltips.

Deep dives

Explore each metric catalog, design choices and data flow in detail.

Catalog

Metric catalog (15)

Every finance metric surfaced in the UI, with formula and data source.

MetricFormulaSource
MRR

Monthly recurring revenue: sum of active subscriptions normalized to a month. Excludes pure trials and lifetime trials.

Σ (effective amount / 100) per active subscription
SQL: compute_mrr_daily
mrr_daily.mrr
ARR

Annualized projection of MRR: straight 12-month extrapolation. Does not account for seasonality.

MRR × 12
metrics.ts
computeArr(mrr)
ARPA

Average revenue per active account. Snapshot of per-customer value.

MRR / active clients
metrics.ts
computeArpa(mrr, activeClients)
30d / 90d client churn

Rolling client loss rate over 30 or 90 days. Recurring-only base when `useRecurring` is set.

clients churned (30d) / avg active clients (30d) × 100
metrics.ts
computeChurnRate(rows, { useRecurring })
Global churn

Client loss rate across all available history. Weighted average of monthly churns (each month weighted by its active base). Long-term view.

Σ monthly churned / Σ monthly active × 100
metrics.ts
computeChurnGlobalWeighted(months)
Trial → paid conversion

Share of ended trials that became paid subscriptions within 30 days.

converted trials / ended trials × 100
metrics.ts
computeConversionRate(funnel)
NRR

On the cohort of clients active at month start (mrr_start > 0): retained MRR + expansion − contraction − churn. New clients and reactivations are excluded from both sides. Above 100% = the existing base grows on its own.

(starting + expansion_on_existing − contraction − churn) / starting × 100
metrics.ts + SQL: gocroco_nrr_grr_monthly
computeNrr({startingMrr, expansionMrr, contractionMrr, churnMrr})
GRR

Like NRR but without expansion. Measures pure retention of the starting base (capped at 100%).

(starting − contraction − churn) / starting × 100
metrics.ts + SQL: gocroco_nrr_grr_monthly
computeGrr({startingMrr, expansionMrr, contractionMrr, churnMrr})
Observed LTV

Realized LTV per cohort: cumulative observed MRR up to the latest tenure, divided by cohort size. No projection — only cash collected.

cumulative observed MRR / cohort size
SQL: gocroco_ltv_by_cohort
computeMedianLtvObserved(rows)
Estimated LTV

LTV projection using the classic SaaS formula. Less reliable than observed LTV: explodes as churn approaches 0. Hidden when monthly churn < 0.5%.

ARPA / monthly churn
metrics.ts
computeLtvEstimated(arpa, monthlyChurnPct)
110 of 15 metrics
Unit economics

Unit economics metrics

Indicators that put revenue next to cost and time-to-recoup.

IndicatorFormulaTargetSource
Rule of 40

Classic SaaS balance indicator between growth and profitability.

NRR% + MRR growth%≥ 40%computeRuleOfForty(nrrPct, mrrGrowthPct)
Magic Number

Efficiency of marketing spend: new MRR generated per euro of marketing.

ΔMRR(quarter) × 4 / marketing spend(quarter)≥ 0.75computeMagicNumber(netNewMrrQuarter, marketingSpendQuarter)
Burn Multiple

Net burn vs new net MRR. Lower is better — shows capital efficiency.

net burn / net new MRR< 2SQL: gocroco_unit_economics_monthly
Losses & forecast

Losses and forecast

Loss breakdown and MRR projection — their mechanics.

Losses: mrr_daily

Daily table feeds the Losses tab. Churn timing charts use churned_mrr grouped by the day the subscription ended. Top movers come from gocroco_finance_top_movers.

Forecast: gocroco_mrr_forecast

12-month projection built on the average of the last 6 months for new / churn / expansion MRR. Confidence band is a heuristic (±15% × m/horizon), not a statistical interval.

Design choices

Design choices that matter

Why some metrics are computed the way they are.

Conversion uses ended trials as the denominator

Only trials that actually ended (paid or expired) are counted in the conversion ratio. Ongoing trials would artificially lower it.

Trade-off: Month-over-month numbers can be noisy when trial volume fluctuates.

Global churn is weighted by monthly base

Monthly churn rates are averaged, each month weighted by its active client base. Simple averaging would overweight small early months.

Trade-off: Newer months have less influence when the business grew fast.

Prefer observed LTV over estimated

Estimated LTV (ARPA / churn) explodes as churn → 0. We show observed LTV from realized cohorts whenever available.

Trade-off: Observed LTV needs mature cohorts; early-stage cohorts show little data.

Effective amount, not list price

MRR uses the effective amount billed per subscription, not the list price. Discounts, credits, and one-shot offsets are respected.

Trade-off: Requires regular Stripe sync to stay fresh.

Recurring MRR excludes one-shots

Founders paid in 12×, lifetime deals paid in tranches, etc. are counted in total MRR but excluded from recurring. Goals align on recurring.

Trade-off: Total MRR and recurring MRR can diverge — pay attention to which one you read.

Churn uses average active clients as denominator

Not end-of-period count. Smooths out month-boundary churn spikes.

Trade-off: Slightly underestimates churn during rapid growth.

Cohort = first paid month

A location joins a cohort the first month it becomes a paying subscriber. Not the trial start, not the signup.

Trade-off: Locations that pause and resume skew to a single cohort.

cancel_at_period_end still counts as active

A subscription scheduled to cancel at the end of the period is still active today — kept in MRR until the actual end date.

Trade-off: Inflates MRR briefly before the scheduled end date.

Sandboxes are filtered by default

Accounts with MRR ≤ €2 are treated as sandboxes and excluded from most metrics. Toggleable per dashboard.

Trade-off: A real micro-plan would be excluded by accident.

Data flow

From Stripe to the UI

The journey of a finance metric, end to end.

  1. 1

    Stripe → stripe_subscriptions

    stripeSync.ts keeps stripe_subscriptions up to date with full history (one row per subscription event). stripe_subscription_current view exposes the latest row per location.

  2. 2

    stripe_subscriptions → location_mrr_daily

    compute_mrr_daily snapshots recurring and total MRR per location per day, applying overrides from subscription_overrides.

  3. 3

    location_mrr_daily → mrr_daily

    Aggregate per-day MRR, new_mrr, expansion, contraction, churned_mrr across all active clients. Source of Pilotage and Losses charts.

  4. 4

    Daily → monthly cohort aggregates

    location_cohort_monthly tracks per-cohort starting / expansion / churn / contraction MRR every month. Feeds NRR, GRR, LTV.

  5. 5

    Aggregates → UI

    Pages under app/finance/* call the matching RPC (gocroco_finance_dashboard, gocroco_nrr_grr_monthly, gocroco_cac_monthly, etc.) and render the results with Recharts + MetricCard.

Sync

Keeping the data fresh

Background jobs that feed the finance domain.

The full finance stack relies on compute_mrr_daily having run for the target day. It runs nightly (mrr_daily_compute_0200) and fills any missing day automatically.

On top of that, Stripe webhooks stream events into stripe_subscriptions in near real-time. A reconciliation job runs hourly to catch any missed event.

If a day looks off, SELECT public.compute_mrr_daily('YYYY-MM-DD') re-snapshots it. audit:reliability includes a reconcile-MRR check.

FAQ

Frequent questions

Short answers to the questions we get most often.

Why does MRR differ from my Stripe MRR report?

Stripe MRR includes one-shots; we split recurring vs one-shot. Also, we respect subscription_overrides (founder deals, excluded amounts) which Stripe doesn't know about.

Why is NRR sometimes higher than 100% while churn is non-zero?

Expansion on existing accounts can more than compensate for churn on the same cohort. That's the whole point of NRR.

Why is estimated LTV hidden for some cohorts?

The formula ARPA / churn explodes when monthly churn < 0.5%. We hide the value in that range to avoid misleading numbers.

Why is my CAC missing for the current month?

CAC requires manually entered marketing + sales spend. Enter the spend on /finance/acquisition to populate it.

Why is there a gap in the retention heatmap?

A cohort's retention appears only after the corresponding month has elapsed. Empty cells are future months.

Why do client churn and MRR churn differ?

Client churn counts accounts; MRR churn counts euros. One big leaving client can push MRR churn high while client churn stays low.

How can I reconcile a specific subscription?

Find the row in /finance/clients, open the drawer to see its history, and compare with Stripe. subscription_overrides can hide a custom deal.

References

Code references

Source files to dig into further.

  • Formulas: lib/analytics/finance/metrics.ts
  • Definitions & tooltips: lib/analytics/finance/glossary.ts + finance.glossary namespace
  • Effective amount: lib/stripeEffectiveAmount.ts
  • Loss timing chart: app/finance/pertes/_components/losses-timeline-chart.tsx
  • Forecast: app/finance/forecast/ + gocroco_mrr_forecast
  • Reliability audit: scripts/audit/run-reliability-audit.ts