Documentation Index Fetch the complete documentation index at: https://village-docs.villagelabs.com/llms.txt
Use this file to discover all available pages before exploring further.
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
)
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
Repurchase obligations peak in 2029
This is when early participants (hired 2014-2016) begin retiring with significant vested balances. Plan sponsors should begin accumulating cash reserves now.
Loan payoff reduces future flexibility
Once the 2020 loan is fully paid (2030), no more suspense shares remain for allocation. Future contributions must be cash, not share releases.
Diversification creates cash demand
Participants over 55 with 10+ years can diversify starting in 2025. This creates additional cash needs beyond repurchases.
Share price growth drives obligations
5% annual growth means account values grow faster than contributions, increasing future repurchase costs.
Next Steps
Multi-Loan Example Model a complex leveraged ESOP with multiple loans
Diversification Planning Focus on diversification impact
API Reference Full API documentation
Data Models Deep dive into object structures