Document Intelligence Pipeline for Private Investments
Overview
The Document Intelligence Pipeline adds automated document classification and structured data extraction to Silvia’s private investment tracking. Users can upload fund statements (quarterly reports, capital calls, distribution notices) and have financial data automatically extracted, validated, and applied to their private investments — with human-in-the-loop confirmation before any data is saved. This builds on the existing Vault/Reducto document infrastructure and adds two new AI layers: document classification and structured financial extraction.Architecture
Two Entry Points
- Import Statement (page-level): Creates a new private investment from a document. Button on the Private Investments page.
- Upload Fund Statement (dialog-level): Updates an existing private investment with data from a new document. Button inside the Add/Edit Investment dialog.
Processing Flow
- File upload → Written to temp file on server
- Reducto parse → Synchronous text extraction (not async QStash job, since user is waiting)
- Document classification → Claude determines document type (fund statement, capital call, K-1, etc.)
- Gate check → If not a financial investment document, returns error with explanation
- Structured extraction → Claude extracts all financial fields with source text attribution
- Validation → Cross-field math checks + comparison against previous values
- User confirmation → Editable dialog showing extracted data, source quotes, confidence scores, and warnings
- Save → User confirms, data is written to
private_investmentstable
Files
New Files
Modified Files
Database Schema Changes
Migration: 20260319_add_private_investment_statement_columns.sql
Must be run before deploying. Safe for production — adds columns with nullable/default values, then backfills investment_type to 'basic' for existing rows and adds a NOT NULL constraint. The backfill UPDATE is fast (single column, single value) and the NOT NULL alter is instantaneous since all rows will already have a value.
New columns on private_investments:
New column on vault_documents:
New indexes:
idx_vault_documents_private_investment_id— partial index whereprivate_investment_id IS NOT NULLidx_private_investments_last_statement_date— partial index whereis_active = true
Investment Types
Theinvestment_type column uses a CHECK constraint with these allowed values:
When importing from a document, the extractor infers the type from document content. When adding manually, it defaults to
basic.
Document Classification
File:lib/document-classifier.ts
Classifies documents into types: fund_quarterly_statement, capital_call, distribution_notice, k1, bank_statement, tax_return, insurance, deed, brokerage_statement, loan_document, other.
Used in two places:
- Parse-statement route — Synchronous classification to gate extraction (rejects non-financial docs)
- Check-document-completion route — Awaited classification inside the QStash handler for all vault documents (populates
vault_documents.document_classification)
Structured Extraction
File:lib/extractors/fund-statement-extractor.ts
Extracts 15 financial fields from fund statements. Every extracted field includes:
- value — The extracted data point
- sourceText — Verbatim quote from the document (max 100 chars)
- sourcePage — Page number where found
- confidence — 0.0 to 1.0
Validation Checks
Built-in validation runs automatically after extraction:- NAV × shares vs total value — Flags if
navPerShare × totalSharesdoesn’t matchtotalValue(2% tolerance) - Commitment - called vs unfunded — Flags if
commitment - capitalCalleddoesn’t matchunfundedCommitment(2% tolerance) - IRR bounds — Flags if IRR is below -100% or above 1000%
- MOIC bounds — Flags if MOIC is below 0x or above 100x
- Negative total value — Flags negative values for review
- Previous value comparison — When updating an existing investment, flags if NAV or total value changed by more than 5x (possible extraction error)
Extraction Confirmation Dialog
File:components/extraction-confirmation-dialog.tsx
The human-in-the-loop guardrail. Shows:
- Document classification badge
- Investment type selector (dropdown, pre-filled from extraction)
- All extracted fields as editable inputs
- Source text quotes (italicized, with page numbers) below each field
- Confidence badges (High/Medium/Low) for each field
- Validation warnings in amber, errors in red
- Cancel and “Confirm & Update” buttons
null value and 0 confidence are automatically hidden (not present in document).
How It Ties Into the Rest of the App
Net Worth and Financial Calculations
Onlytotal_value feeds into net worth. The pipeline:
total_valueis set from extraction (or computed asnavPerShare × totalShares)usePrivateInvestments().totalValuesums all investments’total_valueuseNetWorth()includesprivateInvestmentsValueintotalAssetsfinancial-data-cache.tsuses the sametotal_valuefor cached snapshotsportfolio_daily_snapshotsstorestotal_private_investments
irr, moic, tvpi, dpi, etc.) are display-only — they don’t affect any financial calculations.
Existing Consumers Unaffected
These all continue working without changes:- Dashboard cards and net worth breakdown
- Assets/liabilities page
- AI chat financial tools
- PDF export
- Tax estimate calculations
- Unrealized gains calculations
- CSV/data export
- Daily snapshot cron
Running the Migration
Execute the SQL insupabase/migrations/20260319_add_private_investment_statement_columns.sql against your Supabase database:
Option 1: Supabase SQL Editor — paste and run the SQL directly
Option 2: CLI
investment_type = 'basic' for any existing rows and sets it NOT NULL. No existing queries break. No downtime required.
Future Considerations
- Investment type selector on manual add form — Currently manual adds default to
basic. Could add a type selector to the investment dialog for manual categorization. - Type-specific detail views — Show different metric layouts per investment type (e.g., commitment/unfunded for PE, NAV/redemption for hedge funds).
- Email integration — Route emailed documents through the same classification → extraction pipeline.
- Vault integration — Trigger extraction when documents are uploaded to the vault and classified as fund statements.
- Google Drive / Dropbox connectors — Direct feeds into the classification pipeline.
- History tracking — Store extraction results over time for trend analysis (IRR trajectory, NAV history).