Background
Founder screening was the biggest bottleneck in deal flow. Analysts spent hours each week manually reviewing LinkedIn profiles, and scores varied depending on who was doing the review.
I built an end-to-end system to automate that workflow: fine-tuned models score founders against a proprietary investment rubric, sync with the CRM and deal pipeline, and give the team a conversational interface to query founders and deals. What used to take hours per candidate now runs in seconds.
That meant scoring profiles in seconds instead of hours, feeding analyst corrections back into training, keeping contacts deduped with live pipeline status, and giving the team a plain-language way to query across founders and deals.
Architecture
Analyst Dashboard
Search founders, score profiles, override scores, push to CRM
FastAPI Backend
OpenAI
Scoring + fine-tuning
PostgreSQL
Profiles, scores
CRM API
Synced records
Scraping API
LinkedIn profiles
It's a Python monolith deployed on a PaaS. The backend is built with FastAPI and exposes both synchronous scoring endpoints and async webhook-based flows. The database is PostgreSQL via Supabase. External integrations handle LinkedIn scraping, CRM sync, and LLM inference + fine-tuning.
Scoring Engine
At the center is the scoring engine: given a founder's profile, it returns a scored evaluation with reasoning.
Rubric Engineering
The rubric went through 4 iterations to calibrate the model's judgment against the investment team's. We landed on a structured format with score bands and guardrails that kept evaluations consistent across analysts.
Hard floors and caps were the most important piece: they stop the model from under-scoring strong profiles or over-scoring weak ones, regardless of how polished the LinkedIn copy looks. That turned out to be the single most impactful design decision in the system.
Prompt Design
The scoring prompt follows a system + user pattern. The system prompt sets the investor persona and output format. The user prompt injects the rubric and a canonicalized profile:
1def score_profile(profile, experiences):2 canon = canonicalize(profile, experiences)34 messages = [5 {6 "role": "system",7 "content": "You are an investor. Score 1-9 using the rubric. "8 "Return JSON only: {\"score\": x, \"why\": \"<=60 words\"}."9 },10 {11 "role": "user",12 "content": f"Rubric ->\n{RUBRIC}\n\nProfile ->\n{canon}\n\n"13 "Return JSON only.",14 },15 ]1617 resp = client.chat.completions.create(18 model=MODEL, # base or fine-tuned19 temperature=0,20 messages=messages,21 )22 return json.loads(resp.choices[0].message.content)
Canonicalization is important because it reduces messy scraper JSON into a consistent text format (name, headline, about, top skills, 5 most recent experiences sorted by recency) so the model always sees the same structure regardless of input source.
Autonomous Pipeline
The flagship endpoint handles the full lifecycle in a single API call:
- Cache check: if the profile exists and has a score, return instantly
- Scrape: trigger the scraping service to pull the LinkedIn profile
- Normalize: parse raw JSON, generate a canonical profile ID (MD5 of URL), deduplicate via SHA256
- Score: run the profile through the rubric with the current model
- Persist: store profile, experiences, and score in the database
For production reliability, there's also a webhook-based flow that decouples scraping from scoring. The scraping service calls back to a webhook endpoint when complete, triggering normalization and scoring asynchronously. This prevents timeout issues and allows retry logic.
Fine-Tuning
The human-in-the-loop training loop is what makes the system improve over time. Analysts review AI scores in the dashboard and can override them with their own judgment and reasoning.
Conversational Assistant
Beyond scoring, the system includes a conversational AI layer that gives the investment team a natural language interface across all data sources. It works across email, messaging, API, and the dashboard.
Intent Classification
Inbound messages are classified into intents using a structured JSON-mode call:
1INTENTS = {2 "lookup": "Wants to know if we've seen a founder or company",3 "reminder": "Wants to set a reminder or schedule a task",4 "memo": "Wants to generate a deal memo",5 "draft": "Wants to draft an outbound email or reply",6 "unknown": "Cannot determine; escalate to human",7}
The classifier also extracts entities (company name, founder name, LinkedIn URL, domain) in the same call using JSON mode, so we can immediately route to the correct lookup strategy without a second LLM call.
CRM Lookup
The lookup system searches across multiple data sources in priority order:
- LinkedIn URL (highest confidence): exact match in CRM or profiles table
- Company name: fuzzy search across CRM organizations
- Founder name: search across scored profiles, then cross-reference with CRM
- Domain: fallback to domain-based lookup
Each result includes a confidence level (high, medium, low) so the assistant can flag ambiguous matches instead of guessing. This was important because in a VC context, confidently returning the wrong company is worse than admitting uncertainty.
Reply Generation
The reply generator uses few-shot examples to maintain a consistent voice: concise, factual, never speculative. It injects live CRM and database context before generating:
1def generate_reply(user_message, lookup_result, intent, channel):2 context = build_context_string(lookup_result)34 messages = [5 {"role": "system", "content": SYSTEM_PROMPT + channel_note},6 *FEW_SHOT_EXAMPLES,7 {"role": "system",8 "content": f"Relevant data:\n\n{context}"},9 {"role": "user", "content": user_message},10 ]
The system adapts tone by channel: shorter for messaging, more structured for email. The assistant never fabricates information; if no match is found, it says so clearly and offers to add the entry.
Deployment
The backend is deployed on a PaaS as a single FastAPI service. This simplicity was intentional. Scoring requests are short-lived (under 15 seconds), and the webhook flow handles longer operations, so there's no need for complex orchestration, queues, or workers.
Environment configuration uses the platform's secrets management for all API keys. The service auto-restarts on deploys with zero downtime via health checks against /healthz.
Results
The system went from concept to production in about 3 months, built and maintained as a solo engineer.
Hours → seconds
Screening time per candidate
Consistent
Every founder scored against the same rubric
Self-improving
Analyst corrections feed back into training
Next Steps
These are the natural extensions that would deepen coverage without changing what already works.
Agentic Screening
Move from single-shot scoring to multi-step research, letting the system autonomously gather funding history, co-founder backgrounds, and company traction before producing a score.
RAG over Deal History
Build a retrieval layer over historical deal memos and IC notes to give the assistant deeper context about why the firm passed or invested in similar companies.
Email Automation
Extend the assistant to fully handle inbound deal flow emails: classify, lookup, draft responses, and route to the right team member automatically.
Multi-Firm Generalization
Abstract the rubric and scoring system to support multiple firms with different investment theses, turning it into a platform.