case study · LLM & Data Pipelines / GTM & Business Systems · 2026
Territory Intelligence Pipeline
Province-wide B2B prospecting, automated end to end
Problem
B2B territory management in telecom is mostly manual. Reps identify businesses ad hoc, lose track of who they've already contacted, and have no repeatable way to rank prospects by fit. This pipeline draws on two years of firsthand B2B telecom territory experience across Newfoundland, where manual prospecting consumed hours that should have been selling time.
This pipeline automates the loop: discover every relevant business in the province, enrich the records, classify fit, score, and export a ranked call sheet. It also remembers everything, so the next run only pays for what changed.
Constraints
- Two metered APIs. Google Places and OpenAI both bill per call. A naive pipeline that re-fetches and re-classifies on every run multiplies cost for zero new information.
- Google's terms of service restrict caching and redistributing Places content. The database has to remain a local, single-user operational cache, and real exports stay out of version control.
- LLM output is probabilistic; a ranking cannot be. The final priority order needed to be explainable to the person dialing the phone.
- Solo and zero-ops. No server, no scheduler, no managed database. It has to run from a laptop and resume cleanly after being interrupted.
Decisions
SQLite as accumulating memory. Every business keys on its place_id with idempotent upserts (INSERT … ON CONFLICT DO UPDATE with COALESCE), so a re-run can add information but can never erase an existing value with a null.
Cost gates in front of every paid call. needs_details() skips Google enrichment when a record already has a phone number and Maps URL; should_classify() skips the LLM when fit scores exist and the website hasn't changed. Each run costs in proportion to what's actually new.
Structured LLM output with a repair path. Classification uses gpt-4.1-mini, validated against a Pydantic model. When strict validation fails, a normalization pass coerces strings, floats, and percentage strings into valid integers and re-validates. An occasionally malformed field should not throw away a paid API call.
The LLM classifies; it does not rank. Final scores are a deterministic weighted blend (mobility fit 55%, security 20%, VoIP 15%, fleet 10%), plus small verifiable boosts for a rating of 4.2 or higher, ten or more reviews, a website, and listed hours. Anyone can trace exactly why a business scored 78.
Retries only where they're safe. Google calls go through a tenacity wrapper, up to five attempts with exponential backoff. A failed LLM classification is logged and skipped rather than retried blindly.
Result
A full-province run covers 14 cities and 13 B2B keywords, enriches whatever is missing, classifies up to 200 new or changed businesses, and writes a ranked CSV lead sheet. The database accumulates across runs, roughly 2,000 business records to date, and previously classified businesses cost nothing the second time. A bounded validation mode (about ten Google and ten OpenAI calls) proves the pipeline end to end before a full run's budget is spent.
The output replaces the same discovery-to-priority workflow previously completed by hand, minus the hours.
Honest limitations
- The AI fit scores have no formal accuracy evaluation. They are the model's inference from a business's name, address, type, and homepage text: a sorting signal, not a validated prediction. A human-labelled evaluation harness is the planned v3.
- Homepage context is a single-page fetch truncated to 10,000 characters. No crawling.
- The intelligence is deliberately private: Google's terms mean the accumulated dataset is an operational cache for one user, not a publishable asset.