Skip to main content

Scenario Overview

Let’s model Acme Corporation, a 100-person manufacturing company that established an ESOP in 2020 with a $3M loan. We’ll project 10 years to forecast repurchase obligations.
Company Profile:
  • 100 employees
  • Single ESOP loan from 2020
  • $500K annual contribution budget
  • Current share price: $100/share

Step 1: Define Plan Rules

The legal framework that rarely changes:
from villagelabs import PlanRules, VestingSchedule, DistributionPolicy

plan_rules = PlanRules(
    plan_name="Acme Corporation ESOP",
    plan_year_end="12/31",
    
    # 6-year graded vesting
    vesting_schedule=VestingSchedule(
        type="graded",
        schedule=[0, 0, 20, 40, 60, 80, 100]  # Years 0-6
    ),
    
    # Distribute 1 year after termination
    distribution_policy=DistributionPolicy(
        timing="termination_plus_1_year",
        form="lump_sum",
        in_service_distributions=False
    ),
    
    # Cash usage hierarchy for repurchases
    cash_usage_policy=[
        "unallocated_company_contributions",
        "unallocated_forfeiture_cash",
        "participant_cash_accounts"
    ],
    
    # Diversification per ERISA standards
    diversification_rules={
        "enabled": True,
        "age_requirement": 55,
        "service_requirement": 10,
        "first_election_percentage": 0.25,
        "final_election_percentage": 0.50,
        "final_election_age": 60
    }
)

Step 2: Set Operating Assumptions

The annual business strategy:
from villagelabs import OperatingAssumptions

operating_assumptions = OperatingAssumptions(
    # Fixed $500K contribution
    contribution_policy={
        "type": "fixed_amount",
        "annual_amount": 500_000
    },
    
    # Share valuation assumptions
    share_valuation={
        "current_price": 100.00,
        "annual_growth_rate": 0.05  # 5% growth
    },
    
    # Repurchase timing and funding
    repurchase_strategy={
        "timing": "immediate",  # Buy shares right away
        "funding_source": "trust_cash"  # Use available cash
    },
    
    # Company financial projections
    financial_projections={
        "revenue_growth": 0.06,
        "ebitda_margin": 0.22,
        "payroll_growth": 0.03
    },
    
    # Turnover assumptions
    turnover_assumptions={
        "use_predictive_model": True,
        "base_turnover_rate": 0.08  # 8% annual turnover
    }
)

Step 3: Provide Initial State

Current ESOP status (simplified for example):
from villagelabs import InitialState, Participant, ESOPLoan

initial_state = InitialState(
    census_year=2024,
    
    # Employee census (showing 3 of 100 for brevity)
    participants=[
        Participant(
            id="EMP001",
            age=45,
            hire_date="2016-01-15",
            service_years=8,
            annual_compensation=75_000,
            allocated_shares=950,
            vested_percentage=0.80
        ),
        Participant(
            id="EMP002",
            age=38,
            hire_date="2019-03-20",
            service_years=5,
            annual_compensation=65_000,
            allocated_shares=600,
            vested_percentage=0.60
        ),
        Participant(
            id="EMP003",
            age=55,
            hire_date="2014-08-10",
            service_years=10,
            annual_compensation=95_000,
            allocated_shares=1_200,
            vested_percentage=1.00
        ),
        # ... 97 more employees
    ],
    
    # Trust cash balances
    trust_cash={
        "participant_cash_accounts": 75_000,
        "unallocated_company_contributions": 100_000,
        "unallocated_forfeiture_cash": 25_000
    },
    
    # ESOP loan details
    esop_loans=[
        ESOPLoan(
            loan_id="LOAN_2020_INITIAL",
            origination_date="2020-01-01",
            original_principal=3_000_000,
            principal_balance=2_100_000,  # 4 years paid
            interest_rate=0.065,
            term_years=10,
            payment_schedule="amortizing",
            suspense_shares=21_000,  # 9,000 already released
            original_suspense_shares=30_000
        )
    ],
    
    # Share counts
    total_shares_outstanding=80_000,
    allocated_shares=59_000,  # To 100 participants
    unallocated_shares=0
)

Step 4: Configure Simulation

Runtime settings:
from villagelabs import SystemConfiguration

system_config = SystemConfiguration(
    projection_years=10,  # 2025-2034
    include_turnover_projection=True,
    run_sensitivity_analysis=False,
    output_format="detailed"
)

Step 5: Run Simulation

Execute the forecast:
from villagelabs import RepurchaseEngine

# Initialize engine
engine = RepurchaseEngine(api_key="your_api_key")

# Run simulation
results = engine.simulate(
    plan_rules=plan_rules,
    operating_assumptions=operating_assumptions,
    initial_state=initial_state,
    system_config=system_config
)

print(f"Simulation completed in {results.execution_time_ms}ms")

Step 6: Analyze Results

Summary Metrics

# High-level insights
print(f"10-Year Repurchase Obligation: ${results.total_repurchase_obligation:,.0f}")
# Output: 10-Year Repurchase Obligation: $8,450,000

print(f"Peak Cash Year: {results.peak_cash_year}")
# Output: Peak Cash Year: 2029

print(f"Peak Cash Amount: ${results.peak_cash_amount:,.0f}")
# Output: Peak Cash Amount: $1,250,000

print(f"Average Annual Contribution: ${results.average_annual_contribution:,.0f}")
# Output: Average Annual Contribution: $500,000

print(f"Final Trust Cash (2034): ${results.final_trust_cash:,.0f}")
# Output: Final Trust Cash (2034): $425,000

Year-by-Year Breakdown

# Detailed annual projections
for year in results.annual_projections:
    print(f"\n=== Year {year.year} ===")
    print(f"  Contribution: ${year.company_contribution:,.0f}")
    print(f"  Shares Released: {year.shares_released:,.0f}")
    print(f"  Repurchases: ${year.repurchase_amount:,.0f}")
    print(f"  Ending Cash: ${year.ending_trust_cash:,.0f}")

# Output:
# === Year 2025 ===
#   Contribution: $500,000
#   Shares Released: 3,000
#   Repurchases: $425,000
#   Ending Cash: $275,000
#
# === Year 2026 ===
#   Contribution: $500,000
#   Shares Released: 3,000
#   Repurchases: $680,000
#   Ending Cash: $195,000
# ...

Repurchase Obligation Trend

import matplotlib.pyplot as plt

years = [proj.year for proj in results.annual_projections]
repurchases = [proj.repurchase_amount for proj in results.annual_projections]

plt.figure(figsize=(10, 6))
plt.plot(years, repurchases, marker='o', linewidth=2)
plt.title('Projected Repurchase Obligations')
plt.xlabel('Year')
plt.ylabel('Repurchase Amount ($)')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Participant Analysis

# Focus on a specific participant
emp_003_timeline = results.get_participant_timeline("EMP003")

for snapshot in emp_003_timeline:
    print(f"Year {snapshot.year}: {snapshot.allocated_shares} shares, "
          f"${snapshot.account_value:,.0f} value")

# Output:
# Year 2025: 1,230 shares, $136,290 value
# Year 2026: 1,260 shares, $150,570 value
# Year 2027: 1,295 shares, $166,805 value
# ...

Step 7: Create Scenarios

Compare different contribution strategies:
# Scenario A: Current plan ($500K/year)
results_a = engine.simulate(..., contribution=500_000)

# Scenario B: Reduced contribution ($400K/year)
operating_assumptions_b = operating_assumptions.copy()
operating_assumptions_b.contribution_policy["annual_amount"] = 400_000
results_b = engine.simulate(..., operating_assumptions=operating_assumptions_b)

# Compare
comparison = engine.compare_scenarios(results_a, results_b)

print(f"Contribution difference: ${comparison.contribution_delta:,.0f}")
print(f"Repurchase obligation impact: ${comparison.repurchase_delta:,.0f}")
print(f"Recommendation: {comparison.recommendation}")

# Output:
# Contribution difference: $1,000,000 over 10 years
# Repurchase obligation impact: +$450,000 (Scenario B higher)
# Recommendation: Maintain $500K contribution for better cash management

Key Insights from Example

This is when early participants (hired 2014-2016) begin retiring with significant vested balances. Plan sponsors should begin accumulating cash reserves now.
Once the 2020 loan is fully paid (2030), no more suspense shares remain for allocation. Future contributions must be cash, not share releases.
Participants over 55 with 10+ years can diversify starting in 2025. This creates additional cash needs beyond repurchases.
5% annual growth means account values grow faster than contributions, increasing future repurchase costs.

Next Steps