This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
Every freelancer I know has a secret ritual, and it is not making work. You pick a city, open Google Maps, and start scrolling. Does this pizza place have a website? Does that salon answer its reviews? You open twenty tabs, you copy names into a spreadsheet, and eventually you write an email that begins "I came across your business" when the truth is you scrolled past four hundred businesses to find it.
I have always hated that hour more than any actual client work. So for week 2 of Hacktoberfest I built the thing I wanted: LeadRadar, an AI agent that reads Google Maps for me, tells me which local businesses genuinely need my help, and drafts the outreach email while the coffee is still hot.
You give it your trade and your city, for example graphic designer in Austin, TX. It searches, scores every result, and hands back a ranked list with an email ready for the top lead. Change the trade to content writer or web developer and the flow stays identical, because the trade only changes what the agent looks for, not how it thinks. That claim sounds simple. It took a friend telling me it was false to make it true, and that story is in the second half of this post.
What I Built
LeadRadar has four moving parts:
- A search layer that builds one Google Maps query out of your trade and runs it through SerpApi.
- A scoring layer that ranks every business out of 100. Most of each score comes from facts inside the search result itself: does this place have a website, is its rating quietly hurting it.
- An email layer that drafts a short personal note for your top leads and signs it with your name.
- A Temporal workflow that runs search, scoring and drafting as separate steps with retries, so one rate limit does not kill a scan.
The stack is deliberately boring: Express for the API, MongoDB Atlas for storage, Groq's free tier for the model (allam-2-7b) with Ollama as the local fallback, and SerpApi for search. It deploys as a single Render service, is MIT licensed, and there are 68 tests keeping me honest.
Demo
Live site: https://leadradar-7ukg.onrender.com
It runs on Render's free plan, so the first visit after a quiet period takes about a minute while the instance wakes up.
The scan form asks four things: what you do, where you work, your name, and how many leads you want.
While the workflow runs, each step lights up as it finishes.
Then the list comes back ranked, with the signals next to each score so you can see why a business placed where it did:
And the top lead already has an email written:
It works on a phone too, which matters if you are checking leads between calls.
Code
The whole thing is at github.com/mudit-palvadi/lead-radar under the MIT license.
How I Built It
1. The query is written for your trade, not typed by you (SerpApi)
You never compose a search yourself. You type "graphic designer", and the agent asks a model which local businesses would hire one, then joins the answer into a single Maps query:
coffee shop OR bakery OR restaurant OR bar OR food truck OR real estate agent OR gym OR salon ... in Austin, TX
One SerpApi Google Maps call later, I have the leads: name, address, phone, website, rating, review count, category. Structured data I can reason about, instead of a page I have to scrape and hope the markup did not change.
Two things here were learned the hard way.
The prompt only knew one trade. When my graphic designer friend ran a scan, the agent proposed toy store, pet store, car wash, bookstore (and bookstore twice). Those are businesses, technically. None of them were sitting there waiting for a designer. The prompt had exactly one worked example in it, for web design, and the model never generalized past it. I ran the diagnostic script three times and watched it return the same wrong list, then rewrote the prompt to show examples across trades and to ask a sharper question: which businesses would an owner pay for out of their own pocket? I added deduplication, and taught the peer filter to catch near misses like design studio when the trade is graphic designer. The same script now returns coffee shops, bakeries, food trucks and an art gallery. I reran a real Austin search with it and got 15 businesses I could actually imagine emailing.
SerpApi occasionally returns a row that is not a business. Most often it is the city name handed back with no real title. A lead called "Austin" invites the model to invent details about it, so isUsableResult() drops rows like that before they reach the list. My last scan logged "dropped 1 unusable result", which is exactly one fewer embarrassing email.
2. Facts come from the search, opinions come from the model
The first version of the scorer trusted the model completely. It answered 70 for every single lead, whether the business had a website or not. A score that never changes is not a score, it is decoration.
So the division of labor is now strict. detectSignals() reads the raw SerpApi record and derives facts I can verify: no website is a strong buying signal for any trade, and a low rating with a pile of reviews is another. The model only returns a number and a sentence of reasoning, and the final score is a blend:
// src/workflow/activities.ts
let base = 30;
if (has('no_website')) base += 55;
if (has('bad_reviews_no_response')) base += 15;
const blended = base * 0.75 + clamp(modelScore, 0, 100) * 0.25;
75 percent of the score is arithmetic anyone can read. The model gets a quarter of the vote, enough to break ties, not enough to pretend it saw something it did not.
3. The emails survive contact with a 7B model
The prompt asks the model to sign off and to stop commenting on things it never looked at. The model does it anyway, cheerfully. So the checks live in code: ensureSignOff() looks at the tail of the draft and appends your name if the model forgets (it often does), cleanEmailBody() strips sentences where it confesses it never checked the website and removes the "I hope this email finds you well" it was told not to write, and pitchGuidance() keeps a graphic designer from offering to build a website, which is the kind of thing that makes an outreach email read like it was generated.
The rule I settled on: never fight the model over instructions in the prompt when I can check the output in code.
4. One workflow, one free service
The scan, classification and drafting steps run as Temporal activities. Batches of three classification calls keep me inside Groq's free tier token limit, and retries absorb the rate limits that come with it. Results land in MongoDB Atlas, so a completed scan is still there after a restart.
Render's free plan does not allow background workers, so scripts/start-all.js boots the API and the worker together in one web service. The prompt fix from section 1 went live by itself when I pushed, because everything is defined in one render.yaml blueprint.
What it does not do yet
- Scores only matter next to each other inside one scan. An 81 in Austin is not a promise about your city.
- The emails are drafts. Read them before you send them. Mine survived that test with my friend's leads, but your name is the one on them.
Why Does Open Innovation Matter?
Because the tools that find clients are usually the ones sold back to the people who need them. For years my options for lead discovery were either a monthly database I could not afford or a scraper someone dressed up as a service. Meanwhile the libraries I actually build with are free and readable.
I am not the first to treat Google Maps as a lead source: How I Built a Google Maps Scraper to Generate Leads for My New Agency (And Why I Open-Sourced It) by @j3rry320 and Build a Local Lead Gen Machine: Scraping Google Maps with n8n (Reliably) by @wanjiang go after the same data, and most advice for freelancers is still a list of things to do by hand, like How to Get Freelance Design Clients Without Upwork: 15 Strategies That Actually Make Sense by @ahmed_quadri_4dd8a159217f.
What open changes is the reasoning. Anyone can read why a bakery outranked a boutique, argue with the weights, and send a pull request. The black box lead list you can only buy and the readable one are not the same kind of product. I know which one I would rather hand a freelancer who is just starting out, because that was me not long ago.
My First User
Before publishing this, I gave LeadRadar to two friends who freelance for real.
The web developer ran a scan for his city and was struck by how fast it was. He picked one of the leads, sent the outreach email the agent had drafted, and came back to tell me it had not converted. One email rarely does. What he actually said was that he liked the app and would surely use it again, and now he has a shortlist he did not have before, made in about a minute.
The graphic designer got the opposite result. The app could not find good leads for his trade, and he told me it looked like the app was more optimized for other skills. He was right, and he did not say it politely, which was the more useful part. That complaint is what sent me to the diagnostic script and the prompt rewrite in section 1. After the fix, his kind of scan returns coffee shops, bakeries and event venues instead of toy stores.
Would they pay? Neither has paid me anything, and I did not ask. The developer promising to come back and use it again is the only vote I have so far, and it beats any demo I could record myself.
Two lessons I am keeping:
- Speed impressed the developer more than any feature did. He was comparing LeadRadar against his own afternoon of scrolling.
- A "works for every freelance skill" claim is only honest once it works for every skill a real person has tested. Two trades are tested now. The claim is still smaller than my ambition, and I would rather say that out loud than hide it in a footnote.
Prize Categories
One project, every category it genuinely uses:
- Best Use of SerpApi: the whole discovery layer, not a side feature. Every lead, signal and score starts from one Google Maps query.
- Best Use of Render: one free service runs both the API and the worker, defined in render.yaml, rebuilt on every push.
- Best Use of Temporal: scan, classify and draft run as retried activities, with their progress shown on screen.
- Best Use of MongoDB Atlas: scans, leads and drafted emails are stored in Atlas and survive restarts.
Over to you
Try a scan for your own trade at leadradar-7ukg.onrender.com. If the leads come back as wrong for your trade as my friend's did for his, open an issue and tell me. That is exactly how this gets better.






Top comments (0)