diff --git a/finance/CLAUDE.md b/finance/CLAUDE.md index 59657d1..9b4b895 100644 --- a/finance/CLAUDE.md +++ b/finance/CLAUDE.md @@ -100,3 +100,6 @@ python financial-analyst/scripts/forecast_builder.py forecast_data.json --format **Last Updated:** February 2026 **Skills Deployed:** 1/1 finance skills production-ready **Total Tools:** 4 Python automation tools + +## saas-metrics-coach +SaaS financial health advisor. Calculates ARR, MRR, churn, CAC, LTV, NRR, Quick Ratio. Benchmarks against industry standards. Includes 12-month projection simulator. diff --git a/finance/saas-metrics-coach/SKILL.md b/finance/saas-metrics-coach/SKILL.md new file mode 100644 index 0000000..d9c64af --- /dev/null +++ b/finance/saas-metrics-coach/SKILL.md @@ -0,0 +1,158 @@ +--- +name: saas-metrics-coach +description: SaaS financial health advisor. Use when a user shares revenue or customer numbers, or mentions ARR, MRR, churn, LTV, CAC, NRR, or asks how their SaaS business is doing. +license: MIT +metadata: + version: 1.0.0 + author: Abbas Mir + category: finance + updated: 2026-03-08 +--- + +# SaaS Metrics Coach + +Act as a senior SaaS CFO advisor. Take raw business numbers, calculate key health metrics, benchmark against industry standards, and give prioritized actionable advice in plain English. + +## Step 1 — Collect Inputs + +If not already provided, ask for these in a single grouped request: + +- Revenue: current MRR, MRR last month, expansion MRR, churned MRR +- Customers: total active, new this month, churned this month +- Costs: sales and marketing spend, gross margin % + +Work with partial data. Be explicit about what is missing and what assumptions are being made. + +## Step 2 — Calculate Metrics + +Run `scripts/metrics_calculator.py` with the user's inputs. If the script is unavailable, use the formulas in `references/formulas.md`. + +Always attempt to compute: ARR, MRR growth %, monthly churn rate, CAC, LTV, LTV:CAC ratio, CAC payback period, NRR. + +**Additional Analysis Tools:** +- Use `scripts/quick_ratio_calculator.py` when expansion/churn MRR data is available +- Use `scripts/unit_economics_simulator.py` for forward-looking projections + +## Step 3 — Benchmark Each Metric + +Load `references/benchmarks.md`. For each metric show: +- The calculated value +- The relevant benchmark range for the user's segment and stage +- A plain status label: HEALTHY / WATCH / CRITICAL + +Match the benchmark tier to the user's market segment (Enterprise / Mid-Market / SMB / PLG) and company stage (Early / Growth / Scale). Ask if unclear. + +## Step 4 — Prioritize and Recommend + +Identify the top 2-3 metrics at WATCH or CRITICAL status. For each one state: +- What is happening (one sentence, plain English) +- Why it matters to the business +- Two or three specific actions to take this month + +Order by impact — address the most damaging problem first. + +## Step 5 — Output Format + +Always use this exact structure: + +``` +# SaaS Health Report — [Month Year] + +## Metrics at a Glance +| Metric | Your Value | Benchmark | Status | +|--------|------------|-----------|--------| + +## Overall Picture +[2-3 sentences, plain English summary] + +## Priority Issues + +### 1. [Metric Name] +What is happening: ... +Why it matters: ... +Fix it this month: ... + +### 2. [Metric Name] +... + +## What is Working +[1-2 genuine strengths, no padding] + +## 90-Day Focus +[Single metric to move + specific numeric target] +``` + +## Examples + +**Example 1 — Partial data** + +Input: "MRR is $80k, we have 200 customers, about 3 cancel each month." + +Expected output: Calculates ARPA ($400), monthly churn (1.5%), ARR ($960k), LTV estimate. Flags CAC and growth rate as missing. Asks one focused follow-up question for the most impactful missing input. + +**Example 2 — Critical scenario** + +Input: "MRR $22k (was $23.5k), 80 customers, lost 9, gained 6, spent $15k on ads, 65% gross margin." + +Expected output: Flags negative MoM growth (-6.4%), critical churn (11.25%), and LTV:CAC of 0.64:1 as CRITICAL. Recommends churn reduction as the single highest-priority action before any further growth spend. + +## Key Principles + +- Be direct. If a metric is bad, say it is bad. +- Explain every metric in one sentence before showing the number. +- Cap priority issues at three. More than three paralyzes action. +- Context changes benchmarks. Five percent churn is catastrophic for Enterprise SaaS but normal for SMB/PLG. Always confirm the user's target market before scoring. + +## Reference Files + +- `references/formulas.md` — All metric formulas with worked examples +- `references/benchmarks.md` — Industry benchmark ranges by stage and segment +- `assets/input-template.md` — Blank input form to share with users +- `scripts/metrics_calculator.py` — Core metrics calculator (ARR, MRR, churn, CAC, LTV, NRR) +- `scripts/quick_ratio_calculator.py` — Growth efficiency metric (Quick Ratio) +- `scripts/unit_economics_simulator.py` — 12-month forward projection + +## Tools + +### 1. Metrics Calculator (`scripts/metrics_calculator.py`) +Core SaaS metrics from raw business numbers. + +```bash +# Interactive mode +python scripts/metrics_calculator.py + +# CLI mode +python scripts/metrics_calculator.py --mrr 50000 --customers 100 --churned 5 --json +``` + +### 2. Quick Ratio Calculator (`scripts/quick_ratio_calculator.py`) +Growth efficiency metric: (New MRR + Expansion) / (Churned + Contraction) + +```bash +python scripts/quick_ratio_calculator.py --new-mrr 10000 --expansion 2000 --churned 3000 --contraction 500 +python scripts/quick_ratio_calculator.py --new-mrr 10000 --expansion 2000 --churned 3000 --json +``` + +**Benchmarks:** +- < 1.0 = CRITICAL (losing faster than gaining) +- 1-2 = WATCH (marginal growth) +- 2-4 = HEALTHY (good efficiency) +- \> 4 = EXCELLENT (strong growth) + +### 3. Unit Economics Simulator (`scripts/unit_economics_simulator.py`) +Project metrics forward 12 months based on growth/churn assumptions. + +```bash +python scripts/unit_economics_simulator.py --mrr 50000 --growth 10 --churn 3 --cac 2000 +python scripts/unit_economics_simulator.py --mrr 50000 --growth 10 --churn 3 --cac 2000 --json +``` + +**Use for:** +- "What if we grow at X% per month?" +- Runway projections +- Scenario planning (best/base/worst case) + +## Related Skills + +- **financial-analyst**: Use for DCF valuation, budget variance analysis, and traditional financial modeling. NOT for SaaS-specific metrics like CAC, LTV, or churn. +- **business-growth/customer-success**: Use for retention strategies and customer health scoring. Complements this skill when churn is flagged as CRITICAL. diff --git a/finance/saas-metrics-coach/assets/input-template.md b/finance/saas-metrics-coach/assets/input-template.md new file mode 100644 index 0000000..3630eac --- /dev/null +++ b/finance/saas-metrics-coach/assets/input-template.md @@ -0,0 +1,29 @@ +# SaaS Metrics — Input Template + +Fill in what you know and paste to the SaaS Metrics Coach. Leave blanks empty. + +--- + +**Context** +- Target market: [ ] Enterprise [ ] Mid-Market [ ] SMB [ ] Consumer/PLG +- Stage: [ ] Early (<$1M ARR) [ ] Growth ($1M–$10M) [ ] Scale ($10M+) + +**Revenue** +- Current MRR: $ +- MRR last month: $ +- Expansion MRR this month (upsells/upgrades): $ +- Churned MRR this month: $ +- Contraction MRR (downgrades): $ + +**Customers** +- Total active customers: +- New customers this month: +- Churned customers this month: + +**Costs** +- Sales & Marketing spend this month: $ +- Gross margin %: +- Net profit margin % (optional): + +--- +*Partial data is fine — the coach works with whatever you have.* diff --git a/finance/saas-metrics-coach/references/benchmarks.md b/finance/saas-metrics-coach/references/benchmarks.md new file mode 100644 index 0000000..2596b3a --- /dev/null +++ b/finance/saas-metrics-coach/references/benchmarks.md @@ -0,0 +1,101 @@ +# SaaS Industry Benchmarks + +Industry-standard benchmark ranges for SaaS metrics, segmented by company stage and market segment. + +**Sources:** +- OpenView SaaS Benchmarks 2024 +- Bessemer Venture Partners Cloud Index +- SaaS Capital Index +- Paddle SaaS Metrics Report 2025 + +**Last updated:** March 2026 + +## Stage Definitions + +- Early: < $1M ARR +- Growth: $1M–$10M ARR +- Scale: $10M–$50M ARR +- Late: $50M+ ARR + +--- + +## Monthly Churn Rate + +| Segment | CRITICAL | WATCH | HEALTHY | +|---|---|---|---| +| Enterprise (ACV > $25k) | > 3% | 1–3% | < 1% | +| Mid-Market ($5k–$25k ACV) | > 5% | 2–5% | < 2% | +| SMB / PLG (< $5k ACV) | > 8% | 4–8% | < 4% | +| Consumer | > 10% | 5–10% | < 5% | + +## LTV:CAC Ratio + +| Status | Range | +|---|---| +| CRITICAL | < 1:1 — losing money on every customer | +| POOR | 1:1–2:1 — barely breaking even | +| WATCH | 2:1–3:1 — marginally viable | +| HEALTHY | 3:1–5:1 — industry standard | +| EXCELLENT | > 5:1 — strong unit economics | +| WATCH | > 8:1 — possibly under-investing in growth | + +## CAC Payback Period + +| Status | Range | +|---|---| +| CRITICAL | > 24 months | +| WATCH | 18–24 months | +| HEALTHY | 12–18 months | +| GOOD | 6–12 months | +| EXCELLENT | < 6 months (PLG indicator) | + +## NRR (Net Revenue Retention) + +| Status | Range | +|---|---| +| CRITICAL | < 80% — revenue shrinking from existing base | +| POOR | 80–90% | +| WATCH | 90–100% — flat, not expanding | +| HEALTHY | 100–110% | +| EXCELLENT | 110–120% | +| WORLD-CLASS | > 120% (Snowflake / Datadog territory) | + +## MoM MRR Growth + +| Stage | CRITICAL | WATCH | HEALTHY | EXCELLENT | +|---|---|---|---|---| +| Early (< $1M ARR) | < 5% | 5–10% | 10–20% | > 20% | +| Growth ($1M–$10M) | < 3% | 3–7% | 7–15% | > 15% | +| Scale ($10M+) | < 1% | 1–3% | 3–7% | > 7% | + +## Gross Margin + +| Status | Range | +|---|---| +| CRITICAL | < 50% | +| WATCH | 50–65% | +| HEALTHY | 65–75% | +| EXCELLENT | 75–85% | +| WORLD-CLASS | > 85% (API / infrastructure businesses) | + +## Rule of 40 + +| Score | Status | +|---|---| +| < 20 | CONCERNING | +| 20–40 | DEVELOPING | +| 40–60 | HEALTHY | +| > 60 | EXCELLENT | + +## Quick Reference Card + +``` +Metric Must Hit Good Great +--------------------------------------------- +Monthly Churn < 5% < 3% < 1% +LTV:CAC > 3:1 > 4:1 > 5:1 +CAC Payback < 18 mo < 12 mo < 6 mo +NRR > 100% > 110% > 120% +Gross Margin > 65% > 75% > 80% +MoM Growth > 5% > 10% > 15% +``` diff --git a/finance/saas-metrics-coach/references/formulas.md b/finance/saas-metrics-coach/references/formulas.md new file mode 100644 index 0000000..b595dc9 --- /dev/null +++ b/finance/saas-metrics-coach/references/formulas.md @@ -0,0 +1,103 @@ +# SaaS Metric Formulas + +Complete reference with worked examples for all metrics calculated by the SaaS Metrics Coach. + +## ARR (Annual Recurring Revenue) +``` +ARR = MRR × 12 +``` + +**Example:** +- Current MRR: $50,000 +- ARR = $50,000 × 12 = **$600,000** + +**When to use:** Quick snapshot of annualized revenue run rate. Not the same as actual annual revenue if you have seasonality or one-time fees. + +## MoM MRR Growth Rate +``` +MoM Growth % = ((MRR_now - MRR_last) / MRR_last) × 100 +``` + +**Example:** +- Current MRR: $50,000 +- Last month MRR: $45,000 +- Growth = (($50,000 - $45,000) / $45,000) × 100 = **11.1%** + +**Interpretation:** +- Negative = losing revenue +- 0-5% = slow growth (concerning for early stage) +- 5-15% = healthy growth +- >15% = strong growth (early stage) + +## Monthly Churn Rate +``` +Churn % = (Customers lost / Customers at start of month) × 100 +``` + +**Example:** +- Customers at start of month: 100 +- Customers lost during month: 5 +- Churn = (5 / 100) × 100 = **5%** + +**Annualized impact:** 5% monthly = ~46% annual churn (compounding effect) + +**Critical context:** Churn tolerance varies by segment: +- Enterprise: >3% is critical +- SMB: >8% is critical +- Always confirm segment before judging severity + +## ARPA (Avg Revenue Per Account) +``` +ARPA = MRR / Total active customers +``` + +## CAC (Customer Acquisition Cost) +``` +CAC = Total Sales & Marketing spend / New customers acquired +``` +Example: $20k spend / 10 customers → CAC $2,000 + +## LTV (Customer Lifetime Value) +``` +LTV = (ARPA / Monthly Churn Rate) × Gross Margin % +``` + +**Simplified (no gross margin data):** +``` +LTV = ARPA / Monthly Churn Rate +``` + +**Example:** +- ARPA: $500 +- Monthly churn: 5% (0.05) +- Gross margin: 70% (0.70) +- LTV = ($500 / 0.05) × 0.70 = **$7,000** + +**Simplified (no margin):** $500 / 0.05 = **$10,000** + +**Why it matters:** LTV tells you the total revenue you can expect from an average customer. Must be at least 3x your CAC to have sustainable unit economics. + +## LTV:CAC Ratio +``` +LTV:CAC = LTV / CAC +``` +Example: LTV $10k / CAC $2k = 5:1 + +## CAC Payback Period +``` +Payback (months) = CAC / (ARPA × Gross Margin %) +Simplified: Payback = CAC / ARPA +``` +Example: CAC $2k / ARPA $500 = 4 months + +## NRR (Net Revenue Retention) +``` +NRR % = ((MRR_start + Expansion MRR - Churned MRR - Contraction MRR) / MRR_start) × 100 +``` +Simplified (no expansion data): NRR ≈ (1 - Revenue Churn Rate) × 100 + +## Rule of 40 +``` +Score = Annualized MoM Growth % + Net Profit Margin % +Healthy: ≥ 40 +``` diff --git a/finance/saas-metrics-coach/scripts/metrics_calculator.py b/finance/saas-metrics-coach/scripts/metrics_calculator.py new file mode 100644 index 0000000..bf49f46 --- /dev/null +++ b/finance/saas-metrics-coach/scripts/metrics_calculator.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +""" +SaaS Metrics Calculator — zero external dependencies (stdlib only). + +Usage (interactive): python metrics_calculator.py +Usage (CLI): python metrics_calculator.py --mrr 48000 --customers 160 --json +Usage (import): + from metrics_calculator import calculate, report + results = calculate(mrr=48000, mrr_last=42000, customers=160, + churned=4, new_customers=22, sm_spend=18000, + gross_margin=0.72) + print(report(results)) +""" + +import json +import sys + + +def calculate( + mrr=None, + mrr_last=None, + customers=None, + churned=None, + new_customers=None, + sm_spend=None, + gross_margin=0.70, + expansion_mrr=0, + churned_mrr=0, + contraction_mrr=0, + profit_margin=None, +): + r, missing = {}, [] + + # ── Core revenue ───────────────────────────────────────────────────────── + if mrr is not None: + r["MRR"] = round(mrr, 2) + r["ARR"] = round(mrr * 12, 2) + else: + missing.append("ARR/MRR — need current MRR") + + if mrr and customers: + r["ARPA"] = round(mrr / customers, 2) + else: + missing.append("ARPA — need MRR + customer count") + + # ── Growth ──────────────────────────────────────────────────────────────── + if mrr and mrr_last and mrr_last > 0: + r["MoM_Growth_Pct"] = round(((mrr - mrr_last) / mrr_last) * 100, 2) + else: + missing.append("MoM Growth — need last month MRR") + + # ── Churn ───────────────────────────────────────────────────────────────── + if churned is not None and customers: + r["Churn_Pct"] = round((churned / customers) * 100, 2) + else: + missing.append("Churn Rate — need churned + total customers") + + # ── CAC ─────────────────────────────────────────────────────────────────── + if sm_spend and new_customers and new_customers > 0: + r["CAC"] = round(sm_spend / new_customers, 2) + else: + missing.append("CAC — need S&M spend + new customers") + + # ── LTV ─────────────────────────────────────────────────────────────────── + arpa = r.get("ARPA") + churn_dec = r.get("Churn_Pct", 0) / 100 + if arpa and churn_dec > 0: + r["LTV"] = round((arpa / churn_dec) * gross_margin, 2) + else: + missing.append("LTV — need ARPA and churn rate") + + # ── LTV:CAC ─────────────────────────────────────────────────────────────── + if r.get("LTV") and r.get("CAC") and r["CAC"] > 0: + r["LTV_CAC"] = round(r["LTV"] / r["CAC"], 2) + else: + missing.append("LTV:CAC — need both LTV and CAC") + + # ── Payback ─────────────────────────────────────────────────────────────── + if r.get("CAC") and arpa and arpa > 0: + r["Payback_Months"] = round(r["CAC"] / (arpa * gross_margin), 1) + else: + missing.append("Payback Period — need CAC and ARPA") + + # ── NRR ─────────────────────────────────────────────────────────────────── + if mrr_last and mrr_last > 0 and (expansion_mrr or churned_mrr or contraction_mrr): + nrr = ((mrr_last + expansion_mrr - churned_mrr - contraction_mrr) / mrr_last) * 100 + r["NRR_Pct"] = round(nrr, 2) + elif r.get("Churn_Pct"): + r["NRR_Est_Pct"] = round((1 - r["Churn_Pct"] / 100) * 100, 2) + missing.append("NRR (accurate) — using churn-only estimate; provide expansion MRR for full NRR") + + # ── Rule of 40 ──────────────────────────────────────────────────────────── + if r.get("MoM_Growth_Pct") and profit_margin is not None: + r["Rule_of_40"] = round(r["MoM_Growth_Pct"] * 12 + profit_margin, 1) + + r["_missing"] = missing + r["_gross_margin"] = gross_margin + return r + + +def report(r): + labels = [ + ("MRR", "Monthly Recurring Revenue", "$"), + ("ARR", "Annual Recurring Revenue", "$"), + ("ARPA", "Avg Revenue Per Account/mo", "$"), + ("MoM_Growth_Pct", "MoM MRR Growth", "%"), + ("Churn_Pct", "Monthly Churn Rate", "%"), + ("CAC", "Customer Acquisition Cost", "$"), + ("LTV", "Customer Lifetime Value", "$"), + ("LTV_CAC", "LTV:CAC Ratio", ":1"), + ("Payback_Months", "CAC Payback Period", " months"), + ("NRR_Pct", "NRR (Net Revenue Retention)", "%"), + ("NRR_Est_Pct", "NRR Estimate (churn-only)", "%"), + ("Rule_of_40", "Rule of 40 Score", ""), + ] + + lines = ["=" * 54, " SAAS METRICS CALCULATOR", "=" * 54, ""] + for key, label, unit in labels: + val = r.get(key) + if val is None: + continue + if unit == "$": + fmt = f"${val:,.2f}" + elif unit == "%": + fmt = f"{val}%" + elif unit == ":1": + fmt = f"{val}:1" + else: + fmt = f"{val}{unit}" + lines.append(f" {label:<40} {fmt}") + + if r.get("_missing"): + lines += ["", " Missing / estimated:"] + for m in r["_missing"]: + lines.append(f" - {m}") + + lines.append("=" * 54) + return "\n".join(lines) + + +# ── Interactive mode ────────────────────────────────────────────────────────── + +def _ask(prompt, required=False): + while True: + v = input(f" {prompt}: ").strip() + if not v: + if required: + print(" Required — please enter a value.") + continue + return None + try: + return float(v) + except ValueError: + print(" Enter a number (e.g. 48000 or 72).") + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="SaaS Metrics Calculator") + parser.add_argument("--mrr", type=float, help="Current MRR") + parser.add_argument("--mrr-last", type=float, help="MRR last month") + parser.add_argument("--customers", type=int, help="Total active customers") + parser.add_argument("--churned", type=int, help="Customers churned this month") + parser.add_argument("--new-customers", type=int, help="New customers acquired") + parser.add_argument("--sm-spend", type=float, help="Sales & Marketing spend") + parser.add_argument("--gross-margin", type=float, default=70, help="Gross margin %% (default: 70)") + parser.add_argument("--expansion-mrr", type=float, default=0, help="Expansion MRR") + parser.add_argument("--churned-mrr", type=float, default=0, help="Churned MRR") + parser.add_argument("--contraction-mrr", type=float, default=0, help="Contraction MRR") + parser.add_argument("--profit-margin", type=float, help="Net profit margin %%") + parser.add_argument("--json", action="store_true", help="Output JSON format") + + args = parser.parse_args() + + # CLI mode + if args.mrr is not None: + inputs = { + "mrr": args.mrr, + "mrr_last": args.mrr_last, + "customers": args.customers, + "churned": args.churned, + "new_customers": args.new_customers, + "sm_spend": args.sm_spend, + "gross_margin": args.gross_margin / 100 if args.gross_margin > 1 else args.gross_margin, + "expansion_mrr": args.expansion_mrr, + "churned_mrr": args.churned_mrr, + "contraction_mrr": args.contraction_mrr, + "profit_margin": args.profit_margin, + } + result = calculate(**inputs) + + if args.json: + print(json.dumps(result, indent=2)) + else: + print("\n" + report(result)) + sys.exit(0) + + # Interactive mode + print("\nSaaS Metrics Calculator (press Enter to skip)\n") + + gm = _ask("Gross margin % (default 70)", required=False) or 70 + inputs = dict( + mrr=_ask("Current MRR ($)", required=True), + mrr_last=_ask("MRR last month ($)"), + customers=_ask("Total active customers"), + churned=_ask("Customers churned this month"), + new_customers=_ask("New customers acquired this month"), + sm_spend=_ask("Sales & Marketing spend this month ($)"), + gross_margin=gm / 100 if gm > 1 else gm, + expansion_mrr=_ask("Expansion MRR (upsells) ($)") or 0, + churned_mrr=_ask("Churned MRR ($)") or 0, + contraction_mrr=_ask("Contraction MRR (downgrades) ($)") or 0, + profit_margin=_ask("Net profit margin % (for Rule of 40, optional)"), + ) + + print("\n" + report(calculate(**inputs))) diff --git a/finance/saas-metrics-coach/scripts/quick_ratio_calculator.py b/finance/saas-metrics-coach/scripts/quick_ratio_calculator.py new file mode 100644 index 0000000..6624a8f --- /dev/null +++ b/finance/saas-metrics-coach/scripts/quick_ratio_calculator.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +""" +Quick Ratio Calculator - SaaS growth efficiency metric. + +Quick Ratio = (New MRR + Expansion MRR) / (Churned MRR + Contraction MRR) + +A ratio > 4 indicates healthy, efficient growth. +A ratio < 1 means you're losing revenue faster than gaining it. + +Usage: + python quick_ratio_calculator.py --new-mrr 10000 --expansion 2000 --churned 3000 --contraction 500 + python quick_ratio_calculator.py --new-mrr 10000 --expansion 2000 --churned 3000 --contraction 500 --json +""" + +import json +import sys +import argparse + + +def calculate_quick_ratio(new_mrr, expansion_mrr, churned_mrr, contraction_mrr): + """ + Calculate Quick Ratio and provide interpretation. + + Args: + new_mrr: New MRR from new customers + expansion_mrr: Expansion MRR from existing customers (upsells) + churned_mrr: MRR lost from churned customers + contraction_mrr: MRR lost from downgrades + + Returns: + dict with quick ratio and analysis + """ + # Calculate components + growth_mrr = new_mrr + expansion_mrr + lost_mrr = churned_mrr + contraction_mrr + + # Quick Ratio + if lost_mrr == 0: + quick_ratio = float('inf') if growth_mrr > 0 else 0 + quick_ratio_display = "∞" if growth_mrr > 0 else "0" + else: + quick_ratio = growth_mrr / lost_mrr + quick_ratio_display = f"{quick_ratio:.2f}" + + # Status assessment + if lost_mrr == 0 and growth_mrr > 0: + status = "EXCELLENT" + interpretation = "No revenue loss - perfect retention with growth" + elif quick_ratio >= 4: + status = "EXCELLENT" + interpretation = "Strong, efficient growth - gaining revenue 4x faster than losing it" + elif quick_ratio >= 2: + status = "HEALTHY" + interpretation = "Good growth efficiency - gaining revenue 2x+ faster than losing it" + elif quick_ratio >= 1: + status = "WATCH" + interpretation = "Marginal growth - barely gaining more than losing" + else: + status = "CRITICAL" + interpretation = "Losing revenue faster than gaining - growth is unsustainable" + + # Breakdown percentages + if growth_mrr > 0: + new_pct = (new_mrr / growth_mrr) * 100 + expansion_pct = (expansion_mrr / growth_mrr) * 100 + else: + new_pct = expansion_pct = 0 + + if lost_mrr > 0: + churned_pct = (churned_mrr / lost_mrr) * 100 + contraction_pct = (contraction_mrr / lost_mrr) * 100 + else: + churned_pct = contraction_pct = 0 + + results = { + "quick_ratio": quick_ratio if quick_ratio != float('inf') else None, + "quick_ratio_display": quick_ratio_display, + "status": status, + "interpretation": interpretation, + "components": { + "growth_mrr": round(growth_mrr, 2), + "lost_mrr": round(lost_mrr, 2), + "new_mrr": round(new_mrr, 2), + "expansion_mrr": round(expansion_mrr, 2), + "churned_mrr": round(churned_mrr, 2), + "contraction_mrr": round(contraction_mrr, 2), + }, + "breakdown": { + "new_mrr_pct": round(new_pct, 1), + "expansion_mrr_pct": round(expansion_pct, 1), + "churned_mrr_pct": round(churned_pct, 1), + "contraction_mrr_pct": round(contraction_pct, 1), + }, + } + + return results + + +def format_report(results): + """Format quick ratio results as human-readable report.""" + lines = [] + lines.append("\n" + "=" * 70) + lines.append("QUICK RATIO ANALYSIS") + lines.append("=" * 70) + + # Quick Ratio + lines.append(f"\n⚡ QUICK RATIO: {results['quick_ratio_display']}") + lines.append(f" Status: {results['status']}") + lines.append(f" {results['interpretation']}") + + # Components + comp = results["components"] + lines.append("\n📊 COMPONENTS") + lines.append(f" Growth MRR (New + Expansion): ${comp['growth_mrr']:,.2f}") + lines.append(f" • New MRR: ${comp['new_mrr']:,.2f}") + lines.append(f" • Expansion MRR: ${comp['expansion_mrr']:,.2f}") + lines.append(f" Lost MRR (Churned + Contraction): ${comp['lost_mrr']:,.2f}") + lines.append(f" • Churned MRR: ${comp['churned_mrr']:,.2f}") + lines.append(f" • Contraction MRR: ${comp['contraction_mrr']:,.2f}") + + # Breakdown + bd = results["breakdown"] + lines.append("\n📈 GROWTH BREAKDOWN") + lines.append(f" New customers: {bd['new_mrr_pct']:.1f}%") + lines.append(f" Expansion: {bd['expansion_mrr_pct']:.1f}%") + + lines.append("\n📉 LOSS BREAKDOWN") + lines.append(f" Churn: {bd['churned_mrr_pct']:.1f}%") + lines.append(f" Contraction: {bd['contraction_mrr_pct']:.1f}%") + + # Benchmarks + lines.append("\n🎯 BENCHMARKS") + lines.append(" < 1.0 = CRITICAL (losing revenue faster than gaining)") + lines.append(" 1-2 = WATCH (marginal growth)") + lines.append(" 2-4 = HEALTHY (good growth efficiency)") + lines.append(" > 4 = EXCELLENT (strong, efficient growth)") + + lines.append("\n" + "=" * 70 + "\n") + + return "\n".join(lines) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Calculate SaaS Quick Ratio (growth efficiency metric)" + ) + parser.add_argument( + "--new-mrr", type=float, required=True, help="New MRR from new customers" + ) + parser.add_argument( + "--expansion", type=float, default=0, help="Expansion MRR from upsells (default: 0)" + ) + parser.add_argument( + "--churned", type=float, required=True, help="Churned MRR from lost customers" + ) + parser.add_argument( + "--contraction", type=float, default=0, help="Contraction MRR from downgrades (default: 0)" + ) + parser.add_argument("--json", action="store_true", help="Output JSON format") + + args = parser.parse_args() + + results = calculate_quick_ratio( + new_mrr=args.new_mrr, + expansion_mrr=args.expansion, + churned_mrr=args.churned, + contraction_mrr=args.contraction, + ) + + if args.json: + print(json.dumps(results, indent=2)) + else: + print(format_report(results)) diff --git a/finance/saas-metrics-coach/scripts/unit_economics_simulator.py b/finance/saas-metrics-coach/scripts/unit_economics_simulator.py new file mode 100644 index 0000000..0513ff6 --- /dev/null +++ b/finance/saas-metrics-coach/scripts/unit_economics_simulator.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +""" +Unit Economics Simulator - Project SaaS metrics forward 12 months. + +Usage: + python unit_economics_simulator.py --mrr 50000 --growth 10 --churn 3 --cac 2000 + python unit_economics_simulator.py --mrr 50000 --growth 10 --churn 3 --cac 2000 --json +""" + +import json +import sys +import argparse + + +def simulate( + mrr, + monthly_growth_pct, + monthly_churn_pct, + cac, + gross_margin=0.70, + sm_spend_pct=0.30, + months=12, +): + """ + Simulate unit economics forward. + + Args: + mrr: Starting MRR + monthly_growth_pct: Expected monthly growth rate (%) + monthly_churn_pct: Expected monthly churn rate (%) + cac: Customer acquisition cost + gross_margin: Gross margin (0-1) + sm_spend_pct: Sales & marketing as % of revenue (0-1) + months: Number of months to project + + Returns: + dict with monthly projections and summary + """ + results = { + "inputs": { + "starting_mrr": mrr, + "monthly_growth_pct": monthly_growth_pct, + "monthly_churn_pct": monthly_churn_pct, + "cac": cac, + "gross_margin": gross_margin, + "sm_spend_pct": sm_spend_pct, + }, + "projections": [], + "summary": {}, + } + + current_mrr = mrr + cumulative_sm_spend = 0 + cumulative_gross_profit = 0 + + for month in range(1, months + 1): + # Calculate growth and churn + growth_rate = monthly_growth_pct / 100 + churn_rate = monthly_churn_pct / 100 + + # Net growth = growth - churn + net_growth_rate = growth_rate - churn_rate + new_mrr = current_mrr * (1 + net_growth_rate) + + # Revenue and costs + monthly_revenue = current_mrr + gross_profit = monthly_revenue * gross_margin + sm_spend = monthly_revenue * sm_spend_pct + net_profit = gross_profit - sm_spend + + # Accumulate + cumulative_sm_spend += sm_spend + cumulative_gross_profit += gross_profit + + # ARR + arr = current_mrr * 12 + + results["projections"].append({ + "month": month, + "mrr": round(current_mrr, 2), + "arr": round(arr, 2), + "monthly_revenue": round(monthly_revenue, 2), + "gross_profit": round(gross_profit, 2), + "sm_spend": round(sm_spend, 2), + "net_profit": round(net_profit, 2), + "growth_rate_pct": round(net_growth_rate * 100, 2), + }) + + current_mrr = new_mrr + + # Summary + final_mrr = results["projections"][-1]["mrr"] + final_arr = results["projections"][-1]["arr"] + total_revenue = sum(p["monthly_revenue"] for p in results["projections"]) + total_net_profit = sum(p["net_profit"] for p in results["projections"]) + + results["summary"] = { + "starting_mrr": mrr, + "ending_mrr": round(final_mrr, 2), + "ending_arr": round(final_arr, 2), + "mrr_growth_pct": round(((final_mrr - mrr) / mrr) * 100, 2), + "total_revenue_12m": round(total_revenue, 2), + "total_gross_profit_12m": round(cumulative_gross_profit, 2), + "total_sm_spend_12m": round(cumulative_sm_spend, 2), + "total_net_profit_12m": round(total_net_profit, 2), + "avg_monthly_growth_pct": round((monthly_growth_pct - monthly_churn_pct), 2), + } + + return results + + +def format_report(results): + """Format simulation results as human-readable report.""" + lines = [] + lines.append("\n" + "=" * 70) + lines.append("UNIT ECONOMICS SIMULATION - 12 MONTH PROJECTION") + lines.append("=" * 70) + + # Inputs + inputs = results["inputs"] + lines.append("\n📊 INPUTS") + lines.append(f" Starting MRR: ${inputs['starting_mrr']:,.0f}") + lines.append(f" Monthly Growth: {inputs['monthly_growth_pct']}%") + lines.append(f" Monthly Churn: {inputs['monthly_churn_pct']}%") + lines.append(f" CAC: ${inputs['cac']:,.0f}") + lines.append(f" Gross Margin: {inputs['gross_margin']*100:.0f}%") + lines.append(f" S&M Spend: {inputs['sm_spend_pct']*100:.0f}% of revenue") + + # Summary + summary = results["summary"] + lines.append("\n📈 12-MONTH SUMMARY") + lines.append(f" Starting MRR: ${summary['starting_mrr']:,.0f}") + lines.append(f" Ending MRR: ${summary['ending_mrr']:,.0f}") + lines.append(f" Ending ARR: ${summary['ending_arr']:,.0f}") + lines.append(f" MRR Growth: {summary['mrr_growth_pct']:+.1f}%") + lines.append(f" Total Revenue: ${summary['total_revenue_12m']:,.0f}") + lines.append(f" Total Gross Profit: ${summary['total_gross_profit_12m']:,.0f}") + lines.append(f" Total S&M Spend: ${summary['total_sm_spend_12m']:,.0f}") + lines.append(f" Total Net Profit: ${summary['total_net_profit_12m']:,.0f}") + + # Monthly breakdown (first 3, last 3) + lines.append("\n📅 MONTHLY PROJECTIONS") + lines.append(f"{'Month':<8} {'MRR':<12} {'ARR':<12} {'Revenue':<12} {'Net Profit':<12}") + lines.append("-" * 70) + + projs = results["projections"] + for p in projs[:3]: + lines.append( + f"{p['month']:<8} ${p['mrr']:<11,.0f} ${p['arr']:<11,.0f} " + f"${p['monthly_revenue']:<11,.0f} ${p['net_profit']:<11,.0f}" + ) + + if len(projs) > 6: + lines.append(" ...") + + for p in projs[-3:]: + lines.append( + f"{p['month']:<8} ${p['mrr']:<11,.0f} ${p['arr']:<11,.0f} " + f"${p['monthly_revenue']:<11,.0f} ${p['net_profit']:<11,.0f}" + ) + + lines.append("\n" + "=" * 70 + "\n") + + return "\n".join(lines) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Simulate SaaS unit economics over 12 months" + ) + parser.add_argument("--mrr", type=float, required=True, help="Starting MRR") + parser.add_argument( + "--growth", type=float, required=True, help="Monthly growth rate (%)" + ) + parser.add_argument( + "--churn", type=float, required=True, help="Monthly churn rate (%)" + ) + parser.add_argument("--cac", type=float, required=True, help="Customer acquisition cost") + parser.add_argument( + "--gross-margin", type=float, default=70, help="Gross margin %% (default: 70)" + ) + parser.add_argument( + "--sm-spend", type=float, default=30, help="S&M spend as %% of revenue (default: 30)" + ) + parser.add_argument( + "--months", type=int, default=12, help="Months to project (default: 12)" + ) + parser.add_argument("--json", action="store_true", help="Output JSON format") + + args = parser.parse_args() + + results = simulate( + mrr=args.mrr, + monthly_growth_pct=args.growth, + monthly_churn_pct=args.churn, + cac=args.cac, + gross_margin=args.gross_margin / 100 if args.gross_margin > 1 else args.gross_margin, + sm_spend_pct=args.sm_spend / 100 if args.sm_spend > 1 else args.sm_spend, + months=args.months, + ) + + if args.json: + print(json.dumps(results, indent=2)) + else: + print(format_report(results))