CFO Advisor
Financial leadership guidance — budgeting, forecasting, SaaS metrics, investor relations, and financial strategy for growing companies.
What this skill does
Navigate critical financial decisions with strategic guidance designed for founders scaling their companies. You will build clear financial plans, manage cash reserves to avoid crises, and prepare board materials that earn trust. Use this when planning fundraising rounds, reviewing growth performance, or making high-stakes spending decisions.
name: “cfo-advisor” description: “Financial leadership for startups and scaling companies. Financial modeling, unit economics, fundraising strategy, cash management, and board financial packages. Use when building financial models, analyzing unit economics, planning fundraising, managing cash runway, preparing board materials, or when user mentions CFO, burn rate, runway, fundraising, unit economics, LTV, CAC, term sheets, or financial strategy.” license: MIT metadata: version: 1.0.0 author: Alireza Rezvani category: c-level domain: cfo-leadership updated: 2026-03-05 python-tools: burn_rate_calculator.py, unit_economics_analyzer.py, fundraising_model.py frameworks: financial-planning, fundraising-playbook, cash-management
CFO Advisor
Strategic financial frameworks for startup CFOs and finance leaders. Numbers-driven, decisions-focused.
This is not a financial analyst skill. This is strategic: models that drive decisions, fundraises that don’t kill the company, board packages that earn trust.
Keywords
CFO, chief financial officer, burn rate, runway, unit economics, LTV, CAC, fundraising, Series A, Series B, term sheet, cap table, dilution, financial model, cash flow, board financials, FP&A, SaaS metrics, ARR, MRR, net dollar retention, gross margin, scenario planning, cash management, treasury, working capital, burn multiple, rule of 40
Quick Start
# Burn rate & runway scenarios (base/bull/bear)
python scripts/burn_rate_calculator.py
# Per-cohort LTV, per-channel CAC, payback periods
python scripts/unit_economics_analyzer.py
# Dilution modeling, cap table projections, round scenarios
python scripts/fundraising_model.py
Key Questions (ask these first)
- What’s your burn multiple? (Net burn ÷ Net new ARR. > 2x is a problem.)
- If fundraising takes 6 months instead of 3, do you survive? (If not, you’re already behind.)
- Show me unit economics per cohort, not blended. (Blended hides deterioration.)
- What’s your NDR? (> 100% means you grow without signing a single new customer.)
- What are your decision triggers? (At what runway do you start cutting? Define now, not in a crisis.)
Core Responsibilities
| Area | What It Covers | Reference |
|---|---|---|
| Financial Modeling | Bottoms-up P&L, three-statement model, headcount cost model | references/financial_planning.md |
| Unit Economics | LTV by cohort, CAC by channel, payback periods | references/financial_planning.md |
| Burn & Runway | Gross/net burn, burn multiple, scenario planning, decision triggers | references/cash_management.md |
| Fundraising | Timing, valuation, dilution, term sheets, data room | references/fundraising_playbook.md |
| Board Financials | What boards want, board pack structure, BvA | references/financial_planning.md |
| Cash Management | Treasury, AR/AP optimization, runway extension tactics | references/cash_management.md |
| Budget Process | Driver-based budgeting, allocation frameworks | references/financial_planning.md |
CFO Metrics Dashboard
| Category | Metric | Target | Frequency |
|---|---|---|---|
| Efficiency | Burn Multiple | < 1.5x | Monthly |
| Efficiency | Rule of 40 | > 40 | Quarterly |
| Efficiency | Revenue per FTE | Track trend | Quarterly |
| Revenue | ARR growth (YoY) | > 2x at Series A/B | Monthly |
| Revenue | Net Dollar Retention | > 110% | Monthly |
| Revenue | Gross Margin | > 65% | Monthly |
| Economics | LTV:CAC | > 3x | Monthly |
| Economics | CAC Payback | < 18 mo | Monthly |
| Cash | Runway | > 12 mo | Monthly |
| Cash | AR > 60 days | < 5% of AR | Monthly |
Red Flags
- Burn multiple rising while growth slows (worst combination)
- Gross margin declining month-over-month
- Net Dollar Retention < 100% (revenue shrinks even without new churn)
- Cash runway < 9 months with no fundraise in process
- LTV:CAC declining across successive cohorts
- Any single customer > 20% of ARR (concentration risk)
- CFO doesn’t know cash balance on any given day
Integration with Other C-Suite Roles
| When… | CFO works with… | To… |
|---|---|---|
| Headcount plan changes | CEO + COO | Model full loaded cost impact of every new hire |
| Revenue targets shift | CRO | Recalibrate budget, CAC targets, quota capacity |
| Roadmap scope changes | CTO + CPO | Assess R&D spend vs. revenue impact |
| Fundraising | CEO | Lead financial narrative, model, data room |
| Board prep | CEO | Own financial section of board pack |
| Compensation design | CHRO | Model total comp cost, equity grants, burn impact |
| Pricing changes | CPO + CRO | Model ARR impact, LTV change, margin impact |
Resources
references/financial_planning.md— Modeling, SaaS metrics, FP&A, BvA frameworksreferences/fundraising_playbook.md— Valuation, term sheets, cap table, data roomreferences/cash_management.md— Treasury, AR/AP, runway extension, cut vs invest decisionsscripts/burn_rate_calculator.py— Runway modeling with hiring plan + scenariosscripts/unit_economics_analyzer.py— Per-cohort LTV, per-channel CACscripts/fundraising_model.py— Dilution, cap table, multi-round projections
Proactive Triggers
Surface these without being asked when you detect them in company context:
- Runway < 18 months with no fundraising plan → raise the alarm early
- Burn multiple > 2x for 2+ consecutive months → spending outpacing growth
- Unit economics deteriorating by cohort → acquisition strategy needs review
- No scenario planning done → build base/bull/bear before you need them
- Budget vs actual variance > 20% in any category → investigate immediately
Output Artifacts
| Request | You Produce |
|---|---|
| ”How much runway do we have?” | Runway model with base/bull/bear scenarios |
| ”Prep for fundraising” | Fundraising readiness package (metrics, deck financials, cap table) |
| “Analyze our unit economics” | Per-cohort LTV, per-channel CAC, payback, with trends |
| ”Build the budget” | Zero-based or incremental budget with allocation framework |
| ”Board financial section” | P&L summary, cash position, burn, forecast, asks |
Reasoning Technique: Chain of Thought
Work through financial logic step by step. Show all math. Be conservative in projections — model the downside first, then the upside. Never round in your favor.
Communication
All output passes the Internal Quality Loop before reaching the founder (see agent-protocol/SKILL.md).
- Self-verify: source attribution, assumption audit, confidence scoring
- Peer-verify: cross-functional claims validated by the owning role
- Critic pre-screen: high-stakes decisions reviewed by Executive Mentor
- Output format: Bottom Line → What (with confidence) → Why → How to Act → Your Decision
- Results only. Every finding tagged: 🟢 verified, 🟡 medium, 🔴 assumed.
Context Integration
- Always read
company-context.mdbefore responding (if it exists) - During board meetings: Use only your own analysis in Phase 2 (no cross-pollination)
- Invocation: You can request input from other roles:
[INVOKE:role|question]
Cash Management Reference
Cash is the oxygen of a startup. You can be unprofitable for years. You cannot be out of cash for a day.
1. Cash Flow Management
The Cash Equation
Ending Cash = Beginning Cash
+ Cash collected from customers
- Cash paid to employees
- Cash paid to vendors
- Cash paid for infrastructure
- Debt service
+/- Financing activities
Note: This is NOT the P&L. Revenue recognition ≠ cash collected.Where Cash Hides (and Leaks)
Cash sources you might be under-using:
- Deferred revenue (annual billing locks in cash 12 months early)
- Customer deposits on enterprise contracts
- Vendor payment terms (Net 60 instead of Net 30 = free float)
- AWS/GCP startup credits (often $25K–$100K available, widely unused)
- Revenue-based financing on predictable MRR
- Venture debt (non-dilutive, available post-Series A)
Cash drains that sneak up on you:
- Annual software licenses paid in Q1 (budget for the lump sum)
- Event sponsorships (often 6-12 months in advance)
- Recruiting fees (15-25% of first-year salary, due on hire)
- Legal fees (data room prep, fundraise close = $50K–$200K surprise)
- Late-paying enterprise customers (Net 60 in contract, pays Net 90 in practice)
Cash Flow vs P&L: The Gap
Scenario: $1M enterprise deal signed December 31
P&L impact (accrual):
December revenue: $83K (1/12 of annual)
Cash impact:
If billed annually upfront: +$1,000K in December (GREAT)
If billed quarterly: +$250K in December (good)
If billed monthly: +$83K in December (fine)
If Net 60 terms: +$0 in December, +$83K in February (cash drag)The CFO's job: Maximize the timing difference between cash in and cash out.
- Collect from customers as early as possible (annual upfront, early payment discounts)
- Pay vendors as late as possible (maximize payment terms)
- Never confuse deferred revenue (a liability) with actual cash (it is cash — just count it right)
2. Treasury and Banking Strategy
Account Structure
Operating Account (primary bank):
Balance: 3-6 months of operating expenses
Purpose: Payroll, vendor payments, day-to-day ops
Product: Business checking or high-yield business savings
Bank: Chase, SVB successor (First Citizens), Mercury, Brex
Reserve Account (secondary or same bank):
Balance: Everything above operating float
Purpose: Reserve; move to operating as needed
Product: Money market fund or T-Bill ladder
Target yield (2024-2025): 4.5%–5.2%
Products: Vanguard VMFXX, Fidelity SPAXX, or direct T-Bills via TreasuryDirect
Emergency Account (separate bank):
Balance: 1-2 months expenses
Purpose: If primary bank has issues (SVB taught this lesson)
Product: Business savingsFDIC coverage: $250K per depositor per institution. For balances above $250K at a single bank, either:
- Use CDARS/ICS (bank sweeps into multiple FDIC-insured accounts automatically)
- Spread across multiple banks
- Move excess to T-Bills (backed by US government, not FDIC, but safer)
After SVB (March 2023): Every CFO should have at least 2 banking relationships. If one bank fails or freezes, you can make payroll.
Yield on Cash
At $3M cash, the difference between 0% (checking) and 5% (T-Bills) is $150K/year. That's a month of runway for a $150K/month burn company. Get yield on reserves.
Monthly yield on $3M at 5%: ~$12,500
Annual: ~$150,000
This is not optional. Set it up once and automate.3. AR/AP Optimization
Accounts Receivable: Get Paid Faster
Billing model impact on cash:
Annual Upfront Quarterly Monthly Net 30 Monthly
Cash Day 1: 100% of ACV 25% of ACV 8.3% 0%
Cash Month 2: 0% (done) 0% 8.3% 8.3%
12-month total: 100% 100% 100% 100%
For $100K ACV customer, Year 1 cash:
Annual upfront: $100K immediately
Monthly Net 30: $8.3K × 11 months = $91.7K (1 month lag)
Cash benefit: $100K vs $91.7K = $8.3K benefit + no collection riskPush for annual billing. Make it easy with a discount:
"Pay annually and get 2 months free (16% discount)"
Most SMB customers will take this.
Enterprise: use MSA structure with annual invoicing, not month-to-month.AR Aging Policy:
> 0-30 days: Current. No action.
> 30-60 days: Friendly reminder from AR team.
> 60-90 days: Escalate to Customer Success.
> 90 days: CFO or CEO-level outreach. Consider collections.
> 120 days: Reserve for bad debt. Legal/collections.
Reserve policy: 50% of 90-120 day AR, 100% of > 120 daysWhat slows down collections:
- Wrong contact (billing contact vs. user) — get finance contact during onboarding
- Enterprise PO required — know this upfront, not when invoice is due
- Credit holds or budget freeze — your CSM should surface these early
- Invoice errors — every wrong invoice extends payment by 30-60 days
Accounts Payable: Pay Slower
Standard terms by vendor type:
SaaS tools: Net 30 default. Push for Net 45 or Net 60 at scale.
Cloud providers: Pay as you go. Apply for credits first.
Professional services (agencies, lawyers): Net 30 minimum. Get Net 45 where possible.
Rent/office: Whatever the lease says. Negotiate quarterly payments if you can.
Payroll: Pay on time. Never delay payroll. Ever.Early payment discount trap:
"2/10 Net 30" means: 2% discount if you pay in 10 days, else pay in 30.
Annual cost of NOT taking this: 2% × (365/(30-10)) = ~36% APY
ALWAYS take early payment discounts > 2%.
Never take discounts < 1%.AP workflow:
- All invoices → finance inbox (not individual employees)
- Approval required above threshold ($500 for startups)
- Pay at end of terms, not when invoice arrives
- Batch payments weekly (not daily) to reduce processing overhead
4. Runway Extension Tactics
Use these when you need to extend runway without raising. Ranked by speed and impact.
Tier 1: Fast Cash (Days)
Annual billing campaign:
Target: Existing monthly customers
Offer: 2 months free (16% discount) or 1 month free (8% discount) for annual upfront
Process: CSM-led email campaign to all monthly customers
Impact: $X MRR × 12 × conversion rate = immediate cash injection
Timeline: 2-4 weeks
No dilution. No debt. High impact.Prepayment incentive for pipeline:
For deals in late stage, offer annual upfront pricing with 10-15% discount.
Close rate may increase. Cash timing dramatically improves.Tier 2: Cost Control (2-4 Weeks)
Hiring freeze:
Every unfilled role = salary × 1.25 per month.
For a 30-person company, 3 open roles at $150K average:
Monthly savings: 3 × $150K × 1.25 / 12 = $47K/month
Over 6 months: $280K
Impact: Immediate. No blood.Software audit:
Pull all credit card charges and ACH debits.
Cancel any subscription not used in 30 days.
Typical savings: $3K-$15K/month at Series A stage.
Tools: Vendr, Spendesk, or just a spreadsheet of recurring charges.Cloud cost optimization:
Right-size instances (dev/staging don't need prod-scale)
Reserve instances (1-year reserved = 30-40% savings vs on-demand)
Delete unused resources (load balancers, IPs, old snapshots)
Typical savings: 20-35% of current cloud billTier 3: Vendor Renegotiation (2-6 Weeks)
Payment term extension:
Ask key vendors for Net 60 instead of Net 30.
$500K in AP × 30 days = $500K × (30/365) = ~$41K cash float improvement
Won't always work, but vendors often say yes to good customers.Renewal timing:
Push annual renewals to later in the year.
Preserve cash for Q1 (typically heaviest sales hiring quarter).Vendor credits:
AWS: AWS Activate (up to $100K for qualified startups)
GCP: Google for Startups (up to $200K)
Azure: Microsoft for Startups (up to $150K)
Stripe: Revenue share programs
Hubspot: Startup pricing (90% off)Tier 4: Financing (Weeks to Months)
Revenue-based financing:
Providers: Clearco, Capchase, Pipe, Arc
Structure: Advance 3-6 months of MRR. Repay with % of monthly revenue.
Cost: Typically 6-12% annualized.
Speed: 1-2 weeks to close.
When to use: Bridge to next ARR milestone before raising equity.
When NOT to use: When burn rate is structural (will consume the advance fast).Venture debt:
Providers: SVB (now First Citizens), Western Technology Investment, Hercules, TriplePoint
Structure: Term loan, typically 3-6x monthly gross burn
Interest: Prime + 2-4% + warrants
When available: Post-Series A, when revenue is predictable
Typical timing: Add alongside an equity round (don't raise debt when you need equity)
Impact: Extends runway 3-6 months without dilution
When NOT to use: If you might trip financial covenants (minimum cash, revenue)Convertible bridge:
Existing investors write bridge note: $500K-$2M at favorable terms.
Structure: Converts at discount (10-20%) or cap into next equity round.
When to use: You're 60-90 days from closing an equity round and need cash to get there.
When NOT to use: As a long-term strategy. Bridge-to-bridge is a death spiral.Tier 5: Structural Cost Reduction (Weeks + Impact on Morale)
Salary deferrals (founders first):
Founders take 20-30% salary reduction, accrued for future repayment.
Signals commitment to team and investors.
Only ask employees to follow if founders go first.
Always pay market rate to key non-founder employees — you can't afford to lose them.Reduction in force (RIF):
Threshold: If burn multiple > 3x and growth < 20% YoY, a RIF is likely necessary.
Sizing: Model to achieve at least 12 months runway without fundraising.
Rule: Don't do a RIF twice. Size it right the first time.
Two small RIFs destroy morale worse than one decisive one.
Process: Legal counsel required. WARN Act (60-day notice) if > 100 employees.
Focus cuts: G&A and underperforming sales roles first. Protect engineering and key revenue.5. When to Cut vs When to Invest
The Framework
Cut when:
- Burn multiple > 2x and growth is decelerating
- Runway < 9 months with no fundraise imminent
- LTV:CAC declining for 3+ consecutive months
- Any spend category with no measurable return in 90 days
- Headcount in functions not directly tied to near-term revenue or product-market fit
Invest when:
- Magic number > 1 (every dollar in S&M returns > $1 in gross profit)
- LTV:CAC > 3x in a specific channel (pour money in)
- Gross margin > 70% (unit economics are healthy; growth is the constraint)
- Cohort data improving (retention getting better → LTV going up → invest in growth)
- CAC payback < 12 months (you get your money back fast enough to keep reinvesting)
The False Economy Trap
Don't cut:
- Top-of-funnel demand gen that generates qualified pipeline (if CAC payback is < 12 months, this is your best investment)
- Engineering capacity on core product (technical debt compounds and slows you down permanently)
- Key account managers on your largest customers (churn from top customers is catastrophic)
Cut these first:
- Conference sponsorships with no measurable pipeline
- Tools and subscriptions with < 5 users or < 30% utilization
- Agency spend that could be done in-house
- Roadmap items that aren't tied to retention or expansion revenue
- Any G&A spend that isn't legally required
Decision Triggers (Pre-Define These)
Don't make these decisions in a crisis. Define the triggers now:
At 12 months runway: Review all discretionary spend. Start fundraise process.
At 9 months runway: Implement hiring freeze. Fundraise is mandatory.
At 6 months runway: Cut non-essential spend 20%. If no fundraise term sheet, run RIF model.
At 4 months runway: Execute RIF. Explore all financing options. Notify board.
At 3 months runway: Emergency plan only. All options on table (bridge, strategic, wind down).Key Formulas
# Net burn
net_burn = gross_burn - revenue_collected
# Runway (months)
runway_months = cash_balance / net_burn
# Cash conversion cycle
ccc = days_sales_outstanding + days_inventory_held - days_payable_outstanding
# Lower CCC = better cash efficiency
# Days Sales Outstanding (DSO)
dso = (accounts_receivable / revenue) * 30 # monthly revenue
# Days Payable Outstanding (DPO)
dpo = (accounts_payable / cogs) * 30 # target: maximize this
# Working capital
working_capital = current_assets - current_liabilities
# Quick ratio (liquidity)
quick_ratio_liquidity = (cash + ar) / current_liabilities
# Target: > 1.5 (you can pay short-term obligations without selling assets)
# Free cash flow
fcf = operating_cash_flow - capex Financial Planning Reference
Startup financial modeling frameworks. Build models that drive decisions, not models that impress investors.
1. Startup Financial Modeling
Bottoms-Up vs Top-Down
Top-down model (don't use for operating):
TAM = $10B
SOM = 1% = $100M
Revenue = $100M in year 5This is marketing. You cannot manage a company against these numbers.
Bottoms-up model (use this):
Year 1 Revenue Build:
Sales headcount: 3 AEs by Q1, +2 in Q2, +3 in Q4
Ramp curve: Month 1-3 = 25%, Month 4-6 = 75%, Month 7+ = 100%
Quota per ramped AE: $600K ARR
Effective quota (weighted for ramp): $1.2M ARR in Year 1
Win rate: 25%
Average deal: $48K ACV
Pipeline needed: $1.2M / 25% = $4.8M ARR pipeline
Required meetings to create that pipeline: $4.8M / (conversion 20%) / ($48K ACV × 0.5 to meeting) = ~200 meetingsNow you have something actionable. You know how many SDR calls, how many marketing leads, what conversion rate you need to hold. Every assumption is visible and challengeable.
Building the Operating Model
Revenue Engine
New ARR Model (SaaS):
Month N New ARR:
= Quota-carrying reps (fully ramped equivalent)
× Attainment rate (typically 70-80% of quota)
× Average deal size
+ PLG / self-serve (if applicable)
Quota-carrying reps (ramped equivalent):
= Sum(each rep × their ramp factor)
Ramp schedule:
Month 1-2: 0% (onboarding)
Month 3: 25%
Month 4-6: 50%
Month 7-9: 75%
Month 10+: 100%ARR Bridge (most important recurring visual):
Beginning ARR
+ New ARR (new logos)
+ Expansion ARR (upsells, seat growth)
- Churned ARR (cancellations)
- Contraction ARR (downgrades)
= Ending ARR
Net ARR Added = New + Expansion - Churn - Contraction
Net Dollar Retention (NDR):
= (Beginning ARR + Expansion - Churn - Contraction) / Beginning ARR × 100
Target: > 110% for growth-stage SaaS
World-class: > 130% (Snowflake, Twilio-tier)MRR and ARR Relationship:
ARR = MRR × 12 (simple, always use this)
Never mix monthly and annual contracts in MRR without normalization.
Annual contract booked = ACV / 12 = monthly contribution to ARR
Multi-year contracts: book each year at annual value (not multi-year total)Headcount Model
Headcount is usually 60-80% of total costs. Model it carefully.
For each role:
- Start date
- Department
- Annual salary (from salary bands)
- Loaded cost (salary × 1.25-1.45 depending on benefits + recruiting method)
- Productive from (ramp period)
- Impact on revenue (for revenue-generating roles)
Total headcount cost = Σ (each FTE × loaded cost × months active / 12)Department headcount ratios (Series A benchmarks):
Sales (S&M): 20-30% of headcount
Engineering/Product (R&D): 40-50% of headcount
Customer Success: 15-20% of headcount
G&A: 10-15% of headcountCOGS Model
Gross margin is the most important long-term indicator of business quality.
COGS for SaaS:
1. Hosting / Infrastructure (AWS, GCP, Azure)
- Scale with customer count or usage
- Should be 5-15% of ARR for mature SaaS
- If > 20%: infrastructure optimization needed
2. Customer Success headcount
- Ratio: 1 CSM per $1M-$3M ARR (varies by segment)
- SMB: 1 CSM per $500K ARR (high-touch required)
- Enterprise: 1 CSM per $2-5M ARR (strategic accounts)
3. Third-party licensing / APIs
- Per-customer or usage-based pass-through costs
- Critical to model at scale (margin killer if not tracked)
4. Payment processing
- 2.2-2.9% of revenue for Stripe/Braintree
- Can negotiate to 1.8-2.2% at scale (> $5M ARR)Gross Margin targets:
SaaS: > 65% acceptable, > 75% good, > 80% exceptional
Marketplace: 50-70%
Hardware + software: 40-60%
Services + software: 30-50%If gross margin < 65%:
- Infrastructure cost optimization (rightsizing, reserved instances)
- CS headcount review (automation, pooled CSMs)
- Pricing model review (usage-based pricing if cost is usage-driven)
- Third-party cost renegotiation
Opex Model
Sales & Marketing:
- AE/SDR/SE salaries + OTE (on-target earnings)
- Marketing programs (demand gen budget)
- Tools and technology (CRM, SEO, ads platforms)
- Events and travel
- Benchmark: 40-60% of revenue at growth stage, targeting < 30% at scale
Research & Development:
- Engineering salaries
- Product management
- Design
- Technical infrastructure for development
- Benchmark: 20-35% of revenue
General & Administrative:
- Finance, legal, HR, admin
- Office costs
- SaaS tools / software licenses
- D&O insurance
- Benchmark: 8-15% (target < 10% at scale)Financial Model Do's and Don'ts
| Do | Don't |
|---|---|
| Build assumptions tab with all inputs | Hardcode numbers in formulas |
| Model monthly (not quarterly) at early stage | Use annual model for first 3 years |
| Start with headcount plan, build costs from it | Guess at expense line items |
| Show model to actual customers or users | Show model to investors before internal stress-test |
| Version your model | Overwrite old versions |
| Reconcile cash flow to P&L monthly | Trust P&L without cash flow model |
| Include a sensitivity table | Present single-scenario forecast |
2. Three-Statement Model for Startups
Why All Three Matter
The P&L tells you if you're profitable. The cash flow statement tells you if you're alive. The balance sheet tells you if you're solvent.
Startups that only track P&L miss the gap between revenue recognition and cash collection.
P&L Structure
Q1 Q2 Q3 Q4 FY
Revenue
Subscription ARR $400K $520K $680K $840K $2,440K
Professional Svcs $40K $50K $60K $65K $215K
Total Revenue $440K $570K $740K $905K $2,655K
COGS
Infrastructure $35K $42K $52K $62K $191K
CS Headcount $75K $75K $100K $100K $350K
3rd Party Licensing $15K $18K $22K $28K $83K
Total COGS $125K $135K $174K $190K $624K
Gross Profit $315K $435K $566K $715K $2,031K
Gross Margin 71.6% 76.3% 76.5% 79.0% 76.5%
Operating Expenses
Sales & Marketing $380K $420K $480K $520K $1,800K
Research & Dev $320K $340K $380K $400K $1,440K
General & Admin $120K $130K $140K $150K $540K
Total Opex $820K $890K $1000K $1070K $3,780K
EBITDA ($505K) ($455K) ($434K) ($355K) ($1,749K)
EBITDA Margin (114.8%)(79.8%) (58.6%) (39.2%) (65.9%)Cash Flow Statement
Q1 Q2 Q3 Q4
Operating Activities
Net Income ($510K) ($460K) ($440K) ($360K)
Add: D&A $8K $8K $8K $10K
Working Capital Changes:
AR increase ($45K) ($50K) ($60K) ($55K)
AP increase $20K $15K $20K $15K
Deferred Rev change $80K $60K $80K $90K
Operating Cash Flow ($447K) ($427K) ($392K) ($300K)
Investing Activities
Capex ($15K) ($8K) ($10K) ($12K)
Free Cash Flow ($462K) ($435K) ($402K) ($312K)
Financing Activities
None $0 $0 $0 $0
Net Change in Cash ($462K) ($435K) ($402K) ($312K)
Beginning Cash $3,500K $3,038K $2,603K $2,201K
Ending Cash $3,038K $2,603K $2,201K $1,889K
Runway (months) 13.1 12.1 10.9 10.1Key insight from this model: The deferred revenue offset (customers paying annually upfront) is reducing cash burn by ~$80-90K/quarter versus a pure monthly billing model. This is the CFO's lever — push for annual billing.
Balance Sheet: The Startup Version
At early stage, track these specifically:
Assets:
Cash: Your lifeline. Monitor daily.
Accounts Receivable: What customers owe you. Age it monthly.
Prepaid Expenses: Software licenses, insurance paid upfront.
Liabilities:
Accounts Payable: What you owe vendors. Maximize terms.
Accrued Liabilities: Salaries owed, commissions earned but not paid.
Deferred Revenue: Customer prepayments. Liability until service delivered, but cash is yours.
Debt/Convertible Notes: Face value + interest accrual.
Equity:
Common Stock: Founder shares
Preferred Stock: Investor shares
APIC: Additional paid-in capital
Accumulated Deficit: Your running losses (expected for startups)3. SaaS Metrics That Matter
The Hierarchy of SaaS Metrics
Tier 1 (existential): ARR, Runway, Net Dollar Retention
Tier 2 (strategic): Gross Margin, Burn Multiple, LTV:CAC
Tier 3 (operational): CAC Payback, Churn Rate, ACV
Tier 4 (diagnostic): Logo Churn vs Revenue Churn, Expansion Rate, NPSNever report Tier 4 metrics to your board if Tier 1 metrics are off-track.
Core Metric Definitions
ARR (Annual Recurring Revenue):
ARR = Sum of all active annual contract values (normalized to annual)
What it is NOT: bookings, billings, or TCV
When to use MRR: Companies with mostly monthly contracts
When to use ARR: Companies with majority annual contractsNet Dollar Retention (NDR / NRR):
NDR = (Beginning MRR + Expansion MRR - Churned MRR - Contraction MRR)
/ Beginning MRR × 100
The benchmark everyone quotes: 100% means existing customers are flat.
> 100% means existing customers grow revenue on their own.
World-class (Snowflake, Datadog): 130%+
Why it matters: NDR > 100% means revenue growth even if you sign zero new customers.
At NDR = 120% and $5M ARR: you will reach $7M ARR in 24 months without a single new sale.Gross Revenue Retention (GRR):
GRR = (Beginning MRR - Churned MRR - Contraction MRR) / Beginning MRR × 100
GRR measures the floor of your retention (ignoring expansion).
GRR is always ≤ NDR.
Target: > 85% for SMB SaaS, > 90% for mid-market, > 95% for enterprise.Logo Churn vs Revenue Churn:
Logo churn: % of customers who cancel (ignores size)
Revenue churn: % of ARR that cancels (accounts for size)
Why the distinction matters:
You could have 10% logo churn but 3% revenue churn (churning small customers)
Or 5% logo churn but 12% revenue churn (churning large customers) — much worse
Report both. If they diverge significantly, investigate immediately.ACV (Annual Contract Value):
ACV = Total contract value / contract term in years
Not to be confused with ARR (which only counts recurring, not one-time fees)
Rising ACV: You're moving upmarket (good for efficiency, check if ICP is changing)
Falling ACV: You're moving downmarket (check burn multiple — may not be economic)Rule of 40:
Rule of 40 = Revenue Growth Rate % + EBITDA Margin %
Target: > 40%
Example: 60% growth + (-15%) EBITDA margin = 45. Passing.
Example: 20% growth + 5% EBITDA margin = 25. Failing at growth stage.
At early stage (< $5M ARR): Rule of 40 doesn't apply. Growth is the only metric.
At growth stage ($5-20M ARR): Starting to matter.
At scale ($20M+ ARR): Board and investors will hold you to this.4. FP&A for Startups: What to Measure When
Metrics by Stage
Pre-seed / Seed (< $1M ARR):
Focus on: Cash, pipeline, customer conversations
Measure: Monthly cash burn, weeks of runway, NPS / customer satisfaction
Don't obsess over: EBITDA margin, gross margin (too early)
Frequency: Weekly cash check, monthly everything elseSeries A ($1-5M ARR):
Focus on: Repeatable sales, unit economics
Measure: MRR growth, LTV:CAC, CAC payback by channel, gross margin
Don't obsess over: Profitability, G&A efficiency
Build now: Monthly financial close (< 5 business days), basic FP&A model
Frequency: Monthly board pack, weekly leadership metricsSeries B ($5-20M ARR):
Focus on: Scalable go-to-market, operational efficiency
Measure: NDR, burn multiple, revenue per FTE, OKR attainment
Start building: Budget vs actuals, department-level P&L
Build now: Finance team (first financial controller), ERP or NetSuite
Frequency: Monthly board pack + quarterly deep diveSeries C+ ($20M+ ARR):
Focus on: Path to profitability, market leadership
Measure: Rule of 40, free cash flow, CAC efficiency by segment
Must have: FP&A team, full three-statement model, 5-year plan
Frequency: Monthly financial close (< 3 business days), quarterly earnings prepReporting Cadence
Weekly (CFO + leadership):
- Cash balance (CFO checks daily, reports weekly)
- Pipeline / sales metrics (if in a sales-led motion)
- Any metric that changed dramatically vs. prior week
Monthly (board + leadership):
- Full financial dashboard (ARR, gross margin, burn, runway)
- Budget vs actual with explanations for > 10% variances
- Unit economics update
- Headcount change summary
Quarterly (board + investors):
- Full three-statement model vs budget
- Cohort analysis update
- Scenario planning review and trigger assessment
- Next quarter outlook
5. Budget vs Actual Analysis Framework
The Purpose of BvA
Budget vs actual is not about being right. It's about understanding why you were wrong, so you can make better decisions.
The CFO who reports "we missed budget by 15%" without explanation is failing. The CFO who says "we missed budget by 15% because enterprise deals took 30 more days to close than modeled — here's what we're doing about it" is doing their job.
BvA Template
Category Budget Actual $ Var % Var Explanation
-------------------------------------------------------------------
ARR $2,400K $2,280K ($120K) (5%) 2 enterprise deals slipped to Q1
New ARR $400K $350K ($50K) (13%) Above
Expansion ARR $120K $140K $20K 17% PLG motion outperforming
Churn ($60K) ($80K) ($20K) (33%) 2 unexpected SMB churns (now fixed)
Gross Margin 75.0% 73.2% -1.8% n/a Infrastructure over-provisioned
S&M Spend $820K $840K ($20K) (2%) Within tolerance
R&D Spend $680K $710K ($30K) (4%) Backfill hire started month early
G&A Spend $140K $148K ($8K) (6%) Legal fees for new customer contract
Cash Burn (net) $580K $648K ($68K) (12%) Driven by ARR shortfall + costs
Runway (mo) 14.5 13.0 (1.5) n/a Tracking; fundraise target unchangedVariance Thresholds
< ±5%: Note in appendix, no explanation needed in main pack
5-10%: One-line explanation required
> 10%: Full paragraph: what happened, why, what changes
> 20%: Board conversation required (model assumption was wrong, or unexpected event)Forecasting vs Budgeting
Budget: Set at start of year. Fixed expectation. Updated quarterly. Forecast: Rolling 3-month outlook. Updated monthly. Should converge with budget over time.
Common mistake: Treating forecast as wishful thinking ("what we hope happens")
Correct approach: Forecast is your best current estimate given all known information.
If forecast diverges from budget by > 15%, the budget is wrong.
Reforecast and communicate to board.Rolling forecast (recommended for startups):
Always have a 12-month forward model.
Update it monthly with actuals replacing the first month.
The forecast should always reflect your current operational reality, not your hope.Key Formulas Reference
# ARR and growth
ARR_growth_yoy = (ending_ARR - beginning_ARR) / beginning_ARR
# Net Dollar Retention
NDR = (beginning_MRR + expansion_MRR - churn_MRR - contraction_MRR) / beginning_MRR
# Burn Multiple
burn_multiple = net_cash_burn / net_new_ARR
# Rule of 40
rule_of_40 = revenue_growth_pct + ebitda_margin_pct
# LTV (SaaS)
LTV = (ARPA * gross_margin_pct) / monthly_churn_rate
# CAC Payback (months)
cac_payback = CAC / (ARPA * gross_margin_pct)
# Magic Number (sales efficiency)
magic_number = (net_new_ARR * 4) / prior_quarter_S_and_M_spend
# Gross margin
gross_margin = (revenue - COGS) / revenue
# Quick Ratio (growth efficiency)
quick_ratio = (new_MRR + expansion_MRR) / (churned_MRR + contraction_MRR)
# Target: > 4 for high-growth SaaS Fundraising Playbook
From timing to close. What investors actually look for, how valuation works, and the term sheet clauses that matter.
1. When to Raise
Optimal timing:
Target: 18-24 months runway post-close
Minimum: 12 months runway post-close (leaves no buffer for slip)
Start process when: 9-12 months runway remaining
→ 3-6 months for process (typically 4-5 months for Series A/B)
→ Leaves 3-6 months buffer if process drags
Never start when: < 6 months runway
→ You're negotiating from desperation
→ Investors can smell it
→ Terms get worse, or you don't close at allRule: Your leverage is maximum when you don't need to raise. Raise from a position of momentum, not necessity.
2. What Investors Look For at Each Stage
Pre-seed
- Team (are these people credible for this problem?)
- Problem clarity (is the problem real and meaningful?)
- Early signal (any customers paying, waitlist, prototype)
- Market size (worth building a VC-scale company?)
Typical ask: $500K–$2M | Typical valuation: $3M–$10M pre-money
Seed
- Product-market signal (customers using and paying)
- Founding team with domain expertise
- ARR: $100K–$1M (or strong usage for PLG)
- Clear hypothesis for what Series A looks like
Typical ask: $2M–$5M | Typical valuation: $8M–$20M pre-money
Series A
Investors are buying a repeatable sales motion. Not just customers — a machine.
What they need to see:
- ARR: $1M–$5M growing > 100% YoY
- LTV:CAC > 2.5x (and improving)
- Net Dollar Retention > 100%
- CAC Payback < 18 months
- Gross margin > 65%
- At least 5-10 reference customers (not just lighthouse)
- Sales motion that converts without the founder closing every deal
Typical ask: $8M–$15M | Typical valuation: $25M–$60M pre-money
Series B
Investors are buying scalable go-to-market. Can you pour fuel on the fire?
What they need to see:
- ARR: $5M–$20M growing > 100% YoY
- LTV:CAC > 3x, CAC Payback < 18 months
- Sales capacity model (hiring plan → pipeline → revenue)
- NDR > 110% (expansion motion working)
- Some proof of market expansion (new segments, geographies, use cases)
- Path to category leadership
Typical ask: $15M–$40M | Typical valuation: $60M–$200M pre-money
Series C and Beyond
Investors are buying market leadership and path to profitability.
What they need to see:
- ARR: $20M+ (often $30-50M for credible Series C)
- Rule of 40 > 40 (or credible path)
- Gross margin > 70%
- NDR > 115%
- Evidence of market leadership (brand, win rates, analyst mentions)
- Clear path to $100M+ ARR
3. Valuation Methods
Revenue Multiples (Primary Method for SaaS)
Pre-money Valuation = ARR × Revenue Multiple
Revenue multiple benchmarks (2024-2025):
> 100% YoY growth: 8x–15x ARR
50-100% YoY growth: 4x–8x ARR
20-50% YoY growth: 2x–4x ARR
< 20% YoY growth: 1x–2x ARR
Adjustments:
NDR > 120%: +1x–2x premium
Gross margin > 75%: +0.5x–1x premium
Burn multiple < 1x: +0.5x–1x premium
Capital efficient: Investors pay up for efficiency
Declining growth: Compress multiple aggressivelyThe Investor's Math (Know This)
Every VC has a required return. Work backwards from their constraints:
Investor targets: 3x fund return
Fund size: $200M, check size: $15M (initial), $25M (with follow-on)
Ownership at exit needed: 15%
At 15% ownership: needs $25M / 15% = $167M post-money valuation
Exit needed to return 3x on that check: $25M × 10 = $250M company value
(10x because most deals fail, winners must carry the fund)
Implication: If you think you'll exit for $150M, that VC will pass or price you accordingly.This is why Series A investors rarely lead rounds where they can't see a $300M+ exit path. It's not about your business being bad — it's about fund math.
Comparable Company Analysis
For later stages (Series B+):
1. Find 5-10 comparable public SaaS companies
2. Calculate their EV/NTM Revenue multiples (use latest data)
3. Apply a private market discount (typically 20-40% vs public comps)
4. Adjust for your growth rate relative to comps
Example (2024):
Public SaaS comps: 6x NTM Revenue (median)
Private discount: 30%
Adjusted: ~4.2x
Your NTM Revenue: $8M
Implied valuation: ~$33M pre-moneyDCF (Late Stage Only)
DCF is unreliable for early-stage startups (terminal value dominates, growth rate assumptions are fantasy). Use it as a sanity check at Series C+, not as the primary valuation method.
4. Term Sheet Breakdown
Liquidation Preference (Most Important Economic Term)
This determines who gets paid first in an exit — and how much.
1x Non-Participating Preferred (BEST for founders):
Investor gets 1x money back OR converts to common (their choice).
At acquisition: investor takes larger of {1x invested} or {% ownership × proceeds}
Example: $10M invested, exits at $100M, owns 20%
Option A: $10M (1x)
Option B: $20M (20% of $100M)
Investor takes $20M. Founders split $80M.
1x Participating Preferred (WORSE for founders):
Investor gets 1x money back AND participates in remaining proceeds.
Example: same scenario
$10M (1x) + 20% of remaining $90M = $10M + $18M = $28M
Founders split $72M instead of $80M
Cost to founders: $8M (10% of exit value)
2x Participating (RED FLAG):
Investor gets 2x back AND participates.
Only accept under duress. Push hard against this.
Full Ratchet Anti-Dilution (AVOID):
Down-round triggers full repricing of investor shares to new (lower) price.
Founders get massively diluted. Never accept if alternatives exist.Anti-Dilution Protection
Broad-based weighted average (standard):
Adjusts investor conversion price based on all dilutive securities.
Most founder-friendly anti-dilution. Accept this.
Narrow-based weighted average (slightly worse):
Same mechanism but uses smaller denominator.
Gives investors slightly more protection. Usually acceptable.
Full ratchet (avoid):
Price drops to whatever the new round prices at.
Devastating in down rounds. Fight this.Pro-Rata Rights
Standard pro-rata: Investor can maintain their % ownership in future rounds.
Reasonable. Accept for major investors.
Super pro-rata: Investor can increase their % in future rounds.
Caps your ability to bring in new lead investors.
Avoid unless the investor is exceptional and you want them in future rounds.
Major investor threshold: Typically investors with > $500K–$1M check get pro-rata.
Don't give pro-rata to every small check — clogs future rounds.Board Composition
Seed (3 members): 2 founders, 1 lead investor
Series A (5 members): 2 founders, 2 investors, 1 independent
Series B (5-7 seats): Watch for investor majority — negotiate hard
Rule: Founders should retain majority through Series A.
Independent director should be your choice, not investor's.
Never accept investor majority before Series C.
Board observer rights: Common for smaller investors. No vote but present in meetings.
Limit to 1-2 observers or meetings become unwieldy.Other Terms That Matter
Drag-along: Majority can force minority shareholders to vote for acquisition.
Standard and reasonable. Check what threshold triggers drag.
Information rights: Investors get financial statements.
Standard. Monthly for major investors, quarterly for others.
Redemption rights: Investors can force buyback after X years.
Push to remove or add carve-outs for insufficient funds.
No-shop clause: You can't shop the term sheet to other investors.
Standard (14-30 days). Reasonable.
Exclusivity: Stronger version of no-shop. Sometimes includes no other fundraise discussions.
Acceptable for 30 days; push back on > 45 days.5. Cap Table Management
Dilution Planning Model
Run this before every round. Know your number before walking into any negotiation.
Pre-Seed Post-Seed Post-A Post-B Post-C
Founder A 45.0% 36.0% 26.5% 21.2% 18.7%
Founder B 45.0% 36.0% 26.5% 21.2% 18.7%
Angel 1 5.0% 4.0% 2.9% 2.4% 2.1%
Angel 2 5.0% 4.0% 2.9% 2.4% 2.1%
Seed Fund - 12.0% 8.8% 7.1% 6.2%
Option Pool - 8.0% 12.0% 10.0% 8.0%
Series A - - 20.4% 16.3% 14.4%
Series B - - - 19.5% 17.2%
Series C - - - - 12.6%
Round size / pre-money:
Pre-Seed: $500K / $9M pre = 5% dilution
Seed: $2M / $8M pre = 20% dilution (includes 8% pool)
Series A: $10M / $38M pre = 20.8% dilution (pool refresh to 12%)
Series B: $20M / $80M pre = 20% dilution
Series C: $30M / $170M pre = 15% dilutionOption pool shuffle: Investors often require you to create/expand the option pool before the round closes, which dilutes existing shareholders (not the incoming investor). Model this explicitly — a 20% round with a 5% pool expansion is really 24%+ dilution to founders.
Cap Table Hygiene
Tools: Carta, Pulley, Capshare (all acceptable)
Never: Track cap table in a spreadsheet past seed stage. Errors compound.
Keep it clean:
- Repurchase departed co-founder shares immediately (don't let unvested shares linger)
- Convert SAFEs to equity cleanly at each priced round
- Document every grant with a board resolution
- Cliff + vesting for ALL employees and founders (standard: 1-year cliff, 4-year vest)
- 409A valuation required before every option grant (IRS requirement)6. Data Room Preparation
Core Documents (Required)
Financial:
□ 3 years historical financials (or all history if < 3 years)
□ Monthly P&L and cash flow (last 24 months)
□ Current financial model (18-24 months forward)
□ Budget vs actual (last 4 quarters)
□ Cap table (fully diluted, with all SAFEs/convertibles modeled)
□ Bank statements (last 3-6 months)
Legal:
□ Certificate of incorporation + all amendments
□ All prior financing documents (SAFEs, convertible notes, stock purchase agreements)
□ Cap table (Carta/Pulley export)
□ IP assignment agreements (all founders and employees)
□ Material contracts (top 10 customers, key vendors)
□ Employee list (titles, start dates, salaries, equity grants)
Product & Business:
□ Product demo / walkthrough video
□ Architecture overview (for technical investors)
□ Customer case studies (3-5 named references)
□ NPS / CSAT data
□ Competitive landscape analysis
Metrics:
□ MRR/ARR by month (all history)
□ Cohort retention chart
□ CAC by channel
□ LTV by cohort
□ NPS trendWhat Investors Actually Check First
In order of typical priority during due diligence:
- Cap table — Is it clean? Any concerning structures?
- Cohort retention — Is churn improving or deteriorating?
- Revenue quality — What % is recurring? Any one-time or non-recurring?
- Top 10 customers — Concentration risk? Any logos at risk?
- Bank statements — Does cash match what was reported?
- IP assignments — Does the company own its IP? (Founders who didn't assign IP kill deals)
Red Flags That Kill Deals
- Missing IP assignment agreements for founders (most common deal killer at early stage)
- Cap table with > 20 angels/small investors (messy, hard to get consent for future rounds)
- Customer concentration > 30% in single customer without explanation
- Revenue recognition issues (booking ARR on contracts that allow easy cancellation)
- Cohort data that gets worse in later cohorts
- Bank balance doesn't match reported cash position
7. Investor Communication Cadence
During Fundraise
Week 1-2: Warm intro sourcing, LP/network mapping
Week 3-6: First meetings (aim for 20-30 first meetings)
Week 7-10: Partner meetings, deep dives, due diligence
Week 11-14: Term sheets, negotiation
Week 15-18: Legal, closingParallel process is essential. Never negotiate with one investor at a time. Competition is your leverage.
Post-Close: Investor Updates
Monthly investor update (send within 10 days of month-end):
Subject: [Company] Monthly Update — [Month Year]
Highlights (3 bullets max):
• [Biggest win]
• [Biggest learning/challenge]
• [What we're focused on next month]
Metrics:
ARR: $X (+X% MoM)
Net new ARR: $X
Gross margin: X%
Cash: $X (X months runway)
Headcount: X
Asks (be specific):
• Looking for intro to [persona/company] for [specific reason]
• Need advisor with experience in [specific area]
• [Other concrete ask]Why this matters: Investors who are informed and engaged are better positioned to help when you need it. The investor who hasn't heard from you in 6 months is less likely to write a bridge check or make a warm intro when you ask.
Key Formulas
# Post-money valuation
post_money = pre_money + investment_amount
# Investor ownership %
ownership_pct = investment_amount / post_money
# Dilution to existing shareholders
dilution = investment_amount / post_money # as a fraction
# New shares issued
new_shares = (investment_amount / post_money) * total_post_shares
# equivalent: new_shares = pre_money_shares * (investment_amount / pre_money)
# Option pool expansion impact (pool shuffle)
# Creating X% option pool pre-close dilutes founders:
pool_shares_needed = target_pct * (pre_shares + new_round_shares + pool_shares_needed)
# Solve: pool_shares_needed = target_pct * (pre_shares + new_round_shares) / (1 - target_pct)
# LTV:CAC ratio
ltv_cac = ltv / cac # target: > 3x
# CAC payback (months)
payback_months = cac / (arpa * gross_margin_pct) #!/usr/bin/env python3
"""
Burn Rate & Runway Calculator
==============================
Models startup runway across base/bull/bear scenarios, incorporating
a hiring plan and revenue trajectory. Outputs months of runway,
cash-out dates, and decision trigger points.
Usage:
python burn_rate_calculator.py
python burn_rate_calculator.py --csv # export to CSV
Stdlib only. No dependencies.
"""
import argparse
import csv
import io
import sys
from dataclasses import dataclass, field
from datetime import date, timedelta
from typing import Optional
# ---------------------------------------------------------------------------
# Data structures
# ---------------------------------------------------------------------------
@dataclass
class HiringEntry:
"""A planned hire."""
month: int # months from model start (1-indexed)
role: str
department: str # "sales", "engineering", "cs", "ga"
annual_salary: float
benefits_pct: float = 0.22 # benefits as % of salary
recruiting_cost: float = 0.0 # one-time recruiting fee
@dataclass
class RevenueEntry:
"""Monthly revenue data point (historical or projected)."""
month: int
mrr: float # monthly recurring revenue
one_time: float = 0.0
@dataclass
class ModelConfig:
"""Master configuration for a runway scenario."""
name: str
starting_cash: float
starting_mrr: float
starting_headcount: int
avg_loaded_salary: float # average fully-loaded salary per current employee
base_non_headcount_opex: float # monthly non-headcount costs (infra, tools, etc.)
gross_margin_pct: float # 0.0–1.0
mrr_growth_rate: float # monthly MoM growth rate, 0.0–1.0
hiring_plan: list[HiringEntry] = field(default_factory=list)
model_months: int = 24
start_date: Optional[date] = None
@dataclass
class MonthResult:
"""Single month output."""
month: int
label: str # e.g. "Month 1 (Apr 2025)"
mrr: float
gross_profit: float
headcount: int
headcount_cost: float # total loaded headcount cost this month
other_opex: float
gross_burn: float
net_burn: float
cash_start: float
cash_end: float
runway_months: float # projected runway from this month
cumulative_new_arr: float # for burn multiple
# ---------------------------------------------------------------------------
# Core calculator
# ---------------------------------------------------------------------------
class RunwayCalculator:
def __init__(self, config: ModelConfig):
self.cfg = config
def run(self) -> list[MonthResult]:
cfg = self.cfg
results = []
# Build headcount schedule: month -> list of new hires starting that month
hire_by_month: dict[int, list[HiringEntry]] = {}
for h in cfg.hiring_plan:
hire_by_month.setdefault(h.month, []).append(h)
# Track existing employees
active_employees: list[dict] = []
for _ in range(cfg.starting_headcount):
active_employees.append({
"monthly_loaded": cfg.avg_loaded_salary / 12 * 1.0,
"start_month": 0,
})
cash = cfg.starting_cash
mrr = cfg.starting_mrr
cumulative_new_arr = 0.0
starting_mrr = cfg.starting_mrr
for m in range(1, cfg.model_months + 1):
# Process new hires this month
one_time_recruiting = 0.0
if m in hire_by_month:
for hire in hire_by_month[m]:
monthly_loaded = (
hire.annual_salary * (1 + hire.benefits_pct) / 12
)
active_employees.append({
"monthly_loaded": monthly_loaded,
"start_month": m,
})
one_time_recruiting += hire.recruiting_cost
# Revenue this month
mrr = mrr * (1 + cfg.mrr_growth_rate)
gross_profit = mrr * cfg.gross_margin_pct
# Headcount cost
headcount_cost = sum(e["monthly_loaded"] for e in active_employees)
headcount_cost += one_time_recruiting
# Other opex (infra, SaaS tools, office, etc.)
other_opex = cfg.base_non_headcount_opex
# Burn
gross_burn = headcount_cost + other_opex
net_burn = gross_burn - gross_profit
# Cash
cash_start = cash
cash = cash - net_burn
cash_end = cash
# Projected runway from this month (using current net burn rate)
runway = cash_end / net_burn if net_burn > 0 else float("inf")
# Cumulative new ARR (for burn multiple calc)
new_mrr_added = mrr - starting_mrr if m == 1 else mrr - results[-1].mrr
cumulative_new_arr += new_mrr_added * 12
# Label
if cfg.start_date:
month_date = date(
cfg.start_date.year,
cfg.start_date.month,
1,
) + timedelta(days=32 * (m - 1))
month_date = month_date.replace(day=1)
label = f"Month {m:02d} ({month_date.strftime('%b %Y')})"
else:
label = f"Month {m:02d}"
results.append(MonthResult(
month=m,
label=label,
mrr=mrr,
gross_profit=gross_profit,
headcount=len(active_employees),
headcount_cost=headcount_cost,
other_opex=other_opex,
gross_burn=gross_burn,
net_burn=net_burn,
cash_start=cash_start,
cash_end=cash_end,
runway_months=runway,
cumulative_new_arr=cumulative_new_arr,
))
# Stop if cash runs out
if cash_end <= 0:
break
return results
def cash_out_date(self, results: list[MonthResult]) -> Optional[str]:
"""Return the label of the month cash runs out, or None if model survives."""
for r in results:
if r.cash_end <= 0:
return r.label
return None
def burn_multiple(self, results: list[MonthResult]) -> float:
"""Burn multiple = total net burn / total net new ARR over model period."""
total_net_burn = sum(r.net_burn for r in results if r.net_burn > 0)
first_mrr = results[0].mrr / (1 + self.cfg.mrr_growth_rate) # starting mrr
total_new_arr = (results[-1].mrr - first_mrr) * 12
if total_new_arr <= 0:
return float("inf")
return total_net_burn / total_new_arr
# ---------------------------------------------------------------------------
# Reporting
# ---------------------------------------------------------------------------
def fmt_k(value: float) -> str:
"""Format as $Xk or $X.XM."""
if abs(value) >= 1_000_000:
return f"${value/1_000_000:.2f}M"
if abs(value) >= 1_000:
return f"${value/1_000:.0f}K"
return f"${value:.0f}"
def print_summary(name: str, results: list[MonthResult], calc: RunwayCalculator) -> None:
cash_out = calc.cash_out_date(results)
bm = calc.burn_multiple(results)
last = results[-1]
first = results[0]
print(f"\n{'='*60}")
print(f" SCENARIO: {name}")
print(f"{'='*60}")
print(f" Months modeled: {len(results)}")
print(f" Cash out: {cash_out or 'Does not run out in model period'}")
print(f" Ending cash: {fmt_k(last.cash_end)}")
print(f" Final runway: {last.runway_months:.1f} months")
print(f" Starting MRR: {fmt_k(first.mrr)}")
print(f" Ending MRR: {fmt_k(last.mrr)}")
print(f" Ending headcount: {last.headcount}")
print(f" Burn multiple: {bm:.2f}x")
print(f" Avg net burn: {fmt_k(sum(r.net_burn for r in results)/len(results))}/mo")
# Decision triggers
print(f"\n Decision Triggers:")
triggers = {9: "⚠️ START FUNDRAISE", 6: "🔴 COST REDUCTION PLAN", 4: "🚨 EXECUTE CUTS / BRIDGE"}
shown = set()
for r in results:
for threshold, label in triggers.items():
if r.runway_months <= threshold and threshold not in shown:
print(f" {r.label}: {label} (runway = {r.runway_months:.1f} mo)")
shown.add(threshold)
def print_monthly_table(results: list[MonthResult], max_rows: int = 24) -> None:
header = f"{'Month':<22} {'MRR':>10} {'Hdct':>6} {'Net Burn':>12} {'Cash':>12} {'Runway':>8}"
print(f"\n{header}")
print("-" * len(header))
for r in results[:max_rows]:
runway_str = f"{r.runway_months:.1f}mo" if r.runway_months != float("inf") else "∞"
print(
f"{r.label:<22} "
f"{fmt_k(r.mrr):>10} "
f"{r.headcount:>6} "
f"{fmt_k(r.net_burn):>12} "
f"{fmt_k(r.cash_end):>12} "
f"{runway_str:>8}"
)
def export_csv(scenarios: list[tuple[str, list[MonthResult]]]) -> str:
buf = io.StringIO()
writer = csv.writer(buf)
writer.writerow([
"Scenario", "Month", "Label", "MRR", "Gross Profit", "Headcount",
"Headcount Cost", "Other Opex", "Gross Burn", "Net Burn",
"Cash Start", "Cash End", "Runway Months"
])
for name, results in scenarios:
for r in results:
writer.writerow([
name, r.month, r.label,
round(r.mrr, 2), round(r.gross_profit, 2), r.headcount,
round(r.headcount_cost, 2), round(r.other_opex, 2),
round(r.gross_burn, 2), round(r.net_burn, 2),
round(r.cash_start, 2), round(r.cash_end, 2),
round(r.runway_months, 2),
])
return buf.getvalue()
# ---------------------------------------------------------------------------
# Sample data
# ---------------------------------------------------------------------------
def make_sample_configs() -> list[ModelConfig]:
"""
Sample company: Series A SaaS startup
- $3M cash on hand (post Series A)
- $125K MRR (~$1.5M ARR)
- 18 employees, $150K avg salary
- $80K/mo non-headcount opex (infra, tools, office)
- 72% gross margin
"""
common_kwargs = dict(
starting_cash=3_000_000,
starting_mrr=125_000,
starting_headcount=18,
avg_loaded_salary=150_000,
base_non_headcount_opex=80_000,
gross_margin_pct=0.72,
model_months=24,
start_date=date(2025, 1, 1),
)
# Base: 10% MoM growth, moderate hiring
base_hiring = [
HiringEntry(month=2, role="AE #1", department="sales", annual_salary=120_000, recruiting_cost=18_000),
HiringEntry(month=3, role="Senior SWE #1", department="engineering", annual_salary=160_000, recruiting_cost=24_000),
HiringEntry(month=5, role="SDR #1", department="sales", annual_salary=80_000, recruiting_cost=12_000),
HiringEntry(month=6, role="CSM #1", department="cs", annual_salary=90_000, recruiting_cost=13_500),
HiringEntry(month=8, role="AE #2", department="sales", annual_salary=120_000, recruiting_cost=18_000),
HiringEntry(month=9, role="Senior SWE #2", department="engineering", annual_salary=165_000, recruiting_cost=24_750),
HiringEntry(month=12, role="Controller", department="ga", annual_salary=130_000, recruiting_cost=19_500),
HiringEntry(month=14, role="AE #3", department="sales", annual_salary=125_000, recruiting_cost=18_750),
HiringEntry(month=15, role="ML Engineer", department="engineering", annual_salary=175_000, recruiting_cost=26_250),
HiringEntry(month=18, role="AE #4", department="sales", annual_salary=125_000, recruiting_cost=18_750),
]
# Bull: 15% MoM growth, full hiring plan
bull_hiring = base_hiring + [
HiringEntry(month=4, role="Marketing Manager", department="sales", annual_salary=110_000, recruiting_cost=16_500),
HiringEntry(month=7, role="Senior SWE #3", department="engineering", annual_salary=165_000, recruiting_cost=24_750),
HiringEntry(month=10, role="AE #5", department="sales", annual_salary=125_000, recruiting_cost=18_750),
HiringEntry(month=13, role="DevOps Engineer", department="engineering", annual_salary=150_000, recruiting_cost=22_500),
HiringEntry(month=16, role="AE #6", department="sales", annual_salary=125_000, recruiting_cost=18_750),
]
# Bear: 5% MoM growth, hiring freeze after month 3
bear_hiring = [
HiringEntry(month=2, role="AE #1", department="sales", annual_salary=120_000, recruiting_cost=18_000),
HiringEntry(month=3, role="Senior SWE #1", department="engineering", annual_salary=160_000, recruiting_cost=24_000),
]
return [
ModelConfig(name="BULL (15% MoM, full hiring)", mrr_growth_rate=0.15, hiring_plan=bull_hiring, **common_kwargs),
ModelConfig(name="BASE (10% MoM, planned hiring)", mrr_growth_rate=0.10, hiring_plan=base_hiring, **common_kwargs),
ModelConfig(name="BEAR ( 5% MoM, hiring freeze M3+)", mrr_growth_rate=0.05, hiring_plan=bear_hiring, **common_kwargs),
ModelConfig(name="DISTRESS (0% growth, freeze now)", mrr_growth_rate=0.00, hiring_plan=[], **common_kwargs),
]
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def main() -> None:
parser = argparse.ArgumentParser(description="Startup Burn Rate & Runway Calculator")
parser.add_argument("--csv", action="store_true", help="Export full monthly data as CSV to stdout")
parser.add_argument("--scenario", choices=["bull", "base", "bear", "distress", "all"], default="all")
args = parser.parse_args()
configs = make_sample_configs()
if args.scenario != "all":
configs = [c for c in configs if args.scenario.upper() in c.name.upper()]
all_results: list[tuple[str, list[MonthResult]]] = []
print("\n" + "="*60)
print(" BURN RATE & RUNWAY CALCULATOR")
print(" Sample Company: Series A SaaS Startup")
print(" Starting cash: $3M | Starting MRR: $125K | 18 employees")
print("="*60)
for cfg in configs:
calc = RunwayCalculator(cfg)
results = calc.run()
all_results.append((cfg.name, results))
print_summary(cfg.name, results, calc)
print_monthly_table(results)
# Comparison summary
print("\n" + "="*60)
print(" SCENARIO COMPARISON")
print("="*60)
print(f" {'Scenario':<40} {'Runway':>8} {'Cash Out':<30} {'Burn Mult':>10}")
print(" " + "-"*88)
for cfg, (name, results) in zip(configs, all_results):
calc = RunwayCalculator(cfg)
cash_out = calc.cash_out_date(results) or "Survives model period"
bm = calc.burn_multiple(results)
final_runway = results[-1].runway_months
runway_str = f"{final_runway:.1f}mo" if final_runway != float("inf") else "∞"
bm_str = f"{bm:.2f}x" if bm != float("inf") else "∞"
print(f" {name:<40} {runway_str:>8} {cash_out:<30} {bm_str:>10}")
print("\n Decision Trigger Reference:")
print(" 9 months runway → Start fundraise process")
print(" 6 months runway → Begin cost reduction planning")
print(" 4 months runway → Execute cuts; explore bridge financing")
print(" 3 months runway → Emergency plan only")
if args.csv:
print("\n\n--- CSV EXPORT ---\n")
sys.stdout.write(export_csv(all_results))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Fundraising Model
==================
Cap table management, dilution modeling, and multi-round scenario planning.
Know exactly what you're giving up before you walk into any negotiation.
Covers:
- Cap table state at each round
- Dilution per shareholder per round
- Option pool shuffle impact
- Multi-round projections (Seed → A → B → C)
- Return scenarios at different exit valuations
Usage:
python fundraising_model.py
python fundraising_model.py --exit 150 # model at $150M exit
python fundraising_model.py --csv
Stdlib only. No dependencies.
"""
import argparse
import csv
import io
import sys
from dataclasses import dataclass, field
from typing import Optional
# ---------------------------------------------------------------------------
# Data structures
# ---------------------------------------------------------------------------
@dataclass
class Shareholder:
"""A shareholder in the cap table."""
name: str
share_class: str # "common", "preferred", "option"
shares: float
invested: float = 0.0 # total cash invested
is_option_pool: bool = False
@dataclass
class RoundConfig:
"""Configuration for a financing round."""
name: str # e.g. "Series A"
pre_money_valuation: float
investment_amount: float
new_option_pool_pct: float = 0.0 # % of POST-money to allocate to new options
option_pool_pre_round: bool = True # True = pool created before round (dilutes founders)
lead_investor_name: str = "New Investor"
share_price_override: Optional[float] = None # if None, computed from valuation
@dataclass
class CapTableEntry:
"""A row in the cap table at a point in time."""
name: str
share_class: str
shares: float
pct_ownership: float
invested: float
is_option_pool: bool = False
@dataclass
class RoundResult:
"""Snapshot of cap table after a round closes."""
round_name: str
pre_money_valuation: float
investment_amount: float
post_money_valuation: float
price_per_share: float
new_shares_issued: float
option_pool_shares_created: float
total_shares: float
cap_table: list[CapTableEntry]
@dataclass
class ExitAnalysis:
"""Proceeds to each shareholder at an exit."""
exit_valuation: float
shareholder: str
shares: float
ownership_pct: float
proceeds_common: float # if all preferred converts to common
invested: float
moic: float # multiple on invested capital (for investors)
# ---------------------------------------------------------------------------
# Core cap table engine
# ---------------------------------------------------------------------------
class CapTable:
"""Manages a cap table through multiple rounds."""
def __init__(self):
self.shareholders: list[Shareholder] = []
self._total_shares: float = 0.0
def add_shareholder(self, sh: Shareholder) -> None:
self.shareholders.append(sh)
self._total_shares += sh.shares
def total_shares(self) -> float:
return sum(s.shares for s in self.shareholders)
def snapshot(self, label: str = "") -> list[CapTableEntry]:
total = self.total_shares()
return [
CapTableEntry(
name=s.name,
share_class=s.share_class,
shares=s.shares,
pct_ownership=s.shares / total if total > 0 else 0,
invested=s.invested,
is_option_pool=s.is_option_pool,
)
for s in self.shareholders
]
def execute_round(self, config: RoundConfig) -> RoundResult:
"""
Execute a financing round:
1. (Optional) Create option pool pre-round (dilutes existing shareholders)
2. Issue new shares to investor at round price
Returns a RoundResult with full cap table snapshot.
"""
current_total = self.total_shares()
# Step 1: Option pool shuffle (if pre-round)
option_pool_shares_created = 0.0
if config.new_option_pool_pct > 0 and config.option_pool_pre_round:
# Target: post-round option pool = new_option_pool_pct of total post-money shares
# Solve: pool_shares / (current_total + pool_shares + new_investor_shares) = target_pct
# This requires iteration because new_investor_shares also depends on pool_shares
# Simplification: create pool based on post-round total (slightly approximated)
target_post_round_pct = config.new_option_pool_pct
post_money = config.pre_money_valuation + config.investment_amount
# Estimate shares per dollar (price per share)
price_per_share = config.pre_money_valuation / current_total
new_investor_shares_estimate = config.investment_amount / price_per_share
# Pool shares needed so that pool / total_post = target_pct
total_post_estimate = current_total + new_investor_shares_estimate
pool_shares_needed = (target_post_round_pct * total_post_estimate) / (1 - target_post_round_pct)
# Check if existing pool is sufficient
existing_pool = next(
(s.shares for s in self.shareholders if s.is_option_pool), 0
)
additional_pool_needed = max(0, pool_shares_needed - existing_pool)
if additional_pool_needed > 0:
option_pool_shares_created = additional_pool_needed
# Add to existing pool or create new
pool_sh = next((s for s in self.shareholders if s.is_option_pool), None)
if pool_sh:
pool_sh.shares += additional_pool_needed
else:
self.shareholders.append(Shareholder(
name="Option Pool",
share_class="option",
shares=additional_pool_needed,
is_option_pool=True,
))
# Step 2: Price per share (after pool creation)
current_total_post_pool = self.total_shares()
if config.share_price_override:
price_per_share = config.share_price_override
else:
price_per_share = config.pre_money_valuation / current_total_post_pool
# Step 3: New shares for investor
new_shares = config.investment_amount / price_per_share
# Step 4: Add investor to cap table
self.shareholders.append(Shareholder(
name=config.lead_investor_name,
share_class="preferred",
shares=new_shares,
invested=config.investment_amount,
))
post_money = config.pre_money_valuation + config.investment_amount
total_post = self.total_shares()
return RoundResult(
round_name=config.name,
pre_money_valuation=config.pre_money_valuation,
investment_amount=config.investment_amount,
post_money_valuation=post_money,
price_per_share=price_per_share,
new_shares_issued=new_shares,
option_pool_shares_created=option_pool_shares_created,
total_shares=total_post,
cap_table=self.snapshot(),
)
def analyze_exit(self, exit_valuation: float) -> list[ExitAnalysis]:
"""
Simple exit analysis: all preferred converts to common, proceeds split pro-rata.
(Does not model liquidation preferences — see fundraising_playbook.md for that.)
"""
total = self.total_shares()
price_per_share = exit_valuation / total
results = []
for s in self.shareholders:
if s.is_option_pool:
continue # unissued options don't receive proceeds
proceeds = s.shares * price_per_share
moic = proceeds / s.invested if s.invested > 0 else 0.0
results.append(ExitAnalysis(
exit_valuation=exit_valuation,
shareholder=s.name,
shares=s.shares,
ownership_pct=s.shares / total,
proceeds_common=proceeds,
invested=s.invested,
moic=moic,
))
return sorted(results, key=lambda x: x.proceeds_common, reverse=True)
# ---------------------------------------------------------------------------
# Reporting
# ---------------------------------------------------------------------------
def fmt(value: float, prefix: str = "$") -> str:
if value == float("inf"):
return "∞"
if abs(value) >= 1_000_000:
return f"{prefix}{value/1_000_000:.2f}M"
if abs(value) >= 1_000:
return f"{prefix}{value/1_000:.0f}K"
return f"{prefix}{value:.2f}"
def print_round_result(result: RoundResult, prev_cap_table: Optional[list[CapTableEntry]] = None) -> None:
print(f"\n{'='*70}")
print(f" {result.round_name.upper()}")
print(f"{'='*70}")
print(f" Pre-money valuation: {fmt(result.pre_money_valuation)}")
print(f" Investment: {fmt(result.investment_amount)}")
print(f" Post-money valuation: {fmt(result.post_money_valuation)}")
print(f" Price per share: {fmt(result.price_per_share, '$')}")
print(f" New shares issued: {result.new_shares_issued:,.0f}")
if result.option_pool_shares_created > 0:
print(f" Option pool created: {result.option_pool_shares_created:,.0f} shares")
print(f" ⚠️ Pool created pre-round: dilutes existing shareholders, not new investor")
print(f" Total shares post: {result.total_shares:,.0f}")
print(f"\n {'Shareholder':<22} {'Shares':>12} {'Ownership':>10} {'Invested':>10} {'Δ Ownership':>12}")
print(" " + "-"*68)
prev_map = {e.name: e.pct_ownership for e in prev_cap_table} if prev_cap_table else {}
for entry in result.cap_table:
delta = ""
if entry.name in prev_map:
change = (entry.pct_ownership - prev_map[entry.name]) * 100
delta = f"{change:+.1f}pp"
elif not entry.is_option_pool:
delta = "new"
invested_str = fmt(entry.invested) if entry.invested > 0 else "-"
print(
f" {entry.name:<22} {entry.shares:>12,.0f} "
f"{entry.pct_ownership*100:>9.2f}% {invested_str:>10} {delta:>12}"
)
def print_exit_analysis(results: list[ExitAnalysis], exit_valuation: float) -> None:
print(f"\n{'='*70}")
print(f" EXIT ANALYSIS @ {fmt(exit_valuation)} (all preferred converts to common)")
print(f"{'='*70}")
print(f"\n {'Shareholder':<22} {'Ownership':>10} {'Proceeds':>12} {'Invested':>10} {'MOIC':>8}")
print(" " + "-"*65)
for r in results:
moic_str = f"{r.moic:.1f}x" if r.moic > 0 else "n/a"
invested_str = fmt(r.invested) if r.invested > 0 else "-"
print(
f" {r.shareholder:<22} {r.ownership_pct*100:>9.2f}% "
f"{fmt(r.proceeds_common):>12} {invested_str:>10} {moic_str:>8}"
)
print(f"\n Note: Does not model liquidation preferences.")
print(f" Participating preferred reduces founder proceeds in most real exits.")
print(f" See references/fundraising_playbook.md for full liquidation waterfall.")
def print_dilution_summary(rounds: list[RoundResult]) -> None:
print(f"\n{'='*70}")
print(f" DILUTION SUMMARY — FOUNDER PERSPECTIVE")
print(f"{'='*70}")
# Find all founders (common shareholders who aren't investors or option pool)
founder_names = []
for entry in rounds[0].cap_table:
if entry.share_class == "common" and not entry.is_option_pool:
founder_names.append(entry.name)
if not founder_names:
print(" No common shareholders found in initial cap table.")
return
header = f" {'Round':<16}" + "".join(f" {n:<16}" for n in founder_names) + f" {'Total Inv':>12}"
print(header)
print(" " + "-" * (16 + 18 * len(founder_names) + 14))
for result in rounds:
cap_map = {e.name: e for e in result.cap_table}
total_invested = sum(e.invested for e in result.cap_table if not e.is_option_pool)
row = f" {result.round_name:<16}"
for name in founder_names:
pct = cap_map[name].pct_ownership * 100 if name in cap_map else 0
row += f" {pct:>6.2f}% "
row += f" {fmt(total_invested):>12}"
print(row)
def export_csv_rounds(rounds: list[RoundResult]) -> str:
buf = io.StringIO()
writer = csv.writer(buf)
writer.writerow(["Round", "Shareholder", "Share Class", "Shares", "Ownership Pct",
"Invested", "Pre Money", "Post Money", "Price Per Share"])
for r in rounds:
for entry in r.cap_table:
writer.writerow([
r.round_name, entry.name, entry.share_class,
round(entry.shares, 0), round(entry.pct_ownership * 100, 4),
round(entry.invested, 2), round(r.pre_money_valuation, 0),
round(r.post_money_valuation, 0), round(r.price_per_share, 4),
])
return buf.getvalue()
# ---------------------------------------------------------------------------
# Sample data: typical two-founder Series A/B/C startup
# ---------------------------------------------------------------------------
def build_sample_model() -> tuple[CapTable, list[RoundResult]]:
"""
Sample company:
- 2 founders, started with 10M shares each
- 1M shares for early advisor
- Raises Pre-seed → Seed → Series A → Series B → Series C
"""
cap = CapTable()
SHARES_PER_FOUNDER = 4_000_000
SHARES_ADVISOR = 200_000
# Founding state
cap.add_shareholder(Shareholder("Founder A (CEO)", "common", SHARES_PER_FOUNDER))
cap.add_shareholder(Shareholder("Founder B (CTO)", "common", SHARES_PER_FOUNDER))
cap.add_shareholder(Shareholder("Advisor", "common", SHARES_ADVISOR))
rounds: list[RoundResult] = []
prev_cap = cap.snapshot()
# Round 1: Pre-seed — $500K at $4.5M pre, 10% option pool created
r1 = cap.execute_round(RoundConfig(
name="Pre-seed",
pre_money_valuation=4_500_000,
investment_amount=500_000,
new_option_pool_pct=0.10,
option_pool_pre_round=True,
lead_investor_name="Angel Syndicate",
))
rounds.append(r1)
prev_r1 = r1.cap_table[:]
# Round 2: Seed — $2M at $9M pre, expand option pool to 12%
r2 = cap.execute_round(RoundConfig(
name="Seed",
pre_money_valuation=9_000_000,
investment_amount=2_000_000,
new_option_pool_pct=0.12,
option_pool_pre_round=True,
lead_investor_name="Seed Fund",
))
rounds.append(r2)
# Round 3: Series A — $12M at $38M pre, refresh option pool to 15%
r3 = cap.execute_round(RoundConfig(
name="Series A",
pre_money_valuation=38_000_000,
investment_amount=12_000_000,
new_option_pool_pct=0.15,
option_pool_pre_round=True,
lead_investor_name="Series A Fund",
))
rounds.append(r3)
# Round 4: Series B — $25M at $95M pre, refresh pool to 12%
r4 = cap.execute_round(RoundConfig(
name="Series B",
pre_money_valuation=95_000_000,
investment_amount=25_000_000,
new_option_pool_pct=0.12,
option_pool_pre_round=True,
lead_investor_name="Series B Fund",
))
rounds.append(r4)
# Round 5: Series C — $40M at $185M pre, refresh pool to 10%
r5 = cap.execute_round(RoundConfig(
name="Series C",
pre_money_valuation=185_000_000,
investment_amount=40_000_000,
new_option_pool_pct=0.10,
option_pool_pre_round=True,
lead_investor_name="Series C Fund",
))
rounds.append(r5)
return cap, rounds
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def main() -> None:
parser = argparse.ArgumentParser(description="Fundraising Model — Cap Table & Dilution")
parser.add_argument("--exit", type=float, default=250.0,
help="Exit valuation in $M for return analysis (default: 250)")
parser.add_argument("--csv", action="store_true", help="Export round data as CSV to stdout")
args = parser.parse_args()
exit_valuation = args.exit * 1_000_000
print("\n" + "="*70)
print(" FUNDRAISING MODEL — CAP TABLE & DILUTION ANALYSIS")
print(" Sample Company: Two-founder SaaS startup")
print(" Pre-seed → Seed → Series A → Series B → Series C")
print("="*70)
cap, rounds = build_sample_model()
# Print each round
prev = None
for r in rounds:
print_round_result(r, prev)
prev = r.cap_table
# Dilution summary table
print_dilution_summary(rounds)
# Exit analysis at specified valuation
exit_results = cap.analyze_exit(exit_valuation)
print_exit_analysis(exit_results, exit_valuation)
# Also print at 2x and 5x for sensitivity
print("\n Exit Sensitivity — Founder A Proceeds:")
print(f" {'Exit Valuation':<20} {'Founder A %':>12} {'Founder A $':>14} {'MOIC':>8}")
print(" " + "-"*56)
for mult in [0.5, 1.0, 1.5, 2.0, 3.0, 5.0]:
val = rounds[-1].post_money_valuation * mult
ex = cap.analyze_exit(val)
founder_a = next((r for r in ex if r.shareholder == "Founder A (CEO)"), None)
if founder_a:
print(f" {fmt(val):<20} {founder_a.ownership_pct*100:>11.2f}% "
f"{fmt(founder_a.proceeds_common):>14} {'n/a':>8}")
print("\n Key Takeaways:")
final = rounds[-1].cap_table
total = sum(e.shares for e in final)
founder_a_final = next((e for e in final if e.name == "Founder A (CEO)"), None)
if founder_a_final:
print(f" Founder A final ownership: {founder_a_final.pct_ownership*100:.2f}%")
total_raised = sum(e.invested for e in final)
print(f" Total capital raised: {fmt(total_raised)}")
print(f" Total shares outstanding: {total:,.0f}")
print(f" Final post-money: {fmt(rounds[-1].post_money_valuation)}")
print("\n Run with --exit <$M> to model proceeds at different exit valuations.")
print(" Example: python fundraising_model.py --exit 500")
if args.csv:
print("\n\n--- CSV EXPORT ---\n")
sys.stdout.write(export_csv_rounds(rounds))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Unit Economics Analyzer
========================
Per-cohort LTV, per-channel CAC, payback periods, and LTV:CAC ratios.
Never blended averages — those hide what's actually happening.
Usage:
python unit_economics_analyzer.py
python unit_economics_analyzer.py --csv
Stdlib only. No dependencies.
"""
import argparse
import csv
import io
import sys
from dataclasses import dataclass, field
from typing import Optional
# ---------------------------------------------------------------------------
# Data structures
# ---------------------------------------------------------------------------
@dataclass
class CohortData:
"""
Revenue data for a group of customers acquired in the same period.
Revenue is tracked monthly: revenue[0] = month 1, revenue[1] = month 2, etc.
"""
label: str # e.g. "Q1 2024"
acquisition_period: str # human-readable label
customers_acquired: int
total_cac_spend: float # total S&M spend to acquire this cohort
monthly_revenue: list[float] # revenue per month from this cohort
gross_margin_pct: float = 0.70 # blended gross margin for this cohort
@dataclass
class ChannelData:
"""Acquisition cost and customer data for a single channel."""
channel: str
spend: float
customers_acquired: int
avg_arpa: float # average revenue per account (monthly)
gross_margin_pct: float = 0.70
avg_monthly_churn: float = 0.02 # monthly churn rate for customers from this channel
@dataclass
class UnitEconomicsResult:
"""Computed unit economics for a cohort or channel."""
label: str
customers: int
cac: float
arpa: float # average revenue per account per month
gross_margin_pct: float
monthly_churn: float
ltv: float
ltv_cac_ratio: float
payback_months: float
# Cohort-specific
m1_revenue: Optional[float] = None
m6_revenue: Optional[float] = None
m12_revenue: Optional[float] = None
m24_revenue: Optional[float] = None
m12_ltv: Optional[float] = None # realized LTV through month 12
retention_m6: Optional[float] = None # % of M1 revenue retained at M6
retention_m12: Optional[float] = None
# ---------------------------------------------------------------------------
# Calculators
# ---------------------------------------------------------------------------
def calc_ltv(arpa: float, gross_margin_pct: float, monthly_churn: float) -> float:
"""
LTV = (ARPA × Gross Margin) / Monthly Churn Rate
Assumes constant churn (simplified; cohort method is more accurate).
"""
if monthly_churn <= 0:
return float("inf")
return (arpa * gross_margin_pct) / monthly_churn
def calc_payback(cac: float, arpa: float, gross_margin_pct: float) -> float:
"""
CAC Payback (months) = CAC / (ARPA × Gross Margin)
"""
denominator = arpa * gross_margin_pct
if denominator <= 0:
return float("inf")
return cac / denominator
def analyze_cohort(cohort: CohortData) -> UnitEconomicsResult:
"""Compute full unit economics for a cohort."""
n = cohort.customers_acquired
if n == 0:
raise ValueError(f"Cohort {cohort.label}: customers_acquired cannot be 0")
cac = cohort.total_cac_spend / n
# ARPA from month 1 revenue
m1_rev = cohort.monthly_revenue[0] if cohort.monthly_revenue else 0
arpa = m1_rev / n if n > 0 else 0
# Observed monthly churn from cohort data
# Use revenue decline from M1 to M12 to estimate churn
months_available = len(cohort.monthly_revenue)
if months_available >= 12:
m12_rev = cohort.monthly_revenue[11]
# Revenue retention over 12 months: (M12/M1)^(1/11) per month on average
# Implied monthly retention rate
if m1_rev > 0 and m12_rev > 0:
monthly_retention = (m12_rev / m1_rev) ** (1 / 11)
monthly_churn = 1 - monthly_retention
else:
monthly_churn = 0.02 # default
elif months_available >= 6:
m6_rev = cohort.monthly_revenue[5]
if m1_rev > 0 and m6_rev > 0:
monthly_retention = (m6_rev / m1_rev) ** (1 / 5)
monthly_churn = 1 - monthly_retention
else:
monthly_churn = 0.02
else:
monthly_churn = 0.02 # default if < 6 months data
# Clamp to reasonable range
monthly_churn = max(0.001, min(monthly_churn, 0.30))
ltv = calc_ltv(arpa, cohort.gross_margin_pct, monthly_churn)
payback = calc_payback(cac, arpa, cohort.gross_margin_pct)
ltv_cac = ltv / cac if cac > 0 else float("inf")
# Snapshot revenues
def rev_at(month_idx: int) -> Optional[float]:
if months_available > month_idx:
return cohort.monthly_revenue[month_idx]
return None
m6 = rev_at(5)
m12 = rev_at(11)
m24 = rev_at(23)
# Realized LTV through observed months (actual gross profit)
m12_ltv = sum(cohort.monthly_revenue[:12]) * cohort.gross_margin_pct if months_available >= 12 else None
# Retention rates
ret_m6 = (m6 / m1_rev) if (m6 is not None and m1_rev > 0) else None
ret_m12 = (m12 / m1_rev) if (m12 is not None and m1_rev > 0) else None
return UnitEconomicsResult(
label=cohort.label,
customers=n,
cac=cac,
arpa=arpa,
gross_margin_pct=cohort.gross_margin_pct,
monthly_churn=monthly_churn,
ltv=ltv,
ltv_cac_ratio=ltv_cac,
payback_months=payback,
m1_revenue=m1_rev,
m6_revenue=m6,
m12_revenue=m12,
m24_revenue=m24,
m12_ltv=m12_ltv,
retention_m6=ret_m6,
retention_m12=ret_m12,
)
def analyze_channel(ch: ChannelData) -> UnitEconomicsResult:
"""Compute unit economics for an acquisition channel."""
if ch.customers_acquired == 0:
raise ValueError(f"Channel {ch.channel}: customers_acquired cannot be 0")
cac = ch.spend / ch.customers_acquired
ltv = calc_ltv(ch.avg_arpa, ch.gross_margin_pct, ch.avg_monthly_churn)
payback = calc_payback(cac, ch.avg_arpa, ch.gross_margin_pct)
ltv_cac = ltv / cac if cac > 0 else float("inf")
return UnitEconomicsResult(
label=ch.channel,
customers=ch.customers_acquired,
cac=cac,
arpa=ch.avg_arpa,
gross_margin_pct=ch.gross_margin_pct,
monthly_churn=ch.avg_monthly_churn,
ltv=ltv,
ltv_cac_ratio=ltv_cac,
payback_months=payback,
)
# ---------------------------------------------------------------------------
# Blended metrics (for comparison)
# ---------------------------------------------------------------------------
def blended_cac(channels: list[ChannelData]) -> float:
total_spend = sum(c.spend for c in channels)
total_customers = sum(c.customers_acquired for c in channels)
return total_spend / total_customers if total_customers > 0 else 0
def blended_ltv(channels: list[ChannelData]) -> float:
"""Weighted average LTV by customers acquired."""
total_customers = sum(c.customers_acquired for c in channels)
if total_customers == 0:
return 0
weighted = sum(
calc_ltv(c.avg_arpa, c.gross_margin_pct, c.avg_monthly_churn) * c.customers_acquired
for c in channels
)
return weighted / total_customers
# ---------------------------------------------------------------------------
# Reporting
# ---------------------------------------------------------------------------
def fmt(value: float, prefix: str = "$", decimals: int = 0) -> str:
if value == float("inf"):
return "∞"
if abs(value) >= 1_000_000:
return f"{prefix}{value/1_000_000:.2f}M"
if abs(value) >= 1_000:
return f"{prefix}{value/1_000:.1f}K"
return f"{prefix}{value:.{decimals}f}"
def pct(value: Optional[float]) -> str:
if value is None:
return "n/a"
return f"{value*100:.1f}%"
def rating(ltv_cac: float, payback: float) -> str:
if ltv_cac == float("inf"):
return "∞"
if ltv_cac >= 5 and payback <= 12:
return "🟢 Excellent"
if ltv_cac >= 3 and payback <= 18:
return "🟡 Good"
if ltv_cac >= 2 and payback <= 24:
return "🟠 Marginal"
return "🔴 Poor"
def print_cohort_analysis(results: list[UnitEconomicsResult]) -> None:
print("\n" + "="*80)
print(" COHORT ANALYSIS")
print("="*80)
print(f" {'Cohort':<12} {'Cust':>5} {'CAC':>8} {'ARPA/mo':>9} {'Churn/mo':>10} "
f"{'LTV':>10} {'LTV:CAC':>8} {'Payback':>9} {'Ret@M12':>8}")
print(" " + "-"*88)
for r in results:
payback_str = f"{r.payback_months:.1f}mo" if r.payback_months != float("inf") else "∞"
ltv_str = fmt(r.ltv) if r.ltv != float("inf") else "∞"
ltv_cac_str = f"{r.ltv_cac_ratio:.1f}x" if r.ltv_cac_ratio != float("inf") else "∞"
print(
f" {r.label:<12} {r.customers:>5} {fmt(r.cac):>8} {fmt(r.arpa):>9} "
f"{pct(r.monthly_churn):>10} {ltv_str:>10} {ltv_cac_str:>8} "
f"{payback_str:>9} {pct(r.retention_m12):>8}"
)
# Trend analysis
print("\n Cohort Trend (is the business getting better or worse?):")
if len(results) >= 3:
ltv_cac_values = [r.ltv_cac_ratio for r in results if r.ltv_cac_ratio != float("inf")]
cac_values = [r.cac for r in results]
churn_values = [r.monthly_churn for r in results]
if len(ltv_cac_values) >= 2:
ltv_cac_trend = "↑ Improving" if ltv_cac_values[-1] > ltv_cac_values[0] else "↓ Deteriorating"
else:
ltv_cac_trend = "n/a"
cac_trend = "↓ Decreasing (good)" if cac_values[-1] < cac_values[0] else "↑ Increasing"
churn_trend = "↓ Improving" if churn_values[-1] < churn_values[0] else "↑ Worsening"
print(f" LTV:CAC: {ltv_cac_trend}")
print(f" CAC: {cac_trend}")
print(f" Churn rate: {churn_trend}")
def print_channel_analysis(results: list[UnitEconomicsResult], channels: list[ChannelData]) -> None:
print("\n" + "="*80)
print(" CHANNEL ANALYSIS (Per-Channel vs Blended)")
print("="*80)
print(f" {'Channel':<22} {'Spend':>9} {'Cust':>5} {'CAC':>8} {'LTV':>10} {'LTV:CAC':>8} {'Payback':>9} {'Rating'}")
print(" " + "-"*90)
for r, ch in zip(results, channels):
payback_str = f"{r.payback_months:.1f}mo" if r.payback_months != float("inf") else "∞"
ltv_str = fmt(r.ltv) if r.ltv != float("inf") else "∞"
ltv_cac_str = f"{r.ltv_cac_ratio:.1f}x" if r.ltv_cac_ratio != float("inf") else "∞"
print(
f" {r.label:<22} {fmt(ch.spend):>9} {r.customers:>5} {fmt(r.cac):>8} "
f"{ltv_str:>10} {ltv_cac_str:>8} {payback_str:>9} {rating(r.ltv_cac_ratio, r.payback_months)}"
)
# Blended comparison
b_cac = blended_cac(channels)
b_ltv = blended_ltv(channels)
b_ltv_cac = b_ltv / b_cac if b_cac > 0 else 0
total_spend = sum(c.spend for c in channels)
total_customers = sum(c.customers_acquired for c in channels)
avg_payback = sum(
calc_payback(b_cac, c.avg_arpa, c.gross_margin_pct) * c.customers_acquired
for c in channels
) / total_customers
print(" " + "-"*90)
print(
f" {'BLENDED (dangerous)':<22} {fmt(total_spend):>9} {total_customers:>5} "
f"{fmt(b_cac):>8} {fmt(b_ltv):>10} {b_ltv_cac:.1f}x{'':<7} "
f"{avg_payback:.1f}mo{'':<4} {rating(b_ltv_cac, avg_payback)}"
)
print("\n ⚠️ Blended numbers hide channel-level problems. Manage channels individually.")
# Budget reallocation
print("\n Recommended Budget Reallocation:")
sorted_results = sorted(zip(results, channels), key=lambda x: x[0].ltv_cac_ratio, reverse=True)
for r, ch in sorted_results:
if r.ltv_cac_ratio >= 3:
action = "✅ Scale"
elif r.ltv_cac_ratio >= 2:
action = "🔄 Optimize"
else:
action = "❌ Cut / pause"
print(f" {ch.channel:<22} LTV:CAC = {r.ltv_cac_ratio:.1f}x → {action}")
def export_csv_results(cohort_results: list[UnitEconomicsResult], channel_results: list[UnitEconomicsResult]) -> str:
buf = io.StringIO()
writer = csv.writer(buf)
writer.writerow(["Type", "Label", "Customers", "CAC", "ARPA_Monthly", "Gross_Margin_Pct",
"Monthly_Churn", "LTV", "LTV_CAC_Ratio", "Payback_Months",
"Retention_M6", "Retention_M12"])
for r in cohort_results:
writer.writerow(["cohort", r.label, r.customers, round(r.cac, 2), round(r.arpa, 2),
r.gross_margin_pct, round(r.monthly_churn, 4),
round(r.ltv, 2) if r.ltv != float("inf") else "inf",
round(r.ltv_cac_ratio, 2) if r.ltv_cac_ratio != float("inf") else "inf",
round(r.payback_months, 2) if r.payback_months != float("inf") else "inf",
round(r.retention_m6, 3) if r.retention_m6 else "",
round(r.retention_m12, 3) if r.retention_m12 else ""])
for r in channel_results:
writer.writerow(["channel", r.label, r.customers, round(r.cac, 2), round(r.arpa, 2),
r.gross_margin_pct, round(r.monthly_churn, 4),
round(r.ltv, 2) if r.ltv != float("inf") else "inf",
round(r.ltv_cac_ratio, 2) if r.ltv_cac_ratio != float("inf") else "inf",
round(r.payback_months, 2) if r.payback_months != float("inf") else "inf",
"", ""])
return buf.getvalue()
# ---------------------------------------------------------------------------
# Sample data
# ---------------------------------------------------------------------------
def make_sample_cohorts() -> list[CohortData]:
"""
Series A SaaS company, 8 quarters of cohort data.
Shows a business improving on all dimensions over time.
"""
return [
CohortData(
label="Q1 2023", acquisition_period="Jan-Mar 2023",
customers_acquired=12, total_cac_spend=54_000,
gross_margin_pct=0.68,
monthly_revenue=[
10_200, 9_600, 9_100, 8_700, 8_300, 8_000, # M1-M6
7_800, 7_600, 7_400, 7_200, 7_000, 6_800, # M7-M12
6_700, 6_600, 6_500, 6_400, 6_300, 6_200, # M13-M18
6_100, 6_000, 5_900, 5_800, 5_700, 5_600, # M19-M24
],
),
CohortData(
label="Q2 2023", acquisition_period="Apr-Jun 2023",
customers_acquired=15, total_cac_spend=60_000,
gross_margin_pct=0.69,
monthly_revenue=[
13_500, 12_900, 12_500, 12_100, 11_800, 11_500,
11_300, 11_100, 10_900, 10_700, 10_500, 10_300,
10_200, 10_100, 10_000, 9_900, 9_800, 9_700,
],
),
CohortData(
label="Q3 2023", acquisition_period="Jul-Sep 2023",
customers_acquired=18, total_cac_spend=63_000,
gross_margin_pct=0.70,
monthly_revenue=[
16_200, 15_800, 15_400, 15_100, 14_800, 14_600,
14_400, 14_200, 14_000, 13_900, 13_800, 13_700,
13_600, 13_500, 13_400, 13_300,
],
),
CohortData(
label="Q4 2023", acquisition_period="Oct-Dec 2023",
customers_acquired=22, total_cac_spend=70_400,
gross_margin_pct=0.71,
monthly_revenue=[
20_900, 20_500, 20_200, 19_900, 19_700, 19_500,
19_300, 19_100, 19_000, 18_900, 18_800, 18_700,
],
),
CohortData(
label="Q1 2024", acquisition_period="Jan-Mar 2024",
customers_acquired=28, total_cac_spend=81_200,
gross_margin_pct=0.72,
monthly_revenue=[
27_200, 26_900, 26_600, 26_400, 26_200, 26_000,
25_800, 25_700, 25_600, 25_500,
],
),
CohortData(
label="Q2 2024", acquisition_period="Apr-Jun 2024",
customers_acquired=34, total_cac_spend=91_800,
gross_margin_pct=0.72,
monthly_revenue=[
33_300, 33_000, 32_800, 32_600, 32_400, 32_200,
],
),
CohortData(
label="Q3 2024", acquisition_period="Jul-Sep 2024",
customers_acquired=40, total_cac_spend=100_000,
gross_margin_pct=0.73,
monthly_revenue=[
39_600, 39_400, 39_200,
],
),
CohortData(
label="Q4 2024", acquisition_period="Oct-Dec 2024",
customers_acquired=47, total_cac_spend=112_800,
gross_margin_pct=0.73,
monthly_revenue=[
47_000,
],
),
]
def make_sample_channels() -> list[ChannelData]:
"""
Q4 2024 channel breakdown. Blended looks fine; per-channel reveals problems.
"""
return [
ChannelData("Organic / SEO", spend=9_500, customers_acquired=14, avg_arpa=950, gross_margin_pct=0.73, avg_monthly_churn=0.015),
ChannelData("Paid Search (SEM)", spend=48_000, customers_acquired=18, avg_arpa=980, gross_margin_pct=0.73, avg_monthly_churn=0.020),
ChannelData("Paid Social", spend=32_000, customers_acquired=8, avg_arpa=900, gross_margin_pct=0.72, avg_monthly_churn=0.025),
ChannelData("Content / Inbound", spend=11_000, customers_acquired=6, avg_arpa=1100, gross_margin_pct=0.74, avg_monthly_churn=0.012),
ChannelData("Outbound SDR", spend=22_000, customers_acquired=4, avg_arpa=1200, gross_margin_pct=0.73, avg_monthly_churn=0.022),
ChannelData("Events / Webinars", spend=18_500, customers_acquired=3, avg_arpa=1050, gross_margin_pct=0.72, avg_monthly_churn=0.028),
ChannelData("Partner / Referral", spend=7_800, customers_acquired=7, avg_arpa=1000, gross_margin_pct=0.73, avg_monthly_churn=0.013),
]
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
def main() -> None:
parser = argparse.ArgumentParser(description="Unit Economics Analyzer")
parser.add_argument("--csv", action="store_true", help="Export results as CSV to stdout")
args = parser.parse_args()
cohorts = make_sample_cohorts()
channels = make_sample_channels()
print("\n" + "="*80)
print(" UNIT ECONOMICS ANALYZER")
print(" Sample Company: Series A SaaS | Q4 2024 Snapshot")
print(" Gross Margin: ~72% | Monthly Churn: derived from cohort data")
print("="*80)
cohort_results = [analyze_cohort(c) for c in cohorts]
channel_results = [analyze_channel(c) for c in channels]
print_cohort_analysis(cohort_results)
print_channel_analysis(channel_results, channels)
# Health summary
print("\n" + "="*80)
print(" HEALTH SUMMARY")
print("="*80)
latest = cohort_results[-1]
prev = cohort_results[-4] if len(cohort_results) >= 4 else cohort_results[0]
print(f"\n Latest Cohort ({latest.label}):")
print(f" CAC: {fmt(latest.cac)}")
ltv_str = fmt(latest.ltv) if latest.ltv != float("inf") else "∞"
ltv_cac_str = f"{latest.ltv_cac_ratio:.1f}x" if latest.ltv_cac_ratio != float("inf") else "∞"
payback_str = f"{latest.payback_months:.1f} months" if latest.payback_months != float("inf") else "∞"
print(f" LTV: {ltv_str}")
print(f" LTV:CAC: {ltv_cac_str} (target: > 3x)")
print(f" CAC Payback: {payback_str} (target: < 18mo)")
print(f" Rating: {rating(latest.ltv_cac_ratio, latest.payback_months)}")
# Trend vs 4 quarters ago
print(f"\n Trend vs {prev.label}:")
cac_delta = (latest.cac - prev.cac) / prev.cac * 100
ltv_delta_str = "n/a"
if latest.ltv != float("inf") and prev.ltv != float("inf"):
ltv_delta = (latest.ltv - prev.ltv) / prev.ltv * 100
ltv_delta_str = f"{ltv_delta:+.1f}%"
cac_str = "↓ Better" if cac_delta < 0 else "↑ Worse"
print(f" CAC: {cac_delta:+.1f}% ({cac_str})")
print(f" LTV: {ltv_delta_str}")
print("\n Benchmark Reference:")
print(" LTV:CAC > 5x → Scale aggressively")
print(" LTV:CAC 3-5x → Healthy; grow at current pace")
print(" LTV:CAC 2-3x → Marginal; optimize before scaling")
print(" LTV:CAC < 2x → Acquiring unprofitably; stop and fix")
print(" Payback < 12mo → Outstanding capital efficiency")
print(" Payback 12-18mo → Good for B2B SaaS")
print(" Payback > 24mo → Requires long-dated capital to scale")
if args.csv:
print("\n\n--- CSV EXPORT ---\n")
sys.stdout.write(export_csv_results(cohort_results, channel_results))
if __name__ == "__main__":
main()
Install this Skill
Skills give your AI agent a consistent, structured approach to this task — better output than a one-off prompt.
npx skills add alirezarezvani/claude-skills --skill c-level-advisor/cfo-advisor Community skill by @alirezarezvani. Need a walkthrough? See the install guide →
Works with
Prefer no terminal? Download the ZIP and place it manually.
Details
- Category
- Leadership
- License
- MIT
- Author
- @alirezarezvani
- Source
- GitHub →
- Source file
-
show path
c-level-advisor/cfo-advisor/SKILL.md
People who install this also use
CEO Advisor
Executive leadership coaching — strategic decision-making, organizational development, board governance, and navigating high-stakes business challenges.
@alirezarezvani
Financial Analyst
DCF valuation, budgeting, forecasting, and SaaS metrics analysis — financial modeling and analysis from a professional analyst perspective.
@alirezarezvani