assets/dcf_analysis_template.md
# DCF Valuation Analysis
## Report Header
| Field | Value |
|-------|-------|
| **Company** | [Company Name] |
| **Ticker** | [Ticker Symbol] |
| **Analysis Date** | [Date] |
| **Prepared By** | [Analyst Name] |
| **Current Share Price** | $[X] |
| **Shares Outstanding** | [X]M |
## Executive Summary
[2-3 sentence overview of the valuation conclusion, including the implied value range per share compared to the current market price, and whether the stock appears undervalued, fairly valued, or overvalued.]
### Valuation Summary
| Method | Enterprise Value | Equity Value | Value Per Share | vs Current Price |
|--------|-----------------|-------------|----------------|-----------------|
| DCF (Perpetuity Growth) | $[X]M | $[X]M | $[X] | [X]% |
| DCF (Exit Multiple) | $[X]M | $[X]M | $[X] | [X]% |
| Comparable Companies | $[X]M | $[X]M | $[X] | [X]% |
| **Blended Estimate** | **$[X]M** | **$[X]M** | **$[X]** | **[X]%** |
## Investment Thesis
[Summary of the investment case, including key strengths, risks, and catalysts.]
## Historical Financial Summary
| ($M) | FY-4 | FY-3 | FY-2 | FY-1 | LTM |
|------|------|------|------|------|-----|
| Revenue | [X] | [X] | [X] | [X] | [X] |
| Revenue Growth | [X]% | [X]% | [X]% | [X]% | [X]% |
| Gross Profit | [X] | [X] | [X] | [X] | [X] |
| Gross Margin | [X]% | [X]% | [X]% | [X]% | [X]% |
| EBITDA | [X] | [X] | [X] | [X] | [X] |
| EBITDA Margin | [X]% | [X]% | [X]% | [X]% | [X]% |
| Net Income | [X] | [X] | [X] | [X] | [X] |
| Free Cash Flow | [X] | [X] | [X] | [X] | [X] |
## WACC Calculation
### Cost of Equity (CAPM)
| Component | Value | Source |
|-----------|-------|--------|
| Risk-Free Rate | [X]% | [10-Year Treasury] |
| Equity Risk Premium | [X]% | [Damodaran / internal] |
| Beta (Levered) | [X] | [Bloomberg / regression] |
| Size Premium | [X]% | [Duff & Phelps] |
| Company-Specific Risk | [X]% | [Analyst judgment] |
| **Cost of Equity** | **[X]%** | |
### Cost of Debt
| Component | Value |
|-----------|-------|
| Pre-Tax Cost of Debt | [X]% |
| Tax Rate | [X]% |
| After-Tax Cost of Debt | [X]% |
### Capital Structure
| Component | Market Value ($M) | Weight |
|-----------|------------------|--------|
| Equity | [X] | [X]% |
| Debt | [X] | [X]% |
| **Total Capital** | **[X]** | **100%** |
### WACC Result: [X]%
## Revenue Projections
| ($M) | Year 1 | Year 2 | Year 3 | Year 4 | Year 5 |
|------|--------|--------|--------|--------|--------|
| Revenue | [X] | [X] | [X] | [X] | [X] |
| Growth Rate | [X]% | [X]% | [X]% | [X]% | [X]% |
**Key Revenue Assumptions:**
- [Assumption 1 with supporting rationale]
- [Assumption 2 with supporting rationale]
- [Assumption 3 with supporting rationale]
## Free Cash Flow Projections
| ($M) | Year 1 | Year 2 | Year 3 | Year 4 | Year 5 |
|------|--------|--------|--------|--------|--------|
| Revenue | [X] | [X] | [X] | [X] | [X] |
| EBIT | [X] | [X] | [X] | [X] | [X] |
| Taxes on EBIT | ([X]) | ([X]) | ([X]) | ([X]) | ([X]) |
| NOPAT | [X] | [X] | [X] | [X] | [X] |
| D&A | [X] | [X] | [X] | [X] | [X] |
| CapEx | ([X]) | ([X]) | ([X]) | ([X]) | ([X]) |
| Change in NWC | ([X]) | ([X]) | ([X]) | ([X]) | ([X]) |
| **Unlevered FCF** | **[X]** | **[X]** | **[X]** | **[X]** | **[X]** |
| FCF Margin | [X]% | [X]% | [X]% | [X]% | [X]% |
## Terminal Value
### Perpetuity Growth Method
| Component | Value |
|-----------|-------|
| Terminal FCF | $[X]M |
| Terminal Growth Rate | [X]% |
| WACC | [X]% |
| **Terminal Value** | **$[X]M** |
| TV as % of EV | [X]% |
### Exit Multiple Method
| Component | Value |
|-----------|-------|
| Terminal EBITDA | $[X]M |
| Exit EV/EBITDA Multiple | [X]x |
| **Terminal Value** | **$[X]M** |
| TV as % of EV | [X]% |
## Enterprise Value Bridge
| Component | Perpetuity Growth | Exit Multiple |
|-----------|------------------|---------------|
| PV of Projected FCFs | $[X]M | $[X]M |
| PV of Terminal Value | $[X]M | $[X]M |
| **Enterprise Value** | **$[X]M** | **$[X]M** |
| Less: Net Debt | ($[X]M) | ($[X]M) |
| Less: Minority Interest | ($[X]M) | ($[X]M) |
| **Equity Value** | **$[X]M** | **$[X]M** |
| Diluted Shares (M) | [X] | [X] |
| **Value Per Share** | **$[X]** | **$[X]** |
## Sensitivity Analysis
### WACC vs Terminal Growth Rate (Enterprise Value, $M)
| WACC \ Growth | [g-2]% | [g-1]% | [g]% | [g+1]% | [g+2]% |
|--------------|--------|--------|------|--------|--------|
| [WACC-2]% | [X] | [X] | [X] | [X] | [X] |
| [WACC-1]% | [X] | [X] | [X] | [X] | [X] |
| **[WACC]%** | [X] | [X] | **[X]** | [X] | [X] |
| [WACC+1]% | [X] | [X] | [X] | [X] | [X] |
| [WACC+2]% | [X] | [X] | [X] | [X] | [X] |
### Implied Share Price Range
| Scenario | Share Price | vs Current | Upside/Downside |
|----------|-----------|------------|----------------|
| Bear Case (WACC+2%, g-2%) | $[X] | [X]% | [X]% |
| Base Case | $[X] | [X]% | [X]% |
| Bull Case (WACC-2%, g+2%) | $[X] | [X]% | [X]% |
## Key Risks to Valuation
1. **[Risk 1]** - [Description and potential impact on value]
2. **[Risk 2]** - [Description and potential impact on value]
3. **[Risk 3]** - [Description and potential impact on value]
## Comparable Company Analysis
| Company | EV/Revenue | EV/EBITDA | P/E | Growth | Margin |
|---------|-----------|----------|-----|--------|--------|
| [Comp 1] | [X]x | [X]x | [X]x | [X]% | [X]% |
| [Comp 2] | [X]x | [X]x | [X]x | [X]% | [X]% |
| [Comp 3] | [X]x | [X]x | [X]x | [X]% | [X]% |
| [Comp 4] | [X]x | [X]x | [X]x | [X]% | [X]% |
| **Median** | **[X]x** | **[X]x** | **[X]x** | **[X]%** | **[X]%** |
| **[Target]** | **[X]x** | **[X]x** | **[X]x** | **[X]%** | **[X]%** |
## Conclusion and Recommendation
**Valuation Range:** $[Low] - $[High] per share
**Current Price:** $[X]
**Recommendation:** [Buy / Hold / Sell]
[Final paragraph with investment recommendation rationale, key upside catalysts, and primary risks to monitor.]
---
*Analysis generated using Financial Analyst Skill - DCF Valuation Model*
assets/expected_output.json
{
"_description": "Expected output structure for all 4 scripts. Values are illustrative to show data format.",
"ratio_calculator_output": {
"categories": {
"profitability": {
"roe": {
"value": 0.25,
"formula": "Net Income / Total Equity",
"name": "Return on Equity",
"interpretation": "Good - above average performance"
},
"roa": {
"value": 0.1375,
"formula": "Net Income / Total Assets",
"name": "Return on Assets",
"interpretation": "Excellent - significantly above peers"
},
"gross_margin": {
"value": 0.40,
"formula": "(Revenue - COGS) / Revenue",
"name": "Gross Margin",
"interpretation": "Acceptable - within normal range"
},
"operating_margin": {
"value": 0.16,
"formula": "Operating Income / Revenue",
"name": "Operating Margin",
"interpretation": "Good - above average performance"
},
"net_margin": {
"value": 0.11,
"formula": "Net Income / Revenue",
"name": "Net Margin",
"interpretation": "Good - above average performance"
}
},
"liquidity": {
"current_ratio": {"value": 1.875, "name": "Current Ratio"},
"quick_ratio": {"value": 1.4375, "name": "Quick Ratio"},
"cash_ratio": {"value": 0.625, "name": "Cash Ratio"}
},
"leverage": {
"debt_to_equity": {"value": 0.545, "name": "Debt-to-Equity Ratio"},
"interest_coverage": {"value": 6.67, "name": "Interest Coverage Ratio"},
"dscr": {"value": 2.50, "name": "Debt Service Coverage Ratio"}
},
"efficiency": {
"asset_turnover": {"value": 1.25, "name": "Asset Turnover"},
"inventory_turnover": {"value": 8.57, "name": "Inventory Turnover"},
"receivables_turnover": {"value": 8.33, "name": "Receivables Turnover"},
"dso": {"value": 43.8, "name": "Days Sales Outstanding"}
},
"valuation": {
"pe_ratio": {"value": 81.82, "name": "Price-to-Earnings Ratio"},
"pb_ratio": {"value": 20.45, "name": "Price-to-Book Ratio"},
"ps_ratio": {"value": 9.0, "name": "Price-to-Sales Ratio"},
"ev_ebitda": {"value": 45.7, "name": "EV/EBITDA"},
"peg_ratio": {"value": 6.82, "name": "PEG Ratio"}
}
}
},
"dcf_valuation_output": {
"wacc": 0.085,
"projected_revenue": [55000000, 59950000, 64746000, 69278220, 73434953],
"projected_fcf": [6600000, 7793500, 8416980, 9698951, 10280893],
"terminal_value": {
"perpetuity_growth": 175382225,
"exit_multiple": 176243484
},
"enterprise_value": {
"perpetuity_growth": 149500000,
"exit_multiple": 150100000
},
"equity_value": {
"perpetuity_growth": 142500000,
"exit_multiple": 143100000
},
"value_per_share": {
"perpetuity_growth": 14.25,
"exit_multiple": 14.31
},
"sensitivity_analysis": {
"wacc_values": [0.065, 0.075, 0.085, 0.095, 0.105],
"growth_values": [0.015, 0.020, 0.025, 0.030, 0.035],
"enterprise_value_table": "5x5 nested list of enterprise values",
"share_price_table": "5x5 nested list of share prices"
}
},
"budget_variance_output": {
"executive_summary": {
"period": "Q4 2025",
"company": "Acme Corp",
"total_line_items": 10,
"material_variances_count": 3,
"favorable_count": 4,
"unfavorable_count": 6,
"revenue": {
"actual": 15700000,
"budget": 15500000,
"variance_amount": 200000,
"variance_pct": 1.29
},
"expenses": {
"actual": 13255000,
"budget": 12520000,
"variance_amount": 735000,
"variance_pct": 5.87
},
"net_impact": -535000
},
"material_variances": [
{
"name": "Cost of Goods Sold",
"budget_variance_amount": 600000,
"budget_variance_pct": 8.33,
"favorability": "Unfavorable"
}
],
"department_summary": {
"Sales": {"total_variance": 0, "variance_pct": 0},
"Operations": {"total_variance": 0, "variance_pct": 0}
},
"category_summary": {
"Revenue": {"total_variance": 0, "variance_pct": 0},
"COGS": {"total_variance": 0, "variance_pct": 0}
}
},
"forecast_builder_output": {
"trend_analysis": {
"trend": {
"slope": 650000,
"intercept": 9500000,
"r_squared": 0.98,
"direction": "upward"
},
"average_growth_rate": 0.06,
"seasonality_index": [0.92, 0.97, 1.01, 1.10]
},
"scenario_comparison": {
"comparison": [
{"scenario": "base", "total_revenue": 185000000, "growth_rate": 0.08},
{"scenario": "bull", "total_revenue": 210000000, "growth_rate": 0.12},
{"scenario": "bear", "total_revenue": 165000000, "growth_rate": 0.05}
]
},
"rolling_cash_flow": {
"weeks": 13,
"opening_balance": 2500000,
"closing_balance": 2800000,
"total_inflows": 4200000,
"total_outflows": 3900000,
"minimum_balance": 2100000,
"minimum_balance_week": 4,
"cash_runway_weeks": 12
}
}
}
assets/forecast_report_template.md
# Revenue Forecast Report
## Report Header
| Field | Value |
|-------|-------|
| **Company** | [Company Name] |
| **Forecast Period** | [Start] to [End] |
| **Prepared By** | [Analyst Name] |
| **Date** | [Report Date] |
| **Forecast Type** | [Driver-Based / Trend-Based / Blended] |
## Executive Summary
[2-3 sentence overview of the revenue forecast, key assumptions, and confidence level. Highlight the base case total revenue, expected growth rate, and any significant departures from prior forecast or budget.]
### Key Metrics at a Glance
| Metric | Value |
|--------|-------|
| Base Case Total Revenue | $[X]M |
| Expected Growth Rate | [X]% |
| Forecast Confidence | [High / Medium / Low] |
| Revenue Range (Bear to Bull) | $[X]M - $[X]M |
| Primary Revenue Driver | [Driver description] |
## Historical Trend Analysis
### Revenue Trend
| Period | Revenue | Growth Rate | Gross Margin |
|--------|---------|------------|-------------|
| [Q/Year-4] | $[X]M | - | [X]% |
| [Q/Year-3] | $[X]M | [X]% | [X]% |
| [Q/Year-2] | $[X]M | [X]% | [X]% |
| [Q/Year-1] | $[X]M | [X]% | [X]% |
| [Current] | $[X]M | [X]% | [X]% |
### Trend Statistics
| Metric | Value |
|--------|-------|
| Average Growth Rate | [X]% |
| Trend Direction | [Upward / Flat / Downward] |
| R-squared (fit quality) | [X] |
| Seasonality Detected | [Yes / No] |
## Revenue Drivers
### Primary Drivers
| Driver | Current Value | Projected Value | Growth |
|--------|-------------|-----------------|--------|
| [Units / Customers / etc.] | [X] | [X] | [X]% |
| [Price / ARPU / etc.] | $[X] | $[X] | [X]% |
| [Conversion / Retention] | [X]% | [X]% | [X]pp |
### Driver Assumptions
1. **[Driver 1]:** [Assumption and rationale]
2. **[Driver 2]:** [Assumption and rationale]
3. **[Driver 3]:** [Assumption and rationale]
## Scenario Comparison
### Summary
| Scenario | Total Revenue | Growth Rate | Op. Income | Gross Margin | Probability |
|----------|-------------|-------------|-----------|-------------|-------------|
| Bull | $[X]M | [X]% | $[X]M | [X]% | [X]% |
| **Base** | **$[X]M** | **[X]%** | **$[X]M** | **[X]%** | **[X]%** |
| Bear | $[X]M | [X]% | $[X]M | [X]% | [X]% |
### Scenario Assumptions
**Bull Case:**
- [Key assumption 1]
- [Key assumption 2]
- [Trigger: what conditions would cause this scenario]
**Base Case:**
- [Key assumption 1]
- [Key assumption 2]
**Bear Case:**
- [Key assumption 1]
- [Key assumption 2]
- [Trigger: what conditions would cause this scenario]
## Monthly/Quarterly Forecast Detail (Base Case)
| Period | Revenue | COGS | Gross Profit | OpEx | Op. Income |
|--------|---------|------|-------------|------|-----------|
| [Period 1] | $[X] | $[X] | $[X] | $[X] | $[X] |
| [Period 2] | $[X] | $[X] | $[X] | $[X] | $[X] |
| [Period 3] | $[X] | $[X] | $[X] | $[X] | $[X] |
| [Period 4] | $[X] | $[X] | $[X] | $[X] | $[X] |
| ... | ... | ... | ... | ... | ... |
| **Total** | **$[X]** | **$[X]** | **$[X]** | **$[X]** | **$[X]** |
## 13-Week Rolling Cash Flow
### Summary
| Metric | Value |
|--------|-------|
| Opening Cash Balance | $[X] |
| Projected Closing Balance | $[X] |
| Net Cash Change | $[X] |
| Minimum Cash Balance | $[X] (Week [N]) |
| Cash Runway | [N] weeks |
### Weekly Cash Flow Projection
| Week | Inflows | Outflows | Net Cash Flow | Closing Balance |
|------|---------|----------|--------------|----------------|
| 1 | $[X] | $[X] | $[X] | $[X] |
| 2 | $[X] | $[X] | $[X] | $[X] |
| 3 | $[X] | $[X] | $[X] | $[X] |
| ... | ... | ... | ... | ... |
| 13 | $[X] | $[X] | $[X] | $[X] |
### Cash Flow Notes
- **Week [N]:** [Description of any significant one-time items]
- **Week [N]:** [Description of any significant one-time items]
## Forecast Accuracy Tracking
### vs Prior Forecast
| Metric | Prior Forecast | Current Forecast | Change |
|--------|---------------|-----------------|--------|
| Revenue | $[X]M | $[X]M | [X]% |
| Growth Rate | [X]% | [X]% | [X]pp |
| Gross Margin | [X]% | [X]% | [X]pp |
### Historical Forecast Accuracy (MAPE)
| Period | Forecast | Actual | Error | MAPE |
|--------|----------|--------|-------|------|
| [Period-3] | $[X] | $[X] | $[X] | [X]% |
| [Period-2] | $[X] | $[X] | $[X] | [X]% |
| [Period-1] | $[X] | $[X] | $[X] | [X]% |
| **Average MAPE** | | | | **[X]%** |
## Key Risks and Assumptions
### Upside Risks
1. [Risk/opportunity with quantified potential impact]
2. [Risk/opportunity with quantified potential impact]
### Downside Risks
1. [Risk with quantified potential impact]
2. [Risk with quantified potential impact]
### Critical Assumptions
1. [Assumption that if wrong would materially change the forecast]
2. [Assumption that if wrong would materially change the forecast]
## Recommendations
1. **[Recommendation 1]:** [Specific action with expected impact]
2. **[Recommendation 2]:** [Specific action with expected impact]
3. **[Recommendation 3]:** [Specific action with expected impact]
## Next Steps
| # | Action | Owner | Due Date |
|---|--------|-------|----------|
| 1 | [Action item] | [Name] | [Date] |
| 2 | [Action item] | [Name] | [Date] |
| 3 | [Action item] | [Name] | [Date] |
---
*Report generated using Financial Analyst Skill - Forecast Builder*
assets/sample_financial_data.json
{
"_description": "Sample financial data covering all 4 scripts: ratio_calculator, dcf_valuation, budget_variance_analyzer, and forecast_builder",
"ratio_analysis": {
"income_statement": {
"revenue": 50000000,
"cost_of_goods_sold": 30000000,
"operating_income": 8000000,
"ebitda": 10000000,
"net_income": 5500000,
"interest_expense": 1200000
},
"balance_sheet": {
"total_assets": 40000000,
"current_assets": 15000000,
"cash_and_equivalents": 5000000,
"accounts_receivable": 6000000,
"inventory": 3500000,
"total_equity": 22000000,
"total_debt": 12000000,
"current_liabilities": 8000000
},
"cash_flow": {
"operating_cash_flow": 7500000,
"total_debt_service": 3000000
},
"market_data": {
"share_price": 45.00,
"shares_outstanding": 10000000,
"market_cap": 450000000,
"earnings_growth_rate": 0.12
}
},
"dcf_valuation": {
"historical": {
"revenue": [38000000, 42000000, 45000000, 48000000, 50000000],
"net_income": [3800000, 4200000, 4500000, 5000000, 5500000],
"net_debt": 7000000,
"shares_outstanding": 10000000
},
"assumptions": {
"projection_years": 5,
"revenue_growth_rates": [0.10, 0.09, 0.08, 0.07, 0.06],
"fcf_margins": [0.12, 0.13, 0.13, 0.14, 0.14],
"default_revenue_growth": 0.05,
"default_fcf_margin": 0.10,
"terminal_growth_rate": 0.025,
"terminal_ebitda_margin": 0.20,
"exit_ev_ebitda_multiple": 12.0,
"wacc_inputs": {
"risk_free_rate": 0.04,
"equity_risk_premium": 0.06,
"beta": 1.1,
"cost_of_debt": 0.055,
"tax_rate": 0.25,
"debt_weight": 0.30,
"equity_weight": 0.70
}
}
},
"budget_variance": {
"company": "Acme Corp",
"period": "Q4 2025",
"line_items": [
{
"name": "Product Revenue",
"type": "revenue",
"department": "Sales",
"category": "Revenue",
"actual": 12500000,
"budget": 12000000,
"prior_year": 10800000
},
{
"name": "Service Revenue",
"type": "revenue",
"department": "Sales",
"category": "Revenue",
"actual": 3200000,
"budget": 3500000,
"prior_year": 2900000
},
{
"name": "Cost of Goods Sold",
"type": "expense",
"department": "Operations",
"category": "COGS",
"actual": 7800000,
"budget": 7200000,
"prior_year": 6700000
},
{
"name": "Salaries & Wages",
"type": "expense",
"department": "Human Resources",
"category": "Personnel",
"actual": 2100000,
"budget": 2200000,
"prior_year": 1950000
},
{
"name": "Marketing & Advertising",
"type": "expense",
"department": "Marketing",
"category": "Sales & Marketing",
"actual": 850000,
"budget": 750000,
"prior_year": 680000
},
{
"name": "Software & Technology",
"type": "expense",
"department": "Engineering",
"category": "Technology",
"actual": 420000,
"budget": 400000,
"prior_year": 350000
},
{
"name": "Office & Facilities",
"type": "expense",
"department": "Operations",
"category": "G&A",
"actual": 180000,
"budget": 200000,
"prior_year": 175000
},
{
"name": "Travel & Entertainment",
"type": "expense",
"department": "Sales",
"category": "Sales & Marketing",
"actual": 95000,
"budget": 120000,
"prior_year": 88000
},
{
"name": "Professional Services",
"type": "expense",
"department": "Finance",
"category": "G&A",
"actual": 310000,
"budget": 250000,
"prior_year": 220000
},
{
"name": "R&D Expenses",
"type": "expense",
"department": "Engineering",
"category": "R&D",
"actual": 1500000,
"budget": 1400000,
"prior_year": 1200000
}
]
},
"forecast": {
"historical_periods": [
{"period": "Q1 2024", "revenue": 10500000, "gross_profit": 4200000, "operating_income": 1575000},
{"period": "Q2 2024", "revenue": 11200000, "gross_profit": 4480000, "operating_income": 1680000},
{"period": "Q3 2024", "revenue": 11800000, "gross_profit": 4720000, "operating_income": 1770000},
{"period": "Q4 2024", "revenue": 12500000, "gross_profit": 5000000, "operating_income": 1875000},
{"period": "Q1 2025", "revenue": 12800000, "gross_profit": 5120000, "operating_income": 1920000},
{"period": "Q2 2025", "revenue": 13500000, "gross_profit": 5400000, "operating_income": 2025000},
{"period": "Q3 2025", "revenue": 14100000, "gross_profit": 5640000, "operating_income": 2115000},
{"period": "Q4 2025", "revenue": 15700000, "gross_profit": 6280000, "operating_income": 2355000}
],
"drivers": {
"units": {
"base_units": 5000,
"growth_rate": 0.04
},
"pricing": {
"base_price": 2800,
"annual_increase": 0.03
}
},
"assumptions": {
"revenue_growth_rate": 0.08,
"gross_margin": 0.40,
"opex_pct_revenue": 0.25,
"forecast_periods": 12
},
"scenarios": {
"base": {
"growth_adjustment": 0.0,
"margin_adjustment": 0.0
},
"bull": {
"growth_adjustment": 0.04,
"margin_adjustment": 0.03
},
"bear": {
"growth_adjustment": -0.03,
"margin_adjustment": -0.02
}
},
"cash_flow_inputs": {
"opening_cash_balance": 2500000,
"weekly_revenue": 350000,
"collection_rate": 0.85,
"collection_lag_weeks": 2,
"weekly_payroll": 160000,
"weekly_rent": 15000,
"weekly_operating": 45000,
"weekly_other": 20000,
"one_time_items": [
{"week": 3, "amount": -250000, "description": "Annual insurance premium"},
{"week": 6, "amount": 500000, "description": "Customer prepayment"},
{"week": 9, "amount": -180000, "description": "Equipment purchase"},
{"week": 13, "amount": -75000, "description": "Quarterly tax payment"}
]
},
"forecast_periods": 12
}
}
assets/variance_report_template.md
# Budget Variance Report
## Report Header
| Field | Value |
|-------|-------|
| **Company** | [Company Name] |
| **Period** | [Reporting Period] |
| **Prepared By** | [Analyst Name] |
| **Date** | [Report Date] |
| **Materiality Threshold** | [X]% or $[Y]K |
## Executive Summary
[2-3 sentence overview of overall performance vs budget, highlighting whether the company is tracking ahead or behind plan and the primary drivers of variance.]
### Key Metrics
| Metric | Actual | Budget | Variance ($) | Variance (%) | Status |
|--------|--------|--------|-------------|-------------|--------|
| Total Revenue | $[X] | $[X] | $[X] | [X]% | [Fav/Unfav] |
| Total Expenses | $[X] | $[X] | $[X] | [X]% | [Fav/Unfav] |
| Net Income | $[X] | $[X] | $[X] | [X]% | [Fav/Unfav] |
| Operating Margin | [X]% | [X]% | [X]pp | - | [Fav/Unfav] |
## Material Variances
### [Variance Item 1 - e.g., Product Revenue]
| | Actual | Budget | Variance | |
|---|--------|--------|---------|---|
| Amount | $[X] | $[X] | $[X] | [X]% |
**Root Cause:** [Detailed explanation of why this variance occurred]
**Impact:** [Quantified impact on profitability and cash flow]
**Corrective Action:** [Specific steps being taken to address the variance]
**Responsible:** [Owner] | **Target Date:** [Date]
---
### [Variance Item 2]
| | Actual | Budget | Variance | |
|---|--------|--------|---------|---|
| Amount | $[X] | $[X] | $[X] | [X]% |
**Root Cause:** [Explanation]
**Impact:** [Impact]
**Corrective Action:** [Action items]
**Responsible:** [Owner] | **Target Date:** [Date]
---
## Department Performance
| Department | Actual | Budget | Variance ($) | Variance (%) | Favorable | Unfavorable |
|-----------|--------|--------|-------------|-------------|-----------|-------------|
| Sales | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
| Operations | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
| Marketing | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
| Engineering | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
| Finance | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
| HR | $[X] | $[X] | $[X] | [X]% | [N] | [N] |
## Category Breakdown
| Category | Actual | Budget | Variance ($) | Variance (%) |
|----------|--------|--------|-------------|-------------|
| Revenue | $[X] | $[X] | $[X] | [X]% |
| COGS | $[X] | $[X] | $[X] | [X]% |
| Personnel | $[X] | $[X] | $[X] | [X]% |
| Sales & Marketing | $[X] | $[X] | $[X] | [X]% |
| Technology | $[X] | $[X] | $[X] | [X]% |
| G&A | $[X] | $[X] | $[X] | [X]% |
| R&D | $[X] | $[X] | $[X] | [X]% |
## Prior Year Comparison
| Metric | Current Actual | Prior Year | YoY Change ($) | YoY Change (%) |
|--------|---------------|-----------|---------------|---------------|
| Revenue | $[X] | $[X] | $[X] | [X]% |
| Gross Profit | $[X] | $[X] | $[X] | [X]% |
| Operating Income | $[X] | $[X] | $[X] | [X]% |
| Net Income | $[X] | $[X] | $[X] | [X]% |
## Risks and Opportunities
### Risks
1. [Risk description with quantified impact]
2. [Risk description with quantified impact]
### Opportunities
1. [Opportunity description with quantified upside]
2. [Opportunity description with quantified upside]
## Forecast Impact
Based on current variances, the full-year forecast is adjusted as follows:
| Metric | Original FY Forecast | Revised FY Forecast | Change |
|--------|---------------------|--------------------|---------|
| Revenue | $[X] | $[X] | $[X] |
| EBITDA | $[X] | $[X] | $[X] |
| Net Income | $[X] | $[X] | $[X] |
## Action Items
| # | Action | Owner | Due Date | Status |
|---|--------|-------|----------|--------|
| 1 | [Action description] | [Name] | [Date] | [Open/In Progress/Complete] |
| 2 | [Action description] | [Name] | [Date] | [Open/In Progress/Complete] |
| 3 | [Action description] | [Name] | [Date] | [Open/In Progress/Complete] |
---
*Report generated using Financial Analyst Skill - Budget Variance Analyzer*
references/financial-ratios-guide.md
# Financial Ratios Guide
Comprehensive reference for financial ratio analysis covering formulas, interpretation, and industry benchmarks across five categories.
## 1. Profitability Ratios
Measure a company's ability to generate earnings relative to revenue, assets, or equity.
### Return on Equity (ROE)
**Formula:** Net Income / Total Shareholders' Equity
**Interpretation:**
- Measures how effectively management uses equity to generate profits
- Higher ROE indicates more efficient use of equity capital
- Compare against cost of equity - ROE should exceed it
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Below Average | < 8% |
| Acceptable | 8% - 15% |
| Good | 15% - 25% |
| Excellent | > 25% |
**Caveats:** High leverage can inflate ROE. Use DuPont decomposition (ROE = Margin x Turnover x Leverage) for deeper analysis.
### Return on Assets (ROA)
**Formula:** Net Income / Total Assets
**Interpretation:**
- Measures how efficiently assets generate profit
- Asset-light businesses naturally have higher ROA
- Compare within industry only
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Below Average | < 3% |
| Acceptable | 3% - 6% |
| Good | 6% - 12% |
| Excellent | > 12% |
### Gross Margin
**Formula:** (Revenue - COGS) / Revenue
**Interpretation:**
- Measures production efficiency and pricing power
- Declining gross margin may signal competitive pressure or cost inflation
- Critical for evaluating business model sustainability
**Benchmarks by Industry:**
| Industry | Typical Range |
|----------|--------------|
| Software/SaaS | 70% - 85% |
| Financial Services | 50% - 70% |
| Retail | 25% - 45% |
| Manufacturing | 20% - 40% |
| Grocery | 25% - 30% |
### Operating Margin
**Formula:** Operating Income / Revenue
**Interpretation:**
- Measures operational efficiency after all operating expenses
- Excludes interest and taxes for better operational comparison
- Indicates management effectiveness in controlling costs
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Below Average | < 5% |
| Acceptable | 5% - 15% |
| Good | 15% - 25% |
| Excellent | > 25% |
### Net Margin
**Formula:** Net Income / Revenue
**Interpretation:**
- Bottom-line profitability after all expenses
- Affected by tax strategy, capital structure, and one-time items
- Most comprehensive profitability measure
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Below Average | < 3% |
| Acceptable | 3% - 10% |
| Good | 10% - 20% |
| Excellent | > 20% |
## 2. Liquidity Ratios
Measure a company's ability to meet short-term obligations.
### Current Ratio
**Formula:** Current Assets / Current Liabilities
**Interpretation:**
- Measures short-term solvency
- Too high may indicate inefficient asset use
- Too low signals potential liquidity risk
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Concern | < 1.0 |
| Acceptable | 1.0 - 1.5 |
| Healthy | 1.5 - 3.0 |
| Excessive | > 3.0 |
### Quick Ratio (Acid Test)
**Formula:** (Current Assets - Inventory) / Current Liabilities
**Interpretation:**
- More conservative than current ratio
- Excludes inventory (least liquid current asset)
- Critical for businesses with slow-moving inventory
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Concern | < 0.8 |
| Acceptable | 0.8 - 1.0 |
| Healthy | 1.0 - 2.0 |
| Excessive | > 2.0 |
### Cash Ratio
**Formula:** Cash & Equivalents / Current Liabilities
**Interpretation:**
- Most conservative liquidity measure
- Indicates ability to pay obligations with cash on hand
- Particularly important during credit crunches
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Low | < 0.2 |
| Adequate | 0.2 - 0.5 |
| Strong | 0.5 - 1.0 |
| Excessive | > 1.0 |
## 3. Leverage Ratios
Measure the extent to which a company uses debt financing.
### Debt-to-Equity Ratio
**Formula:** Total Debt / Total Shareholders' Equity
**Interpretation:**
- Measures financial leverage and risk
- Higher ratio = more reliance on debt financing
- Industry norms vary significantly (utilities vs tech)
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Conservative | < 0.3 |
| Moderate | 0.3 - 0.8 |
| Elevated | 0.8 - 2.0 |
| High Risk | > 2.0 |
### Interest Coverage Ratio
**Formula:** Operating Income (EBIT) / Interest Expense
**Interpretation:**
- Measures ability to service debt from operating earnings
- Below 1.5x is a red flag for lenders
- Critical for credit analysis
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Distressed | < 2.0 |
| Adequate | 2.0 - 5.0 |
| Strong | 5.0 - 10.0 |
| Very Strong | > 10.0 |
### Debt Service Coverage Ratio (DSCR)
**Formula:** Operating Cash Flow / Total Debt Service
**Interpretation:**
- Cash-based measure of debt servicing capacity
- Includes principal repayments (unlike interest coverage)
- Required by many loan covenants
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Default Risk | < 1.0 |
| Minimum | 1.0 - 1.5 |
| Comfortable | 1.5 - 2.5 |
| Strong | > 2.5 |
## 4. Efficiency Ratios
Measure how effectively a company uses its assets and manages operations.
### Asset Turnover
**Formula:** Revenue / Total Assets
**Interpretation:**
- Measures revenue generated per dollar of assets
- Higher indicates more efficient asset utilization
- Inversely related to profit margins (DuPont)
**Benchmarks:**
| Industry | Typical Range |
|----------|--------------|
| Retail | 2.0 - 3.0 |
| Manufacturing | 0.8 - 1.5 |
| Utilities | 0.3 - 0.5 |
| Technology | 0.5 - 1.0 |
### Inventory Turnover
**Formula:** COGS / Average Inventory
**Interpretation:**
- Measures how quickly inventory is sold
- Low turnover suggests overstock or obsolescence risk
- High turnover may indicate strong sales or thin inventory
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Slow | < 4x |
| Average | 4x - 8x |
| Efficient | 8x - 12x |
| Very Efficient | > 12x |
### Receivables Turnover
**Formula:** Revenue / Accounts Receivable
**Interpretation:**
- Measures efficiency of credit and collections
- Higher turnover means faster collections
- Monitor trends for credit policy changes
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Slow | < 6x |
| Average | 6x - 10x |
| Efficient | 10x - 15x |
| Very Efficient | > 15x |
### Days Sales Outstanding (DSO)
**Formula:** 365 / Receivables Turnover
**Interpretation:**
- Average days to collect payment after a sale
- Lower DSO = faster cash conversion
- Compare against payment terms
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Excellent | < 30 days |
| Good | 30 - 45 days |
| Acceptable | 45 - 60 days |
| Concern | > 60 days |
## 5. Valuation Ratios
Measure a company's market value relative to financial metrics.
### Price-to-Earnings (P/E) Ratio
**Formula:** Share Price / Earnings Per Share
**Interpretation:**
- Most widely used valuation metric
- High P/E suggests growth expectations or overvaluation
- Use trailing (TTM) and forward P/E for comparison
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Value | < 10x |
| Fair | 10x - 20x |
| Growth | 20x - 35x |
| Premium | > 35x |
### Price-to-Book (P/B) Ratio
**Formula:** Share Price / Book Value Per Share
**Interpretation:**
- Compares market value to accounting value
- Below 1.0 may indicate undervaluation or distress
- Most useful for asset-heavy industries
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Undervalued | < 1.0 |
| Fair | 1.0 - 2.5 |
| Premium | 2.5 - 5.0 |
| Rich | > 5.0 |
### Price-to-Sales (P/S) Ratio
**Formula:** Market Cap / Revenue
**Interpretation:**
- Useful for companies without positive earnings
- Compare within industry only
- Lower = potentially better value
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Value | < 1.0 |
| Fair | 1.0 - 3.0 |
| Growth | 3.0 - 8.0 |
| Premium | > 8.0 |
### EV/EBITDA
**Formula:** Enterprise Value / EBITDA
**Interpretation:**
- Capital-structure-neutral valuation metric
- Preferred for M&A analysis and leveraged buyouts
- More comparable across capital structures than P/E
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Value | < 6x |
| Fair | 6x - 12x |
| Growth | 12x - 20x |
| Premium | > 20x |
### PEG Ratio
**Formula:** P/E Ratio / Earnings Growth Rate (%)
**Interpretation:**
- Growth-adjusted P/E ratio
- PEG of 1.0 suggests fair valuation relative to growth
- Below 1.0 may indicate undervaluation
**Benchmarks:**
| Rating | Range |
|--------|-------|
| Undervalued | < 0.5 |
| Fair | 0.5 - 1.0 |
| Fully Valued | 1.0 - 2.0 |
| Overvalued | > 2.0 |
## Ratio Analysis Best Practices
1. **Compare within industry** - Ratios vary significantly across sectors
2. **Analyze trends** - A single period snapshot is insufficient; look at 3-5 year trends
3. **Use multiple ratios** - No single ratio tells the complete story
4. **Consider context** - Accounting policies, business cycle, and company stage matter
5. **DuPont decomposition** - Break ROE into margin, turnover, and leverage components
6. **Peer comparison** - Compare against direct competitors, not just broad benchmarks
7. **Watch for manipulation** - Revenue recognition changes, off-balance-sheet items, and one-time adjustments can distort ratios
references/forecasting-best-practices.md
# Forecasting Best Practices
Comprehensive reference for financial forecasting including driver-based models, rolling forecasts, accuracy improvement techniques, and scenario planning.
## 1. Driver-Based Forecasting
### Overview
Driver-based forecasting models financial outcomes based on key business drivers rather than extrapolating from historical trends alone. This approach creates more transparent, actionable, and accurate forecasts.
### Identifying Key Drivers
**Revenue Drivers:**
| Business Model | Primary Drivers |
|---------------|----------------|
| SaaS/Subscription | Customers x ARPU x Retention Rate |
| E-commerce | Visitors x Conversion Rate x AOV |
| Manufacturing | Units x Price per Unit |
| Professional Services | Headcount x Utilization x Bill Rate |
| Retail | Stores x Revenue per Store (or sqft) |
| Marketplace | GMV x Take Rate |
**Cost Drivers:**
| Category | Common Drivers |
|----------|---------------|
| COGS | Revenue x (1 - Gross Margin) or Units x Unit Cost |
| Headcount Costs | Employees x Average Compensation x (1 + Benefits Rate) |
| Sales & Marketing | Revenue x S&M % or CAC x New Customers |
| R&D | Engineering Headcount x Avg Salary |
| G&A | Headcount-based + fixed costs |
| CapEx | Revenue x CapEx Intensity or Project-based |
### Building a Driver-Based Model
**Step 1: Map the value chain**
- Revenue = f(volume drivers, pricing drivers, mix drivers)
- Costs = f(variable drivers, fixed components, step functions)
**Step 2: Establish driver relationships**
- Linear: Revenue = Units x Price
- Non-linear: Revenue = Base x (1 + Growth Rate)^t
- Step function: Facilities costs that jump at capacity thresholds
**Step 3: Validate driver assumptions**
- Compare driver values to historical actuals
- Benchmark against industry data
- Stress-test extreme values
**Step 4: Build sensitivity**
- Identify which drivers have the largest impact on output
- Quantify the range of reasonable values for each driver
- Create scenario combinations
### Driver Sensitivity Matrix
Rank drivers by impact and uncertainty:
| | High Impact | Low Impact |
|---|-----------|-----------|
| **High Uncertainty** | Model these carefully, run scenarios | Monitor but don't over-model |
| **Low Uncertainty** | Get these right; high accuracy needed | Use simple assumptions |
## 2. Rolling Forecasts
### What Is a Rolling Forecast?
A rolling forecast continuously extends the forecast horizon as each period closes. Unlike a static annual budget, a rolling forecast always looks forward the same number of periods (typically 12-18 months).
### Rolling Forecast vs Annual Budget
| Feature | Annual Budget | Rolling Forecast |
|---------|--------------|-----------------|
| Time Horizon | Fixed (Jan-Dec) | Rolling (12-18 months) |
| Update Frequency | Once per year | Monthly or quarterly |
| Detail Level | Very detailed | Driver-level |
| Preparation Time | 3-6 months | 2-5 days per cycle |
| Relevance | Declines over time | Stays current |
| Flexibility | Rigid | Adaptive |
### Implementation Steps
1. **Select the horizon** - 12 months rolling is most common (some use 18 months for CapEx planning)
2. **Define update cadence** - Monthly for volatile businesses; quarterly for stable ones
3. **Choose the right detail** - Driver-level, not line-item detail
4. **Automate data feeds** - Reduce manual effort per cycle
5. **Separate actuals from forecast** - Clear delineation between reported and projected periods
6. **Track forecast accuracy** - Measure MAPE (Mean Absolute Percentage Error) over time
### 13-Week Cash Flow Forecast
A specialized rolling forecast for liquidity management:
**Structure:**
- Week-by-week cash inflows and outflows
- Opening and closing cash balances
- Minimum cash threshold alerts
**Key Components:**
| Inflows | Outflows |
|---------|----------|
| Customer collections (by aging) | Payroll (fixed cadence) |
| Other receivables | Rent / Lease payments |
| Asset sales | Vendor payments (by terms) |
| Financing proceeds | Debt service |
| Tax refunds | Tax payments |
| Other income | Capital expenditures |
**Collection Modeling:**
- Apply collection rates by customer segment or aging bucket
- Model DSO trends to project collection timing
- Account for seasonal patterns in payment behavior
## 3. Accuracy Improvement
### Measuring Forecast Accuracy
**Mean Absolute Percentage Error (MAPE):**
```
MAPE = (1/n) x Sum of |Actual - Forecast| / |Actual| x 100%
```
**Accuracy Benchmarks:**
| MAPE | Rating |
|------|--------|
| < 5% | Excellent |
| 5% - 10% | Good |
| 10% - 20% | Acceptable |
| > 20% | Needs improvement |
**Weighted MAPE (WMAPE):**
Use when line items vary significantly in magnitude - weights errors by actual values.
### Techniques to Improve Accuracy
**1. Bias Detection and Correction**
- Track directional bias (consistently over or under forecasting)
- Calculate mean signed error to detect systematic bias
- Adjust driver assumptions to correct persistent bias
**2. Variance Analysis Loop**
- After each period closes, compare actual vs forecast
- Identify root causes of significant variances
- Update driver assumptions based on learnings
- Document what changed and why
**3. Ensemble Approach**
- Combine multiple forecasting methods
- Blend statistical (trend) with judgmental (management input)
- Weight methods by their historical accuracy
**4. Granularity Optimization**
- Forecast at the right level of detail - not too aggregated, not too granular
- Product/segment level usually more accurate than single top-line
- Aggregate bottom-up forecasts for total, then adjust
**5. Leading Indicators**
- Identify metrics that predict financial outcomes 1-3 months ahead
- Pipeline/bookings predict revenue
- Hiring plans predict headcount costs
- Customer churn signals predict retention revenue
### Common Accuracy Killers
1. **Anchoring bias** - Over-relying on last year's numbers
2. **Optimism bias** - Systematic overestimation of growth
3. **Lack of accountability** - No one tracks forecast vs actual
4. **Stale assumptions** - Not updating for market changes
5. **Missing data** - Forecasting without key driver inputs
6. **Over-precision** - False precision in uncertain environments
## 4. Scenario Planning
### Three-Scenario Framework
| Scenario | Description | Probability |
|----------|-------------|-------------|
| **Base Case** | Most likely outcome based on current trajectory | 50-60% |
| **Bull Case** | Favorable conditions, upside realization | 15-25% |
| **Bear Case** | Adverse conditions, downside risks | 15-25% |
### Scenario Construction
**Base Case:**
- Continuation of current trends
- Management's operational plan
- Market consensus assumptions
- Normal competitive dynamics
**Bull Case (apply selectively, not uniformly):**
- Faster customer acquisition or market adoption
- Successful product launch or expansion
- Favorable macro conditions
- Competitor weakness or exit
- Margin expansion from operating leverage
**Bear Case (be realistic, not catastrophic):**
- Slower growth or market contraction
- Increased competition or pricing pressure
- Key customer or contract loss
- Supply chain disruption
- Regulatory headwinds
### Scenario Variables
Map each scenario to specific driver values:
| Driver | Bear | Base | Bull |
|--------|------|------|------|
| Revenue Growth | +2% | +8% | +15% |
| Gross Margin | 35% | 40% | 43% |
| Customer Churn | 8% | 5% | 3% |
| New Customers/Month | 50 | 100 | 180 |
| Price Increase | 0% | 3% | 5% |
### Presenting Scenarios
1. **Show the range** - Management needs to see the potential outcomes
2. **Quantify the gap** - Dollar impact of bull vs bear on key metrics
3. **Identify triggers** - What conditions would cause each scenario
4. **Define actions** - What levers to pull in each scenario
5. **Assign probabilities** - Not all scenarios are equally likely
## 5. Forecast Communication
### Stakeholder Needs
| Audience | Needs |
|----------|-------|
| Board | High-level scenarios, key risks, strategic implications |
| CEO/CFO | Detailed drivers, variance explanations, action items |
| Department Heads | Their specific budget vs forecast, headcount plans |
| Investors | Revenue guidance, margin trajectory, capital allocation |
| Operations | Weekly/monthly targets, resource requirements |
### Presentation Framework
1. **Executive summary** - Key metrics, direction of travel, confidence level
2. **Variance bridge** - Walk from budget/prior forecast to current forecast
3. **Driver analysis** - What changed and why
4. **Scenario comparison** - Range of outcomes
5. **Key risks and opportunities** - What could change the forecast
6. **Action items** - Decisions needed based on forecast
### Forecast Cadence
| Activity | Frequency | Time Required |
|----------|-----------|--------------|
| 13-week cash flow update | Weekly | 1-2 hours |
| Rolling forecast update | Monthly | 1-2 days |
| Full reforecast | Quarterly | 3-5 days |
| Annual budget/plan | Annually | 4-8 weeks |
| Board reporting | Quarterly | 2-3 days |
## 6. Industry-Specific Considerations
### SaaS Metrics in Forecasting
- **MRR/ARR decomposition:** New, expansion, contraction, churn
- **Cohort-based forecasting:** Forecast by customer cohort for retention accuracy
- **Rule of 40:** Revenue growth % + Profit margin % should exceed 40%
- **Net Revenue Retention:** Target > 110% for healthy SaaS
- **CAC Payback:** Should be < 18 months
### Retail Forecasting
- **Same-store sales growth** as primary organic growth metric
- **Seasonal decomposition** for accurate monthly/weekly forecasts
- **Markdown optimization** impact on gross margin
- **Inventory turns** drive working capital forecasts
### Manufacturing Forecasting
- **Order backlog** as a leading indicator
- **Capacity constraints** creating step-function cost increases
- **Raw material price forecasts** for COGS
- **Maintenance CapEx vs growth CapEx** distinction
- **Utilization rates** driving unit cost projections
references/industry-adaptations.md
# Industry Adaptations
Sector-specific metrics, benchmarks, and considerations for financial analysis.
## SaaS / Software
**Key Metrics:**
- ARR / MRR growth rate
- Net Revenue Retention (NRR) — target >110%
- CAC Payback Period — target <18 months
- Rule of 40 (growth rate + profit margin ≥ 40%)
- LTV:CAC ratio — target >3:1
- Gross margin — target >70%
**Valuation Multiples:**
- Revenue multiple: 5-15x ARR (growth-adjusted)
- High-growth (>50%): 15-25x ARR
- Moderate growth (20-50%): 8-15x ARR
- Low growth (<20%): 3-8x ARR
**Considerations:**
- Deferred revenue recognition (ASC 606)
- Stock-based compensation impact on margins
- Cohort analysis critical for retention metrics
## Retail / E-Commerce
**Key Metrics:**
- Same-store sales growth (SSS)
- Gross margin by category
- Inventory turnover — target varies by segment (grocery: 14-20x, fashion: 4-6x)
- Revenue per square foot (physical)
- Customer acquisition cost vs. AOV
- Return rate impact on unit economics
**Valuation Multiples:**
- EV/EBITDA: 8-15x (premium brands higher)
- P/E: 15-25x
**Considerations:**
- Seasonal revenue concentration (Q4 holiday)
- Working capital intensity (inventory cycles)
- Omnichannel attribution complexity
## Manufacturing
**Key Metrics:**
- Gross margin by product line
- Capacity utilization rate — target >80%
- Days Inventory Outstanding (DIO)
- Warranty reserve as % of revenue
- Capex as % of revenue (maintenance vs. growth)
- Order backlog / book-to-bill ratio
**Valuation Multiples:**
- EV/EBITDA: 6-12x
- P/E: 12-20x
**Considerations:**
- Raw material cost volatility
- Currency exposure in supply chain
- Depreciation schedules (straight-line vs. accelerated)
- Regulatory compliance costs (environmental, safety)
## Financial Services
**Key Metrics:**
- Net Interest Margin (NIM)
- Return on Equity (ROE) — target >12%
- Cost-to-Income Ratio — target <60%
- Non-Performing Loan (NPL) ratio
- Tier 1 Capital Ratio — regulatory minimum varies
- Assets Under Management (AUM) growth
**Valuation Multiples:**
- Price-to-Book (P/B): 1.0-2.5x
- P/E: 10-18x
**Considerations:**
- Regulatory capital requirements (Basel III/IV)
- Interest rate sensitivity analysis
- Credit risk provisioning (CECL / IFRS 9)
- Mark-to-market vs. held-to-maturity accounting
## Healthcare
**Key Metrics:**
- Revenue per patient / per bed
- Payor mix (Medicare/Medicaid vs. commercial)
- EBITDAR margin (rent-adjusted for facilities)
- Clinical trial pipeline value (biotech/pharma)
- Patent cliff exposure
- R&D as % of revenue — benchmark 15-25% (pharma)
**Valuation Multiples:**
- EV/EBITDA: 10-18x (medtech), 12-20x (pharma)
- EV/Revenue: 3-8x (services), 5-15x (devices)
**Considerations:**
- Reimbursement rate changes (regulatory risk)
- FDA approval timelines and probability-weighted pipeline
- 340B pricing program impact
- Medical device regulation (MDR, QSR compliance)
references/valuation-methodology.md
# Valuation Methodology Guide
Comprehensive reference for business valuation approaches including DCF analysis, comparable company analysis, and precedent transactions.
## 1. Discounted Cash Flow (DCF) Methodology
### Overview
DCF is an intrinsic valuation method that estimates the present value of a company's expected future free cash flows, discounted at an appropriate rate reflecting the risk of those cash flows.
**Core Principle:** The value of a business equals the present value of all future cash flows it will generate.
**Formula:**
```
Enterprise Value = Sum of [FCF_t / (1 + WACC)^t] + Terminal Value / (1 + WACC)^n
```
Where:
- FCF_t = Free Cash Flow in year t
- WACC = Weighted Average Cost of Capital
- n = number of projection years
### Step 1: Historical Analysis
Before projecting, analyze 3-5 years of historical financials:
- **Revenue growth rates** - Identify organic vs acquisition-driven growth
- **Margin trends** - Gross, operating, and net margin trajectories
- **Capital intensity** - CapEx as % of revenue
- **Working capital** - Cash conversion cycle trends
- **Free cash flow conversion** - FCF / Net Income ratio
### Step 2: Revenue Projections
**Approaches:**
1. **Top-down:** Market size x Market share x Pricing
2. **Bottom-up:** Units x Price, or Customers x ARPU
3. **Growth rate extrapolation:** Historical growth with decay
**Revenue Projection Best Practices:**
- Use 5-7 year explicit projection period
- Growth should converge toward GDP growth by terminal year
- Support assumptions with market data and management guidance
- Model revenue by segment/product line when possible
### Step 3: Free Cash Flow Calculation
**Unlevered Free Cash Flow (UFCF):**
```
UFCF = EBIT x (1 - Tax Rate)
+ Depreciation & Amortization
- Capital Expenditures
- Changes in Net Working Capital
```
**Key Drivers:**
- Operating margin trajectory
- CapEx as % of revenue (maintenance vs growth)
- Working capital requirements (DSO, DIO, DPO)
- Tax rate (effective vs marginal)
### Step 4: WACC Calculation
**Weighted Average Cost of Capital:**
```
WACC = (E/V x Re) + (D/V x Rd x (1 - T))
```
Where:
- E/V = Equity weight (market value)
- D/V = Debt weight (market value)
- Re = Cost of equity
- Rd = Cost of debt (pre-tax)
- T = Marginal tax rate
#### Cost of Equity (CAPM)
```
Re = Rf + Beta x (Rm - Rf) + Size Premium + Company-Specific Risk
```
| Component | Description | Typical Range |
|-----------|-------------|---------------|
| Risk-Free Rate (Rf) | 10-year Treasury yield | 3.5% - 5.0% |
| Equity Risk Premium (ERP) | Market return above risk-free | 5.0% - 7.0% |
| Beta | Systematic risk relative to market | 0.5 - 2.0 |
| Size Premium | Small-cap additional risk | 0% - 5% |
| Company-Specific Risk | Unique risk factors | 0% - 5% |
**Beta Estimation:**
- Use 2-5 year weekly returns against broad market index
- Unlevered betas for comparability, then re-lever to target capital structure
- Consider industry median beta for stability
#### Cost of Debt
```
Rd = Yield on comparable-maturity corporate bonds
OR
Rd = Risk-Free Rate + Credit Spread
```
**Credit Spread by Rating:**
| Rating | Typical Spread |
|--------|---------------|
| AAA | 0.5% - 1.0% |
| AA | 1.0% - 1.5% |
| A | 1.5% - 2.0% |
| BBB | 2.0% - 3.0% |
| BB | 3.0% - 5.0% |
| B | 5.0% - 8.0% |
### Step 5: Terminal Value
Terminal value typically represents 60-80% of total enterprise value. Use two methods and cross-check.
#### Perpetuity Growth Method
```
TV = FCF_n x (1 + g) / (WACC - g)
```
Where g = terminal growth rate (typically 2.0% - 3.0%, should not exceed long-term GDP growth)
**Sensitivity:** Terminal value is highly sensitive to g. A 0.5% change in g can move enterprise value by 15-25%.
#### Exit Multiple Method
```
TV = Terminal Year EBITDA x Exit EV/EBITDA Multiple
```
**Exit Multiple Selection:**
- Use current trading multiples of comparable companies
- Consider whether current multiples are at historical highs/lows
- Apply a discount for lack of marketability if private
**Cross-Check:** Both methods should yield similar results. Large discrepancies signal inconsistent assumptions.
### Step 6: Enterprise to Equity Bridge
```
Enterprise Value
- Net Debt (Total Debt - Cash)
- Minority Interest
- Preferred Equity
+ Equity Method Investments
= Equity Value
Equity Value / Diluted Shares Outstanding = Value Per Share
```
### Step 7: Sensitivity Analysis
Always present results as a range, not a single point estimate.
**Standard Sensitivity Tables:**
1. WACC vs Terminal Growth Rate
2. WACC vs Exit Multiple
3. Revenue Growth vs Operating Margin
**Scenario Analysis:**
- Base case: Management guidance / consensus estimates
- Bull case: Upside scenario with faster growth or margin expansion
- Bear case: Downside scenario with slower growth or margin compression
## 2. Comparable Company Analysis
### Methodology
1. **Select peer group** - Similar size, industry, growth profile, and margins
2. **Calculate trading multiples** for each peer
3. **Determine appropriate multiple range**
4. **Apply to target company's metrics**
### Common Multiples
| Multiple | When to Use |
|----------|-------------|
| EV/Revenue | Pre-profit companies, high-growth tech |
| EV/EBITDA | Most common for mature companies |
| EV/EBIT | When D&A differs significantly across peers |
| P/E | Stable earnings, financial services |
| P/B | Banks, insurance, asset-heavy industries |
| EV/FCF | Capital-light businesses with clean FCF |
### Peer Selection Criteria
- **Industry:** Same or closely adjacent sectors
- **Size:** Within 0.5x to 2x of target revenue/market cap
- **Geography:** Same primary markets
- **Growth profile:** Similar revenue growth rates (within 5-10%)
- **Margin profile:** Similar operating margin structure
- **Business model:** Comparable revenue mix and customer base
### Premium/Discount Adjustments
| Factor | Adjustment |
|--------|-----------|
| Higher growth | Premium of 1-3x on EV/EBITDA |
| Lower margins | Discount of 1-2x |
| Smaller scale | Discount of 10-20% |
| Private company | Discount of 15-30% (illiquidity) |
| Control premium | Premium of 20-40% (for acquisitions) |
## 3. Precedent Transaction Analysis
### Methodology
1. **Identify comparable transactions** in same industry
2. **Calculate transaction multiples** (EV/Revenue, EV/EBITDA)
3. **Adjust for market conditions** and deal-specific factors
4. **Apply adjusted multiples** to target
### Key Considerations
- Transactions include control premiums (typically 20-40%)
- Market conditions at time of deal affect multiples
- Strategic vs financial buyer valuations differ
- Consider synergy expectations embedded in price
- More recent transactions carry greater relevance
## 4. Valuation Framework Selection
| Situation | Primary Method | Secondary Method |
|-----------|---------------|-----------------|
| Profitable, stable | DCF | Comparable companies |
| High growth, pre-profit | Comparable companies (EV/Revenue) | DCF with scenario analysis |
| M&A target | Precedent transactions | DCF |
| Asset-heavy, cyclical | Asset-based valuation | Normalized DCF |
| Financial institution | Dividend discount model | P/B, P/E comps |
| Distressed | Liquidation value | Restructured DCF |
## 5. Common Pitfalls
1. **Hockey stick projections** - Unrealistic growth acceleration in later years
2. **Terminal value dominance** - If TV > 80% of EV, shorten projection period or question assumptions
3. **Circular references** - WACC depends on equity value which depends on WACC
4. **Ignoring working capital** - Can significantly affect FCF
5. **Single-point estimates** - Always present as a range
6. **Stale comparables** - Market conditions change; update regularly
7. **Confirmation bias** - Don't work backward from a desired conclusion
8. **Ignoring dilution** - Use fully diluted shares (treasury stock method for options)
scripts/budget_variance_analyzer.py
#!/usr/bin/env python3
"""
Budget Variance Analyzer
Analyzes actual vs budget vs prior year performance with materiality
threshold filtering, favorable/unfavorable classification, and
department/category breakdown.
Usage:
python budget_variance_analyzer.py budget_data.json
python budget_variance_analyzer.py budget_data.json --format json
python budget_variance_analyzer.py budget_data.json --threshold-pct 5 --threshold-amt 25000
"""
import argparse
import json
import sys
from typing import Any, Dict, List, Optional, Tuple
def safe_divide(numerator: float, denominator: float, default: float = 0.0) -> float:
"""Safely divide two numbers, returning default if denominator is zero."""
if denominator == 0 or denominator is None:
return default
return numerator / denominator
def resolve_input_section(
data: Dict[str, Any], section_key: str, flat_keys: Tuple[str, ...]
) -> Dict[str, Any]:
"""
Accept both supported input shapes:
1. Flat: the expected keys live at the top level of the JSON file.
2. Nested: the data lives under a per-tool section key, as in
assets/sample_financial_data.json (which bundles inputs for all
four financial-analyst scripts in one file).
"""
if any(key in data for key in flat_keys):
return data
section = data.get(section_key)
if isinstance(section, dict):
return section
return data
class BudgetVarianceAnalyzer:
"""Analyze budget variances with materiality filtering and classification."""
def __init__(
self,
data: Dict[str, Any],
threshold_pct: float = 10.0,
threshold_amt: float = 50000.0,
) -> None:
"""
Initialize the analyzer.
Args:
data: Budget data with line items
threshold_pct: Materiality threshold as percentage (default 10%)
threshold_amt: Materiality threshold as dollar amount (default $50K)
"""
self.line_items: List[Dict[str, Any]] = data.get("line_items", [])
self.period: str = data.get("period", "Current Period")
self.company: str = data.get("company", "Company")
self.threshold_pct = threshold_pct
self.threshold_amt = threshold_amt
self.variances: List[Dict[str, Any]] = []
self.material_variances: List[Dict[str, Any]] = []
self.summary: Dict[str, Any] = {}
def classify_favorability(
self, line_type: str, variance_amount: float
) -> str:
"""
Classify variance as favorable or unfavorable.
Revenue: over budget = favorable
Expense: under budget = favorable
"""
if line_type.lower() in ("revenue", "income", "sales"):
return "Favorable" if variance_amount > 0 else "Unfavorable"
else:
# For expenses, under budget (negative variance) is favorable
return "Favorable" if variance_amount < 0 else "Unfavorable"
def calculate_variances(self) -> List[Dict[str, Any]]:
"""Calculate variances for all line items."""
self.variances = []
for item in self.line_items:
name = item.get("name", "Unknown")
line_type = item.get("type", "expense")
department = item.get("department", "General")
category = item.get("category", "Other")
actual = item.get("actual", 0)
budget = item.get("budget", 0)
prior_year = item.get("prior_year", None)
# Budget variance
budget_var_amt = actual - budget
budget_var_pct = safe_divide(budget_var_amt, budget) * 100
# Prior year variance (if available)
py_var_amt = (actual - prior_year) if prior_year is not None else None
py_var_pct = (
safe_divide(py_var_amt, prior_year) * 100
if prior_year is not None
else None
)
favorability = self.classify_favorability(line_type, budget_var_amt)
is_material = (
abs(budget_var_pct) >= self.threshold_pct
or abs(budget_var_amt) >= self.threshold_amt
)
variance_record = {
"name": name,
"type": line_type,
"department": department,
"category": category,
"actual": actual,
"budget": budget,
"prior_year": prior_year,
"budget_variance_amount": budget_var_amt,
"budget_variance_pct": round(budget_var_pct, 2),
"prior_year_variance_amount": py_var_amt,
"prior_year_variance_pct": (
round(py_var_pct, 2) if py_var_pct is not None else None
),
"favorability": favorability,
"is_material": is_material,
}
self.variances.append(variance_record)
# Filter material variances
self.material_variances = [v for v in self.variances if v["is_material"]]
return self.variances
def department_summary(self) -> Dict[str, Dict[str, Any]]:
"""Summarize variances by department."""
departments: Dict[str, Dict[str, float]] = {}
for v in self.variances:
dept = v["department"]
if dept not in departments:
departments[dept] = {
"total_actual": 0.0,
"total_budget": 0.0,
"total_variance": 0.0,
"favorable_count": 0,
"unfavorable_count": 0,
"line_count": 0,
}
departments[dept]["total_actual"] += v["actual"]
departments[dept]["total_budget"] += v["budget"]
departments[dept]["total_variance"] += v["budget_variance_amount"]
departments[dept]["line_count"] += 1
if v["favorability"] == "Favorable":
departments[dept]["favorable_count"] += 1
else:
departments[dept]["unfavorable_count"] += 1
# Add variance percentage
for dept_data in departments.values():
dept_data["variance_pct"] = round(
safe_divide(
dept_data["total_variance"], dept_data["total_budget"]
)
* 100,
2,
)
return departments
def category_summary(self) -> Dict[str, Dict[str, Any]]:
"""Summarize variances by category."""
categories: Dict[str, Dict[str, float]] = {}
for v in self.variances:
cat = v["category"]
if cat not in categories:
categories[cat] = {
"total_actual": 0.0,
"total_budget": 0.0,
"total_variance": 0.0,
"line_count": 0,
}
categories[cat]["total_actual"] += v["actual"]
categories[cat]["total_budget"] += v["budget"]
categories[cat]["total_variance"] += v["budget_variance_amount"]
categories[cat]["line_count"] += 1
for cat_data in categories.values():
cat_data["variance_pct"] = round(
safe_divide(
cat_data["total_variance"], cat_data["total_budget"]
)
* 100,
2,
)
return categories
def generate_executive_summary(self) -> Dict[str, Any]:
"""Generate an executive summary of the variance analysis."""
total_actual = sum(
v["actual"] for v in self.variances if v["type"].lower() in ("revenue", "income", "sales")
)
total_budget = sum(
v["budget"] for v in self.variances if v["type"].lower() in ("revenue", "income", "sales")
)
total_expense_actual = sum(
v["actual"] for v in self.variances if v["type"].lower() not in ("revenue", "income", "sales")
)
total_expense_budget = sum(
v["budget"] for v in self.variances if v["type"].lower() not in ("revenue", "income", "sales")
)
revenue_variance = total_actual - total_budget
expense_variance = total_expense_actual - total_expense_budget
favorable_count = sum(
1 for v in self.variances if v["favorability"] == "Favorable"
)
unfavorable_count = sum(
1 for v in self.variances if v["favorability"] == "Unfavorable"
)
self.summary = {
"period": self.period,
"company": self.company,
"total_line_items": len(self.variances),
"material_variances_count": len(self.material_variances),
"favorable_count": favorable_count,
"unfavorable_count": unfavorable_count,
"revenue": {
"actual": total_actual,
"budget": total_budget,
"variance_amount": revenue_variance,
"variance_pct": round(
safe_divide(revenue_variance, total_budget) * 100, 2
),
},
"expenses": {
"actual": total_expense_actual,
"budget": total_expense_budget,
"variance_amount": expense_variance,
"variance_pct": round(
safe_divide(expense_variance, total_expense_budget) * 100, 2
),
},
"net_impact": revenue_variance - expense_variance,
"materiality_thresholds": {
"percentage": self.threshold_pct,
"amount": self.threshold_amt,
},
}
return self.summary
def run_analysis(self) -> Dict[str, Any]:
"""Run the complete variance analysis."""
self.calculate_variances()
dept_summary = self.department_summary()
cat_summary = self.category_summary()
exec_summary = self.generate_executive_summary()
return {
"executive_summary": exec_summary,
"all_variances": self.variances,
"material_variances": self.material_variances,
"department_summary": dept_summary,
"category_summary": cat_summary,
}
def format_text(self, results: Dict[str, Any]) -> str:
"""Format results as human-readable text."""
lines: List[str] = []
lines.append("=" * 70)
lines.append("BUDGET VARIANCE ANALYSIS")
lines.append("=" * 70)
summary = results["executive_summary"]
lines.append(f"\n Company: {summary['company']}")
lines.append(f" Period: {summary['period']}")
def fmt_money(val: float) -> str:
sign = "+" if val > 0 else ""
if abs(val) >= 1e6:
return f"{sign}${val / 1e6:,.2f}M"
if abs(val) >= 1e3:
return f"{sign}${val / 1e3:,.1f}K"
return f"{sign}${val:,.2f}"
lines.append(f"\n--- EXECUTIVE SUMMARY ---")
rev = summary["revenue"]
exp = summary["expenses"]
lines.append(
f" Revenue: Actual {fmt_money(rev['actual'])} vs "
f"Budget {fmt_money(rev['budget'])} "
f"({fmt_money(rev['variance_amount'])}, {rev['variance_pct']:+.1f}%)"
)
lines.append(
f" Expenses: Actual {fmt_money(exp['actual'])} vs "
f"Budget {fmt_money(exp['budget'])} "
f"({fmt_money(exp['variance_amount'])}, {exp['variance_pct']:+.1f}%)"
)
lines.append(f" Net Impact: {fmt_money(summary['net_impact'])}")
lines.append(
f" Total Items: {summary['total_line_items']} | "
f"Material: {summary['material_variances_count']} | "
f"Favorable: {summary['favorable_count']} | "
f"Unfavorable: {summary['unfavorable_count']}"
)
# Material variances
material = results["material_variances"]
if material:
lines.append(f"\n--- MATERIAL VARIANCES ---")
lines.append(
f" (Threshold: {self.threshold_pct}% or "
f"${self.threshold_amt:,.0f})"
)
for v in material:
lines.append(
f"\n {v['name']} ({v['department']})"
)
lines.append(
f" Actual: {fmt_money(v['actual'])} | "
f"Budget: {fmt_money(v['budget'])}"
)
lines.append(
f" Variance: {fmt_money(v['budget_variance_amount'])} "
f"({v['budget_variance_pct']:+.1f}%) - {v['favorability']}"
)
# Department summary
dept = results["department_summary"]
if dept:
lines.append(f"\n--- DEPARTMENT SUMMARY ---")
for dept_name, d in dept.items():
lines.append(
f" {dept_name}: Variance {fmt_money(d['total_variance'])} "
f"({d['variance_pct']:+.1f}%) | "
f"Fav: {d['favorable_count']} / Unfav: {d['unfavorable_count']}"
)
# Category summary
cat = results["category_summary"]
if cat:
lines.append(f"\n--- CATEGORY SUMMARY ---")
for cat_name, c in cat.items():
lines.append(
f" {cat_name}: Variance {fmt_money(c['total_variance'])} "
f"({c['variance_pct']:+.1f}%)"
)
lines.append("\n" + "=" * 70)
return "\n".join(lines)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Analyze budget variances with materiality filtering"
)
parser.add_argument(
"input_file",
help="Path to JSON file with budget data",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
parser.add_argument(
"--threshold-pct",
type=float,
default=10.0,
help="Materiality threshold percentage (default: 10)",
)
parser.add_argument(
"--threshold-amt",
type=float,
default=50000.0,
help="Materiality threshold dollar amount (default: 50000)",
)
args = parser.parse_args()
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File '{args.input_file}' not found.", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in '{args.input_file}': {e}", file=sys.stderr)
sys.exit(1)
data = resolve_input_section(data, "budget_variance", ("line_items",))
if not data.get("line_items"):
print(
"Error: No budget line items found. Expected a non-empty "
"'line_items' array at the top level or nested under "
"'budget_variance'.",
file=sys.stderr,
)
sys.exit(1)
analyzer = BudgetVarianceAnalyzer(
data,
threshold_pct=args.threshold_pct,
threshold_amt=args.threshold_amt,
)
results = analyzer.run_analysis()
if args.format == "json":
print(json.dumps(results, indent=2))
else:
print(analyzer.format_text(results))
if __name__ == "__main__":
main()
scripts/dcf_valuation.py
#!/usr/bin/env python3
"""
DCF Valuation Model
Discounted Cash Flow enterprise and equity valuation with WACC calculation,
terminal value estimation, and two-way sensitivity analysis.
Uses standard library only (math, statistics) - NO numpy/pandas/scipy.
Usage:
python dcf_valuation.py valuation_data.json
python dcf_valuation.py valuation_data.json --format json
python dcf_valuation.py valuation_data.json --projection-years 7
"""
import argparse
import json
import math
import sys
from statistics import mean
from typing import Any, Dict, List, Optional, Tuple
def safe_divide(numerator: float, denominator: float, default: float = 0.0) -> float:
"""Safely divide two numbers, returning default if denominator is zero."""
if denominator == 0 or denominator is None:
return default
return numerator / denominator
def resolve_input_section(
data: Dict[str, Any], section_key: str, flat_keys: Tuple[str, ...]
) -> Dict[str, Any]:
"""
Accept both supported input shapes:
1. Flat: the expected keys live at the top level of the JSON file.
2. Nested: the data lives under a per-tool section key, as in
assets/sample_financial_data.json (which bundles inputs for all
four financial-analyst scripts in one file).
"""
if any(key in data for key in flat_keys):
return data
section = data.get(section_key)
if isinstance(section, dict):
return section
return data
class DCFModel:
"""Discounted Cash Flow valuation model."""
def __init__(self) -> None:
"""Initialize the DCF model."""
self.historical: Dict[str, Any] = {}
self.assumptions: Dict[str, Any] = {}
self.wacc: float = 0.0
self.projected_revenue: List[float] = []
self.projected_fcf: List[float] = []
self.projection_years: int = 5
self.terminal_value_perpetuity: float = 0.0
self.terminal_value_exit_multiple: float = 0.0
self.enterprise_value_perpetuity: float = 0.0
self.enterprise_value_exit_multiple: float = 0.0
self.equity_value_perpetuity: float = 0.0
self.equity_value_exit_multiple: float = 0.0
self.value_per_share_perpetuity: float = 0.0
self.value_per_share_exit_multiple: float = 0.0
def set_historical_financials(self, historical: Dict[str, Any]) -> None:
"""Set historical financial data."""
self.historical = historical
def set_assumptions(self, assumptions: Dict[str, Any]) -> None:
"""Set projection assumptions."""
self.assumptions = assumptions
self.projection_years = assumptions.get("projection_years", 5)
def calculate_wacc(self) -> float:
"""Calculate Weighted Average Cost of Capital via CAPM."""
wacc_inputs = self.assumptions.get("wacc_inputs", {})
risk_free_rate = wacc_inputs.get("risk_free_rate", 0.04)
equity_risk_premium = wacc_inputs.get("equity_risk_premium", 0.06)
beta = wacc_inputs.get("beta", 1.0)
cost_of_debt = wacc_inputs.get("cost_of_debt", 0.05)
tax_rate = wacc_inputs.get("tax_rate", 0.25)
debt_weight = wacc_inputs.get("debt_weight", 0.30)
equity_weight = wacc_inputs.get("equity_weight", 0.70)
# CAPM: Cost of Equity = Risk-Free Rate + Beta * Equity Risk Premium
cost_of_equity = risk_free_rate + beta * equity_risk_premium
# WACC = (E/V * Re) + (D/V * Rd * (1 - T))
after_tax_cost_of_debt = cost_of_debt * (1 - tax_rate)
self.wacc = (equity_weight * cost_of_equity) + (
debt_weight * after_tax_cost_of_debt
)
return self.wacc
def project_cash_flows(self) -> Tuple[List[float], List[float]]:
"""Project revenue and free cash flow over the projection period."""
base_revenue = self.historical.get("revenue", [])
if not base_revenue:
raise ValueError("Historical revenue data is required")
last_revenue = base_revenue[-1]
revenue_growth_rates = self.assumptions.get("revenue_growth_rates", [])
fcf_margins = self.assumptions.get("fcf_margins", [])
# If growth rates not provided for all years, use average or default
default_growth = self.assumptions.get("default_revenue_growth", 0.05)
default_fcf_margin = self.assumptions.get("default_fcf_margin", 0.10)
self.projected_revenue = []
self.projected_fcf = []
current_revenue = last_revenue
for year in range(self.projection_years):
growth = (
revenue_growth_rates[year]
if year < len(revenue_growth_rates)
else default_growth
)
fcf_margin = (
fcf_margins[year]
if year < len(fcf_margins)
else default_fcf_margin
)
current_revenue = current_revenue * (1 + growth)
fcf = current_revenue * fcf_margin
self.projected_revenue.append(current_revenue)
self.projected_fcf.append(fcf)
return self.projected_revenue, self.projected_fcf
def calculate_terminal_value(self) -> Tuple[float, float]:
"""Calculate terminal value using both perpetuity growth and exit multiple."""
if not self.projected_fcf:
raise ValueError("Must project cash flows before terminal value")
terminal_fcf = self.projected_fcf[-1]
terminal_growth = self.assumptions.get("terminal_growth_rate", 0.025)
exit_multiple = self.assumptions.get("exit_ev_ebitda_multiple", 12.0)
# Perpetuity growth method: TV = FCF * (1+g) / (WACC - g)
if self.wacc > terminal_growth:
self.terminal_value_perpetuity = (
terminal_fcf * (1 + terminal_growth)
) / (self.wacc - terminal_growth)
else:
self.terminal_value_perpetuity = 0.0
# Exit multiple method: TV = Terminal EBITDA * Exit Multiple
terminal_revenue = self.projected_revenue[-1]
ebitda_margin = self.assumptions.get("terminal_ebitda_margin", 0.20)
terminal_ebitda = terminal_revenue * ebitda_margin
self.terminal_value_exit_multiple = terminal_ebitda * exit_multiple
return self.terminal_value_perpetuity, self.terminal_value_exit_multiple
def calculate_enterprise_value(self) -> Tuple[float, float]:
"""Calculate enterprise value by discounting projected FCFs and terminal value."""
if not self.projected_fcf:
raise ValueError("Must project cash flows first")
# Discount projected FCFs
pv_fcf = 0.0
for i, fcf in enumerate(self.projected_fcf):
discount_factor = (1 + self.wacc) ** (i + 1)
pv_fcf += fcf / discount_factor
# Discount terminal values
terminal_discount = (1 + self.wacc) ** self.projection_years
pv_tv_perpetuity = self.terminal_value_perpetuity / terminal_discount
pv_tv_exit = self.terminal_value_exit_multiple / terminal_discount
self.enterprise_value_perpetuity = pv_fcf + pv_tv_perpetuity
self.enterprise_value_exit_multiple = pv_fcf + pv_tv_exit
return self.enterprise_value_perpetuity, self.enterprise_value_exit_multiple
def calculate_equity_value(self) -> Tuple[float, float]:
"""Calculate equity value from enterprise value."""
net_debt = self.historical.get("net_debt", 0)
shares_outstanding = self.historical.get("shares_outstanding", 1)
self.equity_value_perpetuity = (
self.enterprise_value_perpetuity - net_debt
)
self.equity_value_exit_multiple = (
self.enterprise_value_exit_multiple - net_debt
)
self.value_per_share_perpetuity = safe_divide(
self.equity_value_perpetuity, shares_outstanding
)
self.value_per_share_exit_multiple = safe_divide(
self.equity_value_exit_multiple, shares_outstanding
)
return self.equity_value_perpetuity, self.equity_value_exit_multiple
def sensitivity_analysis(
self,
wacc_range: Optional[List[float]] = None,
growth_range: Optional[List[float]] = None,
) -> Dict[str, Any]:
"""
Two-way sensitivity analysis: WACC vs terminal growth rate.
Returns a table of enterprise values using nested lists (no numpy).
"""
if wacc_range is None:
base_wacc = self.wacc
wacc_range = [
round(base_wacc - 0.02, 4),
round(base_wacc - 0.01, 4),
round(base_wacc, 4),
round(base_wacc + 0.01, 4),
round(base_wacc + 0.02, 4),
]
if growth_range is None:
base_growth = self.assumptions.get("terminal_growth_rate", 0.025)
growth_range = [
round(base_growth - 0.01, 4),
round(base_growth - 0.005, 4),
round(base_growth, 4),
round(base_growth + 0.005, 4),
round(base_growth + 0.01, 4),
]
rows = len(wacc_range)
cols = len(growth_range)
# Initialize sensitivity table as nested lists
ev_table = [[0.0] * cols for _ in range(rows)]
share_price_table = [[0.0] * cols for _ in range(rows)]
terminal_fcf = self.projected_fcf[-1] if self.projected_fcf else 0
for i, wacc_val in enumerate(wacc_range):
for j, growth_val in enumerate(growth_range):
if wacc_val <= growth_val:
ev_table[i][j] = float("inf")
share_price_table[i][j] = float("inf")
continue
# Recalculate PV of projected FCFs with this WACC
pv_fcf = 0.0
for k, fcf in enumerate(self.projected_fcf):
pv_fcf += fcf / ((1 + wacc_val) ** (k + 1))
# Terminal value with this growth rate
tv = (terminal_fcf * (1 + growth_val)) / (wacc_val - growth_val)
pv_tv = tv / ((1 + wacc_val) ** self.projection_years)
ev = pv_fcf + pv_tv
ev_table[i][j] = round(ev, 2)
net_debt = self.historical.get("net_debt", 0)
shares = self.historical.get("shares_outstanding", 1)
equity = ev - net_debt
share_price_table[i][j] = round(
safe_divide(equity, shares), 2
)
return {
"wacc_values": wacc_range,
"growth_values": growth_range,
"enterprise_value_table": ev_table,
"share_price_table": share_price_table,
}
def run_full_valuation(self) -> Dict[str, Any]:
"""Run the complete DCF valuation."""
self.calculate_wacc()
self.project_cash_flows()
self.calculate_terminal_value()
self.calculate_enterprise_value()
self.calculate_equity_value()
sensitivity = self.sensitivity_analysis()
return {
"wacc": self.wacc,
"projected_revenue": self.projected_revenue,
"projected_fcf": self.projected_fcf,
"terminal_value": {
"perpetuity_growth": self.terminal_value_perpetuity,
"exit_multiple": self.terminal_value_exit_multiple,
},
"enterprise_value": {
"perpetuity_growth": self.enterprise_value_perpetuity,
"exit_multiple": self.enterprise_value_exit_multiple,
},
"equity_value": {
"perpetuity_growth": self.equity_value_perpetuity,
"exit_multiple": self.equity_value_exit_multiple,
},
"value_per_share": {
"perpetuity_growth": self.value_per_share_perpetuity,
"exit_multiple": self.value_per_share_exit_multiple,
},
"sensitivity_analysis": sensitivity,
}
def format_text(self, results: Dict[str, Any]) -> str:
"""Format valuation results as human-readable text."""
lines: List[str] = []
lines.append("=" * 70)
lines.append("DCF VALUATION ANALYSIS")
lines.append("=" * 70)
def fmt_money(val: float) -> str:
if val == float("inf"):
return "N/A (WACC <= growth)"
if abs(val) >= 1e9:
return f"${val / 1e9:,.2f}B"
if abs(val) >= 1e6:
return f"${val / 1e6:,.2f}M"
if abs(val) >= 1e3:
return f"${val / 1e3:,.1f}K"
return f"${val:,.2f}"
lines.append(f"\n--- WACC ---")
lines.append(f" Weighted Average Cost of Capital: {results['wacc'] * 100:.2f}%")
lines.append(f"\n--- REVENUE PROJECTIONS ---")
for i, rev in enumerate(results["projected_revenue"], 1):
lines.append(f" Year {i}: {fmt_money(rev)}")
lines.append(f"\n--- FREE CASH FLOW PROJECTIONS ---")
for i, fcf in enumerate(results["projected_fcf"], 1):
lines.append(f" Year {i}: {fmt_money(fcf)}")
lines.append(f"\n--- TERMINAL VALUE ---")
lines.append(
f" Perpetuity Growth Method: "
f"{fmt_money(results['terminal_value']['perpetuity_growth'])}"
)
lines.append(
f" Exit Multiple Method: "
f"{fmt_money(results['terminal_value']['exit_multiple'])}"
)
lines.append(f"\n--- ENTERPRISE VALUE ---")
lines.append(
f" Perpetuity Growth Method: "
f"{fmt_money(results['enterprise_value']['perpetuity_growth'])}"
)
lines.append(
f" Exit Multiple Method: "
f"{fmt_money(results['enterprise_value']['exit_multiple'])}"
)
lines.append(f"\n--- EQUITY VALUE ---")
lines.append(
f" Perpetuity Growth Method: "
f"{fmt_money(results['equity_value']['perpetuity_growth'])}"
)
lines.append(
f" Exit Multiple Method: "
f"{fmt_money(results['equity_value']['exit_multiple'])}"
)
lines.append(f"\n--- VALUE PER SHARE ---")
vps = results["value_per_share"]
lines.append(f" Perpetuity Growth Method: ${vps['perpetuity_growth']:,.2f}")
lines.append(f" Exit Multiple Method: ${vps['exit_multiple']:,.2f}")
# Sensitivity table
sens = results["sensitivity_analysis"]
lines.append(f"\n--- SENSITIVITY ANALYSIS (Enterprise Value) ---")
lines.append(f" WACC vs Terminal Growth Rate")
lines.append("")
header = " {:>10s}".format("WACC \\ g")
for g in sens["growth_values"]:
header += f" {g * 100:>8.1f}%"
lines.append(header)
lines.append(" " + "-" * (10 + 10 * len(sens["growth_values"])))
for i, w in enumerate(sens["wacc_values"]):
row = f" {w * 100:>9.1f}%"
for j in range(len(sens["growth_values"])):
val = sens["enterprise_value_table"][i][j]
if val == float("inf"):
row += f" {'N/A':>8s}"
else:
row += f" {fmt_money(val):>8s}"
lines.append(row)
lines.append("\n" + "=" * 70)
return "\n".join(lines)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="DCF Valuation Model - Enterprise and equity valuation"
)
parser.add_argument(
"input_file",
help="Path to JSON file with valuation data",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
parser.add_argument(
"--projection-years",
type=int,
default=None,
help="Number of projection years (overrides input file)",
)
args = parser.parse_args()
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File '{args.input_file}' not found.", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in '{args.input_file}': {e}", file=sys.stderr)
sys.exit(1)
data = resolve_input_section(data, "dcf_valuation", ("historical", "assumptions"))
if "historical" not in data:
print(
"Error: No valuation data found. Expected 'historical' and "
"'assumptions' at the top level or nested under 'dcf_valuation'.",
file=sys.stderr,
)
sys.exit(1)
model = DCFModel()
model.set_historical_financials(data.get("historical", {}))
assumptions = data.get("assumptions", {})
if args.projection_years is not None:
assumptions["projection_years"] = args.projection_years
model.set_assumptions(assumptions)
try:
results = model.run_full_valuation()
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if args.format == "json":
# Handle inf values for JSON serialization
def sanitize(obj: Any) -> Any:
if isinstance(obj, float) and math.isinf(obj):
return None
if isinstance(obj, dict):
return {k: sanitize(v) for k, v in obj.items()}
if isinstance(obj, list):
return [sanitize(v) for v in obj]
return obj
print(json.dumps(sanitize(results), indent=2))
else:
print(model.format_text(results))
if __name__ == "__main__":
main()
scripts/forecast_builder.py
#!/usr/bin/env python3
"""
Forecast Builder
Driver-based revenue forecasting with 13-week rolling cash flow projection,
scenario modeling (base/bull/bear), and trend analysis using simple linear
regression (standard library only).
Usage:
python forecast_builder.py forecast_data.json
python forecast_builder.py forecast_data.json --format json
python forecast_builder.py forecast_data.json --scenarios base,bull,bear
"""
import argparse
import json
import math
import sys
from statistics import mean
from typing import Any, Dict, List, Optional, Tuple
def safe_divide(numerator: float, denominator: float, default: float = 0.0) -> float:
"""Safely divide two numbers, returning default if denominator is zero."""
if denominator == 0 or denominator is None:
return default
return numerator / denominator
def resolve_input_section(
data: Dict[str, Any], section_key: str, flat_keys: Tuple[str, ...]
) -> Dict[str, Any]:
"""
Accept both supported input shapes:
1. Flat: the expected keys live at the top level of the JSON file.
2. Nested: the data lives under a per-tool section key, as in
assets/sample_financial_data.json (which bundles inputs for all
four financial-analyst scripts in one file).
"""
if any(key in data for key in flat_keys):
return data
section = data.get(section_key)
if isinstance(section, dict):
return section
return data
def simple_linear_regression(
x_values: List[float], y_values: List[float]
) -> Tuple[float, float, float]:
"""
Simple linear regression using standard library.
Returns (slope, intercept, r_squared).
"""
n = len(x_values)
if n < 2 or n != len(y_values):
return (0.0, 0.0, 0.0)
x_mean = mean(x_values)
y_mean = mean(y_values)
ss_xy = sum((x - x_mean) * (y - y_mean) for x, y in zip(x_values, y_values))
ss_xx = sum((x - x_mean) ** 2 for x in x_values)
ss_yy = sum((y - y_mean) ** 2 for y in y_values)
slope = safe_divide(ss_xy, ss_xx)
intercept = y_mean - slope * x_mean
# R-squared
r_squared = safe_divide(ss_xy ** 2, ss_xx * ss_yy) if ss_yy > 0 else 0.0
return (slope, intercept, r_squared)
class ForecastBuilder:
"""Driver-based revenue forecasting with scenario modeling."""
def __init__(self, data: Dict[str, Any]) -> None:
"""Initialize the forecast builder."""
self.historical: List[Dict[str, Any]] = data.get("historical_periods", [])
self.drivers: Dict[str, Any] = data.get("drivers", {})
self.assumptions: Dict[str, Any] = data.get("assumptions", {})
self.cash_flow_inputs: Dict[str, Any] = data.get("cash_flow_inputs", {})
self.scenarios_config: Dict[str, Any] = data.get("scenarios", {})
self.forecast_periods: int = data.get("forecast_periods", 12)
def analyze_trends(self) -> Dict[str, Any]:
"""Analyze historical trends using linear regression."""
if not self.historical:
return {"error": "No historical data available"}
# Extract revenue series
revenues = [p.get("revenue", 0) for p in self.historical]
periods = list(range(1, len(revenues) + 1))
slope, intercept, r_squared = simple_linear_regression(
[float(x) for x in periods],
[float(y) for y in revenues],
)
# Calculate growth rates
growth_rates = []
for i in range(1, len(revenues)):
if revenues[i - 1] > 0:
growth = (revenues[i] - revenues[i - 1]) / revenues[i - 1]
growth_rates.append(growth)
avg_growth = mean(growth_rates) if growth_rates else 0.0
# Seasonality detection (if enough data)
seasonality_index: List[float] = []
if len(revenues) >= 4:
overall_avg = mean(revenues)
if overall_avg > 0:
seasonality_index = [r / overall_avg for r in revenues[-4:]]
return {
"trend": {
"slope": round(slope, 2),
"intercept": round(intercept, 2),
"r_squared": round(r_squared, 4),
"direction": "upward" if slope > 0 else "downward" if slope < 0 else "flat",
},
"growth_rates": [round(g, 4) for g in growth_rates],
"average_growth_rate": round(avg_growth, 4),
"seasonality_index": [round(s, 4) for s in seasonality_index],
"historical_revenues": revenues,
}
def build_driver_based_forecast(
self, scenario: str = "base"
) -> Dict[str, Any]:
"""
Build a driver-based revenue forecast.
Drivers may include: units, price, customers, ARPU, conversion rate, etc.
"""
scenario_adjustments = self.scenarios_config.get(scenario, {})
growth_adjustment = scenario_adjustments.get("growth_adjustment", 0.0)
margin_adjustment = scenario_adjustments.get("margin_adjustment", 0.0)
base_revenue = 0.0
if self.historical:
base_revenue = self.historical[-1].get("revenue", 0)
# Driver-based calculation
unit_drivers = self.drivers.get("units", {})
price_drivers = self.drivers.get("pricing", {})
customer_drivers = self.drivers.get("customers", {})
base_growth = self.assumptions.get("revenue_growth_rate", 0.05)
adjusted_growth = base_growth + growth_adjustment
base_margin = self.assumptions.get("gross_margin", 0.40)
adjusted_margin = base_margin + margin_adjustment
cogs_pct = 1.0 - adjusted_margin
opex_pct = self.assumptions.get("opex_pct_revenue", 0.25)
forecast_periods: List[Dict[str, Any]] = []
current_revenue = base_revenue
# If we have unit and price drivers, use them
has_unit_drivers = bool(unit_drivers) and bool(price_drivers)
if has_unit_drivers:
base_units = unit_drivers.get("base_units", 1000)
unit_growth = unit_drivers.get("growth_rate", 0.03) + growth_adjustment
base_price = price_drivers.get("base_price", 100)
price_growth = price_drivers.get("annual_increase", 0.02)
current_units = base_units
current_price = base_price
for period in range(1, self.forecast_periods + 1):
current_units = current_units * (1 + unit_growth / 12)
if period % 12 == 0:
current_price = current_price * (1 + price_growth)
period_revenue = current_units * current_price
cogs = period_revenue * cogs_pct
gross_profit = period_revenue - cogs
opex = period_revenue * opex_pct
operating_income = gross_profit - opex
forecast_periods.append({
"period": period,
"revenue": round(period_revenue, 2),
"units": round(current_units, 0),
"price": round(current_price, 2),
"cogs": round(cogs, 2),
"gross_profit": round(gross_profit, 2),
"gross_margin": round(adjusted_margin, 4),
"opex": round(opex, 2),
"operating_income": round(operating_income, 2),
})
else:
# Simple growth-based forecast
monthly_growth = (1 + adjusted_growth) ** (1 / 12) - 1
for period in range(1, self.forecast_periods + 1):
current_revenue = current_revenue * (1 + monthly_growth)
cogs = current_revenue * cogs_pct
gross_profit = current_revenue - cogs
opex = current_revenue * opex_pct
operating_income = gross_profit - opex
forecast_periods.append({
"period": period,
"revenue": round(current_revenue, 2),
"cogs": round(cogs, 2),
"gross_profit": round(gross_profit, 2),
"gross_margin": round(adjusted_margin, 4),
"opex": round(opex, 2),
"operating_income": round(operating_income, 2),
})
total_revenue = sum(p["revenue"] for p in forecast_periods)
total_operating_income = sum(p["operating_income"] for p in forecast_periods)
return {
"scenario": scenario,
"growth_rate": round(adjusted_growth, 4),
"gross_margin": round(adjusted_margin, 4),
"forecast_periods": forecast_periods,
"total_revenue": round(total_revenue, 2),
"total_operating_income": round(total_operating_income, 2),
"average_monthly_revenue": round(
safe_divide(total_revenue, len(forecast_periods)), 2
),
}
def build_rolling_cash_flow(self, weeks: int = 13) -> Dict[str, Any]:
"""Build a 13-week rolling cash flow projection."""
cfi = self.cash_flow_inputs
opening_balance = cfi.get("opening_cash_balance", 0)
weekly_revenue = cfi.get("weekly_revenue", 0)
collection_rate = cfi.get("collection_rate", 0.85)
collection_lag_weeks = cfi.get("collection_lag_weeks", 2)
# Weekly expenses
weekly_payroll = cfi.get("weekly_payroll", 0)
weekly_rent = cfi.get("weekly_rent", 0)
weekly_operating = cfi.get("weekly_operating", 0)
weekly_other = cfi.get("weekly_other", 0)
total_weekly_expenses = weekly_payroll + weekly_rent + weekly_operating + weekly_other
# One-time items
one_time_items: List[Dict[str, Any]] = cfi.get("one_time_items", [])
weekly_projections: List[Dict[str, Any]] = []
running_balance = opening_balance
# Revenue pipeline for lagged collections
revenue_pipeline: List[float] = [0.0] * collection_lag_weeks
for week in range(1, weeks + 1):
# Revenue collections (lagged)
revenue_pipeline.append(weekly_revenue)
collections = revenue_pipeline.pop(0) * collection_rate
# One-time items for this week
one_time_inflows = 0.0
one_time_outflows = 0.0
one_time_labels: List[str] = []
for item in one_time_items:
if item.get("week") == week:
amount = item.get("amount", 0)
if amount > 0:
one_time_inflows += amount
else:
one_time_outflows += abs(amount)
one_time_labels.append(item.get("description", ""))
total_inflows = collections + one_time_inflows
total_outflows = total_weekly_expenses + one_time_outflows
net_cash_flow = total_inflows - total_outflows
running_balance += net_cash_flow
weekly_projections.append({
"week": week,
"collections": round(collections, 2),
"one_time_inflows": round(one_time_inflows, 2),
"total_inflows": round(total_inflows, 2),
"payroll": round(weekly_payroll, 2),
"rent": round(weekly_rent, 2),
"operating": round(weekly_operating, 2),
"other_expenses": round(weekly_other, 2),
"one_time_outflows": round(one_time_outflows, 2),
"total_outflows": round(total_outflows, 2),
"net_cash_flow": round(net_cash_flow, 2),
"closing_balance": round(running_balance, 2),
"notes": ", ".join(one_time_labels) if one_time_labels else "",
})
# Summary
total_inflows = sum(w["total_inflows"] for w in weekly_projections)
total_outflows = sum(w["total_outflows"] for w in weekly_projections)
min_balance = min(w["closing_balance"] for w in weekly_projections)
min_balance_week = next(
w["week"]
for w in weekly_projections
if w["closing_balance"] == min_balance
)
return {
"weeks": weeks,
"opening_balance": opening_balance,
"closing_balance": round(running_balance, 2),
"total_inflows": round(total_inflows, 2),
"total_outflows": round(total_outflows, 2),
"net_change": round(total_inflows - total_outflows, 2),
"minimum_balance": round(min_balance, 2),
"minimum_balance_week": min_balance_week,
"cash_runway_weeks": (
round(safe_divide(running_balance, total_weekly_expenses))
if total_weekly_expenses > 0
else None
),
"weekly_projections": weekly_projections,
}
def build_scenario_comparison(
self, scenarios: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Build and compare multiple scenarios."""
if scenarios is None:
scenarios = ["base", "bull", "bear"]
scenario_results: Dict[str, Any] = {}
for scenario in scenarios:
scenario_results[scenario] = self.build_driver_based_forecast(scenario)
# Comparison summary
comparison: List[Dict[str, Any]] = []
for scenario in scenarios:
result = scenario_results[scenario]
comparison.append({
"scenario": scenario,
"total_revenue": result["total_revenue"],
"total_operating_income": result["total_operating_income"],
"growth_rate": result["growth_rate"],
"gross_margin": result["gross_margin"],
"avg_monthly_revenue": result["average_monthly_revenue"],
})
return {
"scenarios": scenario_results,
"comparison": comparison,
}
def run_full_forecast(
self, scenarios: Optional[List[str]] = None
) -> Dict[str, Any]:
"""Run the complete forecast analysis."""
trends = self.analyze_trends()
scenario_comparison = self.build_scenario_comparison(scenarios)
cash_flow = self.build_rolling_cash_flow()
return {
"trend_analysis": trends,
"scenario_comparison": scenario_comparison,
"rolling_cash_flow": cash_flow,
}
def format_text(self, results: Dict[str, Any]) -> str:
"""Format forecast results as human-readable text."""
lines: List[str] = []
lines.append("=" * 70)
lines.append("FINANCIAL FORECAST REPORT")
lines.append("=" * 70)
def fmt_money(val: float) -> str:
if abs(val) >= 1e9:
return f"${val / 1e9:,.2f}B"
if abs(val) >= 1e6:
return f"${val / 1e6:,.2f}M"
if abs(val) >= 1e3:
return f"${val / 1e3:,.1f}K"
return f"${val:,.2f}"
# Trend Analysis
trend = results["trend_analysis"]
if "error" not in trend:
lines.append(f"\n--- TREND ANALYSIS ---")
t = trend["trend"]
lines.append(f" Direction: {t['direction']}")
lines.append(f" R-squared: {t['r_squared']:.4f}")
lines.append(
f" Average Historical Growth: "
f"{trend['average_growth_rate'] * 100:.1f}%"
)
if trend["seasonality_index"]:
lines.append(
f" Seasonality Index (last 4): "
f"{', '.join(f'{s:.2f}' for s in trend['seasonality_index'])}"
)
# Scenario Comparison
comp = results["scenario_comparison"]["comparison"]
lines.append(f"\n--- SCENARIO COMPARISON ---")
lines.append(
f" {'Scenario':<10s} {'Revenue':>14s} {'Op. Income':>14s} "
f"{'Growth':>8s} {'Margin':>8s}"
)
lines.append(" " + "-" * 62)
for c in comp:
lines.append(
f" {c['scenario']:<10s} {fmt_money(c['total_revenue']):>14s} "
f"{fmt_money(c['total_operating_income']):>14s} "
f"{c['growth_rate'] * 100:>7.1f}% "
f"{c['gross_margin'] * 100:>7.1f}%"
)
# Base scenario detail
base = results["scenario_comparison"]["scenarios"].get("base", {})
if base and base.get("forecast_periods"):
lines.append(f"\n--- BASE CASE MONTHLY FORECAST ---")
lines.append(
f" {'Period':>6s} {'Revenue':>12s} {'Gross Profit':>12s} "
f"{'Op. Income':>12s}"
)
lines.append(" " + "-" * 48)
for p in base["forecast_periods"]:
lines.append(
f" {p['period']:>6d} {fmt_money(p['revenue']):>12s} "
f"{fmt_money(p['gross_profit']):>12s} "
f"{fmt_money(p['operating_income']):>12s}"
)
# Cash Flow
cf = results["rolling_cash_flow"]
lines.append(f"\n--- 13-WEEK ROLLING CASH FLOW ---")
lines.append(f" Opening Balance: {fmt_money(cf['opening_balance'])}")
lines.append(f" Closing Balance: {fmt_money(cf['closing_balance'])}")
lines.append(f" Net Change: {fmt_money(cf['net_change'])}")
lines.append(
f" Minimum Balance: {fmt_money(cf['minimum_balance'])} "
f"(Week {cf['minimum_balance_week']})"
)
if cf.get("cash_runway_weeks"):
lines.append(f" Cash Runway: {cf['cash_runway_weeks']:.0f} weeks")
lines.append(f"\n Weekly Detail:")
lines.append(
f" {'Wk':>3s} {'Inflows':>10s} {'Outflows':>10s} "
f"{'Net':>10s} {'Balance':>12s}"
)
lines.append(" " + "-" * 50)
for w in cf["weekly_projections"]:
notes = f" {w['notes']}" if w["notes"] else ""
lines.append(
f" {w['week']:>3d} {fmt_money(w['total_inflows']):>10s} "
f"{fmt_money(w['total_outflows']):>10s} "
f"{fmt_money(w['net_cash_flow']):>10s} "
f"{fmt_money(w['closing_balance']):>12s}{notes}"
)
lines.append("\n" + "=" * 70)
return "\n".join(lines)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Driver-based revenue forecasting with scenario modeling"
)
parser.add_argument(
"input_file",
help="Path to JSON file with forecast data",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
parser.add_argument(
"--scenarios",
type=str,
default="base,bull,bear",
help="Comma-separated list of scenarios (default: base,bull,bear)",
)
args = parser.parse_args()
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File '{args.input_file}' not found.", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in '{args.input_file}': {e}", file=sys.stderr)
sys.exit(1)
flat_keys = (
"historical_periods",
"drivers",
"assumptions",
"cash_flow_inputs",
)
data = resolve_input_section(data, "forecast", flat_keys)
if not any(data.get(key) for key in flat_keys):
print(
"Error: No forecast inputs found. Expected at least one of "
f"{', '.join(flat_keys)} at the top level or nested under "
"'forecast'.",
file=sys.stderr,
)
sys.exit(1)
builder = ForecastBuilder(data)
scenarios = [s.strip() for s in args.scenarios.split(",")]
results = builder.run_full_forecast(scenarios)
if args.format == "json":
print(json.dumps(results, indent=2))
else:
print(builder.format_text(results))
if __name__ == "__main__":
main()
scripts/ratio_calculator.py
#!/usr/bin/env python3
"""
Financial Ratio Calculator
Calculates and interprets financial ratios across 5 categories:
profitability, liquidity, leverage, efficiency, and valuation.
Usage:
python ratio_calculator.py financial_data.json
python ratio_calculator.py financial_data.json --format json
python ratio_calculator.py financial_data.json --category profitability
"""
import argparse
import json
import sys
from typing import Any, Dict, List, Optional, Tuple
def safe_divide(numerator: float, denominator: float, default: float = 0.0) -> float:
"""Safely divide two numbers, returning default if denominator is zero."""
if denominator == 0 or denominator is None:
return default
return numerator / denominator
def resolve_input_section(
data: Dict[str, Any], section_key: str, flat_keys: Tuple[str, ...]
) -> Dict[str, Any]:
"""
Accept both supported input shapes:
1. Flat: the expected keys live at the top level of the JSON file.
2. Nested: the data lives under a per-tool section key, as in
assets/sample_financial_data.json (which bundles inputs for all
four financial-analyst scripts in one file).
"""
if any(key in data for key in flat_keys):
return data
section = data.get(section_key)
if isinstance(section, dict):
return section
return data
class FinancialRatioCalculator:
"""Calculate and interpret financial ratios from statement data."""
# Industry benchmark ranges: (low, typical, high)
BENCHMARKS: Dict[str, Tuple[float, float, float]] = {
"roe": (0.08, 0.15, 0.25),
"roa": (0.03, 0.06, 0.12),
"gross_margin": (0.25, 0.40, 0.60),
"operating_margin": (0.05, 0.15, 0.25),
"net_margin": (0.03, 0.10, 0.20),
"current_ratio": (1.0, 1.5, 3.0),
"quick_ratio": (0.8, 1.0, 2.0),
"cash_ratio": (0.2, 0.5, 1.0),
"debt_to_equity": (0.3, 0.8, 2.0),
"interest_coverage": (2.0, 5.0, 10.0),
"dscr": (1.0, 1.5, 2.5),
"asset_turnover": (0.5, 1.0, 2.0),
"inventory_turnover": (4.0, 8.0, 12.0),
"receivables_turnover": (6.0, 10.0, 15.0),
"dso": (30.0, 45.0, 60.0),
"pe_ratio": (10.0, 20.0, 35.0),
"pb_ratio": (1.0, 2.5, 5.0),
"ps_ratio": (1.0, 3.0, 8.0),
"ev_ebitda": (6.0, 12.0, 20.0),
"peg_ratio": (0.5, 1.0, 2.0),
}
def __init__(self, data: Dict[str, Any]) -> None:
"""Initialize with financial statement data."""
self.income = data.get("income_statement", {})
self.balance = data.get("balance_sheet", {})
self.cash_flow = data.get("cash_flow", {})
self.market = data.get("market_data", {})
self.results: Dict[str, Dict[str, Any]] = {}
def calculate_profitability(self) -> Dict[str, Any]:
"""Calculate profitability ratios."""
revenue = self.income.get("revenue", 0)
cogs = self.income.get("cost_of_goods_sold", 0)
operating_income = self.income.get("operating_income", 0)
net_income = self.income.get("net_income", 0)
total_equity = self.balance.get("total_equity", 0)
total_assets = self.balance.get("total_assets", 0)
gross_profit = revenue - cogs
ratios = {
"roe": {
"value": safe_divide(net_income, total_equity),
"formula": "Net Income / Total Equity",
"name": "Return on Equity",
},
"roa": {
"value": safe_divide(net_income, total_assets),
"formula": "Net Income / Total Assets",
"name": "Return on Assets",
},
"gross_margin": {
"value": safe_divide(gross_profit, revenue),
"formula": "(Revenue - COGS) / Revenue",
"name": "Gross Margin",
},
"operating_margin": {
"value": safe_divide(operating_income, revenue),
"formula": "Operating Income / Revenue",
"name": "Operating Margin",
},
"net_margin": {
"value": safe_divide(net_income, revenue),
"formula": "Net Income / Revenue",
"name": "Net Margin",
},
}
for key, ratio in ratios.items():
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
self.results["profitability"] = ratios
return ratios
def calculate_liquidity(self) -> Dict[str, Any]:
"""Calculate liquidity ratios."""
current_assets = self.balance.get("current_assets", 0)
current_liabilities = self.balance.get("current_liabilities", 0)
inventory = self.balance.get("inventory", 0)
cash = self.balance.get("cash_and_equivalents", 0)
ratios = {
"current_ratio": {
"value": safe_divide(current_assets, current_liabilities),
"formula": "Current Assets / Current Liabilities",
"name": "Current Ratio",
},
"quick_ratio": {
"value": safe_divide(
current_assets - inventory, current_liabilities
),
"formula": "(Current Assets - Inventory) / Current Liabilities",
"name": "Quick Ratio",
},
"cash_ratio": {
"value": safe_divide(cash, current_liabilities),
"formula": "Cash & Equivalents / Current Liabilities",
"name": "Cash Ratio",
},
}
for key, ratio in ratios.items():
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
self.results["liquidity"] = ratios
return ratios
def calculate_leverage(self) -> Dict[str, Any]:
"""Calculate leverage ratios."""
total_debt = self.balance.get("total_debt", 0)
total_equity = self.balance.get("total_equity", 0)
operating_income = self.income.get("operating_income", 0)
interest_expense = self.income.get("interest_expense", 0)
operating_cash_flow = self.cash_flow.get("operating_cash_flow", 0)
total_debt_service = self.cash_flow.get(
"total_debt_service", interest_expense
)
ratios = {
"debt_to_equity": {
"value": safe_divide(total_debt, total_equity),
"formula": "Total Debt / Total Equity",
"name": "Debt-to-Equity Ratio",
},
"interest_coverage": {
"value": safe_divide(operating_income, interest_expense),
"formula": "Operating Income / Interest Expense",
"name": "Interest Coverage Ratio",
},
"dscr": {
"value": safe_divide(operating_cash_flow, total_debt_service),
"formula": "Operating Cash Flow / Total Debt Service",
"name": "Debt Service Coverage Ratio",
},
}
for key, ratio in ratios.items():
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
self.results["leverage"] = ratios
return ratios
def calculate_efficiency(self) -> Dict[str, Any]:
"""Calculate efficiency ratios."""
revenue = self.income.get("revenue", 0)
cogs = self.income.get("cost_of_goods_sold", 0)
total_assets = self.balance.get("total_assets", 0)
inventory = self.balance.get("inventory", 0)
accounts_receivable = self.balance.get("accounts_receivable", 0)
receivables_turnover_val = safe_divide(revenue, accounts_receivable)
ratios = {
"asset_turnover": {
"value": safe_divide(revenue, total_assets),
"formula": "Revenue / Total Assets",
"name": "Asset Turnover",
},
"inventory_turnover": {
"value": safe_divide(cogs, inventory),
"formula": "COGS / Inventory",
"name": "Inventory Turnover",
},
"receivables_turnover": {
"value": receivables_turnover_val,
"formula": "Revenue / Accounts Receivable",
"name": "Receivables Turnover",
},
"dso": {
"value": safe_divide(365, receivables_turnover_val)
if receivables_turnover_val > 0
else 0.0,
"formula": "365 / Receivables Turnover",
"name": "Days Sales Outstanding",
},
}
for key, ratio in ratios.items():
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
self.results["efficiency"] = ratios
return ratios
def calculate_valuation(self) -> Dict[str, Any]:
"""Calculate valuation ratios (requires market data)."""
market_cap = self.market.get("market_cap", 0)
share_price = self.market.get("share_price", 0)
shares_outstanding = self.market.get("shares_outstanding", 0)
earnings_growth_rate = self.market.get("earnings_growth_rate", 0)
net_income = self.income.get("net_income", 0)
revenue = self.income.get("revenue", 0)
total_equity = self.balance.get("total_equity", 0)
total_debt = self.balance.get("total_debt", 0)
cash = self.balance.get("cash_and_equivalents", 0)
ebitda = self.income.get("ebitda", 0)
if market_cap == 0 and share_price > 0 and shares_outstanding > 0:
market_cap = share_price * shares_outstanding
eps = safe_divide(net_income, shares_outstanding)
book_value_per_share = safe_divide(total_equity, shares_outstanding)
enterprise_value = market_cap + total_debt - cash
pe = safe_divide(share_price, eps)
ratios = {
"pe_ratio": {
"value": pe,
"formula": "Share Price / Earnings Per Share",
"name": "Price-to-Earnings Ratio",
},
"pb_ratio": {
"value": safe_divide(share_price, book_value_per_share),
"formula": "Share Price / Book Value Per Share",
"name": "Price-to-Book Ratio",
},
"ps_ratio": {
"value": safe_divide(
market_cap, revenue
),
"formula": "Market Cap / Revenue",
"name": "Price-to-Sales Ratio",
},
"ev_ebitda": {
"value": safe_divide(enterprise_value, ebitda),
"formula": "Enterprise Value / EBITDA",
"name": "EV/EBITDA",
},
"peg_ratio": {
"value": safe_divide(pe, earnings_growth_rate * 100)
if earnings_growth_rate > 0
else 0.0,
"formula": "P/E Ratio / Earnings Growth Rate (%)",
"name": "PEG Ratio",
},
}
for key, ratio in ratios.items():
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
self.results["valuation"] = ratios
return ratios
def calculate_all(self) -> Dict[str, Dict[str, Any]]:
"""Calculate all ratio categories."""
self.calculate_profitability()
self.calculate_liquidity()
self.calculate_leverage()
self.calculate_efficiency()
self.calculate_valuation()
return self.results
def interpret_ratio(self, ratio_key: str, value: float) -> str:
"""Interpret a ratio value against benchmarks."""
if value == 0.0:
return "Insufficient data to calculate"
benchmarks = self.BENCHMARKS.get(ratio_key)
if not benchmarks:
return "No benchmark available"
low, typical, high = benchmarks
# DSO is inverse - lower is better
if ratio_key == "dso":
if value <= low:
return "Excellent - collections well above average"
elif value <= typical:
return "Good - collections within normal range"
elif value <= high:
return "Acceptable - monitor collection trends"
else:
return "Concern - collections significantly slower than peers"
# Debt-to-equity - lower generally better (but context matters)
if ratio_key == "debt_to_equity":
if value <= low:
return "Conservative leverage - strong equity position"
elif value <= typical:
return "Moderate leverage - well balanced"
elif value <= high:
return "Elevated leverage - monitor debt levels"
else:
return "High leverage - potential financial risk"
# Standard interpretation (higher is better for most ratios)
if value < low:
return "Below average - needs improvement"
elif value <= typical:
return "Acceptable - within normal range"
elif value <= high:
return "Good - above average performance"
else:
return "Excellent - significantly above peers"
@staticmethod
def format_ratio(value: float, is_percentage: bool = False) -> str:
"""Format a ratio value for display."""
if is_percentage:
return f"{value * 100:.1f}%"
return f"{value:.2f}"
def format_text(self, category: Optional[str] = None) -> str:
"""Format results as human-readable text."""
lines: List[str] = []
lines.append("=" * 70)
lines.append("FINANCIAL RATIO ANALYSIS")
lines.append("=" * 70)
categories = (
{category: self.results[category]}
if category and category in self.results
else self.results
)
percentage_ratios = {
"roe", "roa", "gross_margin", "operating_margin", "net_margin"
}
for cat_name, ratios in categories.items():
lines.append(f"\n--- {cat_name.upper()} ---")
for key, ratio in ratios.items():
is_pct = key in percentage_ratios
formatted = self.format_ratio(ratio["value"], is_pct)
lines.append(f" {ratio['name']}: {formatted}")
lines.append(f" Formula: {ratio['formula']}")
lines.append(f" Assessment: {ratio['interpretation']}")
lines.append("\n" + "=" * 70)
return "\n".join(lines)
def to_json(self, category: Optional[str] = None) -> Dict[str, Any]:
"""Return results as JSON-serializable dict."""
if category and category in self.results:
return {"category": category, "ratios": self.results[category]}
return {"categories": self.results}
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Calculate and interpret financial ratios"
)
parser.add_argument(
"input_file",
help="Path to JSON file with financial statement data",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
parser.add_argument(
"--category",
choices=[
"profitability",
"liquidity",
"leverage",
"efficiency",
"valuation",
],
default=None,
help="Calculate only a specific ratio category",
)
args = parser.parse_args()
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File '{args.input_file}' not found.", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in '{args.input_file}': {e}", file=sys.stderr)
sys.exit(1)
flat_keys = ("income_statement", "balance_sheet", "cash_flow", "market_data")
data = resolve_input_section(data, "ratio_analysis", flat_keys)
if not any(key in data for key in flat_keys):
print(
"Error: No financial statement data found. Expected "
f"{', '.join(flat_keys)} at the top level or nested under "
"'ratio_analysis'.",
file=sys.stderr,
)
sys.exit(1)
calculator = FinancialRatioCalculator(data)
if args.category:
method_map = {
"profitability": calculator.calculate_profitability,
"liquidity": calculator.calculate_liquidity,
"leverage": calculator.calculate_leverage,
"efficiency": calculator.calculate_efficiency,
"valuation": calculator.calculate_valuation,
}
method_map[args.category]()
else:
calculator.calculate_all()
if args.format == "json":
print(json.dumps(calculator.to_json(args.category), indent=2))
else:
print(calculator.format_text(args.category))
if __name__ == "__main__":
main()
SKILL.md
---
name: "financial-analyst"
description: "Analyze financial statements, value a company with a DCF, explain budget variance, build rolling forecasts. Use whenever the user asks what a business is worth or why spend missed budget."
---
# Financial Analyst Skill
## Overview
Production-ready financial analysis toolkit providing ratio analysis, DCF valuation, budget variance analysis, and rolling forecast construction. Designed for financial modeling, forecasting & budgeting, management reporting, business performance analysis, and investment analysis.
## 5-Phase Workflow
### Phase 1: Scoping
- Define analysis objectives and stakeholder requirements
- Identify data sources and time periods
- Establish materiality thresholds and accuracy targets
- Select appropriate analytical frameworks
### Phase 2: Data Analysis & Modeling
- Collect and validate financial data (income statement, balance sheet, cash flow)
- **Validate input data completeness** before running ratio calculations (check for missing fields, nulls, or implausible values)
- Calculate financial ratios across 5 categories (profitability, liquidity, leverage, efficiency, valuation)
- Build DCF models with WACC and terminal value calculations; **cross-check DCF outputs against sanity bounds** (e.g., implied multiples vs. comparables)
- Construct budget variance analyses with favorable/unfavorable classification
- Develop driver-based forecasts with scenario modeling
### Phase 3: Insight Generation
- Interpret ratio trends and benchmark against industry standards
- Identify material variances and root causes
- Assess valuation ranges through sensitivity analysis
- Evaluate forecast scenarios (base/bull/bear) for decision support
### Phase 4: Reporting
- Generate executive summaries with key findings
- Produce detailed variance reports by department and category
- Deliver DCF valuation reports with sensitivity tables
- Present rolling forecasts with trend analysis
### Phase 5: Follow-up
- Track forecast accuracy (target: +/-5% revenue, +/-3% expenses)
- Monitor report delivery timeliness (target: 100% on time)
- Update models with actuals as they become available
- Refine assumptions based on variance analysis
## Tools
### 1. Ratio Calculator (`scripts/ratio_calculator.py`)
Calculate and interpret financial ratios from financial statement data.
**Ratio Categories:**
- **Profitability:** ROE, ROA, Gross Margin, Operating Margin, Net Margin
- **Liquidity:** Current Ratio, Quick Ratio, Cash Ratio
- **Leverage:** Debt-to-Equity, Interest Coverage, DSCR
- **Efficiency:** Asset Turnover, Inventory Turnover, Receivables Turnover, DSO
- **Valuation:** P/E, P/B, P/S, EV/EBITDA, PEG Ratio
```bash
python scripts/ratio_calculator.py assets/sample_financial_data.json
python scripts/ratio_calculator.py assets/sample_financial_data.json --format json
python scripts/ratio_calculator.py assets/sample_financial_data.json --category profitability
```
### 2. DCF Valuation (`scripts/dcf_valuation.py`)
Discounted Cash Flow enterprise and equity valuation with sensitivity analysis.
**Features:**
- WACC calculation via CAPM
- Revenue and free cash flow projections (5-year default)
- Terminal value via perpetuity growth and exit multiple methods
- Enterprise value and equity value derivation
- Two-way sensitivity analysis (discount rate vs growth rate)
```bash
python scripts/dcf_valuation.py assets/sample_financial_data.json
python scripts/dcf_valuation.py assets/sample_financial_data.json --format json
python scripts/dcf_valuation.py assets/sample_financial_data.json --projection-years 7
```
### 3. Budget Variance Analyzer (`scripts/budget_variance_analyzer.py`)
Analyze actual vs budget vs prior year performance with materiality filtering.
**Features:**
- Dollar and percentage variance calculation
- Materiality threshold filtering (default: 10% or $50K)
- Favorable/unfavorable classification with revenue/expense logic
- Department and category breakdown
- Executive summary generation
```bash
python scripts/budget_variance_analyzer.py assets/sample_financial_data.json
python scripts/budget_variance_analyzer.py assets/sample_financial_data.json --format json
python scripts/budget_variance_analyzer.py assets/sample_financial_data.json --threshold-pct 5 --threshold-amt 25000
```
### 4. Forecast Builder (`scripts/forecast_builder.py`)
Driver-based revenue forecasting with rolling cash flow projection and scenario modeling.
**Features:**
- Driver-based revenue forecast model
- 13-week rolling cash flow projection
- Scenario modeling (base/bull/bear cases)
- Trend analysis using simple linear regression (standard library)
```bash
python scripts/forecast_builder.py assets/sample_financial_data.json
python scripts/forecast_builder.py assets/sample_financial_data.json --format json
python scripts/forecast_builder.py assets/sample_financial_data.json --scenarios base,bull,bear
```
## Knowledge Bases
| Reference | Purpose |
|-----------|---------|
| `references/financial-ratios-guide.md` | Ratio formulas, interpretation, industry benchmarks |
| `references/valuation-methodology.md` | DCF methodology, WACC, terminal value, comps |
| `references/forecasting-best-practices.md` | Driver-based forecasting, rolling forecasts, accuracy |
| `references/industry-adaptations.md` | Sector-specific metrics and considerations (SaaS, Retail, Manufacturing, Financial Services, Healthcare) |
## Templates
| Template | Purpose |
|----------|---------|
| `assets/variance_report_template.md` | Budget variance report template |
| `assets/dcf_analysis_template.md` | DCF valuation analysis template |
| `assets/forecast_report_template.md` | Revenue forecast report template |
## Key Metrics & Targets
| Metric | Target |
|--------|--------|
| Forecast accuracy (revenue) | +/-5% |
| Forecast accuracy (expenses) | +/-3% |
| Report delivery | 100% on time |
| Model documentation | Complete for all assumptions |
| Variance explanation | 100% of material variances |
## Input Data Format
All scripts accept JSON input files in either of two shapes:
1. **Flat** — the tool's expected keys at the top level (e.g., `income_statement` / `balance_sheet` for the ratio calculator, `historical` / `assumptions` for DCF, `line_items` for variance, `historical_periods` / `drivers` / `assumptions` / `cash_flow_inputs` for forecasting).
2. **Nested (bundled)** — inputs for all four tools in one file, nested under per-tool keys: `ratio_analysis`, `dcf_valuation`, `budget_variance`, `forecast`. See `assets/sample_financial_data.json` for the complete bundled schema; every quick-start command above runs directly against it.
Each script auto-detects the shape (flat keys win if present) and exits non-zero with a clear error if neither shape yields usable data.
## Dependencies
**None** - All scripts use Python standard library only (`math`, `statistics`, `json`, `argparse`, `datetime`). No numpy, pandas, or scipy required.