A Lead CRM and Call Ranker for a Wholesale Supplier
The sales team was spending hours on the phone. The fix was deciding who to call first.
- Client
- A wholesale meat supplier that sells in bulk to restaurants. Independent work.
- My role
- I scoped the problem with the team, built the CRM, and trained the model that ranks leads.
- Result
- Closing rate per call rose about 50%, from about 8 closes per 100 calls to near 12.
- How it was measured
- The team's known rate before, against a month of calling in ranked order. Not a randomized test.
Result: closing rate per call rose about 50% once the sales team called leads in the order my model ranked them, from about 8 closes per 100 calls to near 12.
The problem
A wholesale meat supplier sells in bulk to restaurants and other food businesses, so each new account is worth a lot. Its sales team spent many hours phoning leads, and the ask was vague: make the calling more efficient. Before building anything, I asked clarifying questions, then costed several options against the current process, including labor hours and API cost per hour.
The CRM
I built the team a private lead CRM in Spanish and English.
- Leads from open data. It imports the city's open dataset of food businesses and keeps the source on every record, so the team always knows where a lead came from. Rerunning the import never duplicates a lead.
- Map and table in sync. A Google Maps view finds leads within a radius and stays in sync with a spreadsheet-style table.
- Calls as records. Each lead keeps its call attempts, outcomes, notes, contacts, and follow-up dates. A rep can stop partway through a calling list and pick up in the same place later.
- Sales metrics. Contact, interest, quote, and customer-conversion rates, plus the follow-ups due.
The ranker
On top of the CRM, I trained a random-forest model that sorts strong leads from weak ones, so reps spend their calls where they count. It runs on Google Cloud's Vertex AI, where it plugs into the CRM. Scoring added only about 10% to the cost per lead, against about 50% more closes per call.
How the result was measured
The business already knew its rate before the ranker: about 8 closes for every 100 calls, where a close is a business agreeing to buy in bulk. After a month of calling in ranked order, the rate was near 12 per 100. That's a before-and-after comparison on the team's own call records, not a randomized test.
Built with (for technical readers)
Next.js and TypeScript, a FastAPI back end, SQLite with a PostGIS option, and Alembic migrations, deployed with Docker Compose behind Caddy with backups and rollback. Python, front-end, and Playwright end-to-end tests run in GitHub Actions.