DEV Community

Cover image for I paid NT$945 for one freelance lead. Now Gemma 4 reads the job post first, in my browser
Hua-I Yu
Hua-I Yu

Posted on AI-assisted

I paid NT$945 for one freelance lead. Now Gemma 4 reads the job post first, in my browser

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

In August I quoted on a points-and-rewards platform for a shop in Taichung. The client's budget was NT$100,000 to 150,000 and I asked for NT$120,000. Soon after, the client opened my contact details, and PRO360, the Taiwanese marketplace where I found the job, charged me NT$945 (about US$30) for it. That is how PRO360 works for this kind of request: you pay when the client reaches for you, before anything is agreed. I still don't have a contract.

Around then I drafted two more quotes that never went out. One client cancelled the request and the other request closed before I hit send. Over the summer I also scrolled past a factory system that needed someone on site, a four-department ERP on a no-code tool I've never used for NT$20,000, and a "phone wall" setup for running a pile of social media accounts without getting caught. I saw that last kind on two platforms.

Upwork, Thumbtack and Bark work the same way in different currencies: Connects, credits, a price per lead. The fee is small next to a real job. Paying it for the wrong job adds up, and so does the half hour each proposal takes.

So I built LeadToll. You paste a job post and it says Pay, Skip or Close call, and every reason comes with the line from the post that backs it up.

What I Built

LeadToll is a single web page. Gemma 4 E2B runs inside it on your own GPU through WebLLM, so the post, your rates and your win rate never leave the page.

LeadToll checking a clinic website job live in the browser: Gemma 4 reads the post, code says Pay and fills in the price

The model only reads. It copies three things out of the post, each with an exact quote:

  • the budget
  • the pieces of work (pages, login, payments, a LINE bot, a data migration, and so on)
  • warning signs (must be on site, account farming, paid only if the app takes off, unpaid "test" work)

Everything after that is plain JavaScript:

  1. Check every quote. If the quote isn't in the post, the claim is thrown out. If the quote is real but doesn't say what the label says, it's thrown out too.
  2. Read the money with code. "預算 3.5 萬到 4.5 萬", "around 2k", "$15-20/hr, about 40 hours" are parsed by a function with unit tests. The model's numbers are never used.
  3. Decide. A hard no ends it. Otherwise one formula:
cost per bid        = lead fee + proposal hours x your floor rate
above your floor    = what the job pays - estimated hours x your floor rate
break-even win rate = cost per bid / above your floor
Enter fullscreen mode Exit fullscreen mode

If you win more of these leads than the break-even rate, pay. Hours come from a rate card you can edit, and your floor rate, minimum project and win rate sit in a panel on the page. Change them and the verdict updates without running the model again.

Who would pay for this? Freelancers who pay for leads. On Upwork the fee is small and the real cost is the half hour spent on a proposal for a job that was never going to work. Nobody has paid me for LeadToll yet, and so far I'm its only user. On my own ten leads it agreed with me nine times, and the leads it would have stopped were ones I never paid for anyway, so for me the saving is reading time. It would matter more if you looked at dozens of paid leads a week on Upwork or Thumbtack. If I sold it, it would be a one-time US$5 download; it costs me nothing per check because the model runs on the buyer's laptop.

Demo

Live: leadtoll.vercel.app

  • Recorded runs (the default tab) shows ten real outputs saved from the same model. The quote checker and the math still run live on them, so changing your rates changes the verdicts. This works on any browser, phones included.
  • Run Gemma 4 here downloads the model (about 2.8 GB, once) and checks any post you paste. It needs Chrome or Edge with WebGPU. On my laptop's RTX 4050 one post takes about 7 to 15 seconds, depending on its length.

The examples are either made up or rewritten from my own notes, and each one is labeled. No client wrote any of the text.

Code

GitHub logo StuartYo / leadtoll

Should you pay for this freelance lead? Gemma 4 reads the post in your browser; code checks every quote and does the math.

LeadToll

Should you pay for this freelance lead?

Upwork (Connects), Thumbtack, Bark and PRO360 charge freelancers before the first conversation with a client. LeadToll reads the job post and tells you whether the fee is worth it: Pay, Skip or Close call, with the quote from the post behind every reason.

Live: https://leadtoll.vercel.app

  • Gemma 4 E2B runs in your browser through WebLLM and WebGPU. The post, your rates and your win rate never leave the page. The model is about 2.8 GB, downloads once from Hugging Face and then works offline.
  • The model only reads. It copies the budget, the pieces of work and any warning signs out of the post, each with an exact quote. Output is held to a small hand-written grammar through WebLLM's constrained decoding, so it is always valid JSON with known labels.
  • Code checks and decides. A quote that is not in the…

MIT licensed. src/core/ is the part that matters: the verifier, the money parser, the rules and the decision, with 43 unit tests. eval/ has every post, the labeling guide and every saved model run.

How I Built It

Stack

Gemma 4 E2B (4-bit, gemma-4-E2B-it-q4f16_1-MLC) on WebLLM 0.2.86, a Web Worker so the page doesn't freeze, Vite, and OpenCC for one problem I'll get to. Hosted as a static site on Vercel. Tools: Claude (Anthropic) helped me brainstorm and wrote most of the code and the first draft of this post. I chose the problem, supplied my own lead notes and rules, and reviewed and edited everything. No image generation was used; the cover and screenshots are rendered from the app's own HTML.

The test

Before writing any code I wrote down my rules as a labeling guide: remote only, my floor rate, what I don't build, and four hard no's. Then I measured four setups on the same posts with the same Gemma:

  • Gemma alone: one prompt with all the same rules, asked for a verdict and its reasons with quotes.
  • Rules alone: regexes and the money parser, no model.
  • Gemma, unchecked: LeadToll's prompt, but every claim trusted.
  • LeadToll: Gemma reads, code checks and decides.

There were three sets of posts. A dev set of 32 (22 made up, 10 rewritten from my own leads) that I used while building. Holdout 1, 12 posts written by a separate agent that only saw the labeling guide. Holdout 2, 16 more written the same way, run once after I froze the code.

Scored with the code I froze before running holdout 2 (commit 1d97c7b):

Verdict matches the label Dev (32) Holdout 1 (12) Holdout 2 (16, run once)
Gemma alone 20 6 11
Rules alone 29 9 10
Gemma, unchecked 27 9 12
LeadToll 30 9 14

On holdout 2, the set nobody tuned anything on, "rules alone" let through 4 of the 5 hard-no posts and would have paid for all 4. Gemma alone paid for 1. LeadToll also paid for 1, and that one is a story below. Gemma alone also quoted text that isn't in the post 4 times out of 21.

What the checker throws out

Across the three sets, the checker I shipped drops 43 of the 193 claims Gemma made. 24 are deadline flags (more on those below) and 10 are "budgets" with no number in them, like "Budget we can discuss". Only one quote was missing from the post: when Gemma is asked to copy, it mostly copies. When the same model was asked for a verdict with reasons, 4 of its 21 quotes on holdout 2 weren't in the post.

The other 8 are real quotes under the wrong label, and 7 of those labels would have ended the lead. One of them, from the made-up post asking for "an Airbnb for pet sitters":

Gemma: shopify_theme, because "I have a business plan and logo ready"

The checker throwing out two of Gemma's labels on the

The quote is in the post. It has nothing to do with Shopify. Out-of-stack labels decide whether a lead gets skipped, so for those labels the quote has to contain a word that fits (Shopify, Liquid, Ragic, iOS, game, office, on site...). Same for the hard no's. "Fixed price $800." filed as "paid only on success" is out. "不用到店裡" ("no need to come to the shop") filed as "must be on site" is out, because the code looks for the negation.

Constrained decoding

My first version asked for JSON in the prompt. Gemma renamed my keys in 16 of 44 answers (component_counts, component_flags) and sometimes left out the quotes entirely. WebLLM can hold the output to a JSON schema, so I tried that next. It fixed the keys, and then 5 of the 32 dev answers stopped halfway through the JSON and filled the rest of the 700-token limit with blank lines. The schema let the model pick its own whitespace and it never stopped picking. I replaced the schema with a twelve-line grammar that spells out every newline and space, and since then every answer has parsed.

What I Got Wrong

  • I tuned the rules on my test set. After the first dev run I fixed every regex that missed, and "rules alone" went to 29 of 32, one behind the full system. Holdout 1 showed what that was worth: LeadToll got 6 of 12. The model had renamed keys, my parser read "3.5 萬到 4.5 萬" as 3 dollars, and a regex read "不用到店裡" as an on-site requirement. I fixed those, so holdout 1 doesn't count anymore, and I had another agent write holdout 2.
  • The quote checker cost me a hard no. On holdout 2, a Kenting guesthouse job needed an engineer at the property four times to install door locks. Gemma caught it, but it copied the quote in Simplified Chinese (测试, 安装) from a post written in Traditional (測試, 安裝). The checker did its job and threw the quote out, and LeadToll said Pay. Now a quote that misses is converted with OpenCC and checked again, and drafts get the same treatment, because Gemma wrote my Chinese drafts in Simplified too. With the current code no hard no in any of the three sets gets paid. Holdout 2 still scores 14, because I also added a rule after looking at the recorded examples: if Gemma finds no piece of work it can count, LeadToll won't say Pay, since an hours estimate of "setup only" means nothing. That turned one correct Pay into a Close call. I'd rather have that than a confident Pay built on four hours of estimated work.
  • Gemma calls everything a rush. "Launch in six to eight weeks" and "before the end of next quarter" were both flagged as a too-short deadline. A rush flag never changes the verdict, and now it needs an urgency word in the quote to show up at all.
  • The hours are only as good as what Gemma notices. For my real Ragic-to-CRM lead (66 people using it every day, a few hundred thousand records, a hard deadline) LeadToll estimated 20 to 68 hours and said Pay. I walked away from that job. Gemma saw "migration" and missed the size. Since then, when Gemma counts one piece of work or none, plain text rules add the obvious ones it skipped (booking, login, payments, a CMS), but nothing in the code knows how big a migration is.

Why Does Open Innovation Matter?

The post stays on the laptop. Leads on Taiwanese platforms often include the shop's district, the size of the team and what they currently use, and my floor rate and win rate are numbers I don't want in anyone's logs either. With the model in the tab, nothing you type goes to a server. The only downloads are the page, WebLLM's runtime file from GitHub and the model weights from Hugging Face; even the fonts are bundled.

I could change how the model writes, too. The whitespace loop and the renamed keys were fixed with a grammar I wrote, applied token by token inside WebLLM. I could only find the Simplified Chinese problem because I had every raw output saved next to the post it came from.

Nobody needs an account. A freelancer opens a link, with no key to paste and no bill per check.

Swapping the model is one line, MODEL_ID in src/llm.js. The checker, the money parser and the decision don't care which model reads the post.

A closed model would do better in two places. A bigger hosted model would probably have caught the size of that CRM migration, and would have answered Traditional Chinese in Traditional Chinese. The 2.8 GB download and the WebGPU requirement also leave out phones and Safari, which is why the recorded tab exists.

My First User

Me. I ran LeadToll on the ten leads I wrote down this summer, rewritten from my notes. The lead fee is real for the first one; the rest use NT$200 per quote, from my notes on PRO360's points (NT$150 to 250), or zero on Tasker.

Lead What actually happened LeadToll
Points platform, Taichung, NT$100-150k Quoted NT$120,000, charged NT$945, no contract yet Pay (break-even 1.5%)
Export food site, NT$30-50k Client cancelled before I sent it Pay
Business site, under NT$30k Request closed before I sent it Pay
Trading company site, NT$50-100k Quoted NT$54,000, no reply yet Pay
Factory system, on site Never bid Skip: must be on site
Four-department ERP on Ragic, NT$20k Never bid Skip: not my stack
Mobile game, no budget Never bid Skip: not my stack
Shopify theme edits Never bid Skip: not my stack
Phone farm for social accounts Never bid Skip: breaks platform rules
Ragic to CRM migration, NT$500k-1M Walked away Pay (wrong)

It agrees with every lead I skipped except the last one. And on the NT$945 lead it says I should have paid, which I'd do again: on paper it was a good lead. What I learned from my own table is that on PRO360 the fee was never the problem. My break-even rate was between 0.7 and 4.2 percent on every lead I quoted. The money I'd actually save is the time spent reading the bad ones, and on Thumbtack-style prices the formula starts to matter.

Prize Categories

Best Use of Gemma. Gemma 4 E2B is the only model in the project. It runs locally in the browser through WebLLM, held to a custom grammar, and every one of its claims is checked against the post before it can change a verdict. The eval compares it with itself (alone, unchecked, checked) on 60 posts, and every raw output is in the repo.

Top comments (0)