You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

6.8 KiB

Quickstart: Portfolio Performance Views

Feature: 003-portfolio-performance-views Date: 2026-03-16

Overview

This feature adds three analytical views to the portfolio management application:

  1. Portfolio Summary — Entity-level rollup of financial performance metrics
  2. Asset Class Summary — Same metrics grouped by asset class
  3. Activity Detail — Transaction-level ledger with tax basis tracking

All three views live on a single tabbed page at route /portfolio-views.

Architecture

┌─────────────────────────────────┐
│  Angular Client                 │
│  /portfolio-views               │
│  ┌─────┐ ┌───────┐ ┌────────┐  │
│  │Tab 1│ │Tab 2  │ │Tab 3   │  │
│  │Port.│ │Asset  │ │Activity│  │
│  │Summ.│ │Class  │ │Detail  │  │
│  └──┬──┘ └──┬────┘ └──┬─────┘  │
│     │       │          │        │
│  FamilyOfficeDataService        │
│     │       │          │        │
└─────┼───────┼──────────┼────────┘
      │       │          │
      ▼       ▼          ▼
┌─────────────────────────────────┐
│  NestJS API                     │
│  FamilyOfficeController         │
│  GET /portfolio-summary         │
│  GET /asset-class-summary       │
│  GET /activity                  │
│     │                           │
│  FamilyOfficeService            │
│     │                           │
│  FamilyOfficePerformanceCalc    │
│  (existing XIRR/TVPI/DPI/RVPI) │
│     │                           │
│  PrismaService → PostgreSQL     │
└─────────────────────────────────┘

Key Decisions

# Decision See
R-001 Reuse existing XIRR calculator with merged cash flows research.md
R-002 Asset class = majority PartnershipAsset.assetType research.md
R-003 Extend K1Data JSON with tax basis fields research.md
R-004 New /portfolio-views page with 3 Material tabs research.md
R-005 Custom Angular pipe for accounting number format research.md
R-006 Page-level valuation year filter research.md

Data Flow

Portfolio Summary / Asset Class Summary

1. Client selects valuation year (default: current year)
2. GET /family-office/portfolio-summary?valuationYear=2025
3. Backend:
   a. Load all user entities with active memberships
   b. For each entity, for each membership:
      - Sum capitalCommitment → Original Commitment
      - Sum capitalContributed → Paid-In
      - Load latest NAV ≤ year-end × ownershipPercent → Residual
      - Load distributions ≤ year-end → Distributions
      - Build dated cash flows (contributions, distributions, terminal NAV)
   c. Per entity: compute DPI, RVPI, TVPI, XIRR from aggregated flows
   d. Compute "All Entities" totals row
4. Return IPortfolioSummary
5. Client renders mat-table with accounting format pipe

Asset Class Summary follows the same flow but groups by partnership's dominant asset type instead of entity.

Activity Detail

1. Client optionally selects entity, partnership, year filters
2. GET /family-office/activity?entityId=...&year=2024&skip=0&take=50
3. Backend:
   a. Find all (entity, partnership, year) combinations from:
      - PartnershipMembership (entity-partnership links)
      - KDocument (partnership-year income data)
      - Distribution (entity-partnership dated amounts)
   b. For each combination:
      - Pull K1Data fields → income components
      - Pull extended fields → tax basis
      - Sum distributions for that year
      - Compute derived fields (totalIncome, excessDistribution, etc.)
   c. Apply filters, paginate
4. Return IActivityDetail with filter options
5. Client renders mat-table with conditional row highlighting

Files to Create/Modify

New Files

File Purpose
apps/client/src/app/pages/portfolio-views/portfolio-views-page.component.ts Main tabbed page
apps/client/src/app/pages/portfolio-views/portfolio-views-page.html Template with 3 tabs
apps/client/src/app/pages/portfolio-views/portfolio-views-page.scss Styles
apps/client/src/app/pages/portfolio-views/portfolio-views-page.routes.ts Route config
apps/client/src/app/pipes/accounting-number.pipe.ts Custom number format pipe

Modified Files

File Changes
apps/api/src/app/family-office/family-office.controller.ts Add 3 new GET endpoints
apps/api/src/app/family-office/family-office.service.ts Add getPortfolioSummary(), getAssetClassSummary(), getActivity()
apps/client/src/app/services/family-office-data.service.ts Add 3 new fetch methods
apps/client/src/app/app.routes.ts Add portfolio-views route
libs/common/src/lib/interfaces/family-office.interface.ts Add response interfaces
libs/common/src/lib/interfaces/k-document.interface.ts Extend K1Data with tax basis fields

Reused (no changes)

File What's Reused
apps/api/src/app/portfolio/calculator/family-office/performance-calculator.ts XIRR, TVPI, DPI, RVPI computations
libs/ui/src/lib/performance-metrics/ Metric display patterns (reference for styling)

Testing Strategy

Layer What How
Unit (API) getPortfolioSummary() aggregation logic Jest: mock Prisma, verify metric calculations match expected values
Unit (API) getAssetClassSummary() grouping Jest: verify partnerships correctly grouped by asset type
Unit (API) getActivity() K1Data → Activity row mapping Jest: verify all derived fields computed correctly
Unit (Client) Accounting number pipe Jest: verify "(1,000)", "1,000,000", "-" formatting
Integration API endpoints return correct response shapes Jest: hit endpoints with test data, verify IPortfolioSummary/etc. shapes
E2E Tab navigation and data display Manual or Playwright: navigate to /portfolio-views, switch tabs, verify data

Dependencies on Existing Code

  • FamilyOfficePerformanceCalculator — Must remain stable (no breaking changes)
  • FamilyOfficeService.getUserPartnerships() — Existing private method for scoping by user/entity
  • PrismaService — All database access
  • AuthGuard — Route protection
  • permissions.readEntity — Endpoint authorization
  • FamilyOfficeAssetType enum — Asset class categorization
  • K1Data interface — Extended but backward-compatible