Instructions assets/input-template.md references/benchmarks.md references/formulas.md scripts/metrics_calculator.py scripts/quick_ratio_calculator.py scripts/unit_economics_simulator.py
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.
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.
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
1. Metrics Calculator (scripts/metrics_calculator.py)
Core SaaS metrics from raw business numbers.
# 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)
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.
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)
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.
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.
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% 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 #!/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 ( " \n SaaS 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)))
#!/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))
#!/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 (pct)"
)
parser.add_argument(
"--churn" , type = float , required = True , help = "Monthly churn rate (pct)"
)
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))