Helm Portfolio for Dummies
Syllabus
Module 10 of 10

Lab Exercises

Hands-on practice with sample data. Build a fund from scratch, run a direct investment lifecycle, debug import errors, and test your understanding of everything from Modules 1-9.

45 minutes All previous modules
How to use these exercises

Each exercise walks you through a realistic scenario. You can work through them as thought exercises (reading the data and predicting outcomes) or hands-on with a live Portfolio instance (building the XLSX files and importing them). The answer keys are hidden behind reveal buttons — try to work through each step before checking.

Exercises vs. sample CSV files

The exercises below use their own investor/asset names (INV-ALICE, FUND-HGF1, etc.) and a 4-investor $11M fund. The samples/ directory contains a separate scenario with different names (INV-001, FUND-001) and a 6-investor $17M fund. Both are self-contained — don't mix data between them.

Getting a local environment

To run these exercises against a live instance:

  1. Clone the portfolio repo and run docker-compose up
  2. Seed demo data: php artisan db:seed --class=DatabaseSeederDemo
  3. The repo includes a ready-made import file at demo/Portfolio Bulk Import with data.xlsx — try uploading it to see the full pipeline in action
  4. A Postman collection at demo/Delio Portfolio.postman_collection.json has example requests for every endpoint

You can also download blank import templates from the Data Import page in the UI (or via GET /api/bulk-import/template/{name}/download) and fill them in using the exercise data.

1 Build a Fund from Scratch
15 minutes PE Fund lifecycle

Walk through the complete lifecycle of a PE fund -- from creating investors to calculating performance metrics. You'll build the XLSX import step by step.

Step 1: Create Investors

Add these four investors to the Investors sheet:

client_referencefirst_namelast_nameemail
INV-ALICEAliceSmithalice@example.com
INV-BOBBobJohnsonbob@example.com
INV-CAROLCarolWilliamscarol@example.com
INV-DAVIDDavidBrowndavid@example.com

Step 2: Create the Fund Asset

Add this asset to the Assets sheet:

FieldValue
client_referenceFUND-HGF1
nameHelm Growth Fund I
classequity
structureprivate_equity_fund
currencyUSD
subscription_typedrawdown
is_openfalse
Remember: Enable the toggle

After importing the asset, you must enable the INVESTOR_SUBSCRIPTION_DISTRIBUTION toggle via the UI or API before importing subscriptions. This is Gotcha #10 from Module 9.

Step 3: Create Subscriptions

Add these subscriptions to the Subscriptions sheet (total commitment: $11M):

investorassetamountdate
INV-ALICEFUND-HGF150000002025-01-15
INV-BOBFUND-HGF130000002025-01-15
INV-CAROLFUND-HGF120000002025-01-15
INV-DAVIDFUND-HGF110000002025-01-15

Step 4: Issue a 25% Capital Call

First, define the investor group in the Capital Call Groups sheet:

group_idinvestor
GRP-CC1INV-ALICE
GRP-CC1INV-BOB
GRP-CC1INV-CAROL
GRP-CC1INV-DAVID

Then add the capital call in the Capital Calls sheet:

assetgroup_idpercentagedate
FUND-HGF1GRP-CC1252025-03-01

This calls 25% of each investor's commitment: Alice $1.25M, Bob $750K, Carol $500K, David $250K. Total called: $2.75M.

Step 5: Add a Q4 2025 Valuation

Add to the Valuations sheet:

assetinvestorvaluedate
FUND-HGF1all120000002025-12-31

Using all for the investor creates a valuation record for every investor who holds units. The $12M total NAV is distributed pro-rata based on each investor's ownership percentage.

Step 6: Distribute Dividends

Add to the Distributions sheet (pro-rata $500K dividend):

assetinvestoramounttypedate
FUND-HGF1INV-ALICE227272.73dividend2025-12-31
FUND-HGF1INV-BOB136363.64dividend2025-12-31
FUND-HGF1INV-CAROL90909.09dividend2025-12-31
FUND-HGF1INV-DAVID45454.54dividend2025-12-31

Distribution amounts are pro-rata based on ownership: Alice 45.45%, Bob 27.27%, Carol 18.18%, David 9.09%.

Expected Outcomes

After all imports are processed:
Total Committed: $11,000,000
Total Called: $2,750,000
Paid-In Capital: $2,750,000
NAV (Q4 2025): $12,000,000
Total Distributed: $500,000
DPI: ~0.18x
RVPI: ~4.36x
TVPI: ~4.55x

DPI = Total Distributions / Paid-In Capital = $500,000 / $2,750,000 = 0.1818x

RVPI = NAV / Paid-In Capital = $12,000,000 / $2,750,000 = 4.3636x

TVPI = (NAV + Total Distributions) / Paid-In Capital = ($12,000,000 + $500,000) / $2,750,000 = 4.5454x

Note: Paid-In Capital equals the total called amount ($2.75M), not the total committed amount ($11M). This is a common source of confusion. TVPI appears extremely high because only 25% of committed capital has been called, yet the fund is valued at $12M. The math is correct for the exercise -- the numbers are chosen for pedagogical clarity, not realism.

Sheet order in workbook: Investors → Assets → Subscriptions → Capital Call Groups → Capital Calls → Valuations → Distributions

2 Direct Investment Lifecycle
10 minutes Direct equity investment

Model a direct equity investment with unit-based pricing, multiple valuation points, and a dividend distribution.

Step 1: Create the Direct Asset

Add to the Direct - Assets sheet:

FieldValue
client_referenceDIRECT-TECHCO
nameTechCo Series B
classequity
structuredirect
currencyUSD

Step 2: Create Investments

Add to the Direct - Investments sheet (reusing investors from Exercise 1):

investorassetamountunitsprice_per_unitdate
INV-ALICEDIRECT-TECHCO100000010000010.002025-01-15
INV-BOBDIRECT-TECHCO5000005000010.002025-01-15

Step 3: Add Valuations

Add two valuation points to the Valuations sheet. For direct assets, valuations use value per unit (VPU):

assetinvestorvalue_per_unitdateNote
DIRECT-TECHCOall15.002025-03-31Q1: 50% gain
DIRECT-TECHCOall18.002025-06-30Q2: 80% gain from cost

Step 4: Distribute Dividends

Add a $50K dividend (pro-rata by ownership: Alice 66.67%, Bob 33.33%):

assetinvestoramounttypedate
DIRECT-TECHCOINV-ALICE33333.33dividend2025-06-30
DIRECT-TECHCOINV-BOB16666.67dividend2025-06-30

Expected Outcomes

At Q2 2025 ($18/unit):
Alice invested: $1,000,000
Alice current value: $1,800,000
Alice unrealized gain: $800,000
Alice dividend income: $33,333
Bob invested: $500,000
Bob current value: $900,000
Bob unrealized gain: $400,000
Bob dividend income: $16,667
Total NAV: $2,700,000
Total invested: $1,500,000

Alice: 100,000 units x $18/unit = $1,800,000 current value. Cost basis: $1,000,000. Unrealized gain: $800,000 (80% return on capital). Plus $33,333 dividend received.

Bob: 50,000 units x $18/unit = $900,000 current value. Cost basis: $500,000. Unrealized gain: $400,000 (80% return on capital). Plus $16,667 dividend received.

Key difference from PE Fund: Direct investments track units and VPU (value per unit), while PE fund assets use NAV. The calc service handles both models, but the underlying data structures differ. A direct investment's valuation is always VPU x units held.

Sheet order: Direct - Assets → Direct - Investments → Valuations → Distributions

3 The Shared Sheet Challenge
10 minutes Processing order

The Shared sheet lets you mix transaction types in a single sheet. It auto-sorts by priority, but you need to understand the sorting to predict what happens.

Scenario

You have a Shared sheet with the following rows (assume investors and assets already exist):

Rowtransaction_typeinvestorassetamountdate
1DistributionINV-ALICEFUND-HGF11000002025-06-15
2ValuationallFUND-HGF180000002025-06-01
3SubscriptionINV-ALICEFUND-HGF120000002025-01-01
4TransferINV-ALICEFUND-HGF15000002025-03-01
5Valuation-ReplaceallFUND-HGF190000002025-06-01

Question: In what order will the Shared sheet process these rows?

The Shared sheet sorts by priority, then by settlement date within each priority:

Priority 0 (Subscriptions): Row 3 -- Subscription (2025-01-01)

Priority 1 (Transfers): Row 4 -- Transfer (2025-03-01)

Priority 2 (Valuations): Row 2 -- Valuation (2025-06-01), then Row 5 -- Valuation-Replace (2025-06-01)

Priority 4 (Distributions): Row 1 -- Distribution (2025-06-15)

Final order: 3 → 4 → 2 → 5 → 1

Note that the Valuation (Row 2) processes before the Valuation-Replace (Row 5), both on the same date. The Replace will overwrite the value from Row 2, so the final NAV for 2025-06-01 will be $9M, not $8M.

Also note: if this were NOT a Shared sheet (i.e., separate dedicated sheets), you'd need to manually arrange the sheets in this order. The Shared sheet handles the sorting automatically -- that's its key advantage.

Debug Challenge

Now consider this broken Shared sheet. What's wrong?

Rowtransaction_typeinvestorassetamountdate
1DistributionINV-ALICEFUND-XYZ500002025-06-15
2ValuationallFUND-XYZ50000002025-06-01

Problem: There's no Subscription row in the Shared sheet, and this is for asset FUND-XYZ which presumably doesn't have existing subscriptions. Without a subscription, the investor doesn't hold units in the asset.

The Valuation (Row 2) will fail because the all investor keyword looks for investors who hold units -- but no one does. The Distribution (Row 1) will also fail because Alice doesn't hold units.

Even though the Shared sheet re-orders by priority (subscription first, then valuation, then distribution), there's no subscription row to process. The fix: add a Subscription row for INV-ALICE in FUND-XYZ.

Also note: the Shared sheet has saga compensation. If the Valuation succeeds but the Distribution fails, the Valuation is automatically reversed. In this case, both would fail.

4 Debug This Import
10 minutes Error diagnosis

For each error message below, identify the most likely cause and the fix. Click to reveal the answer.

Row 7. The asset field must exist.
What's the most likely cause?

Cause: The asset column in this row contains a client_reference that doesn't match any existing asset. Common reasons:

  • Typo in the client_reference (e.g., "FUND-HGF1" vs "FUND_HGF1")
  • The Assets sheet appears after this sheet in the workbook, so the asset hasn't been created yet
  • The asset was soft-deleted (deleted_at is not null)

Fix: Check the exact client_reference value. Verify the Assets sheet comes first in the workbook. Check for soft-deleted records in the database.

Row 12. The investor field is required.
What's the most likely cause?

Cause: The investor column is empty or missing in row 12. This typically happens with non-pro-rata fund assets where each transaction must specify an investor. For pro-rata funds you can sometimes use all, but for non-pro-rata, an investor reference is always required.

Fix: Add the investor's client_reference to the investor column. If you intended to apply to all investors, check whether the asset is configured as pro-rata -- non-pro-rata assets don't support the all shorthand for this transaction type.

Row 3. The asset must have subscriptions/distributions enabled.
What's the most likely cause?

Cause: The asset referenced in row 3 doesn't have the INVESTOR_SUBSCRIPTION_DISTRIBUTION toggle enabled. This is Gotcha #10 from Module 9. The toggle is a prerequisite for importing subscriptions, capital calls, distributions, and cash pool transactions.

Fix: Go to the asset's configuration in the UI (Asset Settings) and enable the INVESTOR_SUBSCRIPTION_DISTRIBUTION toggle, or call the REST API to update the asset configuration. Then re-run the import.

Row 5. The valuation already exists for this asset and date combination.
What's the most likely cause?

Cause: You're using the Valuations sheet (additive only), and a valuation already exists for this asset + date + investor combination. This often happens when re-running an import after a partial failure, or when trying to correct a previously imported valuation.

Fix: Use the Valuations - Replace sheet instead, which will overwrite existing valuations for the same date. Or use the all - replace investor value on the regular Valuations sheet. Remember that the Replace sheet uses heading row 1 (not row 2), so adjust your row references accordingly.

Row 9. The investor does not hold units in this asset.
What's the most likely cause?

Cause: You're trying to import a distribution or valuation for an investor who doesn't have a subscription (PE fund) or investment (Direct) in the asset. Units are created by subscriptions/investments -- without them, the investor has no position in the asset.

Common scenarios:

  • The Subscriptions sheet appears after the Distributions sheet in the workbook (ordering problem)
  • The investor's client_reference doesn't match the one used in the subscription
  • The subscription import failed silently (check import history)

Fix: Ensure subscriptions/investments are imported before distributions/valuations. Verify the investor holds units by checking the subscription import history or the investor's position in the UI.

Quick Reference Card

Lab exercise checklist
  1. Exercise 1: PE fund lifecycle -- investors, asset, subscriptions, capital call, valuation, distribution. Key metrics: TVPI ~4.55x, DPI ~0.18x.
  2. Exercise 2: Direct investment -- unit-based pricing, VPU valuations, dividend distribution. Key: track units and VPU, not NAV.
  3. Exercise 3: Shared sheet ordering -- priority 0 (subscription) through priority 4 (distribution). Auto-sorted, with saga compensation on failure.
  4. Exercise 4: Error diagnosis -- the five most common import errors and their root causes.
Sample data files

If you're working hands-on, check the samples/ directory for CSV files matching the data in these exercises. You can use them as starting points when building XLSX import files from the official templates (downloadable from the Data Import page in the UI).

Course Complete

You've made it through all 10 modules of Portfolio for Dummies. You now understand:

The most important thing you've learned is what Portfolio is not: it's not a general ledger, not a fund accounting system, and not a fully automated data pipeline. Knowing those boundaries is what separates a successful implementation from a painful one.

Congratulations!
You've completed Portfolio for Dummies. Go forth and onboard clients without triggering Gotcha #1.