akshay7 commited on
Commit
1e4f9fd
·
1 Parent(s): 42aef51

add: broken single code file into logical modules

Browse files
Files changed (5) hide show
  1. charts.py +248 -0
  2. equity_calculator.py +0 -563
  3. interface.py +235 -0
  4. main.py +95 -0
  5. models.py +228 -0
charts.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Chart generation for equity analysis visualization
3
+ """
4
+ import plotly.graph_objects as go
5
+ from plotly.subplots import make_subplots
6
+ from typing import List, Optional
7
+ from models import ScenarioResult, CapTable, EquityCalculator
8
+
9
+
10
+ class EquityCharts:
11
+ """Handles all chart generation for equity analysis"""
12
+
13
+ @staticmethod
14
+ def create_multi_scenario_comparison(results: List[ScenarioResult]) -> Optional[go.Figure]:
15
+ """Create comparison chart showing option values and exit valuations"""
16
+ if not results:
17
+ return None
18
+
19
+ scenario_names = [r.scenario_name for r in results]
20
+ option_values = [r.option_value for r in results]
21
+ exit_values = [r.exit_valuation for r in results]
22
+
23
+ fig = make_subplots(
24
+ rows=2, cols=1,
25
+ subplot_titles=("Your Option Value by Scenario", "Exit Valuation by Scenario"),
26
+ vertical_spacing=0.20,
27
+ specs=[[{"secondary_y": False}], [{"secondary_y": False}]]
28
+ )
29
+
30
+ # Option values bar chart
31
+ fig.add_trace(
32
+ go.Bar(
33
+ x=scenario_names,
34
+ y=option_values,
35
+ name="Option Value",
36
+ marker_color='#2E86AB',
37
+ text=[f"${val:,.0f}" for val in option_values],
38
+ textposition='outside'
39
+ ),
40
+ row=1, col=1
41
+ )
42
+
43
+ # Exit valuations bar chart
44
+ fig.add_trace(
45
+ go.Bar(
46
+ x=scenario_names,
47
+ y=exit_values,
48
+ name="Exit Valuation",
49
+ marker_color='#F18F01',
50
+ text=[f"${val:,.0f}" for val in exit_values],
51
+ textposition='outside',
52
+ showlegend=False
53
+ ),
54
+ row=2, col=1
55
+ )
56
+
57
+ fig.update_layout(
58
+ title="Multi-Scenario Equity Analysis",
59
+ height=650,
60
+ showlegend=True,
61
+ margin=dict(t=80, b=50, l=80, r=50)
62
+ )
63
+
64
+ # Add extra space for text labels above bars
65
+ if option_values:
66
+ fig.update_yaxes(title_text="Your Option Value ($)", row=1, col=1, range=[0, max(option_values) * 1.15])
67
+ if exit_values:
68
+ fig.update_yaxes(title_text="Company Valuation ($)", row=2, col=1, range=[0, max(exit_values) * 1.15])
69
+
70
+ return fig
71
+
72
+ @staticmethod
73
+ def create_liquidation_waterfall(
74
+ cap_table: CapTable,
75
+ exit_valuation: float,
76
+ scenario_name: str = "Best Scenario"
77
+ ) -> go.Figure:
78
+ """Create detailed liquidation waterfall chart for a specific exit value"""
79
+
80
+ calculator = EquityCalculator(cap_table)
81
+ remaining_proceeds = exit_valuation
82
+ waterfall_data = []
83
+ participating_shareholders = []
84
+
85
+ # Sort funding rounds (newest first for liquidation preferences)
86
+ sorted_rounds = sorted(cap_table.funding_rounds,
87
+ key=lambda x: ['Seed', 'Series A', 'Series B', 'Series C'].index(x.name)
88
+ if x.name in ['Seed', 'Series A', 'Series B', 'Series C'] else 999,
89
+ reverse=True)
90
+
91
+ # Phase 1: Liquidation preferences
92
+ for round in sorted_rounds:
93
+ if round.shares_issued > 0 and round.capital_raised > 0:
94
+ preference_payout = min(remaining_proceeds, round.liquidation_preference)
95
+ remaining_proceeds -= preference_payout
96
+
97
+ if round.is_participating:
98
+ participating_shareholders.append({
99
+ 'round': round.name,
100
+ 'shares': round.shares_issued
101
+ })
102
+
103
+ waterfall_data.append({
104
+ 'Round': f'{round.name} (Pref)',
105
+ 'Payout': preference_payout,
106
+ 'Type': 'Preference'
107
+ })
108
+
109
+ # Phase 2: Participating preferred and common distribution
110
+ participating_preferred_shares = sum(p['shares'] for p in participating_shareholders)
111
+ total_participating_shares = cap_table.common_shares + participating_preferred_shares
112
+
113
+ if total_participating_shares > 0:
114
+ price_per_share = remaining_proceeds / total_participating_shares
115
+ common_proceeds = price_per_share * cap_table.common_shares
116
+
117
+ # Add participating preferred distributions
118
+ for participant in participating_shareholders:
119
+ participating_payout = price_per_share * participant['shares']
120
+ waterfall_data.append({
121
+ 'Round': f"{participant['round']} (Part.)",
122
+ 'Payout': participating_payout,
123
+ 'Type': 'Participation'
124
+ })
125
+ else:
126
+ common_proceeds = remaining_proceeds
127
+
128
+ # Add common stock
129
+ waterfall_data.append({
130
+ 'Round': 'Common Stock',
131
+ 'Payout': common_proceeds,
132
+ 'Type': 'Common'
133
+ })
134
+
135
+ # Create the chart
136
+ fig = go.Figure()
137
+
138
+ color_map = {
139
+ 'Preference': '#FF6B6B', # Red for liquidation preferences
140
+ 'Participation': '#4ECDC4', # Teal for participating preferred
141
+ 'Common': '#F7DC6F' # Yellow for common stock
142
+ }
143
+
144
+ for item in waterfall_data:
145
+ if item['Payout'] > 0:
146
+ color = color_map.get(item['Type'], '#96CEB4')
147
+ fig.add_trace(go.Bar(
148
+ x=[item['Round']],
149
+ y=[item['Payout']],
150
+ name=f"{item['Round']} (${item['Payout']:,.0f})",
151
+ marker_color=color,
152
+ text=f"${item['Payout']:,.0f}",
153
+ textposition='outside'
154
+ ))
155
+
156
+ fig.update_layout(
157
+ title=f"Liquidation Waterfall - ${exit_valuation:,.0f} Exit",
158
+ xaxis_title="Stakeholder",
159
+ yaxis_title="Payout ($)",
160
+ height=450,
161
+ showlegend=True,
162
+ margin=dict(t=60, b=50, l=80, r=50)
163
+ )
164
+
165
+ return fig
166
+
167
+ @staticmethod
168
+ def create_roi_analysis(results: List[ScenarioResult], investment_cost: float) -> Optional[go.Figure]:
169
+ """Create ROI analysis chart"""
170
+ if not results:
171
+ return None
172
+
173
+ roi_data = []
174
+ for result in results:
175
+ roi = result.roi_percentage(investment_cost)
176
+ # Cap very high ROI for display purposes
177
+ display_roi = roi if roi < 999999 else 999999
178
+ roi_data.append({
179
+ 'scenario': result.scenario_name,
180
+ 'roi': display_roi,
181
+ 'absolute_gain': result.option_value - investment_cost
182
+ })
183
+
184
+ fig = go.Figure()
185
+ fig.add_trace(go.Bar(
186
+ x=[d['scenario'] for d in roi_data],
187
+ y=[d['roi'] for d in roi_data],
188
+ name="ROI %",
189
+ marker_color='#28A745',
190
+ text=[f"{d['roi']:.0f}%" if d['roi'] < 999999 else "∞%" for d in roi_data],
191
+ textposition='outside'
192
+ ))
193
+
194
+ fig.update_layout(
195
+ title="Return on Investment (ROI) by Scenario",
196
+ xaxis_title="Scenario",
197
+ yaxis_title="ROI (%)",
198
+ height=450,
199
+ margin=dict(t=60, b=50, l=60, r=50)
200
+ )
201
+
202
+ return fig
203
+
204
+
205
+ def format_results_table(results: List[ScenarioResult]) -> str:
206
+ """Format scenario results as a markdown table"""
207
+ if not results:
208
+ return "No scenarios to display"
209
+
210
+ table = "## 📊 Exit Scenario Comparison\n\n"
211
+ table += "| Scenario | Exit Value | Your Option Value | Value per Option | Common Proceeds |\n"
212
+ table += "|----------|------------|-------------------|------------------|------------------|\n"
213
+
214
+ for result in results:
215
+ table += f"| **{result.scenario_name}** | ${result.exit_valuation:,.0f} | "
216
+ table += f"${result.option_value:,.2f} | ${result.value_per_option:.4f} | "
217
+ table += f"${result.common_proceeds:,.0f} |\n"
218
+
219
+ return table
220
+
221
+
222
+ def format_equity_summary(summary: dict, results: List[ScenarioResult]) -> str:
223
+ """Format complete equity analysis summary"""
224
+
225
+ results_table = format_results_table(results)
226
+
227
+ summary_text = f"""
228
+ ## 💰 Your Equity Summary
229
+
230
+ **Your Option Grant:** {summary['your_options']:,} options
231
+ **Strike Price:** ${summary['strike_price']:.4f} per share
232
+ **Your Equity Stake:** {summary['your_equity_percentage']:.3f}%
233
+
234
+ {results_table}
235
+
236
+ ## 🏗️ Cap Table Summary
237
+
238
+ **Total Shares:** {summary['total_shares']:,}
239
+ **Common Shares:** {summary['common_shares']:,}
240
+ **Preferred Shares:** {summary['preferred_shares']:,}
241
+
242
+ **Liquidation Terms:** {' | '.join(summary['participating_status']) if summary['participating_status'] else 'No preferred rounds'}
243
+
244
+ **Break-even Price per Share:** ${summary['break_even_price']:.4f}
245
+ *(Price needed for your options to have positive value)*
246
+ """
247
+
248
+ return summary_text
equity_calculator.py DELETED
@@ -1,563 +0,0 @@
1
- import gradio as gr
2
- import pandas as pd
3
- import plotly.graph_objects as go
4
- import plotly.express as px
5
- from plotly.subplots import make_subplots
6
-
7
- def calculate_single_scenario(
8
- exit_valuation, total_shares, your_options, strike_price,
9
- seed_shares, seed_capital, seed_multiple, seed_participating,
10
- series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
11
- series_b_shares, series_b_capital, series_b_multiple, series_b_participating
12
- ):
13
- """Calculate equity value for a single exit scenario"""
14
-
15
- # Calculate common shares
16
- total_preferred_shares = seed_shares + series_a_shares + series_b_shares
17
- common_shares = total_shares - total_preferred_shares
18
-
19
- if common_shares <= 0:
20
- return {
21
- 'exit_valuation': exit_valuation,
22
- 'option_value': 0,
23
- 'price_per_share': 0,
24
- 'common_proceeds': 0,
25
- 'error': 'Preferred shares exceed total shares'
26
- }
27
-
28
- # Phase 1: Pay liquidation preferences
29
- remaining_proceeds = exit_valuation
30
- participating_shareholders = []
31
-
32
- # Series B (most recent)
33
- series_b_preference_payout = 0
34
- if series_b_shares > 0 and series_b_capital > 0:
35
- series_b_preference = series_b_capital * series_b_multiple
36
- series_b_preference_payout = min(remaining_proceeds, series_b_preference)
37
- remaining_proceeds -= series_b_preference_payout
38
- if series_b_participating:
39
- participating_shareholders.append({'shares': series_b_shares})
40
-
41
- # Series A
42
- series_a_preference_payout = 0
43
- if series_a_shares > 0 and series_a_capital > 0:
44
- series_a_preference = series_a_capital * series_a_multiple
45
- series_a_preference_payout = min(remaining_proceeds, series_a_preference)
46
- remaining_proceeds -= series_a_preference_payout
47
- if series_a_participating:
48
- participating_shareholders.append({'shares': series_a_shares})
49
-
50
- # Seed
51
- seed_preference_payout = 0
52
- if seed_shares > 0 and seed_capital > 0:
53
- seed_preference = seed_capital * seed_multiple
54
- seed_preference_payout = min(remaining_proceeds, seed_preference)
55
- remaining_proceeds -= seed_preference_payout
56
- if seed_participating:
57
- participating_shareholders.append({'shares': seed_shares})
58
-
59
- # Phase 2: Handle non-participating conversions
60
- participating_preferred_shares = sum([p['shares'] for p in participating_shareholders])
61
- total_participating_shares = common_shares + participating_preferred_shares
62
-
63
- # Check conversions for non-participating preferred
64
- if series_b_shares > 0 and series_b_capital > 0 and not series_b_participating:
65
- conversion_value = (series_b_shares / total_shares) * exit_valuation
66
- if conversion_value > series_b_preference_payout:
67
- remaining_proceeds += series_b_preference_payout
68
- total_participating_shares += series_b_shares
69
-
70
- if series_a_shares > 0 and series_a_capital > 0 and not series_a_participating:
71
- conversion_value = (series_a_shares / total_shares) * exit_valuation
72
- if conversion_value > series_a_preference_payout:
73
- remaining_proceeds += series_a_preference_payout
74
- total_participating_shares += series_a_shares
75
-
76
- if seed_shares > 0 and seed_capital > 0 and not seed_participating:
77
- conversion_value = (seed_shares / total_shares) * exit_valuation
78
- if conversion_value > seed_preference_payout:
79
- remaining_proceeds += seed_preference_payout
80
- total_participating_shares += seed_shares
81
-
82
- # Final distribution
83
- if total_participating_shares > 0:
84
- price_per_participating_share = remaining_proceeds / total_participating_shares
85
- common_proceeds = price_per_participating_share * common_shares
86
- else:
87
- common_proceeds = remaining_proceeds
88
-
89
- price_per_common_share = common_proceeds / common_shares if common_shares > 0 else 0
90
- option_value_per_share = max(0, price_per_common_share - strike_price)
91
- total_option_value = option_value_per_share * your_options
92
-
93
- return {
94
- 'exit_valuation': exit_valuation,
95
- 'option_value': total_option_value,
96
- 'price_per_share': price_per_common_share,
97
- 'common_proceeds': common_proceeds,
98
- 'error': None
99
- }
100
-
101
- def calculate_equity_value(
102
- # Cap table inputs
103
- total_shares, your_options, strike_price,
104
- # Seed round
105
- seed_shares, seed_capital, seed_multiple, seed_participating,
106
- # Series A
107
- series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
108
- # Series B
109
- series_b_shares, series_b_capital, series_b_multiple, series_b_participating,
110
- # Multiple exit scenarios
111
- exit_scenario_1, scenario_1_name,
112
- exit_scenario_2, scenario_2_name,
113
- exit_scenario_3, scenario_3_name,
114
- exit_scenario_4, scenario_4_name,
115
- exit_scenario_5, scenario_5_name
116
- ):
117
- """Calculate startup equity value with liquidation preferences for multiple exit scenarios"""
118
-
119
- # Handle None values and provide defaults
120
- total_shares = total_shares or 10000000
121
- your_options = your_options or 0
122
- strike_price = strike_price or 0
123
- seed_shares = seed_shares or 0
124
- seed_capital = seed_capital or 0
125
- seed_multiple = seed_multiple or 1.0
126
- seed_participating = seed_participating or False
127
- series_a_shares = series_a_shares or 0
128
- series_a_capital = series_a_capital or 0
129
- series_a_multiple = series_a_multiple or 1.0
130
- series_a_participating = series_a_participating or False
131
- series_b_shares = series_b_shares or 0
132
- series_b_capital = series_b_capital or 0
133
- series_b_multiple = series_b_multiple or 1.0
134
- series_b_participating = series_b_participating or False
135
-
136
- # Handle exit scenarios
137
- exit_scenario_1 = exit_scenario_1 or 0
138
- exit_scenario_2 = exit_scenario_2 or 0
139
- exit_scenario_3 = exit_scenario_3 or 0
140
- exit_scenario_4 = exit_scenario_4 or 0
141
- exit_scenario_5 = exit_scenario_5 or 0
142
- scenario_1_name = scenario_1_name or "Scenario 1"
143
- scenario_2_name = scenario_2_name or "Scenario 2"
144
- scenario_3_name = scenario_3_name or "Scenario 3"
145
- scenario_4_name = scenario_4_name or "Scenario 4"
146
- scenario_5_name = scenario_5_name or "Scenario 5"
147
-
148
- # Input validation
149
- if total_shares <= 0:
150
- return "Invalid inputs - please check your values", None, None, None
151
-
152
- # Calculate scenarios
153
- scenarios = []
154
- exit_values = [exit_scenario_1, exit_scenario_2, exit_scenario_3, exit_scenario_4, exit_scenario_5]
155
- scenario_names = [scenario_1_name, scenario_2_name, scenario_3_name, scenario_4_name, scenario_5_name]
156
-
157
- for exit_val, name in zip(exit_values, scenario_names):
158
- if exit_val > 0: # Only calculate scenarios with positive exit values
159
- scenario_result = calculate_single_scenario(
160
- exit_val, total_shares, your_options, strike_price,
161
- seed_shares, seed_capital, seed_multiple, seed_participating,
162
- series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
163
- series_b_shares, series_b_capital, series_b_multiple, series_b_participating
164
- )
165
- scenario_result['name'] = name
166
- scenarios.append(scenario_result)
167
-
168
- if not scenarios:
169
- return "Please enter at least one exit scenario with a positive value", None, None, None
170
-
171
- # Calculate common shares and basic info
172
- total_preferred_shares = seed_shares + series_a_shares + series_b_shares
173
- common_shares = total_shares - total_preferred_shares
174
- your_equity_percentage = (your_options / total_shares) * 100 if total_shares > 0 else 0
175
-
176
- # Build results summary
177
- participating_status = []
178
- if seed_shares > 0: participating_status.append(f"Seed: {'Participating' if seed_participating else 'Non-Participating'}")
179
- if series_a_shares > 0: participating_status.append(f"Series A: {'Participating' if series_a_participating else 'Non-Participating'}")
180
- if series_b_shares > 0: participating_status.append(f"Series B: {'Participating' if series_b_participating else 'Non-Participating'}")
181
-
182
- # Create scenario comparison table
183
- scenario_table = "## 📊 Exit Scenario Comparison\n\n"
184
- scenario_table += "| Scenario | Exit Value | Your Option Value | Value per Option | Common Proceeds |\n"
185
- scenario_table += "|----------|------------|-------------------|------------------|------------------|\n"
186
-
187
- for scenario in scenarios:
188
- scenario_table += f"| **{scenario['name']}** | ${scenario['exit_valuation']:,.0f} | "
189
- scenario_table += f"${scenario['option_value']:,.2f} | ${scenario['option_value']/your_options if your_options > 0 else 0:.4f} | "
190
- scenario_table += f"${scenario['common_proceeds']:,.0f} |\n"
191
-
192
- results = f"""
193
- ## 💰 Your Equity Summary
194
-
195
- **Your Option Grant:** {your_options:,} options
196
- **Strike Price:** ${strike_price:.4f} per share
197
- **Your Equity Stake:** {your_equity_percentage:.3f}%
198
-
199
- {scenario_table}
200
-
201
- ## 🏗️ Cap Table Summary
202
-
203
- **Total Shares:** {total_shares:,}
204
- **Common Shares:** {common_shares:,}
205
- **Preferred Shares:** {total_preferred_shares:,}
206
-
207
- **Liquidation Terms:** {' | '.join(participating_status) if participating_status else 'No preferred rounds'}
208
-
209
- **Break-even Price per Share:** ${strike_price:.4f}
210
- *(Price needed for your options to have positive value)*
211
- """
212
-
213
- # Create comparison bar chart
214
- if scenarios:
215
- scenario_names = [s['name'] for s in scenarios]
216
- option_values = [s['option_value'] for s in scenarios]
217
- exit_values = [s['exit_valuation'] for s in scenarios]
218
-
219
- fig = make_subplots(
220
- rows=2, cols=1,
221
- subplot_titles=("Your Option Value by Scenario", "Exit Valuation by Scenario"),
222
- vertical_spacing=0.20,
223
- specs=[[{"secondary_y": False}], [{"secondary_y": False}]]
224
- )
225
-
226
- # Option values bar chart
227
- fig.add_trace(
228
- go.Bar(
229
- x=scenario_names,
230
- y=option_values,
231
- name="Option Value",
232
- marker_color='#2E86AB',
233
- text=[f"${val:,.0f}" for val in option_values],
234
- textposition='outside'
235
- ),
236
- row=1, col=1
237
- )
238
-
239
- # Exit valuations bar chart
240
- fig.add_trace(
241
- go.Bar(
242
- x=scenario_names,
243
- y=exit_values,
244
- name="Exit Valuation",
245
- marker_color='#F18F01',
246
- text=[f"${val:,.0f}" for val in exit_values],
247
- textposition='outside',
248
- showlegend=False
249
- ),
250
- row=2, col=1
251
- )
252
-
253
- fig.update_layout(
254
- title="Multi-Scenario Equity Analysis",
255
- height=650,
256
- showlegend=True,
257
- margin=dict(t=80, b=50, l=80, r=50)
258
- )
259
-
260
- # Add extra space for text labels above bars
261
- fig.update_yaxes(title_text="Your Option Value ($)", row=1, col=1, range=[0, max(option_values) * 1.15])
262
- fig.update_yaxes(title_text="Company Valuation ($)", row=2, col=1, range=[0, max(exit_values) * 1.15])
263
-
264
- comparison_chart = fig
265
- else:
266
- comparison_chart = None
267
-
268
- # Create detailed breakdown chart for the highest scenario
269
- if scenarios:
270
- # Find the scenario with highest option value for detailed analysis
271
- best_scenario = max(scenarios, key=lambda x: x['option_value'])
272
-
273
- # Create a detailed waterfall for this scenario
274
- detailed_fig = calculate_single_scenario_waterfall(
275
- best_scenario['exit_valuation'], total_shares, your_options, strike_price,
276
- seed_shares, seed_capital, seed_multiple, seed_participating,
277
- series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
278
- series_b_shares, series_b_capital, series_b_multiple, series_b_participating
279
- )
280
-
281
- detailed_chart = detailed_fig
282
- else:
283
- detailed_chart = None
284
-
285
- # Create ROI comparison
286
- if scenarios and your_options > 0:
287
- roi_data = []
288
- investment_cost = your_options * strike_price
289
-
290
- for scenario in scenarios:
291
- if investment_cost > 0:
292
- roi = ((scenario['option_value'] - investment_cost) / investment_cost) * 100
293
- else:
294
- roi = float('inf') if scenario['option_value'] > 0 else 0
295
-
296
- roi_data.append({
297
- 'scenario': scenario['name'],
298
- 'roi': roi if roi != float('inf') else 999999, # Cap at very high number for display
299
- 'absolute_gain': scenario['option_value'] - investment_cost
300
- })
301
-
302
- roi_fig = go.Figure()
303
- roi_fig.add_trace(go.Bar(
304
- x=[d['scenario'] for d in roi_data],
305
- y=[d['roi'] for d in roi_data],
306
- name="ROI %",
307
- marker_color='#28A745',
308
- text=[f"{d['roi']:.0f}%" if d['roi'] < 999999 else "∞%" for d in roi_data],
309
- textposition='outside'
310
- ))
311
-
312
- roi_fig.update_layout(
313
- title="Return on Investment (ROI) by Scenario",
314
- xaxis_title="Scenario",
315
- yaxis_title="ROI (%)",
316
- height=450,
317
- margin=dict(t=60, b=50, l=60, r=50)
318
- )
319
-
320
- roi_chart = roi_fig
321
- else:
322
- roi_chart = None
323
-
324
- return results, comparison_chart, detailed_chart, roi_chart
325
-
326
- def calculate_single_scenario_waterfall(
327
- exit_valuation, total_shares, your_options, strike_price,
328
- seed_shares, seed_capital, seed_multiple, seed_participating,
329
- series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
330
- series_b_shares, series_b_capital, series_b_multiple, series_b_participating
331
- ):
332
- """Create detailed waterfall chart for a single scenario"""
333
-
334
- common_shares = total_shares - (seed_shares + series_a_shares + series_b_shares)
335
- remaining_proceeds = exit_valuation
336
- waterfall_data = []
337
- participating_shareholders = []
338
-
339
- # Phase 1: Liquidation preferences
340
- if series_b_shares > 0 and series_b_capital > 0:
341
- series_b_preference = series_b_capital * series_b_multiple
342
- series_b_payout = min(remaining_proceeds, series_b_preference)
343
- remaining_proceeds -= series_b_payout
344
-
345
- if series_b_participating:
346
- participating_shareholders.append({'round': 'Series B', 'shares': series_b_shares})
347
-
348
- waterfall_data.append({
349
- 'Round': 'Series B (Pref)',
350
- 'Payout': series_b_payout,
351
- 'Type': 'Preference'
352
- })
353
-
354
- if series_a_shares > 0 and series_a_capital > 0:
355
- series_a_preference = series_a_capital * series_a_multiple
356
- series_a_payout = min(remaining_proceeds, series_a_preference)
357
- remaining_proceeds -= series_a_payout
358
-
359
- if series_a_participating:
360
- participating_shareholders.append({'round': 'Series A', 'shares': series_a_shares})
361
-
362
- waterfall_data.append({
363
- 'Round': 'Series A (Pref)',
364
- 'Payout': series_a_payout,
365
- 'Type': 'Preference'
366
- })
367
-
368
- if seed_shares > 0 and seed_capital > 0:
369
- seed_preference = seed_capital * seed_multiple
370
- seed_payout = min(remaining_proceeds, seed_preference)
371
- remaining_proceeds -= seed_payout
372
-
373
- if seed_participating:
374
- participating_shareholders.append({'round': 'Seed', 'shares': seed_shares})
375
-
376
- waterfall_data.append({
377
- 'Round': 'Seed (Pref)',
378
- 'Payout': seed_payout,
379
- 'Type': 'Preference'
380
- })
381
-
382
- # Phase 2: Check conversions and final distribution
383
- participating_preferred_shares = sum([p['shares'] for p in participating_shareholders])
384
- total_participating_shares = common_shares + participating_preferred_shares
385
-
386
- # Simplified conversion logic for visualization
387
- if total_participating_shares > 0:
388
- price_per_share = remaining_proceeds / total_participating_shares
389
- common_proceeds = price_per_share * common_shares
390
-
391
- # Add participating preferred distributions
392
- for participant in participating_shareholders:
393
- participating_payout = price_per_share * participant['shares']
394
- waterfall_data.append({
395
- 'Round': f"{participant['round']} (Part.)",
396
- 'Payout': participating_payout,
397
- 'Type': 'Participation'
398
- })
399
- else:
400
- common_proceeds = remaining_proceeds
401
-
402
- # Add common stock
403
- waterfall_data.append({
404
- 'Round': 'Common Stock',
405
- 'Payout': common_proceeds,
406
- 'Type': 'Common'
407
- })
408
-
409
- # Create the chart
410
- fig = go.Figure()
411
-
412
- color_map = {
413
- 'Preference': '#FF6B6B',
414
- 'Participation': '#4ECDC4',
415
- 'Common': '#F7DC6F'
416
- }
417
-
418
- for item in waterfall_data:
419
- if item['Payout'] > 0:
420
- color = color_map.get(item['Type'], '#96CEB4')
421
- fig.add_trace(go.Bar(
422
- x=[item['Round']],
423
- y=[item['Payout']],
424
- name=f"{item['Round']} (${item['Payout']:,.0f})",
425
- marker_color=color,
426
- text=f"${item['Payout']:,.0f}",
427
- textposition='outside'
428
- ))
429
-
430
- fig.update_layout(
431
- title=f"Liquidation Waterfall - ${exit_valuation:,.0f} Exit",
432
- xaxis_title="Stakeholder",
433
- yaxis_title="Payout ($)",
434
- height=450,
435
- showlegend=True,
436
- margin=dict(t=60, b=50, l=80, r=50)
437
- )
438
-
439
- return fig
440
-
441
- # Create Gradio interface
442
- with gr.Blocks(title="Startup Equity Calculator", theme=gr.themes.Soft()) as app:
443
- gr.Markdown("# 🚀 Startup Equity Calculator")
444
- gr.Markdown("Calculate the value of your stock options based on cap table structure and liquidation preferences")
445
-
446
- with gr.Row():
447
- with gr.Column():
448
- gr.Markdown("## Cap Table Structure")
449
- total_shares = gr.Number(label="Total Fully Diluted Shares", value=10000000, precision=0)
450
- your_options = gr.Number(label="Your Option Grant", value=0, precision=0)
451
- strike_price = gr.Number(label="Strike Price per Share ($)", value=0.10, precision=4)
452
-
453
- gr.Markdown("## Funding Rounds")
454
-
455
- with gr.Accordion("Seed Round", open=False):
456
- seed_shares = gr.Number(label="Seed Shares Issued", value=0, precision=0)
457
- seed_capital = gr.Number(label="Seed Capital Raised ($)", value=0, precision=0)
458
- seed_multiple = gr.Number(label="Liquidation Multiple", value=1.0, precision=1)
459
- seed_participating = gr.Checkbox(label="Participating Preferred", value=False)
460
-
461
- with gr.Accordion("Series A", open=False):
462
- series_a_shares = gr.Number(label="Series A Shares Issued", value=0, precision=0)
463
- series_a_capital = gr.Number(label="Series A Capital Raised ($)", value=0, precision=0)
464
- series_a_multiple = gr.Number(label="Liquidation Multiple", value=1.0, precision=1)
465
- series_a_participating = gr.Checkbox(label="Participating Preferred", value=False)
466
-
467
- with gr.Accordion("Series B", open=False):
468
- series_b_shares = gr.Number(label="Series B Shares Issued", value=0, precision=0)
469
- series_b_capital = gr.Number(label="Series B Capital Raised ($)", value=0, precision=0)
470
- series_b_multiple = gr.Number(label="Liquidation Multiple", value=1.0, precision=1)
471
- series_b_participating = gr.Checkbox(label="Participating Preferred", value=False)
472
-
473
- with gr.Column():
474
- gr.Markdown("## Exit Scenarios")
475
- gr.Markdown("*Define multiple exit scenarios to compare side-by-side*")
476
-
477
- with gr.Row():
478
- with gr.Column():
479
- scenario_1_name = gr.Textbox(label="Scenario 1 Name", value="Conservative", placeholder="e.g., Conservative")
480
- exit_scenario_1 = gr.Number(label="Exit Valuation ($)", value=25000000, precision=0)
481
-
482
- with gr.Column():
483
- scenario_2_name = gr.Textbox(label="Scenario 2 Name", value="Base Case", placeholder="e.g., Base Case")
484
- exit_scenario_2 = gr.Number(label="Exit Valuation ($)", value=50000000, precision=0)
485
-
486
- with gr.Row():
487
- with gr.Column():
488
- scenario_3_name = gr.Textbox(label="Scenario 3 Name", value="Optimistic", placeholder="e.g., Optimistic")
489
- exit_scenario_3 = gr.Number(label="Exit Valuation ($)", value=100000000, precision=0)
490
-
491
- with gr.Column():
492
- scenario_4_name = gr.Textbox(label="Scenario 4 Name", value="", placeholder="e.g., Moon Shot")
493
- exit_scenario_4 = gr.Number(label="Exit Valuation ($)", value=0, precision=0)
494
-
495
- with gr.Row():
496
- with gr.Column():
497
- scenario_5_name = gr.Textbox(label="Scenario 5 Name", value="", placeholder="e.g., IPO")
498
- exit_scenario_5 = gr.Number(label="Exit Valuation ($)", value=0, precision=0)
499
-
500
- calculate_btn = gr.Button("🚀 Calculate All Scenarios", variant="primary", size="lg")
501
-
502
- results_text = gr.Markdown()
503
-
504
- with gr.Row():
505
- comparison_plot = gr.Plot(label="Multi-Scenario Comparison")
506
-
507
- with gr.Row():
508
- waterfall_plot = gr.Plot(label="Detailed Waterfall (Best Scenario)")
509
- roi_plot = gr.Plot(label="Return on Investment")
510
-
511
- # Set up the calculation trigger
512
- inputs = [
513
- total_shares, your_options, strike_price,
514
- seed_shares, seed_capital, seed_multiple, seed_participating,
515
- series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
516
- series_b_shares, series_b_capital, series_b_multiple, series_b_participating,
517
- exit_scenario_1, scenario_1_name,
518
- exit_scenario_2, scenario_2_name,
519
- exit_scenario_3, scenario_3_name,
520
- exit_scenario_4, scenario_4_name,
521
- exit_scenario_5, scenario_5_name
522
- ]
523
-
524
- outputs = [results_text, comparison_plot, waterfall_plot, roi_plot]
525
-
526
- calculate_btn.click(calculate_equity_value, inputs=inputs, outputs=outputs)
527
-
528
- # Auto-calculate on input changes
529
- for input_component in inputs:
530
- input_component.change(calculate_equity_value, inputs=inputs, outputs=outputs)
531
-
532
- gr.Markdown("""
533
- ## 📚 How to Use This Calculator
534
-
535
- ### 🎯 Multi-Scenario Analysis
536
- **This is where the real value lies!** Instead of guessing one exit value, define multiple realistic scenarios:
537
- - **Conservative**: What if growth is slower than expected?
538
- - **Base Case**: Most likely scenario based on current trajectory
539
- - **Optimistic**: If everything goes right
540
- - **Moon Shot**: Best case scenario (10x+ returns)
541
-
542
- ### 📊 Key Outputs
543
- 1. **Comparison Table**: Side-by-side option values across all scenarios
544
- 2. **Visual Charts**: See how your returns scale with different exits
545
- 3. **ROI Analysis**: Understand your return on investment potential
546
- 4. **Detailed Waterfall**: How liquidation preferences affect distributions
547
-
548
- ### 💡 Decision Framework
549
- Use this to evaluate:
550
- - **Risk vs Reward**: How much upside vs downside?
551
- - **Opportunity Cost**: Compare to other job offers or investments
552
- - **Negotiation Power**: Understanding your equity's potential value range
553
-
554
- ### 🔧 Liquidation Preferences
555
- - **Non-Participating**: Investors choose preference OR convert to common (better for employees)
556
- - **Participating**: Investors get preference AND share upside (worse for employees)
557
- - **Multiples**: How many times their investment investors get back first
558
-
559
- **Pro Tip**: Try toggling participating preferred on/off to see the dramatic impact on your equity value!
560
- """)
561
-
562
- if __name__ == "__main__":
563
- app.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
interface.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gradio interface components for the equity calculator
3
+ """
4
+ import gradio as gr
5
+ from typing import Tuple, List, Optional
6
+ from models import create_cap_table, EquityCalculator, ExitScenario
7
+ from charts import EquityCharts, format_equity_summary
8
+
9
+
10
+ def create_cap_table_inputs():
11
+ """Create the cap table input components"""
12
+ with gr.Column():
13
+ gr.Markdown("## Cap Table Structure")
14
+ total_shares = gr.Number(label="Total Fully Diluted Shares", value=10000000, precision=0)
15
+ your_options = gr.Number(label="Your Option Grant", value=0, precision=0)
16
+ strike_price = gr.Number(label="Strike Price per Share ($)", value=0.10, precision=4)
17
+
18
+ gr.Markdown("## Funding Rounds")
19
+
20
+ with gr.Accordion("Seed Round", open=False):
21
+ seed_shares = gr.Number(label="Seed Shares Issued", value=0, precision=0)
22
+ seed_capital = gr.Number(label="Seed Capital Raised ($)", value=0, precision=0)
23
+ seed_multiple = gr.Number(label="Liquidation Multiple", value=1.0, precision=1)
24
+ seed_participating = gr.Checkbox(label="Participating Preferred", value=False)
25
+
26
+ with gr.Accordion("Series A", open=False):
27
+ series_a_shares = gr.Number(label="Series A Shares Issued", value=0, precision=0)
28
+ series_a_capital = gr.Number(label="Series A Capital Raised ($)", value=0, precision=0)
29
+ series_a_multiple = gr.Number(label="Liquidation Multiple", value=1.0, precision=1)
30
+ series_a_participating = gr.Checkbox(label="Participating Preferred", value=False)
31
+
32
+ with gr.Accordion("Series B", open=False):
33
+ series_b_shares = gr.Number(label="Series B Shares Issued", value=0, precision=0)
34
+ series_b_capital = gr.Number(label="Series B Capital Raised ($)", value=0, precision=0)
35
+ series_b_multiple = gr.Number(label="Liquidation Multiple", value=1.0, precision=1)
36
+ series_b_participating = gr.Checkbox(label="Participating Preferred", value=False)
37
+
38
+ return [
39
+ total_shares, your_options, strike_price,
40
+ seed_shares, seed_capital, seed_multiple, seed_participating,
41
+ series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
42
+ series_b_shares, series_b_capital, series_b_multiple, series_b_participating
43
+ ]
44
+
45
+
46
+ def create_scenario_inputs():
47
+ """Create the exit scenario input components"""
48
+ with gr.Column():
49
+ gr.Markdown("## Exit Scenarios")
50
+ gr.Markdown("*Define multiple exit scenarios to compare side-by-side*")
51
+
52
+ with gr.Row():
53
+ with gr.Column():
54
+ scenario_1_name = gr.Textbox(label="Scenario 1 Name", value="Conservative", placeholder="e.g., Conservative")
55
+ exit_scenario_1 = gr.Number(label="Exit Valuation ($)", value=25000000, precision=0)
56
+
57
+ with gr.Column():
58
+ scenario_2_name = gr.Textbox(label="Scenario 2 Name", value="Base Case", placeholder="e.g., Base Case")
59
+ exit_scenario_2 = gr.Number(label="Exit Valuation ($)", value=50000000, precision=0)
60
+
61
+ with gr.Row():
62
+ with gr.Column():
63
+ scenario_3_name = gr.Textbox(label="Scenario 3 Name", value="Optimistic", placeholder="e.g., Optimistic")
64
+ exit_scenario_3 = gr.Number(label="Exit Valuation ($)", value=100000000, precision=0)
65
+
66
+ with gr.Column():
67
+ scenario_4_name = gr.Textbox(label="Scenario 4 Name", value="", placeholder="e.g., Moon Shot")
68
+ exit_scenario_4 = gr.Number(label="Exit Valuation ($)", value=0, precision=0)
69
+
70
+ with gr.Row():
71
+ with gr.Column():
72
+ scenario_5_name = gr.Textbox(label="Scenario 5 Name", value="", placeholder="e.g., IPO")
73
+ exit_scenario_5 = gr.Number(label="Exit Valuation ($)", value=0, precision=0)
74
+
75
+ calculate_btn = gr.Button("🚀 Calculate All Scenarios", variant="primary", size="lg")
76
+ results_text = gr.Markdown()
77
+
78
+ return [
79
+ exit_scenario_1, scenario_1_name,
80
+ exit_scenario_2, scenario_2_name,
81
+ exit_scenario_3, scenario_3_name,
82
+ exit_scenario_4, scenario_4_name,
83
+ exit_scenario_5, scenario_5_name,
84
+ calculate_btn, results_text
85
+ ]
86
+
87
+
88
+ def create_output_components():
89
+ """Create the output chart components"""
90
+ with gr.Row():
91
+ comparison_plot = gr.Plot(label="Multi-Scenario Comparison")
92
+
93
+ with gr.Row():
94
+ waterfall_plot = gr.Plot(label="Detailed Waterfall (Best Scenario)")
95
+ roi_plot = gr.Plot(label="Return on Investment")
96
+
97
+ return [comparison_plot, waterfall_plot, roi_plot]
98
+
99
+
100
+ def process_inputs(
101
+ # Cap table inputs
102
+ total_shares, your_options, strike_price,
103
+ seed_shares, seed_capital, seed_multiple, seed_participating,
104
+ series_a_shares, series_a_capital, series_a_multiple, series_a_participating,
105
+ series_b_shares, series_b_capital, series_b_multiple, series_b_participating,
106
+ # Scenario inputs
107
+ exit_scenario_1, scenario_1_name,
108
+ exit_scenario_2, scenario_2_name,
109
+ exit_scenario_3, scenario_3_name,
110
+ exit_scenario_4, scenario_4_name,
111
+ exit_scenario_5, scenario_5_name
112
+ ) -> Tuple[str, Optional[gr.Plot], Optional[gr.Plot], Optional[gr.Plot]]:
113
+ """Process all inputs and return formatted results and charts"""
114
+
115
+ # Handle None values with defaults
116
+ total_shares = total_shares or 10000000
117
+ your_options = your_options or 0
118
+ strike_price = strike_price or 0.10
119
+
120
+ # Validate inputs
121
+ if total_shares <= 0:
122
+ return "Invalid inputs - please check your values", None, None, None
123
+
124
+ # Create cap table
125
+ try:
126
+ cap_table = create_cap_table(
127
+ total_shares=total_shares,
128
+ your_options=your_options,
129
+ strike_price=strike_price,
130
+ seed_shares=seed_shares or 0,
131
+ seed_capital=seed_capital or 0,
132
+ seed_multiple=seed_multiple or 1.0,
133
+ seed_participating=seed_participating or False,
134
+ series_a_shares=series_a_shares or 0,
135
+ series_a_capital=series_a_capital or 0,
136
+ series_a_multiple=series_a_multiple or 1.0,
137
+ series_a_participating=series_a_participating or False,
138
+ series_b_shares=series_b_shares or 0,
139
+ series_b_capital=series_b_capital or 0,
140
+ series_b_multiple=series_b_multiple or 1.0,
141
+ series_b_participating=series_b_participating or False
142
+ )
143
+ except Exception as e:
144
+ return f"Error creating cap table: {str(e)}", None, None, None
145
+
146
+ # Create scenarios
147
+ scenarios = []
148
+ scenario_data = [
149
+ (exit_scenario_1 or 0, scenario_1_name or "Scenario 1"),
150
+ (exit_scenario_2 or 0, scenario_2_name or "Scenario 2"),
151
+ (exit_scenario_3 or 0, scenario_3_name or "Scenario 3"),
152
+ (exit_scenario_4 or 0, scenario_4_name or "Scenario 4"),
153
+ (exit_scenario_5 or 0, scenario_5_name or "Scenario 5")
154
+ ]
155
+
156
+ for exit_val, name in scenario_data:
157
+ if exit_val > 0:
158
+ scenarios.append(ExitScenario(name=name, exit_valuation=exit_val))
159
+
160
+ if not scenarios:
161
+ return "Please enter at least one exit scenario with a positive value", None, None, None
162
+
163
+ # Calculate results
164
+ calculator = EquityCalculator(cap_table)
165
+ try:
166
+ results = calculator.calculate_multiple_scenarios(scenarios)
167
+ summary = calculator.get_liquidation_summary()
168
+ except Exception as e:
169
+ return f"Error calculating results: {str(e)}", None, None, None
170
+
171
+ if not results:
172
+ return "No valid scenarios to calculate", None, None, None
173
+
174
+ # Generate charts
175
+ charts = EquityCharts()
176
+
177
+ try:
178
+ # Multi-scenario comparison
179
+ comparison_chart = charts.create_multi_scenario_comparison(results)
180
+
181
+ # Detailed waterfall for best scenario
182
+ best_result = max(results, key=lambda x: x.option_value)
183
+ waterfall_chart = charts.create_liquidation_waterfall(
184
+ cap_table,
185
+ best_result.exit_valuation,
186
+ best_result.scenario_name
187
+ )
188
+
189
+ # ROI analysis
190
+ investment_cost = cap_table.your_options * cap_table.strike_price
191
+ roi_chart = charts.create_roi_analysis(results, investment_cost)
192
+
193
+ except Exception as e:
194
+ return f"Error generating charts: {str(e)}", None, None, None
195
+
196
+ # Format summary
197
+ try:
198
+ summary_text = format_equity_summary(summary, results)
199
+ except Exception as e:
200
+ return f"Error formatting summary: {str(e)}", comparison_chart, waterfall_chart, roi_chart
201
+
202
+ return summary_text, comparison_chart, waterfall_chart, roi_chart
203
+
204
+
205
+ def create_help_section():
206
+ """Create the help/documentation section"""
207
+ gr.Markdown("""
208
+ ## 📚 How to Use This Calculator
209
+
210
+ ### 🎯 Multi-Scenario Analysis
211
+ **This is where the real value lies!** Instead of guessing one exit value, define multiple realistic scenarios:
212
+ - **Conservative**: What if growth is slower than expected?
213
+ - **Base Case**: Most likely scenario based on current trajectory
214
+ - **Optimistic**: If everything goes right
215
+ - **Moon Shot**: Best case scenario (10x+ returns)
216
+
217
+ ### 📊 Key Outputs
218
+ 1. **Comparison Table**: Side-by-side option values across all scenarios
219
+ 2. **Visual Charts**: See how your returns scale with different exits
220
+ 3. **ROI Analysis**: Understand your return on investment potential
221
+ 4. **Detailed Waterfall**: How liquidation preferences affect distributions
222
+
223
+ ### 💡 Decision Framework
224
+ Use this to evaluate:
225
+ - **Risk vs Reward**: How much upside vs downside?
226
+ - **Opportunity Cost**: Compare to other job offers or investments
227
+ - **Negotiation Power**: Understanding your equity's potential value range
228
+
229
+ ### 🔧 Liquidation Preferences
230
+ - **Non-Participating**: Investors choose preference OR convert to common (better for employees)
231
+ - **Participating**: Investors get preference AND share upside (worse for employees)
232
+ - **Multiples**: How many times their investment investors get back first
233
+
234
+ **Pro Tip**: Try toggling participating preferred on/off to see the dramatic impact on your equity value!
235
+ """)
main.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Main application file - Startup Equity Calculator
3
+ Entry point for the Gradio web application
4
+ """
5
+ import gradio as gr
6
+ from interface import (
7
+ create_cap_table_inputs,
8
+ create_scenario_inputs,
9
+ create_output_components,
10
+ process_inputs,
11
+ create_help_section
12
+ )
13
+
14
+
15
+ def create_app():
16
+ """Create and configure the Gradio application"""
17
+
18
+ with gr.Blocks(title="Startup Equity Calculator", theme=gr.themes.Soft()) as app:
19
+
20
+ # Header
21
+ gr.Markdown("# 🚀 Startup Equity Calculator")
22
+ gr.Markdown("Calculate the value of your stock options based on cap table structure and liquidation preferences")
23
+
24
+ # Main interface
25
+ with gr.Row():
26
+ # Left column: Cap table inputs
27
+ cap_table_components = create_cap_table_inputs()
28
+
29
+ # Right column: Scenario inputs and results
30
+ scenario_components = create_scenario_inputs()
31
+
32
+ # Output charts
33
+ output_components = create_output_components()
34
+
35
+ # Extract components for event handling
36
+ calculate_btn = scenario_components[-2] # Second to last component
37
+ results_text = scenario_components[-1] # Last component
38
+
39
+ # All input components
40
+ all_inputs = cap_table_components + scenario_components[:-2] # Exclude button and results
41
+
42
+ # All output components
43
+ all_outputs = [results_text] + output_components
44
+
45
+ # Set up event handlers
46
+ calculate_btn.click(
47
+ process_inputs,
48
+ inputs=all_inputs,
49
+ outputs=all_outputs
50
+ )
51
+
52
+ # Auto-calculate on input changes (optional - can be enabled/disabled)
53
+ for input_component in cap_table_components:
54
+ input_component.change(
55
+ process_inputs,
56
+ inputs=all_inputs,
57
+ outputs=all_outputs
58
+ )
59
+
60
+ # Help section
61
+ create_help_section()
62
+
63
+ return app
64
+
65
+
66
+ def main():
67
+ """Main function to launch the application"""
68
+ print("🚀 Starting Startup Equity Calculator...")
69
+ print("📊 Loading models and interface...")
70
+
71
+ try:
72
+ app = create_app()
73
+ print("✅ Application created successfully!")
74
+ print("🌐 Launching web interface...")
75
+
76
+ # Launch with custom settings
77
+ app.launch(
78
+ server_name="0.0.0.0", # Allow external access
79
+ server_port=7860, # Default Gradio port
80
+ share=False, # Set to True to create public link
81
+ debug=False, # Set to True for development
82
+ show_error=True # Show detailed errors
83
+ )
84
+
85
+ except ImportError as e:
86
+ print(f"❌ Import Error: {e}")
87
+ print("Make sure all required modules (models.py, charts.py, interface.py) are in the same directory")
88
+
89
+ except Exception as e:
90
+ print(f"❌ Error launching application: {e}")
91
+ print("Check that all dependencies are installed: gradio, plotly, pandas")
92
+
93
+
94
+ if __name__ == "__main__":
95
+ main()
models.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Data models and core equity calculation logic
3
+ """
4
+ from dataclasses import dataclass
5
+ from typing import List, Optional, Dict, Any
6
+
7
+
8
+ @dataclass
9
+ class FundingRound:
10
+ """Represents a funding round (Seed, Series A, etc.)"""
11
+ name: str
12
+ shares_issued: int
13
+ capital_raised: float
14
+ liquidation_multiple: float
15
+ is_participating: bool
16
+
17
+ @property
18
+ def liquidation_preference(self) -> float:
19
+ """Total liquidation preference for this round"""
20
+ return self.capital_raised * self.liquidation_multiple
21
+
22
+
23
+ @dataclass
24
+ class CapTable:
25
+ """Represents the company's capitalization table"""
26
+ total_shares: int
27
+ your_options: int
28
+ strike_price: float
29
+ funding_rounds: List[FundingRound]
30
+
31
+ @property
32
+ def total_preferred_shares(self) -> int:
33
+ """Total preferred shares across all rounds"""
34
+ return sum(round.shares_issued for round in self.funding_rounds)
35
+
36
+ @property
37
+ def common_shares(self) -> int:
38
+ """Total common shares available"""
39
+ return self.total_shares - self.total_preferred_shares
40
+
41
+ @property
42
+ def your_equity_percentage(self) -> float:
43
+ """Your equity percentage of total company"""
44
+ return (self.your_options / self.total_shares) * 100 if self.total_shares > 0 else 0
45
+
46
+
47
+ @dataclass
48
+ class ExitScenario:
49
+ """Represents an exit scenario with name and valuation"""
50
+ name: str
51
+ exit_valuation: float
52
+
53
+
54
+ @dataclass
55
+ class ScenarioResult:
56
+ """Result of calculating equity value for one exit scenario"""
57
+ scenario_name: str
58
+ exit_valuation: float
59
+ option_value: float
60
+ price_per_share: float
61
+ common_proceeds: float
62
+ error: Optional[str] = None
63
+
64
+ @property
65
+ def value_per_option(self) -> float:
66
+ """Value per individual option"""
67
+ return self.price_per_share
68
+
69
+ def roi_percentage(self, investment_cost: float) -> float:
70
+ """Calculate ROI percentage"""
71
+ if investment_cost <= 0:
72
+ return float('inf') if self.option_value > 0 else 0
73
+ return ((self.option_value - investment_cost) / investment_cost) * 100
74
+
75
+
76
+ class EquityCalculator:
77
+ """Core equity calculation engine"""
78
+
79
+ def __init__(self, cap_table: CapTable):
80
+ self.cap_table = cap_table
81
+
82
+ def calculate_scenario(self, exit_scenario: ExitScenario) -> ScenarioResult:
83
+ """Calculate equity value for a single exit scenario"""
84
+
85
+ if self.cap_table.common_shares <= 0:
86
+ return ScenarioResult(
87
+ scenario_name=exit_scenario.name,
88
+ exit_valuation=exit_scenario.exit_valuation,
89
+ option_value=0,
90
+ price_per_share=0,
91
+ common_proceeds=0,
92
+ error='Preferred shares exceed total shares'
93
+ )
94
+
95
+ # Phase 1: Pay liquidation preferences (newest rounds first)
96
+ remaining_proceeds = exit_scenario.exit_valuation
97
+ participating_shareholders = []
98
+
99
+ # Sort funding rounds by reverse order (newest first)
100
+ sorted_rounds = sorted(self.cap_table.funding_rounds,
101
+ key=lambda x: ['Seed', 'Series A', 'Series B', 'Series C'].index(x.name)
102
+ if x.name in ['Seed', 'Series A', 'Series B', 'Series C'] else 999,
103
+ reverse=True)
104
+
105
+ preference_payouts = {}
106
+
107
+ for round in sorted_rounds:
108
+ if round.shares_issued > 0 and round.capital_raised > 0:
109
+ preference_payout = min(remaining_proceeds, round.liquidation_preference)
110
+ remaining_proceeds -= preference_payout
111
+ preference_payouts[round.name] = preference_payout
112
+
113
+ if round.is_participating:
114
+ participating_shareholders.append({
115
+ 'round': round.name,
116
+ 'shares': round.shares_issued
117
+ })
118
+
119
+ # Phase 2: Handle non-participating conversions
120
+ participating_preferred_shares = sum(p['shares'] for p in participating_shareholders)
121
+ total_participating_shares = self.cap_table.common_shares + participating_preferred_shares
122
+
123
+ # Check if non-participating preferred should convert
124
+ for round in sorted_rounds:
125
+ if (round.shares_issued > 0 and round.capital_raised > 0
126
+ and not round.is_participating):
127
+
128
+ # Calculate conversion value vs preference value
129
+ conversion_value = (round.shares_issued / self.cap_table.total_shares) * exit_scenario.exit_valuation
130
+ preference_value = preference_payouts.get(round.name, 0)
131
+
132
+ if conversion_value > preference_value:
133
+ # They convert - add back their preference and include in common distribution
134
+ remaining_proceeds += preference_value
135
+ total_participating_shares += round.shares_issued
136
+
137
+ # Phase 3: Final distribution to common + participating preferred
138
+ if total_participating_shares > 0:
139
+ price_per_participating_share = remaining_proceeds / total_participating_shares
140
+ common_proceeds = price_per_participating_share * self.cap_table.common_shares
141
+ else:
142
+ common_proceeds = remaining_proceeds
143
+
144
+ # Calculate option value
145
+ price_per_common_share = common_proceeds / self.cap_table.common_shares if self.cap_table.common_shares > 0 else 0
146
+ option_value_per_share = max(0, price_per_common_share - self.cap_table.strike_price)
147
+ total_option_value = option_value_per_share * self.cap_table.your_options
148
+
149
+ return ScenarioResult(
150
+ scenario_name=exit_scenario.name,
151
+ exit_valuation=exit_scenario.exit_valuation,
152
+ option_value=total_option_value,
153
+ price_per_share=price_per_common_share,
154
+ common_proceeds=common_proceeds
155
+ )
156
+
157
+ def calculate_multiple_scenarios(self, scenarios: List[ExitScenario]) -> List[ScenarioResult]:
158
+ """Calculate equity value for multiple exit scenarios"""
159
+ results = []
160
+ for scenario in scenarios:
161
+ if scenario.exit_valuation > 0: # Only calculate positive exit values
162
+ result = self.calculate_scenario(scenario)
163
+ results.append(result)
164
+ return results
165
+
166
+ def get_liquidation_summary(self) -> Dict[str, Any]:
167
+ """Get summary of liquidation terms"""
168
+ participating_status = []
169
+ for round in self.cap_table.funding_rounds:
170
+ if round.shares_issued > 0:
171
+ status = 'Participating' if round.is_participating else 'Non-Participating'
172
+ participating_status.append(f"{round.name}: {status}")
173
+
174
+ return {
175
+ 'total_shares': self.cap_table.total_shares,
176
+ 'common_shares': self.cap_table.common_shares,
177
+ 'preferred_shares': self.cap_table.total_preferred_shares,
178
+ 'your_options': self.cap_table.your_options,
179
+ 'your_equity_percentage': self.cap_table.your_equity_percentage,
180
+ 'strike_price': self.cap_table.strike_price,
181
+ 'participating_status': participating_status,
182
+ 'break_even_price': self.cap_table.strike_price
183
+ }
184
+
185
+
186
+ def create_cap_table(
187
+ total_shares: int, your_options: int, strike_price: float,
188
+ seed_shares: int = 0, seed_capital: float = 0, seed_multiple: float = 1.0, seed_participating: bool = False,
189
+ series_a_shares: int = 0, series_a_capital: float = 0, series_a_multiple: float = 1.0, series_a_participating: bool = False,
190
+ series_b_shares: int = 0, series_b_capital: float = 0, series_b_multiple: float = 1.0, series_b_participating: bool = False
191
+ ) -> CapTable:
192
+ """Factory function to create a CapTable from individual parameters"""
193
+
194
+ funding_rounds = []
195
+
196
+ if seed_shares > 0 or seed_capital > 0:
197
+ funding_rounds.append(FundingRound(
198
+ name='Seed',
199
+ shares_issued=seed_shares,
200
+ capital_raised=seed_capital,
201
+ liquidation_multiple=seed_multiple,
202
+ is_participating=seed_participating
203
+ ))
204
+
205
+ if series_a_shares > 0 or series_a_capital > 0:
206
+ funding_rounds.append(FundingRound(
207
+ name='Series A',
208
+ shares_issued=series_a_shares,
209
+ capital_raised=series_a_capital,
210
+ liquidation_multiple=series_a_multiple,
211
+ is_participating=series_a_participating
212
+ ))
213
+
214
+ if series_b_shares > 0 or series_b_capital > 0:
215
+ funding_rounds.append(FundingRound(
216
+ name='Series B',
217
+ shares_issued=series_b_shares,
218
+ capital_raised=series_b_capital,
219
+ liquidation_multiple=series_b_multiple,
220
+ is_participating=series_b_participating
221
+ ))
222
+
223
+ return CapTable(
224
+ total_shares=total_shares,
225
+ your_options=your_options,
226
+ strike_price=strike_price,
227
+ funding_rounds=funding_rounds
228
+ )