24 KiB
Data Model: Single Family Office Platform
Phase 1 Output | Date: 2026-03-15
Overview
This document defines 9 new Prisma models and 8 new enums that extend the existing Ghostfolio schema. The design adds an entity/ownership layer above the existing User → Account → Order hierarchy, plus partnership, distribution, K-document, and document management.
Entity Relationship Diagram (Conceptual)
User (existing)
├── Entity[] ─────────────┬── Ownership[] ──── Account (existing, composite PK)
│ (trust, LLC, etc.) ├── PartnershipMembership[] ──── Partnership
│ ├── Distribution[] (received) ├── PartnershipMembership[]
│ └── Document[] ├── PartnershipAsset[] ── AssetValuation[]
│ ├── PartnershipValuation[]
│ ├── Distribution[] (paid)
│ ├── KDocument[]
│ └── Document[]
└── Account[] (existing, unchanged)
└── Order[] (existing, unchanged)
New Enums
EntityType
Represents the legal structure of a family office entity.
| Value | Description |
|---|---|
INDIVIDUAL |
Natural person |
TRUST |
Revocable or irrevocable trust |
LLC |
Limited liability company |
LP |
Limited partnership (entity acting as an LP) |
CORPORATION |
C-Corp or S-Corp |
FOUNDATION |
Private foundation |
ESTATE |
Estate of a deceased individual |
PartnershipType
Represents the legal structure of an investment partnership.
| Value | Description |
|---|---|
LP |
Limited partnership |
GP |
General partnership |
LLC |
Limited liability company (operating as partnership) |
JOINT_VENTURE |
Joint venture |
FUND |
Investment fund (hedge fund, PE fund, etc.) |
DistributionType
Categorizes cash flows from partnerships to entities.
| Value | Description |
|---|---|
INCOME |
Ordinary income distribution |
RETURN_OF_CAPITAL |
Return of contributed capital |
CAPITAL_GAIN |
Capital gain distribution |
GUARANTEED_PAYMENT |
Guaranteed payment to partner |
DIVIDEND |
Dividend distribution |
INTEREST |
Interest income distribution |
KDocumentType
Type of tax document.
| Value | Description |
|---|---|
K1 |
Schedule K-1 (Form 1065) |
K3 |
Schedule K-3 (international) |
KDocumentStatus
Filing lifecycle of a K-document.
| Value | Description |
|---|---|
DRAFT |
Initial entry, not yet received from fund |
ESTIMATED |
Estimated figures, pre-final K-1 |
FINAL |
Final K-1 received and verified |
FamilyOfficeAssetType
Asset classes for partnership-held assets.
| Value | Description |
|---|---|
PUBLIC_EQUITY |
Publicly traded stocks/ETFs |
PRIVATE_EQUITY |
Private company investments |
REAL_ESTATE |
Real property |
HEDGE_FUND |
Hedge fund allocation |
VENTURE_CAPITAL |
VC fund allocation |
FIXED_INCOME |
Bonds, notes, fixed income |
COMMODITY |
Physical commodities |
ART_COLLECTIBLE |
Art, wine, collectibles |
CRYPTOCURRENCY |
Digital assets |
CASH |
Cash and equivalents |
OTHER |
Uncategorized |
ValuationSource
How a valuation was determined.
| Value | Description |
|---|---|
APPRAISAL |
Third-party appraisal |
MARKET |
Market-based pricing |
MANUAL |
Manual entry by administrator |
NAV_STATEMENT |
Fund administrator NAV statement |
FUND_ADMIN |
Fund administrator report |
DocumentType
Categories for uploaded documents.
| Value | Description |
|---|---|
K1 |
K-1 tax document PDF |
K3 |
K-3 tax document PDF |
CAPITAL_CALL |
Capital call notice |
DISTRIBUTION_NOTICE |
Distribution notice |
NAV_STATEMENT |
NAV statement |
APPRAISAL |
Appraisal report |
TAX_RETURN |
Tax return |
SUBSCRIPTION_AGREEMENT |
Subscription/partnership agreement |
OTHER |
Other document |
New Models
Entity
A legal person or structure that can own assets and be a member of partnerships.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String |
PK, UUID, auto-generated | Unique identifier |
name |
String |
Required, indexed | Entity display name |
type |
EntityType |
Required, indexed | Legal structure type |
taxId |
String? |
Optional | Tax identification number (EIN, SSN) |
createdAt |
DateTime |
Default: now() | Creation timestamp |
updatedAt |
DateTime |
Auto-updated | Last modification timestamp |
userId |
String |
FK → User.id, indexed | Administering user |
Relations:
user→User(many-to-one, cascade delete)ownerships←Ownership[](one-to-many)memberships←PartnershipMembership[](one-to-many)distributionsReceived←Distribution[](one-to-many)documents←Document[](one-to-many)
Partnership
An investment vehicle or legal structure that holds assets and has members.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String |
PK, UUID, auto-generated | Unique identifier |
name |
String |
Required, indexed | Partnership display name |
type |
PartnershipType |
Required, indexed | Legal structure type |
inceptionDate |
DateTime |
Required | Date partnership was formed |
fiscalYearEnd |
Int |
Default: 12 | Month number (1-12) of fiscal year end |
currency |
String |
Required | Base currency for NAV reporting |
createdAt |
DateTime |
Default: now() | Creation timestamp |
updatedAt |
DateTime |
Auto-updated | Last modification timestamp |
userId |
String |
FK → User.id, indexed | Administering user |
Relations:
user→User(many-to-one, cascade delete)members←PartnershipMembership[](one-to-many)assets←PartnershipAsset[](one-to-many)valuations←PartnershipValuation[](one-to-many)distributions←Distribution[](one-to-many)kDocuments←KDocument[](one-to-many)documents←Document[](one-to-many)
PartnershipMembership
The relationship between an entity and a partnership with ownership details.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String |
PK, UUID, auto-generated | Unique identifier |
entityId |
String |
FK → Entity.id, indexed | Member entity |
partnershipId |
String |
FK → Partnership.id, indexed | Partnership |
ownershipPercent |
Decimal |
Required | Ownership percentage (0-100) |
capitalCommitment |
Decimal? |
Optional | Total capital committed |
capitalContributed |
Decimal? |
Optional | Capital contributed to date |
classType |
String? |
Optional | Class designation (e.g., "Class A LP", "GP Interest") |
effectiveDate |
DateTime |
Required | Date this membership/percentage took effect |
endDate |
DateTime? |
Optional | Date membership ended (null = active) |
createdAt |
DateTime |
Default: now() | Creation timestamp |
updatedAt |
DateTime |
Auto-updated | Last modification timestamp |
Relations:
entity→Entity(many-to-one, cascade delete)partnership→Partnership(many-to-one, cascade delete)
Unique constraint: @@unique([entityId, partnershipId, effectiveDate]) — prevents duplicate membership records for the same entity-partnership on the same date.
Ownership
The relationship between an entity and a directly-owned account.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String |
PK, UUID, auto-generated | Unique identifier |
entityId |
String |
FK → Entity.id, indexed | Owning entity |
accountId |
String |
FK (composite) → Account.id | Owned account |
accountUserId |
String |
FK (composite) → Account.userId | Account's user |
ownershipPercent |
Decimal |
Required | Ownership percentage (0-100) |
acquisitionDate |
DateTime? |
Optional | Date ownership was acquired |
costBasis |
Decimal? |
Optional | Cost basis of ownership stake |
effectiveDate |
DateTime |
Required | Date this ownership took effect |
endDate |
DateTime? |
Optional | Date ownership ended (null = active) |
createdAt |
DateTime |
Default: now() | Creation timestamp |
updatedAt |
DateTime |
Auto-updated | Last modification timestamp |
Relations:
entity→Entity(many-to-one, cascade delete)account→Account(many-to-one, cascade delete, composite FK on[accountId, accountUserId])
Unique constraint: @@unique([entityId, accountId, accountUserId, effectiveDate]) — prevents duplicate ownership records for the same entity-account on the same date.
Distribution
A cash flow from a partnership or investment to a receiving entity.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String |
PK, UUID, auto-generated | Unique identifier |
partnershipId |
String? |
FK → Partnership.id, optional, indexed | Source partnership |
entityId |
String |
FK → Entity.id, indexed | Receiving entity |
type |
DistributionType |
Required | Distribution category |
amount |
Decimal |
Required | Distribution amount |
date |
DateTime |
Required, indexed | Distribution date |
currency |
String |
Required | Currency of distribution |
taxWithheld |
Decimal? |
Default: 0 | Tax withheld at source |
notes |
String? |
Optional | Free-text notes |
createdAt |
DateTime |
Default: now() | Creation timestamp |
updatedAt |
DateTime |
Auto-updated | Last modification timestamp |
Relations:
partnership→Partnership?(many-to-one, optional, cascade delete)entity→Entity(many-to-one, cascade delete)
KDocument
Structured K-1 or K-3 tax document data for a partnership tax year.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String |
PK, UUID, auto-generated | Unique identifier |
partnershipId |
String |
FK → Partnership.id, indexed | Associated partnership |
type |
KDocumentType |
Required | K-1 or K-3 |
taxYear |
Int |
Required, indexed | Tax year (e.g., 2025) |
filingStatus |
KDocumentStatus |
Default: DRAFT | Current filing status |
data |
Json |
Required | Structured K-1/K-3 box data (see K1Data interface) |
documentFileId |
String? |
FK → Document.id, optional | Linked uploaded PDF |
createdAt |
DateTime |
Default: now() | Creation timestamp |
updatedAt |
DateTime |
Auto-updated | Last modification timestamp |
Relations:
partnership→Partnership(many-to-one, cascade delete)documentFile→Document?(many-to-one, optional)
Unique constraint: @@unique([partnershipId, type, taxYear]) — one K-1 and one K-3 per partnership per tax year.
PartnershipAsset
An underlying asset held within a partnership.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String |
PK, UUID, auto-generated | Unique identifier |
partnershipId |
String |
FK → Partnership.id, indexed | Owning partnership |
assetType |
FamilyOfficeAssetType |
Required, indexed | Asset class |
name |
String |
Required | Asset display name |
description |
String? |
Optional | Detailed description |
acquisitionDate |
DateTime? |
Optional | Date acquired |
acquisitionCost |
Decimal? |
Optional | Cost at acquisition |
currentValue |
Decimal? |
Optional | Latest known value |
currency |
String |
Required | Asset currency |
metadata |
Json? |
Optional | Flexible metadata (address for real estate, etc.) |
createdAt |
DateTime |
Default: now() | Creation timestamp |
updatedAt |
DateTime |
Auto-updated | Last modification timestamp |
Relations:
partnership→Partnership(many-to-one, cascade delete)valuations←AssetValuation[](one-to-many)
AssetValuation
A point-in-time value assessment of a partnership asset.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String |
PK, UUID, auto-generated | Unique identifier |
partnershipAssetId |
String |
FK → PartnershipAsset.id, indexed | Valued asset |
date |
DateTime |
Required, indexed | Valuation date |
value |
Decimal |
Required | Assessed value |
source |
ValuationSource |
Required | How value was determined |
notes |
String? |
Optional | Valuation notes |
createdAt |
DateTime |
Default: now() | Creation timestamp |
updatedAt |
DateTime |
Auto-updated | Last modification timestamp |
Relations:
partnershipAsset→PartnershipAsset(many-to-one, cascade delete)
Unique constraint: @@unique([partnershipAssetId, date]) — one valuation per asset per date.
PartnershipValuation
A point-in-time NAV for an entire partnership.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String |
PK, UUID, auto-generated | Unique identifier |
partnershipId |
String |
FK → Partnership.id, indexed | Valued partnership |
date |
DateTime |
Required, indexed | Valuation date |
nav |
Decimal |
Required | Net Asset Value |
source |
ValuationSource |
Required | How NAV was determined |
notes |
String? |
Optional | Valuation notes |
createdAt |
DateTime |
Default: now() | Creation timestamp |
updatedAt |
DateTime |
Auto-updated | Last modification timestamp |
Relations:
partnership→Partnership(many-to-one, cascade delete)
Unique constraint: @@unique([partnershipId, date]) — one NAV per partnership per date.
Document
A file (PDF, etc.) associated with an entity, partnership, or K-document.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
String |
PK, UUID, auto-generated | Unique identifier |
entityId |
String? |
FK → Entity.id, optional, indexed | Associated entity |
partnershipId |
String? |
FK → Partnership.id, optional, indexed | Associated partnership |
type |
DocumentType |
Required | Document category |
name |
String |
Required | Display name / filename |
filePath |
String |
Required | Filesystem path to file |
fileSize |
Int? |
Optional | File size in bytes |
mimeType |
String? |
Optional | MIME type (e.g., application/pdf) |
taxYear |
Int? |
Optional | Associated tax year |
createdAt |
DateTime |
Default: now() | Creation timestamp |
updatedAt |
DateTime |
Auto-updated | Last modification timestamp |
Relations:
entity→Entity?(many-to-one, optional, cascade delete)partnership→Partnership?(many-to-one, optional, cascade delete)kDocuments←KDocument[](one-to-many, back-reference)
Modifications to Existing Models
Account (existing)
Add one optional back-reference — no column changes, no migration impact on existing data:
| New Field | Type | Description |
|---|---|---|
ownerships |
Ownership[] |
Back-reference to ownership records |
User (existing)
Add two optional back-references — no column changes, no migration impact:
| New Field | Type | Description |
|---|---|---|
entities |
Entity[] |
Entities administered by this user |
partnerships |
Partnership[] |
Partnerships administered by this user |
Application-Layer Types
K1Data (TypeScript interface, stored as JSON)
interface K1Data {
ordinaryIncome: number; // Box 1
netRentalIncome: number; // Box 2
otherRentalIncome: number; // Box 3
guaranteedPayments: number; // Box 4
interestIncome: number; // Box 5
dividends: number; // Box 6a
qualifiedDividends: number; // Box 6b
royalties: number; // Box 7
capitalGainLossShortTerm: number; // Box 8
capitalGainLossLongTerm: number; // Box 9a
unrecaptured1250Gain: number; // Box 9b
section1231GainLoss: number; // Box 9c
otherIncome: number; // Box 11
section179Deduction: number; // Box 12
otherDeductions: number; // Box 13
selfEmploymentEarnings: number; // Box 14
foreignTaxesPaid: number; // Box 16
alternativeMinimumTaxItems: number; // Box 17
distributionsCash: number; // Box 19a
distributionsProperty: number; // Box 19b
}
Validation Rules
- Ownership percentage:
0 < ownershipPercent <= 100. Sum of active ownership percentages for a single account must not exceed 100. - Partnership membership percentage:
0 < ownershipPercent <= 100. Sum of active membership percentages for a single partnership must not exceed 100. - Distribution date: Must be >= partnership inception date.
- K-document tax year: Must be >= year of partnership inception date.
- Partnership fiscal year end: Must be 1-12.
- Entity deletion: Blocked if entity has any active ownerships or memberships (endDate is null).
- Partnership deletion: Cascades to all memberships, assets, valuations, distributions, K-documents, and documents.