Spaces:
Sleeping
Sleeping
| """ | |
| Data models and core equity calculation logic | |
| """ | |
| from dataclasses import dataclass | |
| from typing import List, Optional, Dict, Any | |
| class FundingRound: | |
| """Represents a funding round (Seed, Series A, etc.)""" | |
| name: str | |
| shares_issued: int | |
| capital_raised: float | |
| liquidation_multiple: float | |
| is_participating: bool | |
| def liquidation_preference(self) -> float: | |
| """Total liquidation preference for this round""" | |
| return self.capital_raised * self.liquidation_multiple | |
| class CapTable: | |
| """Represents the company's capitalization table""" | |
| total_shares: int | |
| your_options: int | |
| strike_price: float | |
| funding_rounds: List[FundingRound] | |
| def total_preferred_shares(self) -> int: | |
| """Total preferred shares across all rounds""" | |
| return sum(round.shares_issued for round in self.funding_rounds) | |
| def common_shares(self) -> int: | |
| """Total common shares available""" | |
| return self.total_shares - self.total_preferred_shares | |
| def your_equity_percentage(self) -> float: | |
| """Your equity percentage of total company""" | |
| return (self.your_options / self.total_shares) * 100 if self.total_shares > 0 else 0 | |
| class ExitScenario: | |
| """Represents an exit scenario with name and valuation""" | |
| name: str | |
| exit_valuation: float | |
| class ScenarioResult: | |
| """Result of calculating equity value for one exit scenario""" | |
| scenario_name: str | |
| exit_valuation: float | |
| option_value: float | |
| price_per_share: float | |
| common_proceeds: float | |
| error: Optional[str] = None | |
| def value_per_option(self) -> float: | |
| """Value per individual option""" | |
| return self.price_per_share | |
| def roi_percentage(self, investment_cost: float) -> float: | |
| """Calculate ROI percentage""" | |
| if investment_cost <= 0: | |
| return float('inf') if self.option_value > 0 else 0 | |
| return ((self.option_value - investment_cost) / investment_cost) * 100 | |
| class EquityCalculator: | |
| """Core equity calculation engine""" | |
| def __init__(self, cap_table: CapTable): | |
| self.cap_table = cap_table | |
| def calculate_scenario(self, exit_scenario: ExitScenario) -> ScenarioResult: | |
| """Calculate equity value for a single exit scenario""" | |
| if self.cap_table.common_shares <= 0: | |
| return ScenarioResult( | |
| scenario_name=exit_scenario.name, | |
| exit_valuation=exit_scenario.exit_valuation, | |
| option_value=0, | |
| price_per_share=0, | |
| common_proceeds=0, | |
| error='Preferred shares exceed total shares' | |
| ) | |
| # Phase 1: Pay liquidation preferences (newest rounds first) | |
| remaining_proceeds = exit_scenario.exit_valuation | |
| participating_shareholders = [] | |
| # Sort funding rounds by reverse order (newest first) | |
| sorted_rounds = sorted(self.cap_table.funding_rounds, | |
| key=lambda x: ['Seed', 'Series A', 'Series B', 'Series C'].index(x.name) | |
| if x.name in ['Seed', 'Series A', 'Series B', 'Series C'] else 999, | |
| reverse=True) | |
| preference_payouts = {} | |
| for round in sorted_rounds: | |
| if round.shares_issued > 0 and round.capital_raised > 0: | |
| preference_payout = min(remaining_proceeds, round.liquidation_preference) | |
| remaining_proceeds -= preference_payout | |
| preference_payouts[round.name] = preference_payout | |
| if round.is_participating: | |
| participating_shareholders.append({ | |
| 'round': round.name, | |
| 'shares': round.shares_issued | |
| }) | |
| # Phase 2: Handle non-participating conversions | |
| participating_preferred_shares = sum(p['shares'] for p in participating_shareholders) | |
| total_participating_shares = self.cap_table.common_shares + participating_preferred_shares | |
| # Check if non-participating preferred should convert | |
| for round in sorted_rounds: | |
| if (round.shares_issued > 0 and round.capital_raised > 0 | |
| and not round.is_participating): | |
| # Calculate conversion value vs preference value | |
| conversion_value = (round.shares_issued / self.cap_table.total_shares) * exit_scenario.exit_valuation | |
| preference_value = preference_payouts.get(round.name, 0) | |
| if conversion_value > preference_value: | |
| # They convert - add back their preference and include in common distribution | |
| remaining_proceeds += preference_value | |
| total_participating_shares += round.shares_issued | |
| # Phase 3: Final distribution to common + participating preferred | |
| if total_participating_shares > 0: | |
| price_per_participating_share = remaining_proceeds / total_participating_shares | |
| common_proceeds = price_per_participating_share * self.cap_table.common_shares | |
| else: | |
| common_proceeds = remaining_proceeds | |
| # Calculate option value | |
| price_per_common_share = common_proceeds / self.cap_table.common_shares if self.cap_table.common_shares > 0 else 0 | |
| option_value_per_share = max(0, price_per_common_share - self.cap_table.strike_price) | |
| total_option_value = option_value_per_share * self.cap_table.your_options | |
| return ScenarioResult( | |
| scenario_name=exit_scenario.name, | |
| exit_valuation=exit_scenario.exit_valuation, | |
| option_value=total_option_value, | |
| price_per_share=price_per_common_share, | |
| common_proceeds=common_proceeds | |
| ) | |
| def calculate_multiple_scenarios(self, scenarios: List[ExitScenario]) -> List[ScenarioResult]: | |
| """Calculate equity value for multiple exit scenarios""" | |
| results = [] | |
| for scenario in scenarios: | |
| if scenario.exit_valuation > 0: # Only calculate positive exit values | |
| result = self.calculate_scenario(scenario) | |
| results.append(result) | |
| return results | |
| def get_liquidation_summary(self) -> Dict[str, Any]: | |
| """Get summary of liquidation terms""" | |
| participating_status = [] | |
| for round in self.cap_table.funding_rounds: | |
| if round.shares_issued > 0: | |
| status = 'Participating' if round.is_participating else 'Non-Participating' | |
| participating_status.append(f"{round.name}: {status}") | |
| return { | |
| 'total_shares': self.cap_table.total_shares, | |
| 'common_shares': self.cap_table.common_shares, | |
| 'preferred_shares': self.cap_table.total_preferred_shares, | |
| 'your_options': self.cap_table.your_options, | |
| 'your_equity_percentage': self.cap_table.your_equity_percentage, | |
| 'strike_price': self.cap_table.strike_price, | |
| 'participating_status': participating_status, | |
| 'break_even_price': self.cap_table.strike_price | |
| } | |
| def create_cap_table( | |
| total_shares: int, your_options: int, strike_price: float, | |
| seed_shares: int = 0, seed_capital: float = 0, seed_multiple: float = 1.0, seed_participating: bool = False, | |
| series_a_shares: int = 0, series_a_capital: float = 0, series_a_multiple: float = 1.0, series_a_participating: bool = False, | |
| series_b_shares: int = 0, series_b_capital: float = 0, series_b_multiple: float = 1.0, series_b_participating: bool = False | |
| ) -> CapTable: | |
| """Factory function to create a CapTable from individual parameters""" | |
| funding_rounds = [] | |
| if seed_shares > 0 or seed_capital > 0: | |
| funding_rounds.append(FundingRound( | |
| name='Seed', | |
| shares_issued=seed_shares, | |
| capital_raised=seed_capital, | |
| liquidation_multiple=seed_multiple, | |
| is_participating=seed_participating | |
| )) | |
| if series_a_shares > 0 or series_a_capital > 0: | |
| funding_rounds.append(FundingRound( | |
| name='Series A', | |
| shares_issued=series_a_shares, | |
| capital_raised=series_a_capital, | |
| liquidation_multiple=series_a_multiple, | |
| is_participating=series_a_participating | |
| )) | |
| if series_b_shares > 0 or series_b_capital > 0: | |
| funding_rounds.append(FundingRound( | |
| name='Series B', | |
| shares_issued=series_b_shares, | |
| capital_raised=series_b_capital, | |
| liquidation_multiple=series_b_multiple, | |
| is_participating=series_b_participating | |
| )) | |
| return CapTable( | |
| total_shares=total_shares, | |
| your_options=your_options, | |
| strike_price=strike_price, | |
| funding_rounds=funding_rounds | |
| ) |