← Back to all files
File 004 · Case StudyStatus: Live

Lead Qualification Agent

A pipeline that matches messy inbound leads to real company data, scores them against a transparent ICP model and routes them, so an SDR doesn't have to read 400 form fills a week.

Classification
Systems design · SQL as business logic · Applied AI judgment
Stack
Python · SQLite · SQL · TypeSafe AI (Jev) · Ollama · SEC EDGAR API
Subject
Inbound triage, SLA decay, and the protection of an AE's time.
Results

Tested on 400 leads.

94%
Cheaper than a comparable hosted LLM
$0.008 vs. $0.14 for the 320 leads that reached the model step (the other 80 of the 400 were filtered out earlier). Jev's tokens are measured; the Haiku figure is estimated from the same prompts. Jev answers three questions per lead (intent, buying stage, priority) for less than one question would cost on Claude Haiku 4.5.
93.2%94.0%
Intent classification — pre- vs. post-Jev
Same 400 leads, same labels. Before: a free-text prompt to a local Ollama model, parsed as JSON. After: typed questions to TypeSafe AI's Jev, answered in parallel with a confidence score. Exhibit B below shows the code.
100%97.4%
P1 recall — pre- vs. post-Jev
112 of 115. The one number the rebuild made worse: three leads that used to route correctly now don't. A more accurate classifier didn't improve every downstream metric.
99.7%
Company name resolution
No wrong guesses on resolvable leads. This stage still runs on Ollama and wasn't affected by the Jev rebuild.
99.1%
P1 precision
A false-positive P1 wastes an AE’s morning, so this is the number that protects their time.
10/10
Prompt-injection resistance
All ten adversarial attempts were refused, with the evidence published in the repo.
The Brief

A personal build that matches messy inbound company names to real filings data, scores each lead with a SQL rubric a RevOps lead can read and retune, reads intent with a typed model call, and routes the result: P1 to an AE now, P2 to nurture, P3 archived with a reason.


Commercial Impact

Inbound funnels bury sales teams in volume: students, competitors, wrong-fit companies. The few real buyers go stale while triage takes days. I built this around the qualification problem I work on every week in sales. It takes manual triage off an SDR's plate and keeps false-positive P1s off an AE's calendar, with 99.1% P1 precision on a 400-lead test set.

Design Thesis

Deterministic where possible, agentic where valuable.

Scoring is SQL, so it can be audited, tested and retuned without touching code. Judgment calls, like resolving an ambiguous company name or reading intent from a vague message, go to a model, and only where a model does better than a rule. Each of those two judgments uses a different model, chosen for the job. Everywhere else, rules win.

The Pipeline

One lead, six stages.

A lead moves left to right. Solid stages are deterministic SQL or keyword matching. Only two steps hand off to a model, and each uses a different one. RESOLVE calls a local LLM (Ollama) only when two company-name matches are tied. CLASSIFYcalls TypeSafe AI's Jev, a typed-judgment model rather than a chat model, to read intent from the free-text message. Exhibit B, further down, shows what that difference looks like in code.

lead_0047
Stage 01
INGEST
A raw form fill arrives: name, email, company string and a free-text message.
DETERMINISTIC
Stage 02
RESOLVE
Messy company string matched to real data (SEC EDGAR, Wikipedia, site scrape).
OLLAMA · ONLY WHEN 2 MATCHES TIE
Stage 03
ENRICH
Firmographics attached: size, industry, public/private, geography. Free-tier only.
DETERMINISTIC
Stage 04
CLASSIFY
Reads which of four intents the free-text message signals. See Exhibit B below.
JEV · TYPED JUDGMENT MODEL
Stage 05
SCORE
CORE · SQL RUBRIC
A transparent SQL rubric produces an ICP score and priority tier. This is the core of the system.
DETERMINISTIC
Stage 06
ROUTE
P1 to an AE now, P2 to nurture, P3 archived with a reason.
DETERMINISTIC
SECURITY LAYERINJECTION / SPAM / PERSONA — DETERMINISTIC BY DESIGNRuns across all six stages. A security classifier shouldn't depend on the judgment of the model an attacker is trying to manipulate.
P1
AE, now
Hot buyer, routed to a human immediately.
P2
Nurture
Real, but not ready. Into a sequence.
P3
Archive, with reason
Logged and closed, with every rejection auditable.
Solid ink = deterministicDashed oxblood = model-assisted (Ollama or Jev, see each stage's badge)
Stage 04, Rebuilt

Where Jev sits, and what it replaced.

CLASSIFY is the one step where a message has to be read rather than looked up. “What's your pricing, and how does it compare to X?” could come from a buyer asking about cost, or from a competitor doing research. That call used to go to a hand-written prompt on a local LLM, with the hope that clean JSON came back. I rebuilt that step, and only that step, on TypeSafe AI's Jev.

The rebuilt step is more accurate, much cheaper and simpler to maintain.

01Better
93.2% → 94.0%, with a confidence score

Accuracy went up slightly. The bigger change is the confidence score, which the old prompt never had: 96% of Jev's remaining errors get flagged for a person before they reach a rep. Before, a wrong answer looked exactly like a right one.

02Cheaper
About 6% of the cost of Claude Haiku

$0.008 vs. an estimated $0.14 for the same 320 model calls, using tokens measured on this workload. That's three judgments per lead (intent, buying stage, priority) for less than one question would cost on Haiku.

03The Build
Typed answers instead of parsed JSON

There's no prompt template to maintain, no JSON to parse, and no retry-then-guess fallback for malformed responses. A typed question goes in and a typed answer with a confidence score comes out. Exhibit B below shows the before and after.

One regression:three leads that used to route correctly no longer do. A better overall accuracy number didn't carry through to every downstream metric, which is why I re-tested the whole pipeline and not just the classifier. The full write-up is in jev-rebuild-notes.md.

EXHIBIT B — CLASSIFY, BEFORE / AFTER
-- BEFORE: free-text prompt to Ollama, parsed as hopeful JSON
prompt = f"Classify intent into one of 4 categories...\n"
f'Respond ONLY with JSON: {{"intent":..,"confidence":..}}'
parsed = call_llm(prompt) # might not parse
if parsed is None or parsed["intent"] not in VALID:
intent = keyword_fallback(message) # silent degrade, no confidence
-- AFTER: typed questions to Jev, answers constrained by schema
questions = {
"intent": Choice(criteria={4 real options, real text}),
"buying_stage": Score(criteria=[...]), -- new signal
}
response = client.system_one(state={"title":.., "message":..}, questions=questions)
intent, confidence = response.choices["intent"].choice, .confidence
if confidence < 0.6: flag_for_review() -- low confidence goes to a person

Condensed for the page, with the real signatures and field names. Full source: jev_qualify.py.

What This Demonstrates

What the build shows.

01

Design a scoring model, then check it against real judgment calls.

A hand audit found company size overriding strong buying signals. A CFO with approved budget shouldn't score lower because their company is large. Rebuilding the scorer around that finding exposed a second gap, which I also fixed. The rubric that shipped had been wrong twice first.

02

Know where AI belongs, and where it doesn't.

Injection detection, spam filtering and persona classification are deterministic on purpose. A model only runs where the task is ambiguous, and the two ambiguous judgments use different tools: a local LLM to break a tie between company names, and a typed judgment model (Jev) to read intent from a message.

03

Go looking for failures.

Every phase turned up a bug, because I tested for them on purpose. The fixes are documented alongside the code.

FIELD NOTE · RESOLVE

A text-cleaning step erased the exact detail that was supposed to prevent ambiguous matches.

FIELD NOTE · ENRICH

SEC data extraction silently failed for every foreign-incorporated public company. There was no error, just missing data.

FIELD NOTE · SCORE

A similarity feature looked plausible but was quietly comparing companies with nothing in common.

FIELD NOTE · CLASSIFY

The Jev rebuild dropped to 86.8% accuracy on its first live run. The cause was a one-line class description; fixing it brought accuracy to 94.0%.

EXHIBIT A — SCORING RUBRIC (SQL)
-- ICP scoring rubric: a RevOps lead can read and retune this without touching app code.
WITH signals AS (
SELECT lead_id,
title_seniority_score, -- VP+ / CxO weighted highest
budget_authority_flag, -- explicit budget language in message
company_size_band, -- 1 (SMB) .. 5 (enterprise)
industry_fit_score
FROM enriched_leads
)
SELECT lead_id,
-- FIX: a buying signal must be able to override size.
(budget_authority_flag * 40)
+ (title_seniority_score * 30)
+ LEAST(company_size_band * 4, 20) -- size capped, never dominant
+ (industry_fit_score * 10) AS icp_score,
CASE WHEN icp_score >= 70 THEN 'P1'
WHEN icp_score >= 40 THEN 'P2'
ELSE 'P3' END AS priority_tier
FROM signals;

Highlighted: the fix that lets a CFO with approved budget outrank the company-size penalty. This is a simplified fragment; the shipped rubric is in the repo.

Notes & Provenance
  • No customer data touches this project. Every lead is synthetic; every company is real.
  • Free-tier enrichment only (SEC EDGAR, Wikipedia, direct site scraping). No paid vendor data.
  • Intent classification runs on TypeSafe AI's Jev, with a deterministic keyword fallback if the call fails. Company disambiguation and brief generation still run locally on Ollama and can switch to a hosted provider with a one-line change.