-
-
The bargain panel: open at, fair, never more than
-
pricecheck: is this OLX price fair?
-
Paste any OLX link
-
My friend's filters: 27 inch, IPS, around Rs 35k
-
Main pipeline: 6 stages
-
Live again after the repair
-
A live result for a pasted listing
-
TabPFN vs gradient boosting vs a simple rule, 510 listings
-
pricecheck opens an issue when OLX changes
-
How it works
-
The repair report
-
The reviewer agent: 6 checks, APPROVE
-
A release for each repair
-
The medic agent's diagnosis
-
The self-repair loop
Inspiration
My friend in Islamabad wanted a used 27" monitor on OLX, Pakistan's biggest classifieds site, with a budget of about Rs 35,000. In his words: "Don't know how to bargain too. If I had some frame of reference, like someone put a monitor at 55k and I can bring it down to 35k, that would be easy for me."
On OLX every price is an asking price and every buyer is expected to bargain. If you don't know what similar monitors usually go for, you either overpay or lowball and lose the deal. His problem wasn't "which monitor is best". It was "what number do I say out loud". Nothing on OLX answers that.
What it does
Try it: the live site (500+ priced monitors, no setup) | the code
1. Prices a listing
Paste an OLX link, or browse with filters. pricecheck tells you what similar listings usually ask (a range, not one number), a verdict (good deal, fair, overpriced, suspiciously cheap, or hard to judge), and three numbers to bargain with: open at, fair, never more than. It also shows what it read from the listing, what to check in person, and what Pakistani shops charge for the same model new.
With my friend's filters (27", IPS, Islamabad, Rs 35,000), one listing matched: an ASUS PB278Q asking Rs 40,000, which pricecheck marks overpriced, 24% above usual. Half of similar listings ask Rs 26,583 to 39,679, so it says: open at Rs 24,500, a fair deal is Rs 26,500 to 32,000, never above Rs 39,500.
Then a listing it had never seen: a Dell SE2717HX posted that day for Rs 20,500. Pasting the link checks it live: fetch, read, price, advise. Verdict: good deal, 29% below usual.
2. Fixes itself when OLX changes
A scraper breaks the day the site moves its data. pricecheck notices (LayoutChanged), saves a scrubbed copy of the page, and opens an issue. That starts a three-agent flow:
- a medic writes a failing test that reproduces the break, fixes the parser for the new layout and every old one, and opens a merge request;
- a separate reviewer checks that no test was weakened, older layouts still parse, the fixture has no personal data, and the pipeline is green, then answers APPROVE or stops for a human;
- a shipper moves
mainto exactly the approved commit. The pipeline then tests, scans, packages, deploys, releases and smoke-tests the live site.
Does it work? Measured
Pricing
510 single-monitor listings, each priced without seeing its own price (5-fold):
| Method | Typical error | Within 15% |
|---|---|---|
| Same-specs median (what a person would do by hand) | 28.6% | 26.3% |
| Gradient boosting | 22.4% | 35.3% |
| TabPFN | 19.2% | 39.6% |
The "usual range" (25th to 75th percentile) held the real asking price 51% of the time; about 50% is the target, so the ranges are honest, not too narrow to look confident or too wide to be useless. 19% sounds large until you remember it's predicting asking prices, which are noisy on their own. The goal is a frame of reference, which is the phrase my friend used.
Self-repair
4 rehearsals on real OLX pages rewritten the way a redesign would, each new to the agents:
| Run | Redesign | Result |
|---|---|---|
| 1 | results moved | fixed, but its first pipeline failed on 2 of my own tests; the agent fixed them properly, I merged by hand |
| 2 | page data renamed | 3m45s from break to main, no human |
| 3 | listing id renamed | reviewer 6/6 checks, shipped in 8m02s |
| 4 (recorded) | title field renamed | shipped at +9:51, live again at +16:57 |
The project went from 0 to 28 tests and 7 layout fixtures, and every older layout still parses.
How I built it
Collect
pricecheck reads OLX's public monitor category, one page every 2 seconds, nothing behind a login. Phone numbers are stripped before anything is saved (933 removed) and seller names are never stored: 1,180 listings.
Gemma reads each listing once
Listings look like "27 inch ips lush condition 10/10 box available urgent sale". Gemma 4 E4B, running locally through Ollama on a 6 GB GTX 1660 Super, turns that into columns: brand, model, size, panel, resolution, refresh rate, condition, whether it has a fault. 643 listings read, no API bill per listing. Turning off thinking mode and forcing a JSON schema took it from about 20 s to 5 to 7 s per listing.
TabPFN prices it from the others
TabPFN (a foundation model for tables) predicts the 25th, 50th and 75th percentile price from the other listings, with no training loop on my side.
Code decides the advice
The models supply facts; plain Python decides what to tell a person about to spend money. Two rules matter most: it never tells you to pay more than the seller asked, and if similar listings are spread too wide (75th / 25th above 2.2) it says "hard to judge" instead of inventing confidence.
def advice(price, q25, q50, q75):
"""Turn a price range into what to do. Code decides; the model only supplied the range."""
gap = price / q50 - 1
if q75 / q25 > 2.2:
verdict, say = "unsure", "Similar listings vary too much in price to judge this one. ..."
elif price < q25 * 0.6:
verdict, say = "too_cheap", "Far below similar listings. Check carefully in person: ..."
elif price <= q25:
verdict, say = "good_deal", "Cheaper than most similar listings. ..."
elif price <= q75:
verdict, say = "fair", "In the usual range. There's some room to bargain."
else:
verdict, say = "overpriced", "Above the usual range. Bargain hard or ..."
target_low, target_high = min(price, q25), min(price, q50)
return {
"open_at": round500(min(price, q25) * 0.92),
"fair_from": round500(target_low),
"fair_to": round500(target_high),
"max_pay": round500(min(price, q75)), # never more than they ask, never above the usual range
...
}
Shop prices and tracing
SerpApi searches Google Shopping for Pakistani shops selling the same model new (found for 45 listings, 35 searches). Sentry traces every live check as an agent run: an invoke_agent span, a chat gemma span for the read, and tool spans for fetch_olx_listing, tabpfn_price_range, bargain_advice and serpapi_new_price. One trace of the Dell check: 19.04 s total, OLX fetch 1.06 s, Gemma 5.18 s, TabPFN 10.88 s, the advice 6.56 ms, SerpApi 1.84 s. Without it I'd have guessed Gemma was the slow step; it's TabPFN.
The self-repair loop
olx.py knows each layout and raises LayoutChanged instead of a cryptic KeyError. The three agents run on GitLab's Duo Agent Platform (their YAML is in the repo), and a 6-stage CI pipeline does test, secure, package, deploy, release and monitor.
Any OLX category
Monitors is one "recipe": a small TOML file listing where to collect from and which fields matter. python -m pricecheck.new_recipe phones "<OLX link>" lets Gemma draft a new recipe from real listings, which I then read and fix by hand. Nothing runs on a schedule; prices only update when I run the update.
Running it yourself
The live site is static, so anyone can browse the priced listings. To check brand-new links, run it locally: the README walks through installing Ollama, pulling Gemma and getting a free TabPFN key, and python -m pricecheck.doctor says what's missing.
Why open models
A price check has to cost close to nothing, because nobody pays much to be told what to offer for a 20,000-rupee monitor.
- No bill per listing. Gemma read 643 listings on my own 6 GB GPU, and each update only reads new ones. The only outside calls are SerpApi (35 searches, inside the free tier) and Sentry (free tier).
- I could change how the model behaves. Because I run Gemma myself, I turned thinking mode off and forced its answer into a fixed JSON schema, which cut each read from about 20 s to 5 to 7 s and added the "has a fault" field.
- Swapping models is one setting. On a weaker PC,
PRICECHECK_MODEL=gemma4:e2b-it-qatswitches to the smaller Gemma. Nothing else changes. - No listing goes to a model provider. Only the shop-price searches and the traces leave my PC, and phone numbers are stripped before anything is saved.
Challenges
- Gemma marked working monitors as broken. One schema field mixed "what kind of listing" with "is something wrong". Splitting it fixed it.
- Nonsense prices for rare items. A 27" 4K monitor "usually" cost 79k to 398k because a few Apple displays stretched the range. Size is now required, wild prices are kept out of training, and "hard to judge" covers the rest.
- A cold model is very slow. Sentry's first trace showed one check taking 1.84 minutes because Gemma wasn't loaded on the GPU yet. Warm, the Gemma step is about 5 s.
- Shop prices from Google are noisy. One Lenovo model was 34k at one shop and 97k at another, so the page only shows a single shop price when shops agree.
- The agent token couldn't merge (403), so the shipper fast-forwards
mainto the approved commit instead. - The agent sandbox had no internet and Python 3.9: pytest and friends ship as wheels in the repo.
- A real shell-injection bug in my own pipeline: a backtick in an agent's commit message ran as a command in the release job. Notes now go through a file, so a message can never execute.
What it can't do
- OLX shows asking prices, never sold prices. pricecheck tells you what sellers ask, not what buyers paid.
- It doesn't know a model's release year.
- It can't see the monitor: dead pixels and backlight bleed only show up in person, so each listing gets a "check this in person" list.
- Monitors only so far. Other categories use the same recipe system but haven't been tested properly.
My first user
My friend tried it before I wrote any of this: "W, this is nice. Does this take into account how old a model is? Good though, very helpful." I asked whether he would pay for it: "lol if i had the money yeah. but yeah def a lot of ppl would". Fair: a student hunting for a 35k monitor has little to spare. My guess is that the people who'd pay are resellers, who buy and sell every week and lose more on one bad price than a month of a subscription, but I haven't asked one yet. He hasn't bought a monitor yet either, so I can't say how the bargaining went.
What's next
Answer his question: add each model's release year, then test other categories (phones, laptops), and show it to a few resellers, who have more at stake per listing than a student buyer.
Timeline: what existed before the event and what was built during it
- Before Oct 16 (pre-existing): the price checker (OLX reader, Gemma spec extraction, TabPFN pricing, web page, Sentry tracing), built Oct 13–14; the first version of the self-repair flow (medic agent, break detection, rehearsal pages, rehearsal 1), Oct 15.
- During the event (Oct 16 onward): the independent reviewer agent and the approve-only shipper, the package / release / smoke-test pipeline stages, real pytest inside the agent sandbox, and rehearsals 2, 3 and 4 (4 is the recorded run).
- Third-party: Gemma (Google, open weights), TabPFN (Prior Labs), GitLab Duo Agent Platform, SerpApi, Sentry. AI assistance: built with Claude Code.
Built With
- edge-tts
- fastapi
- gemma
- gitlab-ci
- gitlab-duo
- glab
- ollama
- pandas
- playwright
- pytest
- python
- remotion
- requests
- scikit-learn
- sentry
- serpapi
- tabpfn
- toml
- uvicorn
- vercel
Log in or sign up for Devpost to join the conversation.