10 KiB
Data Model: Portfolio Performance Views
Feature: 003-portfolio-performance-views Date: 2026-03-16
Entity Relationship Overview
User ─1:N─► Entity ─1:N─► PartnershipMembership ◄─N:1─ Partnership
│ │
│ ├─1:N─► PartnershipAsset (has assetType)
│ ├─1:N─► PartnershipValuation (has nav, date)
│ └─1:N─► KDocument (has K1Data JSON, taxYear)
│
└─1:N─► Distribution (has amount, date, type)
Entities
Entity (existing — no changes)
| Field | Type | Description |
|---|---|---|
| id | String (UUID) | Primary key |
| name | String | Display name (e.g., "Entity #1") |
| type | EntityType enum | INDIVIDUAL, TRUST, LLC, LP, CORPORATION, FOUNDATION, ESTATE |
| taxId | String? | Tax identification number |
| userId | String | Owner user FK |
Role in feature: Primary grouping dimension for Portfolio Summary view. Each row = one entity.
Partnership (existing — no changes)
| Field | Type | Description |
|---|---|---|
| id | String (UUID) | Primary key |
| name | String | Display name (e.g., "Partnership #1") |
| type | PartnershipType enum | LP, GP, LLC, JOINT_VENTURE, FUND |
| inceptionDate | DateTime | Used as date for initial contribution cash flow |
| fiscalYearEnd | String? | Month (e.g., "12" for December) |
| currency | String | Default "USD" |
| userId | String | Owner user FK |
Role in feature: Contributes to all three views. Asset class determined by its PartnershipAsset records.
PartnershipMembership (existing — no changes)
| Field | Type | Description |
|---|---|---|
| id | String (UUID) | Primary key |
| entityId | String | FK → Entity |
| partnershipId | String | FK → Partnership |
| ownershipPercent | Decimal | Ownership percentage (0–100) |
| capitalCommitment | Decimal | Original committed amount |
| capitalContributed | Decimal | Cumulative contributed to date |
| classType | String? | Interest class (e.g., "Class A") |
| effectiveDate | DateTime | Membership start |
| endDate | DateTime? | Null = active |
Role in feature: Links entities to partnerships. Provides Original Commitment and capitalized amounts for Portfolio Summary. Unique constraint: [entityId, partnershipId, effectiveDate].
Distribution (existing — no changes)
| Field | Type | Description |
|---|---|---|
| id | String (UUID) | Primary key |
| partnershipId | String? | FK → Partnership |
| entityId | String | FK → Entity |
| type | DistributionType enum | INCOME, RETURN_OF_CAPITAL, CAPITAL_GAIN, GUARANTEED_PAYMENT, DIVIDEND, INTEREST |
| amount | Decimal | Distribution amount |
| date | DateTime | Distribution date |
| currency | String | Default "USD" |
| taxWithheld | Decimal? | Withheld tax amount |
| notes | String? | Free-text notes |
Role in feature: Feeds Distributions column in summaries, cash flow series for XIRR, and per-year distribution totals in Activity view.
PartnershipAsset (existing — no changes)
| Field | Type | Description |
|---|---|---|
| id | String (UUID) | Primary key |
| partnershipId | String | FK → Partnership |
| assetType | FamilyOfficeAssetType enum | PUBLIC_EQUITY, PRIVATE_EQUITY, REAL_ESTATE, HEDGE_FUND, VENTURE_CAPITAL, FIXED_INCOME, COMMODITY, ART_COLLECTIBLE, CRYPTOCURRENCY, CASH, OTHER |
| name | String | Asset name |
| currentValue | Decimal | Latest value |
Role in feature: Determines which asset class a partnership belongs to (majority assetType). Feeds Asset Class Summary grouping.
PartnershipValuation (existing — no changes)
| Field | Type | Description |
|---|---|---|
| id | String (UUID) | Primary key |
| partnershipId | String | FK → Partnership |
| date | DateTime | Valuation date |
| nav | Decimal | Net asset value |
| source | ValuationSource enum | APPRAISAL, MARKET, MANUAL, NAV_STATEMENT, FUND_ADMIN |
Role in feature: Provides "Residual Used" (latest NAV × ownership%) for RVPI and TVPI. Terminal value for XIRR cash flows.
KDocument (existing — data JSON schema extended)
| Field | Type | Description |
|---|---|---|
| id | String (UUID) | Primary key |
| partnershipId | String | FK → Partnership |
| type | KDocumentType enum | K1, K3 |
| taxYear | Int | Tax year (e.g., 2024) |
| filingStatus | KDocumentStatus enum | DRAFT, ESTIMATED, FINAL |
| data | Json | K1Data or K3Data structured JSON |
| documentFileId | String? | FK → Document |
Unique constraint: [partnershipId, type, taxYear]
Role in feature: Primary data source for Activity view income components and tax basis fields.
K1Data JSON Schema (extended)
Existing fields (no changes)
| Field | K-1 Box | Type | Activity Column Mapping |
|---|---|---|---|
| ordinaryIncome | Box 1 | number | Part of "Remaining K-1 Income/Ded." |
| netRentalIncome | Box 2 | number | Part of "Remaining K-1 Income/Ded." |
| otherRentalIncome | Box 3 | number | Part of "Remaining K-1 Income/Ded." |
| guaranteedPayments | Box 4 | number | Part of "Remaining K-1 Income/Ded." |
| interestIncome | Box 5 | number | "Interest" column |
| dividends | Box 6a | number | "Dividends" column |
| qualifiedDividends | Box 6b | number | (sub-detail of dividends) |
| royalties | Box 7 | number | Part of "Remaining K-1 Income/Ded." |
| capitalGainLossShortTerm | Box 8 | number | Part of "Cap Gains" |
| capitalGainLossLongTerm | Box 9a | number | Part of "Cap Gains" |
| unrecaptured1250Gain | Box 9b | number | Part of "Cap Gains" |
| section1231GainLoss | Box 9c | number | Part of "Cap Gains" |
| otherIncome | Box 11 | number | Part of "Remaining K-1 Income/Ded." |
| section179Deduction | Box 12 | number | Part of "Remaining K-1 Income/Ded." (negative) |
| otherDeductions | Box 13 | number | Part of "Remaining K-1 Income/Ded." (negative) |
| selfEmploymentEarnings | Box 14 | number | Part of "Remaining K-1 Income/Ded." |
| foreignTaxesPaid | Box 16 | number | Part of "Remaining K-1 Income/Ded." (negative) |
| alternativeMinimumTaxItems | Box 17 | number | (informational) |
| distributionsCash | Box 19a | number | Part of "Distributions" |
| distributionsProperty | Box 19b | number | Part of "Distributions" |
New fields (added for tax basis tracking)
| Field | Source | Type | Description |
|---|---|---|---|
| beginningTaxBasis | Prior year ending or manual | number? | Beginning of year tax basis |
| endingTaxBasis | Computed or manual | number? | End of year tax basis |
| endingGLBalance | General ledger | number? | Ending GL balance per books |
| k1CapitalAccount | K-1 Schedule L | number? | Ending K-1 capital account |
| otherAdjustments | K-1 Box 18c etc. | number? | Other basis adjustments |
| activityNotes | Manual entry | string? | Per-year notes (e.g., "AJE Completed") |
All new fields are optional (?) to maintain backward compatibility with existing K-1 documents.
Derived/Computed Fields
These are computed at query time and not stored:
Summary View Computations (per entity or per asset class)
| Metric | Formula | Notes |
|---|---|---|
| Original Commitment | Σ membership.capitalCommitment | Across active memberships in scope |
| Paid-In (ABS) | Σ membership.capitalContributed | Absolute value of contributions |
| % Called | Paid-In ÷ Original Commitment × 100 | Percentage; "N/A" if commitment = 0 |
| Unfunded Commitment | Original Commitment − Paid-In | Can be 0 |
| Distributions | Σ distribution.amount | All distributions in scope |
| Residual Used | Σ (latestValuation.nav × membership.ownershipPercent / 100) | Per-partnership NAV allocated by ownership |
| DPI | Distributions ÷ Paid-In | Decimal multiple; 0 if Paid-In = 0 |
| RVPI | Residual Used ÷ Paid-In | Decimal multiple; 0 if Paid-In = 0 |
| TVPI | (Distributions + Residual Used) ÷ Paid-In | Decimal multiple; 0 if Paid-In = 0 |
| IRR (XIRR) | Newton-Raphson on merged cash flows | null → "N/A" if < 2 cash flows |
Activity View Computations (per row)
| Field | Formula |
|---|---|
| Capital Gains | shortTerm + longTerm + unrecaptured1250Gain + section1231GainLoss |
| Remaining K-1 Income/Ded. | ordinaryIncome + netRentalIncome + otherRentalIncome + guaranteedPayments + royalties + otherIncome + selfEmploymentEarnings − section179Deduction − otherDeductions − foreignTaxesPaid |
| Total Income | Interest + Dividends + Capital Gains + Remaining K-1 Income/Ded. |
| Book-to-Tax Adj | endingGLBalance − endingTaxBasis |
| K-1 Capital vs Tax Basis Diff | k1CapitalAccount − endingTaxBasis |
| Excess Distribution | max(0, Distributions − (beginningTaxBasis + Contributions + Total Income + otherAdjustments)) |
| Negative Basis? | endingTaxBasis < 0 → "YES" |
| Δ Ending Basis vs Prior Year | endingTaxBasis − beginningTaxBasis |
State Transitions
KDocument Filing Status
DRAFT → ESTIMATED → FINAL
Each transition may update the K1Data values (including the new tax basis fields). The Activity view always shows the latest data regardless of filing status.
Validation Rules
| Rule | Scope | Description |
|---|---|---|
| capitalCommitment ≥ 0 | PartnershipMembership | Commitment cannot be negative |
| capitalContributed ≥ 0 | PartnershipMembership | Contributed cannot be negative |
| capitalContributed ≤ capitalCommitment | PartnershipMembership | Cannot contribute more than committed |
| ownershipPercent ∈ (0, 100] | PartnershipMembership | Must be positive percentage |
| distribution.amount > 0 | Distribution | Distributions are positive flows |
| taxYear ∈ [1900, current+1] | KDocument | Reasonable year range |
| nav ≥ 0 | PartnershipValuation | NAV cannot be negative |
| Σ ownershipPercent per partnership ≤ 100 | PartnershipMembership | Total ownership cannot exceed 100% |
FamilyOfficeAssetType → Display Label Mapping
| Enum Value | Display Label |
|---|---|
| REAL_ESTATE | Real Estate |
| VENTURE_CAPITAL | Venture Capital |
| PRIVATE_EQUITY | Private Equity |
| HEDGE_FUND | Hedge Fund |
| FIXED_INCOME | Credit |
| COMMODITY | Natural Resources |
| OTHER | Other |
| PUBLIC_EQUITY | Public Equity |
| ART_COLLECTIBLE | Art & Collectibles |
| CRYPTOCURRENCY | Cryptocurrency |
| CASH | Cash |
Note: "Co-Investment" and "Infrastructure" from the spec map to PRIVATE_EQUITY and OTHER respectively until dedicated enum values are added.