Turn a messy list of names into a ranked action list with ready-to-send outreach emails — so your sales team never has to guess who to call first.
The most time-consuming part of sales isn't the pitch — it's figuring out which few names in a giant list are actually worth a phone call. LeadRadar hands that job to AI:
- Define your ideal customer in plain English — describe your ICP in a sentence (e.g. "cross-border e-commerce teams, 30-200 people, with a DTC storefront, currently hiring for ops") instead of wrestling with a rigid filter form. Change the profile any time and every score updates with it.
- AI scoring isn't a black box — every lead gets a 0-100 score, an A/B/C tier, a one-line reason, and a list of matched and missed signals, so your reps understand why, not just a number.
- One-click CSV import — bulk-import an existing list, or add a single lead by hand.
- Outreach emails, not mail-merge templates — a personalized first-touch email is generated per lead in both English and Chinese, referencing that lead's actual industry, size, and notes — not variable substitution.
- Failures are visible, not swept under the rug — a scoring failure (missing fields, model refusal, network issue) is saved to the record and shown inline, right in the row, no hovering required.
- Works with zero API keys — without an
ANTHROPIC_API_KEY, LeadRadar automatically runs in demo mode with deterministic rule-based scoring and template emails, so you can try the full workflow before wiring up a real key.
No dropdowns, no filter builder — just plain language: "Cross-border e-commerce teams with 30-200 employees that already run a DTC storefront and are actively hiring for operations roles, in industries like cross-border e-commerce, DTC brands going global, and owned-audience retention marketing." That sentence is the single source of truth behind every score and ranking — edit it once, and every lead re-sorts.
Click into any lead to see its 0-100 score, A/B/C tier, matched and missed signal badges, and a personalized English and Chinese outreach email — not a template with variables swapped in, but copy that actually references the company's industry, size, and storefront status. Your reps don't get a lonely number; they get a complete case for whether this call is worth making.
git clone https://github.com/LuciferLiu/leadradar.git
cd leadradar
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # add ANTHROPIC_API_KEY if you have one — works fine without it (demo mode)
python seed_demo.py # generates demo data: 1 ICP + 12 leads
uvicorn app.main:app --reload --port 8030The demo data is scored by the built-in deterministic engine, so you can run the entire import → score → generate email → export flow offline.
cp .env.example .env
docker compose up -dEverything is controlled through environment variables — see .env.example:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
sqlite:///./leadradar.db |
Swap in a Postgres connection string to change databases |
ANTHROPIC_API_KEY |
empty | Leave unset to run fully offline in demo mode |
CLAUDE_MODEL |
claude-opus-5 |
Model used for scoring and email generation, overridable |
app/
├── main.py FastAPI routes + application lifecycle
├── config.py Environment variables and demo-mode detection
├── db.py SQLAlchemy engine and session, SQLite foreign-key PRAGMA
├── models.py Data models: Lead, ICPProfile
├── schemas.py Pydantic request/response models + Claude structured-output contract
├── service.py Core logic: ICP storage → AI/rule-based scoring → email generation
└── static/
└── index.html Dashboard (vanilla JS, no build step)
Three deliberate trade-offs went into this design:
Scoring is a pure function, and AI and the rule engine share the same output contract. Whether it goes through Claude's structured output (messages.parse) or the demo-mode keyword matcher, ScoreResult (score / tier / reason / matched_points / missed_points) comes back in exactly the same shape. main.py and the frontend never need to know whether a model call actually happened — the switch lives in exactly one place, in service.py.
The ICP is a paragraph of natural language, not a filter form. A sales profile is inherently fuzzy — a signal like "currently hiring for ops" doesn't fit cleanly into a dropdown. Handing that interpretation to an LLM matches how salespeople actually think about their targets, far better than forcing it into structured fields. The cost is that you lose precise SQL filtering — but scoring itself is already a fuzzy match, so that trade-off is one worth making.
Failures are persisted, not retried or swallowed. Scoring and email generation fail independently of each other; the failure reason is written straight back onto that row instead of going into a log file or a toast that disappears in three seconds. The thing a sales dashboard can least afford is a lead that just sits there with no score, with no way to tell whether it was never tried or it failed. So failure is treated as a definite state, not a missing one.
Once the service is running, interactive docs are available at /docs.
| Method | Path | Description |
|---|---|---|
GET |
/api/leads |
List leads, sorted by score descending (unscored leads last) |
POST |
/api/leads |
Add a single lead by hand |
POST |
/api/leads/import |
Bulk import via CSV (multipart form, field name file) |
PATCH |
/api/leads/{id} |
Update lead fields |
DELETE |
/api/leads/{id} |
Delete a lead |
POST |
/api/leads/{id}/score |
Score a single lead |
POST |
/api/leads/score-all |
Score every lead in bulk |
POST |
/api/leads/{id}/email |
Generate an English + Chinese outreach email for a lead |
GET |
/api/icp |
Get the current ICP profile |
PUT |
/api/icp |
Update the ICP profile |
GET |
/api/export |
Export all leads as CSV (including score, tier, reason) |
GET |
/api/stats |
Dashboard summary metrics |
CSV import example:
curl -X POST http://localhost:8030/api/leads/import \
-F "file=@leads.csv"CSV headers can be in either English or Chinese, pick whichever: company_name/公司名, website/网站, industry/行业, company_size/规模, contact_name/联系人, contact_email/邮箱, notes/备注. Company name is the only required field — rows missing it are skipped, with reasons listed in the response.
- In live mode, both scoring and email generation go through Claude's structured output (
messages.parse), costing a few hundred tokens per lead — try a small batch before scoring a large list in bulk. - The demo-mode scoring engine is keyword matching plus a size-range comparison, meant only to exercise the full workflow and preview the UI — it doesn't represent real semantic understanding. Score quality improves substantially once a real key is wired up.
- The ICP profile and leads are currently single-tenant (one ICP for the whole database) — multi-team setups will need to add their own tenant isolation.
MIT


