SPEC 014 — Reports
| Field | Value |
|---|---|
| Status | DRAFT |
| Priority | P1 — Core Platform |
| Backend | None dedicated — data sourced from cap table, ESOP, and holdings entities via shared loaders |
| Frontend | equa-web/src/modules/reports/ |
1. Feature Purpose
Reports provide organization administrators and stakeholders with tabular views of cap table data. Three report types are available: Holder Report (per-member ownership summary), Pools Report (option pool and convertible instrument utilization), and Holdings Report (individual certificate-level detail). All reports support dynamic filtering, sortable columns, and footer totals. Reports are read-only views over existing cap table data — no dedicated backend module exists.2. Current State (Verified)
2.1 Holder Report
| Detail | Value |
|---|---|
| File | equa-web/src/modules/reports/pages/holder-report.tsx |
| Component | HolderReportsPage |
| Data loader | loadCaptableData('false') |
| Permission | BuiltInPermission.viewCapTable OR BuiltInPermission.viewSelf |
| HOC chain | withPermissions → withLoadingCachedMultiple |
| Columns | Holder (avatar + name link), Investment (USD), Outstanding (shares), Ownership (%), Fully Diluted (shares), Ownership (%) |
| Footer | Row totals across all columns |
| Row filtering | Excludes members with no holdings and no voided records |
| Page size | 100,000 (effectively unbounded) |
| Member link | Opens in new tab via organizationMemberPath |
2.2 Pools Report
| Detail | Value |
|---|---|
| File | equa-web/src/modules/reports/pages/pools-report.tsx |
| Component | PoolsReportsPage |
| Data loader | loadCaptablePools |
| Permission | BuiltInPermission.viewCapTable OR BuiltInPermission.viewSelf |
| Columns | Pool (name link), Equity (link), Type, Authorized, Treasury, Investment (USD), Fully Diluted, Ownership (%) |
| Filters | Equity (dynamic from pool data), Type (Incentive / Convertible) |
| Filter component | dynamicFilter shared component |
| Footer | Row totals for Authorized, Treasury, Investment, Fully Diluted, Ownership |
| Pool link | Links to poolPath for incentive pools, viewConvertibleInstrumentPath for convertibles |
2.3 Holdings Report
| Detail | Value |
|---|---|
| File | equa-web/src/modules/reports/pages/holdings-report.tsx |
| Component | HoldingsReportsPage |
| Data loader | loadQueryData |
| Permission | BuiltInPermission.viewCapTable OR BuiltInPermission.viewSelf |
| Columns | Cert ID (certificate image + link), Holder (link), Class, # (index), Type, Issue Date, Investment (USD), Outstanding, Ownership (%), Fully Diluted, Ownership (%) |
| Filters | Holder (dynamic), Class (dynamic), Type (dynamic) |
| Exclusions | Convertible instruments, pools, and org assets are filtered out of the holdings list |
| Footer | Row totals for Investment, Outstanding, Ownership, Fully Diluted, Ownership |
2.4 Module Exports
| File | Exports |
|---|---|
pages/index.ts | HolderReportsPage, PoolsReportsPage, HoldingsReportsPage (re-exported from page files) |
index.ts | Re-exports all pages |
3. Data Model
Reports do not maintain their own data model. They consume data from:Source: Cap Table Dashboard (GetCaptableDashboardResponse)
| Field | Type | Used in |
|---|---|---|
members | Array of member records with holdings, voided, memberInvestment, memberOutstanding, memberOutstandingPer, memberFullyDiluted, memberFullyDilutedPer | Holder Report |
Source: Pools Report (GetPoolsReportResponse)
| Field | Type | Used in |
|---|---|---|
pools | Array with name, equity, equityName, internalType, authorized, treasury, investment, fullyDiluted, ownership, plan, hash | Pools Report |
Source: Holdings Query (QueryResponse)
| Field | Type | Used in |
|---|---|---|
holding | Array with name, holderName, class, internalType, issueDate, capitalContribution, outstanding, outstandingPercentage, fullyDiluted, fullyDilutedPercentage, holdingType, owner | Holdings Report |
Enums Referenced
| Enum | Values | Used in |
|---|---|---|
HoldingRecordType | convertibleInstrument, pool, orgAsset (excluded from holdings report) | Holdings Report filter |
BuiltInPermission | viewCapTable, viewSelf | All reports |
4. API Endpoints
Reports consume existing cap table endpoints — no dedicated report endpoints exist.| Method | Path | Auth | Description | Used by |
|---|---|---|---|---|
| GET | /api/v1/organization/:organization/captable/dashboard | Session + permission | Cap table member summary | Holder Report |
| GET | /api/v1/organization/:organization/captable/pools | Session + permission | Pool summary data | Pools Report |
| GET | /api/v1/organization/:organization/captable/query | Session + permission | Individual holdings with certificate data | Holdings Report |
5. Frontend Components
Module: equa-web/src/modules/reports/
| Component | File | Purpose |
|---|---|---|
HolderReportsPage | pages/holder-report.tsx | Table of holders with investment, outstanding, ownership, fully diluted |
PoolsReportsPage | pages/pools-report.tsx | Table of option pools and convertible instruments with utilization metrics |
HoldingsReportsPage | pages/holdings-report.tsx | Table of individual holdings/certificates with filtering by holder, class, type |
Shared Dependencies
| Component | Source | Purpose |
|---|---|---|
Table | @components/tables/table | Base table component with sorting, pagination, borders |
dynamicFilter | @shared/components/dynamic-filter | Creates filter dropdowns from data |
PathLink | @components/navigation | Navigation links to member/security/pool pages |
Avatar | @components/avatar | Member avatar in holder report rows |
CertificateLink | @modules/captable/components | Certificate image + link in holdings report |
withPermissions | @shared/hocs/with-permissions | Permission guard HOC |
withLoadingCachedMultiple | @components/loading | Data loading with caching |
PageContent / PageContentHeader | @components/pages | Page layout wrappers |
6. Business Rules
- Permission gating: All three reports require either
viewCapTableorviewSelfpermission. Users withviewSelfsee only their own holdings in the data. - Holder Report excludes members who have no holdings AND no voided records (i.e., members with zero equity involvement are hidden).
- Holdings Report excludes records of type
convertibleInstrument,pool, andorgAsset— these appear in their own dedicated views. - Pools Report classifies pools as either
Incentive(ESOP plans) orConvertible(convertible instruments). - Ownership percentages are displayed to 2 decimal places (
.toFixed(2)). - Investment values are formatted as USD using
optionalUsdString. - Share counts use
toCommaFloatfor comma-separated formatting. - Footer totals aggregate across all visible rows (post-filter).
- All links from reports open in a new browser tab (
target='_blank'). - Page size is set to 100,000 across all reports — effectively no client-side pagination.
7. Acceptance Criteria
- Holder Report displays all members with equity involvement, with correct investment, outstanding, ownership, and fully diluted values
- Holder Report footer shows accurate totals for all numeric columns
- Pools Report displays incentive pools and convertible instruments with correct metrics
- Pools Report filter by Equity and Type works correctly
- Holdings Report displays individual certificates with all metadata fields
- Holdings Report filter by Holder, Class, and Type works correctly
- All percentage columns display 2 decimal places
- All USD columns are properly formatted
- Links to members, securities, pools, and certificates open in new tabs
- Users without
viewCapTableorviewSelfpermission cannot access reports - Reports load successfully for organizations with large cap tables (1000+ holdings)
8. Risks
| Risk | Impact | Mitigation |
|---|---|---|
| No server-side pagination (page size 100,000) | Browser performance with very large cap tables | Implement server-side pagination or virtual scrolling for 1000+ rows |
| No CSV/PDF export | Users must screenshot or copy data manually | Add export functionality (referenced in platform feature list but not yet implemented in reports module) |
| Permission check uses OR logic (viewCapTable OR viewSelf) | viewSelf users may see aggregate totals that reveal other holders’ data | Ensure server filters response data to only the requesting user’s holdings when viewSelf is the sole permission |
| Reports pull full cap table data on each page load | Redundant API calls when switching between report types | Shared data cache across report pages within same session |
| No date range filtering | Cannot view historical snapshots | Future enhancement: add as-of-date parameter for point-in-time reporting |