Merge pull request #298 from abbasmir12/feat/finance-saas-metrics-coach

feat(finance): add saas-metrics-coach skill
This commit is contained in:
Alireza Rezvani
2026-03-10 13:57:22 +01:00
committed by GitHub
8 changed files with 989 additions and 0 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.*

View File

@@ -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% | 13% | < 1% |
| Mid-Market ($5k$25k ACV) | > 5% | 25% | < 2% |
| SMB / PLG (< $5k ACV) | > 8% | 48% | < 4% |
| Consumer | > 10% | 510% | < 5% |
## LTV:CAC Ratio
| Status | Range |
|---|---|
| CRITICAL | < 1:1 — losing money on every customer |
| POOR | 1:12:1 — barely breaking even |
| WATCH | 2:13:1 — marginally viable |
| HEALTHY | 3:15: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 | 1824 months |
| HEALTHY | 1218 months |
| GOOD | 612 months |
| EXCELLENT | < 6 months (PLG indicator) |
## NRR (Net Revenue Retention)
| Status | Range |
|---|---|
| CRITICAL | < 80% — revenue shrinking from existing base |
| POOR | 8090% |
| WATCH | 90100% — flat, not expanding |
| HEALTHY | 100110% |
| EXCELLENT | 110120% |
| WORLD-CLASS | > 120% (Snowflake / Datadog territory) |
## MoM MRR Growth
| Stage | CRITICAL | WATCH | HEALTHY | EXCELLENT |
|---|---|---|---|---|
| Early (< $1M ARR) | < 5% | 510% | 1020% | > 20% |
| Growth ($1M$10M) | < 3% | 37% | 715% | > 15% |
| Scale ($10M+) | < 1% | 13% | 37% | > 7% |
## Gross Margin
| Status | Range |
|---|---|
| CRITICAL | < 50% |
| WATCH | 5065% |
| HEALTHY | 6575% |
| EXCELLENT | 7585% |
| WORLD-CLASS | > 85% (API / infrastructure businesses) |
## Rule of 40
| Score | Status |
|---|---|
| < 20 | CONCERNING |
| 2040 | DEVELOPING |
| 4060 | 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%
```

View File

@@ -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
```

View File

@@ -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)))

View File

@@ -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))

View File

@@ -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))