Sync pipeline company financials to CRM.
Keep every CRM company record current with the latest financials and round history without manual entry.
Helper code snippet
Reusable code that powers a step inside this automation — loop bodies, parsers, routers, and other glue.
- PitchBook Company Enrichment →Python · pitchbook_enrich.py
One-shot PitchBook enrichment: investors, geography, round type, total raised, last round size & valuation — with USD enforcement and a configurable top-investor watchlist.
Reference build
A working reference build that runs in production. When a deal team flips "Get Financial Data = Yes" on a company in the CRM pipeline, the orchestrator resolves the company against the data provider, pulls investor roster, geography, and full round history, normalizes everything to USD, and writes the structured fields back onto the CRM record — plus updates each matched top-investor's portfolio list. End-to-end in ~30 seconds, no analyst touch.
Vendors below are our choices. The flow is roles-not-vendors; every layer swaps cleanly.
Fields written back to the CRM
The Python step always returns the same shape, whether resolution succeeded or not. Stable output contract = stable downstream filters and write steps.
| Field | What it holds |
|---|---|
| investors | Full investor roster from PB, comma-joined |
| matched_investors | Subset that maps to our top-investor watchlist (canonical names + aliases) |
| match_count | How many watchlist investors are on the cap table |
| company_geography | HQ city, state, country from PB bio |
| last_round_type | Most recent financing deal type (e.g. Series B) |
| total_funding_amount | Lifetime money raised (USD-enforced) |
| last_funding_amount | Most recent round size (USD-enforced) |
| last_funding_valuation | Post-money valuation, falls back to last known valuation |
| last_funding_date | Date of the most recent financing |
Gotchas
The things you only learn by running this in anger for a year.
- 01USD-only enforcement: any non-USD currency on totalMoneyRaised, lastFinancingSize, lastFinancingValuation, or lastKnownValuation raises. Multi-currency funds break field comparability — fail loud instead of silently writing GBP into a USD column.
- 02Graceful no-data path: every PB call is wrapped in a safe_get that returns {} on error. If search returns nothing, the flow still emits a full output shape with data_status = 'no data' so downstream filters and writes don't NPE.
- 03Company-first search resolution: prefer pbId of an item with primaryFirmType.type = 'COMPANY'. Otherwise fall back to the first item. Avoids resolving to investors/people with the same name.
- 04Investor matching uses two passes: an exact case-insensitive canonical map plus a hand-maintained ALIASES dict ('Coatue Management' → 'Coatue', 'M12 - Microsoft's venture fund' → 'M12'). Without aliases, ~15% of matches are missed.
- 05Loop step iterates matched_investors and finds-or-skips each in the Company object, then writes the portfolio company onto the investor record. Iteration cap = 500 to prevent runaway loops on bad parses.
- 06Trigger gate: only runs when 'Get Financial Data' attribute = Yes on the deal-pipeline entry. Prevents accidental re-runs from unrelated field edits burning PB API budget.
Swap matrix
Every layer is replaceable. The orchestrator owns the wiring so the CRM, the data provider, and the transform layer all move independently.