Skip to main content

Overview

Step 8 is the final step in the annual processing cycle. It rolls account balances forward, applies yearly evolution to employee data (age, service, compensation), and captures the complete year’s results.
This step transforms in-progress allocations into opening balances for the next year and advances the simulation forward in time.

Core Responsibilities

Balance Rollover

Move allocated/diversified amounts to opening balances

Yearly Evolution

Age employees by 1 year, grow compensation, increment service

Remove Paid-Out Participants

Clean up fully distributed accounts

Capture Results

Store year-end snapshots and KPIs

Processing Phases

Phase 1: Roll Balances Forward

For active employees (not terminated), consolidate the year’s activity:
for employee in active_employees:
    if not employee.termination_date:  # Active employees only
        # Roll shares forward
        employee.opening_shares = (
            employee.opening_shares +      # What they started with
            employee.allocated_shares -    # Plus this year's allocation
            employee.diversified_shares    # Minus diversification
        )
        
        # Roll cash forward
        employee.opening_cash = (
            employee.opening_cash +
            employee.allocated_cash -
            employee.diversified_cash
        )
        
        # Reset annual accumulators
        employee.allocated_shares = 0
        employee.allocated_cash = 0
Example:
FieldBeginningStep 3 AllocationStep 6 DiversificationYear-End
opening_shares1,500N/AN/A2,000
allocated_shares0+500N/A0 (reset)
diversified_shares0N/A00
Result: Employee starts next year with 2,000 opening shares.
Terminated employees do NOT roll balances—their accounts are frozen and distributions are handled via pending_distributions in Step 7.

Phase 2: Identify Fully Paid-Out Participants

Participants who have received all scheduled distributions are marked for removal:
fully_paid_out_threshold = 0.01  # e.g., $0.01 or 0.01 shares

for employee in active_employees:
    if employee.termination_date:  # Only check terminated employees
        if (employee.remaining_payout_shares < threshold AND
            employee.remaining_payout_cash < threshold):
            employee.is_fully_paid_out = True
Cleanup: At the end of Step 8, fully paid-out employees are removed from the active list to improve performance in future years.
active_employee_list.remove_fully_paid_out()
Example:
  • Employee terminated in 2020
  • 5-year distribution schedule completed in 2025
  • Remaining payout: $0.00
  • Result: Removed from active list after 2025
Removing fully paid-out participants keeps the simulation efficient. They’re preserved in historical snapshots but don’t participate in future processing.

Phase 3: Capture Annual Results

Create a comprehensive year-end snapshot:
annual_results[year] = YearResult(
    year=year,
    share_price=current_share_price,
    total_shares_allocated=sum(emp.allocated_shares for emp in active),
    total_cash_allocated=sum(emp.allocated_cash for emp in active),
    total_forfeitures=...,  # Aggregated from Step 7
    total_distributions=..., # Aggregated from Step 7
    participant_count=len([emp for emp in active if not emp.termination_date]),
    compliance_events=[...]  # All events from this year
)
Included Metrics:
  • ✅ Share price at year-end
  • ✅ Total shares allocated this year
  • ✅ Total cash allocated
  • ✅ Forfeitures captured
  • ✅ Distributions paid
  • ✅ Active participant count
  • ✅ All compliance events (audit trail)

Phase 4: Apply Yearly Evolution

For remaining active employees, advance time by one year:
for employee in active_employees:
    employee.age = employee.age + 1
Example:
  • Beginning: Age 35
  • End: Age 36
This affects eligibility for diversification (age 55+) and RMD (age 73+).
Timing: Yearly evolution happens at year-end (Step 8), not at the beginning (Step 1).This means that all processing for year N uses the employee data from the beginning of year N, and evolution is applied only after all steps complete.

Phase 5: TrustCashLedger Reconciliation

Emit a complete reconciliation of the TrustCashLedger (no investment earnings in v0.3):
ledger_summary = {
    "boy": {
        "participant_cash_accounts": 150_000,
        "unallocated_company_contributions": 50_000,
        "unallocated_forfeiture_cash": 25_000
    },
    "deposits": {
        "unallocated_company_contributions": 500_000
    },
    "draws": [
        {"source": "unallocated_company_contributions", "amount": 200_000},
        {"source": "unallocated_forfeiture_cash", "amount": 100_000},
        {"source": "participant_cash_accounts", "amount": 50_000}
    ],
    "transfers": [
        {"from": "unallocated_forfeiture_cash", "to": "participant_cash_accounts", "amount": 25_000}
    ],
    "eoy": {
        "participant_cash_accounts": 200_000,
        "unallocated_company_contributions": 200_000,
        "unallocated_forfeiture_cash": 0
    }
}
Formula:
EOY per source = BOY + Deposits - Draws + Transfers(net)
After emitting the summary, reset any year-accumulators for next year.

Data Flow

Inputs

{
  "employee_id": "EMP001",
  "age": 35,
  "service_years": 3.5,
  "compensation": 100000.0,
  "opening_shares": 1500.0,
  "allocated_shares": 500.0,
  "diversified_shares": 0.0,
  "opening_cash": 5000.0,
  "allocated_cash": 0.0,
  "diversified_cash": 0.0,
  "termination_date": null
}

Outputs

{
  "employee_id": "EMP001",
  "age": 36,                    # +1 year
  "service_years": 4.5,         # +1 year
  "compensation": 103000.0,     # Grown by 3%
  "opening_shares": 2000.0,     # Rolled forward
  "allocated_shares": 0.0,      # Reset
  "diversified_shares": 0.0,
  "opening_cash": 5000.0,       # Rolled forward
  "allocated_cash": 0.0,        # Reset
  "diversified_cash": 0.0
}

Compliance Events

Per employee:
{
  "year": 2025,
  "phase": "year_end",
  "event": "yearly_evolution_applied",
  "entity_type": "employee",
  "entity_id": "EMP042",
  "inputs": {
    "prev_age": 45,
    "prev_service_years": 12.0,
    "prev_compensation": 150000.0,
    "comp_growth_rate": 0.03
  },
  "outputs": {
    "age": 46,
    "service_years": 13.0,
    "compensation": 154500.0
  }
}

Edge Cases

Do NOT roll balances or evolve:
  • Balances frozen at termination
  • Age/service/comp don’t increment
  • Distributions handled via pending_distributions
Only active employees are evolved.
Employees who terminated during year:
  • Participated in Steps 1-7 with beginning-of-year data
  • Do NOT evolve in Step 8
  • Marked as terminated for future years
In rare cases, corrections might create negative balances:
opening_shares = max(0, opening + allocated - diversified)
Floor at zero to prevent negative opening balances.
If no compensation growth assumption:
comp_growth_rate = financial_assumptions.get("combined_comp_growth_rate", 0.0)
employee.compensation = employee.compensation * (1 + comp_growth_rate)
Compensation stays flat year-over-year.

Impact on Next Year

Step 8 sets the stage for the next year’s simulation:
What was allocated_shares this year becomes opening_shares next year:Year 2025 End:
  • opening_shares: 2,000
  • allocated_shares: 0
Year 2026 Start:
  • opening_shares: 2,000 ← from 2025 rollover
  • allocated_shares: 0 ← fresh accumulator

Performance Optimization

Purpose: Reduce active employee list size over timeImpact:
  • Fewer employees to iterate in Steps 1-8
  • Faster projections in later years
  • No loss of data (preserved in snapshots)
Threshold: 0.01 shares or $0.01 cash
Clear year-specific accumulators to prepare for next year:
  • allocated_shares = 0
  • allocated_cash = 0
  • OIA tracking variables reset
Prevents carryover of stale data.
All employees evolved in single loop for efficiency:
for emp in active_employees:
    emp.age += 1
    emp.service_years += 1.0
    emp.compensation *= (1 + growth_rate)
Vectorization opportunities if using NumPy/Pandas.


Summary

Step 8 is the year-end finalization step that:
  • ✅ Rolls share/cash balances forward for active employees
  • ✅ Identifies and removes fully paid-out participants
  • ✅ Captures comprehensive year-end results and KPIs
  • ✅ Applies yearly evolution (age +1, service +1, comp growth)
  • ✅ Reconciles OIA ledger with sources/uses/earnings
  • ✅ Reconciles TrustCashLedger movements (no earnings in v0.3)
  • ✅ Resets accumulators for next year
  • ✅ Emits evolution events for audit trail
Key Insight: Step 8 is the bridge between years. It transforms the current year’s processing results into the starting state for the next year, ensuring continuity and accuracy across multi-year projections.
After Step 8 completes, the engine is ready to loop back to Step 1 for the next year, repeating the 8-step cycle until the projection period ends.