Skip to main content

Overview

Step 7 is the most complex step in the ESOP projection engine, handling the full lifecycle of repurchase obligations from termination through final distribution.
This step processes 9 distinct phases including QDRO processing, distribution schedules, RMD enforcement, and multi-strategy repurchase execution.

Why This Matters

When employees terminate, the ESOP must:
  1. Calculate what they’re owed (vested shares + cash)
  2. Create a payment schedule (deferred installments or lump sum)
  3. Execute repurchases using available cash
  4. Apply the company’s chosen repurchase strategy
This step determines the cash flow impact on the company and trust.

Processing Phases

Step 7 executes in strict sequential order:
1

Phase 1: QDRO Processing

Handle divorce settlements and court orders
2

Phase 2: Forfeiture Recording

Track non-vested share and cash forfeitures
3

Phase 3: Distribution Schedule Creation

Build payment timelines for new terminations
4

Phase 4: RMD Enforcement

Force distributions for participants age 73+
5

Phase 5: Execute Due Payments

Process scheduled distributions for current year
6

Phase 6: Distribution Form Conversion

Convert stock to cash if required by plan rules
7

Phase 7: (Deprecated) Segregation Policy

Removed in v0.3 to focus on core ESOP mechanics
8

Phase 8: Execute Repurchase Strategy

Apply recycle/redeem/releverage strategy
9

Phase 9: Funding Reconciliation

Reconcile TrustCashLedger draws and balances

Detailed Phase Breakdown

Phase 1: QDRO Processing

QDRO = Qualified Domestic Relations Order (divorce/separation settlements)
When a divorce decree requires splitting an ESOP account:
  1. Apply Split Percentage (typically 50%)
  2. Respect Vesting - Only vested amounts are split
  3. Pro-Rata Across Securities - In multi-class mode, split proportionally
  4. Reduce Participant Balance - Deduct from employee account
  5. Create Immediate Repurchase - Alternate payee is paid immediately
  6. Mark as Processed - Prevent reprocessing in future years
# Example QDRO processing
for employee in active_employees:
    if employee.qdro_orders:
        for order in employee.qdro_orders:
            if not order.processed_this_year:
                # Determine vesting
                vesting_pct = vesting_schedule[employee.service_years]
                
                # Apply split (e.g., 50%)
                split_pct = order.percent  # 0.50 for 50%
                
                # For each security holding
                for security_id, holding in employee.holdings:
                    vested_shares = holding.shares * vesting_pct
                    split_shares = vested_shares * split_pct
                    
                    # Reduce employee, add to repurchase
                    holding.shares -= split_shares
                    repurchase_queue[security_id] += split_shares
                
                # Mark processed
                order.processed_year = current_year
In multi-class mode, QDROs split proportionally across all securities:Example:
  • Employee holds: 100 Class A shares, 200 Class B shares
  • Vesting: 80%
  • QDRO: 50% split
Calculation:
  • Class A vested: 100 * 0.80 = 80 shares
  • Class A split: 80 * 0.50 = 40 shares → to alternate payee
  • Class B vested: 200 * 0.80 = 160 shares
  • Class B split: 160 * 0.50 = 80 shares → to alternate payee
Every QDRO generates compliance events:
{
  "event": "qdro_processed",
  "employee_id": "EMP042",
  "percent": 0.50,
  "shares_by_security": {
    "CLASS_A": 40.0,
    "CLASS_B": 80.0
  },
  "cash_paid": 2500.00
}

Phase 2: Forfeiture Recording

When participants terminate before fully vested, non-vested amounts are forfeited.
Two policy options:
  1. reallocate_next_year (Most Common)
    • Add forfeitures to next year’s share pool
    • Used to fund next year’s allocations
    • Reduces company cash contribution need
  2. reallocate_on_payout (Less Common)
    • Hold forfeitures until employee fully paid out
    • Then reallocate to remaining participants
    • Used when plan wants to delay forfeiture recognition
if forfeiture_policy == "reallocate_next_year":
    carry_over_state.forfeited_shares_for_next_year += forfeited_amount
    carry_over_state.forfeited_cash_for_next_year += forfeited_cash

elif forfeiture_policy == "reallocate_on_payout":
    carry_over_state.forfeited_shares_from_payout[employee_id] = forfeited_amount
    carry_over_state.forfeited_cash_from_payout[employee_id] = forfeited_cash

Phase 3: Distribution Schedule Creation

When an employee terminates, the engine creates a multi-year payment schedule.
From DistributionRule matched to termination trigger:
{
  "trigger": "retirement",           # or "death", "disability", "termination"
  "payment_years": 5,                # Installment period
  "defer_years": 1,                  # Wait period before first payment
  "lump_sum_threshold": 5000,        # Force lump sum if account < this
  "installment_frequency": "annual", # or "quarterly", "monthly"
  "distribution_form": "cash"        # or "stock", "stock_with_mandatory_put"
}

Phase 4: RMD Enforcement

RMD = Required Minimum Distribution (IRS tax rule)
Participants age 73+ must begin receiving distributions, regardless of employment status or deferral preferences.
RMD_AGE = 73  # As of 2025 (was 72 pre-SECURE Act 2.0)

for employee in active_employees:
    if employee.age >= RMD_AGE:
        # Check if already has active schedule
        has_due_schedule = any(
            sched for sched in employee.pending_distributions
            if sched.start_year <= current_year
        )
        
        # If no schedule and has balance, force immediate distribution
        if not has_due_schedule and (employee.vested_shares > 0 or employee.vested_cash > 0):
            # Create immediate schedule
            employee.pending_distributions.append({
                "trigger": "rmd",
                "start_year": current_year,  # Immediate
                "years_total": 1,
                "annual_shares": employee.vested_shares,
                "annual_cash": employee.vested_cash,
                "remaining_shares": employee.vested_shares,
                "remaining_cash": employee.vested_cash
            })

Phase 5: Execute Due Payments

Process all distribution schedules where current_year >= start_year.
for employee in active_employees:
    for schedule in employee.pending_distributions:
        # Check if payment due
        if current_year >= schedule.start_year or employee.age >= RMD_AGE:
            if schedule.years_paid < schedule.years_total:
                # Calculate this year's payment
                payment_shares = min(
                    schedule.annual_shares, 
                    schedule.remaining_shares
                )
                payment_cash = min(
                    schedule.annual_cash, 
                    schedule.remaining_cash
                )
                
                # Update schedule
                schedule.remaining_shares -= payment_shares
                schedule.remaining_cash -= payment_cash
                schedule.years_paid += 1
                
                # Add to repurchase queue
                total_shares_to_repurchase += payment_shares
                total_payment_value += (payment_shares * share_price) + payment_cash

Phase 6: Distribution Form Conversion

If the plan requires cash-only distributions, convert remaining stock to cash.
Three options per plan rules:
  1. stock - Distribute shares as shares
  2. cash - Convert all shares to cash at current price
  3. stock_with_mandatory_put - Distribute shares with put option (treated as cash in modeling)
distribution_rule = get_distribution_rule(employee.termination_reason)

if distribution_rule.distribution_form == "cash":
    if employee.remaining_payout_shares > 0:
        # Convert shares to cash value
        cash_value = employee.remaining_payout_shares * current_share_price
        
        # Update employee account
        employee.remaining_payout_cash += cash_value
        employee.remaining_payout_shares = 0
        
        # Add shares to repurchase queue
        total_shares_to_repurchase += employee.remaining_payout_shares

Phase 7: (Deprecated) Segregation Policy

Segregation is removed in v0.3 to focus on core, high-fidelity ESOP mechanics. The model no longer documents or enforces segregation_policy.

Phase 8: Execute Repurchase Strategy

The company’s strategic choice for handling repurchased shares.

Recycle

Keep shares in the plan
  • Add to next year’s allocation pool
  • Shares remain outstanding
  • Use OIA cash if available
  • Most cash-efficient

Redeem

Retire shares permanently
  • Reduce outstanding shares
  • Increases ownership % for remaining participants
  • Shares cannot be reissued

Releverage

Create new ESOP loan
  • Borrow to fund repurchase
  • Default term: 10 years
  • Shares held in suspense
  • Released as loan paid down

Phase 9: Funding Reconciliation

Final accounting for the year’s cash flows.
Reconcile TrustCashLedger movements for the year (no investment earnings in v0.3):
ledger_summary = {
    "boy": trust.cash_ledger_snapshot_boy,
    "draws": result.transactions,  # from distributions and repurchases
    "deposits": company_contributions,
    "transfers": internal_transfers,
    "eoy": trust.cash_ledger_snapshot_eoy
}

Multi-Class Security Support

New in v0.2: Full multi-class security support with per-security tracking.
When multi_class_mode=True and securities exist:

Key Differences

AspectSingle-ClassMulti-Class
Share TrackingAggregate totalPer-security holdings
PricingSingle priceSecurity-specific prices
Pro-Rata LogicN/AWeighted by holdings
RepurchaseSingle queuePer-security queues
Recycled PoolSingle poolPer-security pools
Releverage LoansGeneric loanSecurity-tagged loans

Example: Pro-Rata Distribution

# Employee has mixed holdings
employee.holdings = {
    "CLASS_A": Holding(shares=100, vested=True),
    "CLASS_B": Holding(shares=200, vested=True)
}

# Total: 300 shares
# Payment due: 75 shares

# Pro-rata calculation
total_shares = 300
weight_A = 100 / 300 = 0.333
weight_B = 200 / 300 = 0.667

# Distribute
payment_A = 75 * 0.333 = 25 shares
payment_B = 75 * 0.667 = 50 shares

# Value calculation (different prices)
price_A = 500.00
price_B = 450.00

value_A = 25 * 500 = $12,500
value_B = 50 * 450 = $22,500
total_value = $35,000

Security-Specific Repurchase

# Repurchase tracking by security
repurchase_by_security = {
    "CLASS_A": 125.0,
    "CLASS_B": 250.0
}

# Strategy: 60% recycle, 40% redeem
for security_id, quantity in repurchase_by_security.items():
    recycle_qty = quantity * 0.60
    redeem_qty = quantity * 0.40
    
    # Update security-specific pools
    carry_over_state.recycled_shares_by_security[security_id] += recycle_qty
    securities[security_id].total_outstanding_shares -= redeem_qty

Configuration Reference

Key configuration parameters used in Step 7:
regulatory_limits = {
    "rmd_age": 73  # Age when distributions must begin (IRS requirement)
}
distribution_timing_rules = {
    "retirement_death_disability": {
        "termination_types": ["retirement", "death", "disability"],
        "max_deferral_years": 1  # Must start within 1 year
    },
    "termination": {
        "termination_types": ["termination"],
        "max_deferral_years": 5  # Can defer up to 5 years
    },
    "latest_commencement": {
        "min_age": 65,
        "min_service_years": 10,
        "max_deferral_years": 1  # Age 65+ with 10+ years: max 1 year defer
    }
}
default_loan_terms = {
    "releverage_years": 10  # Default term for releverage loans
}
default_rates = {
    "oia_yield_rate": 0.03  # 3% annual yield on OIA balance
}
calculation_precision = {
    "precision_string": "0.0001"  # Round to 4 decimal places
}
account_thresholds = {
    "division_by_zero_default": 1  # Denominator default when total_shares = 0
}

The Funding Waterfall

See Simulation Core for complete waterfall explanation. Quick Summary:
cash_usage_policy = [
    "unallocated_company_contributions",  # 1st priority
    "unallocated_forfeiture_cash",        # 2nd priority
    "participant_cash_accounts"           # 3rd priority
]

# Draw cash in specified order
for source in cash_usage_policy:
    if remaining_need > 0:
        available = trust_cash_ledger[source]
        amount_to_use = min(available, remaining_need)
        trust_cash_ledger[source] -= amount_to_use
        remaining_need -= amount_to_use

Common Scenarios

Setup:
  • Employee retires, age 65, 15 years service
  • Vested: 2,000 shares at 500/share=500/share = 1M
  • Distribution rule: 5-year installment, 1-year defer
  • Repurchase strategy: 100% recycle
Processing:
  1. Phase 3: Create schedule starting 2026, 5 annual payments of 400 shares each
  2. Phase 5: In 2026, pay first installment of 400 shares
  3. Phase 8: Recycle 400 shares → added to 2027 allocation pool
Result:
  • Employee receives 400 shares in 2026 (valued at current price)
  • Trust repurchases and recycles shares
  • Company has 400 shares for next year’s allocation
Setup:
  • Active employee, age 42, divorce decree
  • QDRO: 50% split to ex-spouse
  • Account: 1,500 vested shares, $25K cash
  • Repurchase strategy: 100% redeem
Processing:
  1. Phase 1: Process QDRO
    • Split: 750 shares + $12,500 to ex-spouse
    • Employee retains: 750 shares + $12,500
    • Immediate payout to ex-spouse
  2. Phase 8: Redeem 750 shares
    • Outstanding shares reduced by 750
Result:
  • Ex-spouse paid immediately
  • Employee continues with reduced account
  • Company ownership percentages recalculated
Setup:
  • Employee terminated at age 68 in 2020
  • Distribution deferred to 2025 (5-year max)
  • Employee reaches age 73 in 2023
  • Vested balance: 1,200 shares
Processing:
  1. Phase 4: In 2023, RMD check triggers
    • Age 73 reached, no active distribution
    • Create immediate RMD schedule
  2. Phase 5: Force distribution in 2023 (overrides 2025 deferral)
Result:
  • Distribution starts in 2023 instead of 2025
  • Ensures IRS compliance
  • Prevents tax penalties
Setup:
  • Total repurchase: 3,000 shares
  • Strategy: 60% recycle, 30% redeem, 10% releverage
  • Share price: $500
Processing:
  1. Phase 8: Apply weighted strategy
    • Recycle: 3,000 × 60% = 1,800 shares
    • Redeem: 3,000 × 30% = 900 shares
    • Releverage: 3,000 × 10% = 300 shares
Recycle:
  • Add 1,800 shares to next year’s pool
  • Use OIA: $900K if available
Redeem:
  • Reduce outstanding shares by 900
Releverage:
  • Create loan: 150K(300shares×150K (300 shares × 500)
  • Term: 10 years
  • Annual release: 30 shares/year
Result:
  • Mixed strategy provides flexibility
  • 1,800 shares available for reallocation
  • 900 shares permanently retired
  • 300 shares financed via new loan
Setup:
  • Employee holds: 500 Class A (600/share),1,000ClassB(600/share), 1,000 Class B (400/share)
  • Payment due: 300 shares total
  • Strategy: 100% recycle
Processing:
  1. Phase 5: Pro-rata distribution
    • Total shares: 1,500
    • Class A weight: 500 / 1,500 = 33.3%
    • Class B weight: 1,000 / 1,500 = 66.7% Payment:
    • Class A: 300 × 33.3% = 100 shares → value $60K
    • Class B: 300 × 66.7% = 200 shares → value $80K
    • Total value: $140K
  2. Phase 8: Security-specific recycle
    • Class A: Add 100 to recycled_shares_by_security[“CLASS_A”]
    • Class B: Add 200 to recycled_shares_by_security[“CLASS_B”]
Result:
  • Each security maintains separate recycled pools
  • Next year’s allocation can draw from both pools
  • Preserves security mix in the plan

Data Dependencies

Inputs Required

{
  "employee_id": "EMP042",
  "age": 65,
  "service_years": 15.5,
  "termination_date": "2025-06-30",
  "termination_reason": "retirement",
  "vested_shares": 2000.0,
  "vested_cash": 50000.0,
  "potential_forfeitures": 0.0,
  "holdings": {  # Multi-class mode
    "CLASS_A": {"shares": 1200, "vested": True},
    "CLASS_B": {"shares": 800, "vested": True}
  },
  "qdro_orders": [
    {"percent": 0.50, "processed_year": null}
  ],
  "pending_distributions": []  # Schedules created in Phase 3
}

Outputs Generated

{
  "pending_distributions": [
    {
      "trigger": "retirement",
      "start_year": 2026,
      "years_total": 5,
      "years_paid": 1,
      "annual_shares": 400.0,
      "remaining_shares": 1600.0,
      ...
    }
  ],
  "remaining_payout_shares": 1600.0,
  "remaining_payout_cash": 40000.0,
  "forfeited_shares": 0.0,
  "holdings": {  # Updated after payment
    "CLASS_A": {"shares": 1120},  # Reduced by 80
    "CLASS_B": {"shares": 680}    # Reduced by 120
  }
}

Performance Considerations

Step 7 is computationally intensive due to multiple passes over employee list and complex pro-rata calculations.
Optimization Strategies:
  1. Index by Termination Status
    • Pre-filter terminated employees
    • Avoid scanning full census each phase
  2. Cache Vesting Calculations
    • Vesting percentages calculated multiple times
    • Cache lookups by service year
  3. Precision Rounding
    • Apply rounding at calculation boundaries
    • Use Decimal for financial amounts
    • Avoid floating-point arithmetic
  4. Multi-Class Overhead
    • Pro-rata calculations scale with # securities
    • Consider performance impact with 5+ securities

Testing Scenarios

From tests/engine/test_engine_step7.py:
  1. Recycle accumulates recycled shares
    • Strategy: "recycle"
    • Verify: carry_over_state.recycled_shares > 0
  2. Redeem reduces outstanding shares
    • Strategy: "redeem"
    • Verify: total_outstanding_shares decreased
  3. Cash forfeitures tracked
    • Service: 1 year (20% vested)
    • Opening cash: $1,000
    • Verify: 80% forfeited to carryover
  4. Reallocate on payout defers forfeiture
    • Policy: "reallocate_on_payout"
    • Verify: Forfeitures held until payout complete


Summary

Step 7 is the most complex and critical step in the ESOP projection engine: Key Responsibilities:
  • ✅ QDRO processing (divorce settlements)
  • ✅ Forfeiture tracking (non-vested amounts)
  • ✅ Distribution schedule creation (multi-year installments)
  • ✅ RMD enforcement (age 73+ compliance)
  • ✅ Payment execution (scheduled distributions)
  • ✅ Distribution form conversion (stock → cash)
  • ❌ Segregation policy (removed in v0.3)
  • ✅ Repurchase strategy execution (recycle/redeem/releverage)
  • ✅ Funding reconciliation (TrustCashLedger balances)
Complexity Factors:
  • 9 sequential phases
  • Multi-class security support
  • Pro-rata calculations across securities
  • Regulatory compliance checks
  • Multi-year distribution schedules
  • Weighted strategy execution
  • Comprehensive event logging
Performance Profile:
  • Most expensive step (1,130 lines of code)
  • Multiple passes over employee list
  • Pro-rata calculations scale with # securities
  • Precision rounding for financial accuracy
For Developers: Step 7 processes are highly interdependent. Changing one phase may affect downstream phases. Always run full test suite after modifications.