A shared local web app for my business and Admins: find leads (Google Places), work them through a status pipeline, book clients on a shared calendar, and keep records — all with individual logins and individual API keys.
cd lead_suite
pip install -r requirements.txtpython app.pyOpen http://localhost:5000. The first person to open it creates the admin account (and can optionally add their own API key right away). Everything after that — leads, searches, bookings — is shared across whoever logs in.
No built-in HTTPS or lockout protection — this is for a small trusted group for your own machine or private network, not the open internet, unless you add proper hardening (HTTPS, a real reverse proxy, etc.) yourself first.
Log in as admin → Settings → Team → Add a teammate. Give them a username and a temporary password, tell them to log in and change it, and have them add their own Google API key in their own Settings page (each person's searches are billed against their own key/credit).
- Find Leads — location, radius, review sweet spot, business types, chain/no-website filters.
- Recent team searches — shown right under the search form, so nobody duplicates an area someone already covered. Shows who searched what, when, and how many leads it found/added
- All Leads / Active Leads — one shared pool. Every lead shows who
found it (
Found by). Status (New/Contacted/Quoted/Booked/Won/Lost) and Notes are editable inline and save automatically for everyone to see. Active Leads is a shortcut view of just Contacted/Quoted/Booked — your working pipeline. All Leads also has a search bar (matches name, category, address, or notes), a "Has notes" filter tab, and sortable column headers — click Name/Score/Rating/Reviews/Found by to sort, click again to flip ascending/descending - Calendar — a shared month view for booking clients in. Anyone can add a booking (title, client name, optional link to an existing lead, start/end time, notes); everyone sees who booked what
- Settings — each person manages their own API key and password; the chain-exclusion list is shared/edited by anyone; only admins can add teammates
Cost warnings before you run a search. The Find Leads page now shows a live estimate (search areas, API calls, $ range, and how much of your monthly free credit it'd use) that updates as you change radius/density — and if a search would use $2+ of credit, you'll get a confirmation prompt before it actually runs.
Minimum rating filter. "Does well but has no website" needs a quality bar, not just a review count — you can now require a minimum average rating (3.5/4.0/4.3/4.5+) alongside the review count range.
Grouped, easier-to-scan business types with Select all / Clear all, plus a per-group toggle, instead of one long flat checkbox list.
Google Places search has two sharp edges that will burn you if you don't know about them — both are handled now, but worth understanding:
-
No type filter ≠ "search everything correctly." If you leave every business-type checkbox unchecked, the app now searches a curated list of ~48 common local business categories (shops, food/drink, health & beauty, trades, professional services, etc.) instead of asking Google for literally every place type — which used to return bus stops, train stations, hospitals, and government buildings alongside real businesses.
-
Google caps every single search circle at 60 results, ranked by "popularity" by default. In a dense area (a city center, a busy high street) there are often thousands of real candidates in one circle — Google keeps whichever 60 are most prominent and drops the rest. Since a business with no website is by definition less "prominent" online, that's exactly the wrong bias for this tool. Two fixes are built in:
- Searches rank by distance instead of popularity
- The Search density dropdown on the Find Leads page controls how
small the sub-search cells are:
Quick(cheap, sparser),Standard(good default),Thorough(many more, smaller cells — use this for dense city centers). More cells = more API calls = more cost, so it's a real trade-off, not a free upgrade — the dashboard's credit chart will show it.
One limitation no setting can fix: in a commercially mature, high-footfall area (central London, a major city's main shopping district), most established businesses already have a website — so a "no website" filter will genuinely return few results there no matter how thorough the search is. That's not a bug, it's the actual state of those businesses. You'll get a much higher hit rate in suburbs, small towns, and less web-savvy business categories (independent trades, small local shops) than in a capital city's central commercial core.
Google Places is queried fresh every search (there's no way to ask Google "only show me things I haven't seen" — each run costs the same in API credit regardless). What the shared pool prevents is duplicate lead records: if a lead's Place ID already exists (found by anyone, any time), a new search skips re-adding it, so nobody's status/notes get overwritten or duplicated.
- Drops closed businesses, chains (per
chains.txt), and anything outside your review count range - Flags "social page only" (website field is really just a Facebook/ Instagram link) separately from a real website
- Lead Score = rating×10 + review count (capped at 50) + social-presence bonus + phone-number bonus — a prioritization heuristic, not a hard rule
Each teammate's dashboard shows their own estimated spend against their own $200/month free Google Maps Platform credit (search calls with rating/ review/website data are billed at Google's higher "Enterprise" tier, ~$35 per 1,000 calls as of mid-2026). This is a planning estimate — Google Cloud Console is the source of truth for real billing.
lead_suite/
app.py Flask app: auth, leads, search, calendar, settings
lead_finder_core.py Google Places search / filter / scoring logic
chains.txt Shared, editable chain/franchise exclusion list
requirements.txt
templates/ HTML pages
static/ CSS + JS
data/
app_config.json Created on first run (just the session secret key)
leads.db Created on first run — SQLite: users, leads,
searches, usage, bookings all live here
All data stays in data/leads.db on whichever machine runs the app —
nothing goes anywhere except direct calls to Google's API from each
teammate's own browser session.