#!/usr/bin/env python3 """ Scenario War Room — Multi-Variable Cascade Modeler Models cascading effects of compound adversity across business domains. Stdlib only. Run with: python scenario_modeler.py """ import json import sys from dataclasses import dataclass, field from typing import Dict, List, Optional, Tuple from enum import Enum class Severity(Enum): BASE = "base" # One variable hits STRESS = "stress" # Two variables hit SEVERE = "severe" # All variables hit class Domain(Enum): FINANCIAL = "Financial (CFO)" REVENUE = "Revenue (CRO)" PRODUCT = "Product (CPO)" ENGINEERING = "Engineering (CTO)" PEOPLE = "People (CHRO)" OPERATIONS = "Operations (COO)" SECURITY = "Security (CISO)" MARKET = "Market (CMO)" @dataclass class Variable: name: str description: str probability: float # 0.0-1.0 arrt_impact_pct: float # % of ARR at risk (negative = loss) runway_impact_months: float # months lost from runway (negative = reduction) affected_domains: List[Domain] timeline_days: int # when it hits @dataclass class CascadeEffect: trigger_domain: Domain caused_domain: Domain mechanism: str # how A causes B severity_multiplier: float # compounds the base impact @dataclass class Hedge: action: str cost_usd: int impact_description: str owner: str deadline_days: int reduces_probability: float # how much it reduces scenario probability @dataclass class Scenario: name: str variables: List[Variable] cascades: List[CascadeEffect] hedges: List[Hedge] # Company baseline current_arr_usd: int = 2_000_000 current_runway_months: int = 14 monthly_burn_usd: int = 140_000 def calculate_impact( scenario: Scenario, severity: Severity ) -> Dict: """Calculate combined impact for a given severity level.""" variables = scenario.variables # Select variables by severity if severity == Severity.BASE: active_vars = variables[:1] elif severity == Severity.STRESS: active_vars = variables[:2] else: active_vars = variables # Direct impacts total_arr_loss_pct = sum(abs(v.arrt_impact_pct) for v in active_vars) total_runway_reduction = sum(abs(v.runway_impact_months) for v in active_vars) arr_at_risk = scenario.current_arr_usd * (total_arr_loss_pct / 100) new_arr = scenario.current_arr_usd - arr_at_risk new_runway = scenario.current_runway_months - total_runway_reduction # Cascade multiplier (stress/severe amplify via domain cascades) cascade_multiplier = 1.0 if len(active_vars) > 1: active_domains = set(d for v in active_vars for d in v.affected_domains) for cascade in scenario.cascades: if (cascade.trigger_domain in active_domains and cascade.caused_domain in active_domains): cascade_multiplier *= cascade.severity_multiplier # Apply cascade effective_arr_loss = arr_at_risk * cascade_multiplier effective_arr = scenario.current_arr_usd - effective_arr_loss effective_runway = max(0, new_runway - (cascade_multiplier - 1.0) * 2) # New burn multiple new_monthly_burn = scenario.monthly_burn_usd * cascade_multiplier burn_multiple = (new_monthly_burn * 12) / max(effective_arr, 1) # Affected domains affected = set(d for v in active_vars for d in v.affected_domains) return { "severity": severity.value, "active_variables": [v.name for v in active_vars], "arr_at_risk_usd": int(effective_arr_loss), "arr_at_risk_pct": round(effective_arr_loss / scenario.current_arr_usd * 100, 1), "projected_arr_usd": int(effective_arr), "runway_months": round(effective_runway, 1), "runway_change": round(effective_runway - scenario.current_runway_months, 1), "cascade_multiplier": round(cascade_multiplier, 2), "new_burn_multiple": round(burn_multiple, 1), "affected_domains": [d.value for d in affected], "existential_risk": effective_runway < 6.0, "board_escalation_required": effective_runway < 9.0, } def identify_triggers(variables: List[Variable]) -> List[Dict]: """Generate early warning triggers for each variable.""" triggers = [] for var in variables: trigger = { "variable": var.name, "timeline": f"Watch from day 1; expect signal ~{var.timeline_days // 2} days before impact", "signals": _generate_signals(var), "response_owner": _domain_to_owner(var.affected_domains[0] if var.affected_domains else Domain.FINANCIAL), } triggers.append(trigger) return triggers def _generate_signals(var: Variable) -> List[str]: """Generate plausible early warning signals based on variable type.""" signals = [] name_lower = var.name.lower() if any(k in name_lower for k in ["customer", "churn", "account"]): signals = [ "Executive sponsor unreachable for >2 weeks", "Product usage drops >20% month-over-month", "No QBR scheduled within 90 days of contract renewal", "Support ticket volume spikes >50% without explanation", ] elif any(k in name_lower for k in ["fundraise", "raise", "capital", "investor"]): signals = [ "Fewer than 3 term sheets after 60 days of active process", "Lead investor requests 30+ day extension on diligence", "Comparable company raises at lower valuation (market signal)", "Investor meeting conversion rate below 20%", ] elif any(k in name_lower for k in ["engineer", "people", "team", "resign", "quit"]): signals = [ "2+ engineers receive above-market counter-offer in 90 days", "Glassdoor activity increases from engineering team", "Key person requests 1:1 to 'talk about career' unexpectedly", "Referral interview requests from engineers increase", ] elif any(k in name_lower for k in ["market", "competitor", "competition"]): signals = [ "Competitor raises $10M+ funding round", "Win/loss rate shifts >10% in 60 days", "Multiple prospects cite competitor by name in objections", "Competitor poaches 2+ of your customers in a quarter", ] else: signals = [ f"Leading indicator for '{var.name}' deteriorates 20%+ vs baseline", "Weekly metric review shows 3-week trend in wrong direction", "External validation from customers or partners confirms risk", ] return signals[:3] # Top 3 def _domain_to_owner(domain: Domain) -> str: mapping = { Domain.FINANCIAL: "CFO", Domain.REVENUE: "CRO", Domain.PRODUCT: "CPO", Domain.ENGINEERING: "CTO", Domain.PEOPLE: "CHRO", Domain.OPERATIONS: "COO", Domain.SECURITY: "CISO", Domain.MARKET: "CMO", } return mapping.get(domain, "CEO") def format_currency(amount: int) -> str: if amount >= 1_000_000: return f"${amount / 1_000_000:.1f}M" elif amount >= 1_000: return f"${amount / 1_000:.0f}K" return f"${amount}" def print_report(scenario: Scenario) -> None: """Print full scenario analysis report.""" print("\n" + "=" * 70) print(f"SCENARIO WAR ROOM: {scenario.name.upper()}") print("=" * 70) # Baseline print(f"\nšŸ“Š BASELINE") print(f" Current ARR: {format_currency(scenario.current_arr_usd)}") print(f" Monthly Burn: {format_currency(scenario.monthly_burn_usd)}") print(f" Runway: {scenario.current_runway_months} months") # Variables print(f"\n⚔ SCENARIO VARIABLES ({len(scenario.variables)})") for i, var in enumerate(scenario.variables, 1): prob_pct = int(var.probability * 100) print(f"\n Variable {i}: {var.name}") print(f" {var.description}") print(f" Probability: {prob_pct}% | Timeline: {var.timeline_days} days") print(f" ARR impact: -{var.arrt_impact_pct}% | " f"Runway impact: -{var.runway_impact_months} months") print(f" Affected: {', '.join(d.value for d in var.affected_domains)}") # Combined probability combined_prob = 1.0 for var in scenario.variables: combined_prob *= var.probability print(f"\n Combined probability (all hit): {combined_prob * 100:.1f}%") # Severity Levels print(f"\n{'=' * 70}") print("SEVERITY ANALYSIS") print("=" * 70) for severity in Severity: if severity == Severity.BASE and len(scenario.variables) < 1: continue if severity == Severity.STRESS and len(scenario.variables) < 2: continue impact = calculate_impact(scenario, severity) icon = {"base": "🟔", "stress": "šŸ”“", "severe": "šŸ’€"}[impact["severity"]] print(f"\n{icon} {impact['severity'].upper()} SCENARIO") print(f" Variables: {', '.join(impact['active_variables'])}") print(f" ARR at risk: {format_currency(impact['arr_at_risk_usd'])} " f"({impact['arr_at_risk_pct']}%)") print(f" Projected ARR: {format_currency(impact['projected_arr_usd'])}") print(f" Runway: {impact['runway_months']} months " f"({impact['runway_change']:+.1f} months)") print(f" Burn multiple: {impact['new_burn_multiple']}x") if impact['cascade_multiplier'] > 1.0: print(f" Cascade amplifier: {impact['cascade_multiplier']}x " f"(domains interact)") print(f" Board escalation: {'āš ļø YES' if impact['board_escalation_required'] else 'No'}") print(f" Existential risk: {'🚨 YES' if impact['existential_risk'] else 'No'}") # Cascade Map if scenario.cascades: print(f"\n{'=' * 70}") print("CASCADE MAP") print("=" * 70) for i, cascade in enumerate(scenario.cascades, 1): print(f"\n [{i}] {cascade.trigger_domain.value}") print(f" ↓ {cascade.mechanism}") print(f" → {cascade.caused_domain.value} " f"(amplified {cascade.severity_multiplier}x)") # Early Warning Triggers print(f"\n{'=' * 70}") print("EARLY WARNING TRIGGERS") print("=" * 70) triggers = identify_triggers(scenario.variables) for trigger in triggers: print(f"\n šŸ“” {trigger['variable']}") print(f" Watch: {trigger['timeline']}") print(f" Owner: {trigger['response_owner']}") for signal in trigger['signals']: print(f" • {signal}") # Hedges if scenario.hedges: print(f"\n{'=' * 70}") print("HEDGING STRATEGIES (act now)") print("=" * 70) sorted_hedges = sorted(scenario.hedges, key=lambda h: h.reduces_probability, reverse=True) for hedge in sorted_hedges: print(f"\n āœ… {hedge.action}") print(f" Cost: {format_currency(hedge.cost_usd)}/year | " f"Owner: {hedge.owner} | Deadline: {hedge.deadline_days} days") print(f" Impact: {hedge.impact_description}") print(f" Risk reduction: {int(hedge.reduces_probability * 100)}%") print(f"\n{'=' * 70}\n") def build_sample_scenario() -> Scenario: """Sample: Customer churn + fundraise miss compound scenario.""" variables = [ Variable( name="Top customer churn", description="Largest customer (28% of ARR) gives 60-day termination notice", probability=0.15, arrt_impact_pct=28.0, runway_impact_months=4.0, affected_domains=[ Domain.FINANCIAL, Domain.REVENUE, Domain.OPERATIONS ], timeline_days=60, ), Variable( name="Series A delayed 6 months", description="Fundraise process extends beyond target close; bridge required", probability=0.25, arrt_impact_pct=0.0, # No ARR impact directly runway_impact_months=3.0, # Bridge terms reduce effective runway affected_domains=[ Domain.FINANCIAL, Domain.PEOPLE, Domain.OPERATIONS ], timeline_days=120, ), Variable( name="Lead engineer resigns", description="Engineering lead + 1 senior resign during uncertainty", probability=0.20, arrt_impact_pct=5.0, # Roadmap slip causes some revenue impact runway_impact_months=1.0, affected_domains=[ Domain.ENGINEERING, Domain.PRODUCT, Domain.REVENUE ], timeline_days=30, ), ] cascades = [ CascadeEffect( trigger_domain=Domain.REVENUE, caused_domain=Domain.FINANCIAL, mechanism="ARR loss increases burn multiple; runway compresses", severity_multiplier=1.3, ), CascadeEffect( trigger_domain=Domain.FINANCIAL, caused_domain=Domain.PEOPLE, mechanism="Hiring freeze + uncertainty triggers attrition risk", severity_multiplier=1.2, ), CascadeEffect( trigger_domain=Domain.PEOPLE, caused_domain=Domain.PRODUCT, mechanism="Engineering attrition slips roadmap; customer value drops", severity_multiplier=1.15, ), ] hedges = [ Hedge( action="Establish $750K revolving credit line", cost_usd=7_500, impact_description="Buys 4+ months if churn hits before fundraise closes", owner="CFO", deadline_days=45, reduces_probability=0.40, ), Hedge( action="12-month retention bonuses for 3 key engineers", cost_usd=90_000, impact_description="Locks critical talent through fundraise uncertainty", owner="CHRO", deadline_days=30, reduces_probability=0.60, ), Hedge( action="Diversify revenue: reduce top customer to <20% ARR in 2 quarters", cost_usd=0, impact_description="Structural risk reduction; takes 6+ months to achieve", owner="CRO", deadline_days=14, reduces_probability=0.30, ), Hedge( action="Accelerate fundraise: start parallel process, compress timeline", cost_usd=15_000, impact_description="Closes before scenarios compound; reduces bridge risk", owner="CEO", deadline_days=7, reduces_probability=0.35, ), ] return Scenario( name="Customer Churn + Fundraise Miss + Eng Attrition", variables=variables, cascades=cascades, hedges=hedges, current_arr_usd=2_000_000, current_runway_months=14, monthly_burn_usd=140_000, ) def interactive_mode() -> Scenario: """Simple CLI for building a custom scenario.""" print("\nšŸ”“ SCENARIO WAR ROOM — Custom Scenario Builder") print("=" * 50) print("Define up to 3 scenario variables.\n") name = input("Scenario name: ").strip() or "Custom Scenario" current_arr = int(input("Current ARR ($): ").strip() or "2000000") current_runway = int(input("Current runway (months): ").strip() or "14") monthly_burn = int(current_arr / current_runway) if current_runway > 0 else 140000 variables = [] for i in range(1, 4): print(f"\nVariable {i} (press Enter to skip):") var_name = input(" Name: ").strip() if not var_name: break desc = input(" Description: ").strip() or var_name prob = float(input(" Probability (0-100%): ").strip() or "20") / 100 arr_impact = float(input(" ARR impact (%): ").strip() or "10") runway_impact = float(input(" Runway impact (months): ").strip() or "2") timeline = int(input(" Timeline (days): ").strip() or "90") variables.append(Variable( name=var_name, description=desc, probability=prob, arrt_impact_pct=arr_impact, runway_impact_months=runway_impact, affected_domains=[Domain.FINANCIAL, Domain.REVENUE], timeline_days=timeline, )) if not variables: print("No variables defined. Using sample scenario.") return build_sample_scenario() return Scenario( name=name, variables=variables, cascades=[], hedges=[], current_arr_usd=current_arr, current_runway_months=current_runway, monthly_burn_usd=monthly_burn, ) def main(): print("\nšŸ”“ SCENARIO WAR ROOM") print("Multi-variable cascade modeler for startup adversity planning\n") if "--interactive" in sys.argv or "-i" in sys.argv: scenario = interactive_mode() else: print("Running sample scenario: Customer Churn + Fundraise Miss + Eng Attrition") print("(Use --interactive or -i for custom scenario)\n") scenario = build_sample_scenario() print_report(scenario) if "--json" in sys.argv: results = {} for severity in Severity: impact = calculate_impact(scenario, severity) results[severity.value] = impact print(json.dumps(results, indent=2)) if __name__ == "__main__": main()