This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
Built with PriorLabs-TabPFN.
What I Built
I know someone who runs a shop. Everything in it is done by hand: how much stock there is, which item is about to finish, which item people are asking for. With so much done by hand, mistakes happen. The item that sells and matters often is not there when customers come. Other times the shelf fills with stock nobody wants.
That is the problem I picked. I do not have that shop's data, and I did not build this with them. So I used a public gift-shop export and treated it as the shop.
tidy-predict reads a shop's messy order export and gives back three things: a cleaned file, next week's expected units for each regular seller with a likely range, and a short restock note in plain English. The top of the range is the "stock for a busy week" number.
The real page, one real run. Parts are sped up and say so on screen. The file is demo data: a public UK gift-shop export, with the column names changed to "Bill No, Item Code, Maal, Kitne, Tarikh, Rate" to test odd headers.
It is a small agent with fixed steps: clean, forecast with TabPFN-2, write with Gemma 4 E2B, check every number, retry once or fall back to a template. Both models run on the laptop's CPU. The shop's orders never go to an AI API.
Demo
â–¶ Watch the 69-second demo video
A 67-second video of the same run, with an illustrated, labelled fictional shop owner (Meera) for the story parts. Demo data is labelled in the video. The log part is sped up and labelled.
Narration: an AI copy of my own voice. I recorded a voice sample and agreed to this; the open-source Chatterbox model (MIT licence) generated the narration from it. The script was written with my AI coding agent. It is not a live recording.
Here is what the run gives back:
Try it without installing anything: the 28 Nov 2011 replay report (a static page from the run above; public demo data, no models online).
The data is UCI Online Retail II: 1,067,371 real, anonymised order lines from a UK gift shop (CC BY 4.0). The demo replays the past. It pretends today is Sunday 27 Nov 2011, forecasts the week of 28 Nov, and then shows what really sold. This is a replay, not a live client.
The week it got wrong
I want to show the miss first, because it is the most useful part.
The report flags every product that sold above its busy-week number. Rabbit night light: expected 1,249, busy-week number 2,592, really sold 4,452. Small popcorn holder: 3,271 against 2,596.
The model expected rabbit night lights to drop 58%, to 1,249 units. They sold 4,452. That was their second-best week in the whole file, and far above the busy-week number of 2,592. The small popcorn holder also sold above its range (3,271 against 2,596).
Overall, 17 of the 20 products landed inside their likely range. But the middle numbers added up to 13,386 units and 19,101 really sold. The week of 28 Nov is the start of the December rush, and that broke the usual pattern. The 20 products are picked because they sold a lot recently, and such products usually fall back a little (the simple rule learned the same thing: its fitted factor was 0.78). This week they did not.
So the honest reading is: the range worked for most products, the middle number was too low, and one product would have run out. A shop owner should read the busy-week number as a ceiling to plan for, not a promise. The first item in the note, Red harmonica in box (2,993), shows the same thing the other way. It is lumpy wholesale stock: 22 sold the week before and 918 a week on average, so the range is very wide. It sold 1,120.
Code
https://github.com/xexpertai/tidy-predict
MIT licence. 28 tests. A script behind every number in this post.
How I Built It
I built this with an AI coding agent (Claude Code). I decided what to enter, and I read the notes, ran the tests and checked the numbers. I also checked the logic, reviewed the code, and changed some flows.
1. "Bill No / Maal / Kitne / Tarikh"
A real shop's export will not have tidy English headers. So the first step finds the columns. Plain rules do it first. For the headers the rules cannot place, Gemma 4 E2B (running locally) is asked, and a JSON schema means it can only answer with real header names.
I tested this on 12 header sets written while building it (by me and the coding agent). Rules alone got 3 of 12. My first Gemma version got 8 of 12 but made 4 wrong guesses on optional columns (a batch number taken as the product code, for example). Wrong guesses are worse than no guess. So Gemma now only fills the three required gaps: date, quantity and product. That gave 12 of 12 with 0 wrong. But those same 12 sets were used while fixing it, so treat 12/12 as optimistic.
2. Cleaning, and nothing deleted silently
In the demo file the cleaning set aside 70,254 rows. Of 1,067,371 lines: 34,335 exact duplicates (22,523 of them because the file's two Excel sheets overlap on 1 to 9 Dec 2010), 5,869 postage and fee lines, 21,302 returns, 6,180 sales those returns undo, and 2,568 free or negative-price lines. 997,117 lines remain as sales. Every set-aside row is listed with its reason in issues.csv.
3. TabPFN-2, and why not 3.5
One TabPFN-2 regressor covers the shop's 20 regular sellers. Its features come only from past weeks: recent weeks, the same week last year, a few averages, and the week of the year. TabPFN is a tabular foundation model. It predicts from the training rows in one pass and gives back a full distribution. That gives the middle number and the P10 to P90 range (the band real sales should land in 8 weeks out of 10).
I used TabPFN-2 on purpose. Prior Labs' newest model, TabPFN-3.5, is free for research and testing, and the commercial use needs a paid licence. If I want to sell this weekly, even its outputs would be a problem. TabPFN-2's weights are open under Apache 2.0 with an attribution clause ("Built with PriorLabs-TabPFN"). That is what makes a no-budget side hustle possible from day one.
4. Gemma writes, code checks
Gemma only sees a small facts table. The biggest rise and the biggest drop are worked out in code. A checker pulls every number out of Gemma's draft and rejects it if any number is not in the facts. Gemma then gets one retry, and after that a template is used. On 12 test weeks, all 12 first drafts passed (about 36 seconds each).
The checker cannot tell whether a number sits next to the right product. So I read the notes myself. An early prompt leaked field names into the text ("change vs 4wk avg pct of -53"). The checker passed it, a person would not. Now the facts are simpler.
5. Testing it once
The setup was picked on 7 validation weeks (Jul to Aug 2011). Then the test was run once, on the last 12 full weeks (240 product-weeks), against the strongest simple rival: the 4-week average times a factor k, with k fitted on the validation weeks.
| 240 test product-weeks | TabPFN-2 (no per-shop factors) | 4-week average x k (k fitted on Jul-Aug) |
|---|---|---|
| Real sales inside the P10 to P90 range (target 80%) | 80.0% | 85.4% |
| Range width / sales (narrower is better) | 1.50x | 1.78x |
| Stocking to the busy-week number: units left over | 160k | 178k |
| Stocking to the busy-week number: units short | 19.5k | 13.0k |
| Middle number: average miss as a share of units sold | 0.502 | 0.486 |
TabPFN-2's ranges came out calibrated, exactly on the 80% target, and 16% narrower than the tuned rule's. Stocking to them left 17k fewer units over, but 6.5k more units short. On the middle number it is a tie, and the tuned average is slightly better. The difference is that the simple rule needed a factor fitted on two earlier months of this shop's data. Every new shop is a new shop. TabPFN-2 needed no such fitting.
A few more numbers, because the tie needs them. Against the plain 4-week average (no factor), TabPFN-2 had the smaller miss in 7 of the 12 weeks and was ahead over the first 11 (0.502 against 0.514). The 28 Nov rush week decided the tie: over all 12 weeks the plain average is slightly ahead (0.491 against 0.502). TabPFN-2 also beat a gradient-boosting model trained on the same features (0.527) and two naive rules (last week 0.631, same week last year 0.884).
What I Got Wrong (and what it cannot do)
- The first Gemma header mapping made 4 wrong guesses out of 12 (see above).
- An early note prompt leaked field names into the text, and the number checker could not catch it.
- My "not a product" rule treats stock codes with no digits as postage or fees. In the demo data it also set aside 50 lines with codes like
DCGSSGIRLthat look like real products. - It forecasts regular sellers only (sold in at least 20 of the last 26 weeks). New or occasional products get no number.
- One week ahead only. Holidays and promotions are only modelled through "week of the year" and last year's same week. The 28 Nov miss is the proof.
- This is one shop's data and 12 weeks. Run the test on a client's own history before promising anything.
- Nobody has used it yet. No shop has seen this tool, and I am not claiming any user.
Price and who would pay
My plan: ₹199 a week, first month free. This is a plan. Nobody has paid and I have no revenue.
The shop sends its export. I run the tool on a laptop. The shop gets the cleaned file, the table and the note. My cost side is measured: one full run on the 1-million-row demo file took 52 seconds on a 2019 laptop CPU from the command line, with no API tokens.
What I hope: a tool like this helps a shop like the one I know a lot. It takes some of the work they do by hand and does it every week, and that should help them grow. That is a hope, not a result yet.
Why Does Open Innovation Matter?
The licence is the business model. TabPFN-2 and Gemma 4 are both Apache 2.0. I can sell a weekly service on them from day one. The newest TabPFN would have needed a commercial licence.
The orders stay on the laptop. The forecast was run on the full demo file inside a Linux network namespace with no network at all, and it finished (eval/results/offline_check.log). No shop's sales data has to go to a server.
No cost per run. No API key, no metered tokens.
Open weights can be bent to the job. Gemma's answers are forced into a JSON schema, its "thinking" mode is switched off for CPU speed, and either model can be swapped with one line.
Run it yourself
Needs Python 3.10+, about 3.5 GB of disk and about 3 GB of free RAM. No GPU, no API keys.
git clone https://github.com/xexpertai/tidy-predict && cd tidy-predict
python -m venv .venv && source .venv/bin/activate
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -e ".[dev]"
scripts/get-model.sh # llama.cpp + Gemma 4 E2B + TabPFN-2 weights
scripts/start-model.sh & # local model server
tidy-predict serve # open http://127.0.0.1:8765, pick the export, click "Make the restock note"
# or: tidy-predict orders.csv --out out/ (add --no-llm for a template note)
Prize Categories
- Best Use of TabPFN: TabPFN-2 is the forecasting step of a small prediction agent (clean, forecast, write, check). With no per-shop factors fitted, its likely ranges came out calibrated (80.0% inside, target 80%) and 16% narrower than a tuned rival's, and it beat gradient boosting on the same features. The busy-week number in every note is its P90.
- Best Use of Gemma: Gemma 4 E2B runs locally. It reads odd column headers under a JSON schema and writes the client note under a number checker.
AI assistance: this project, including the code, the evaluation, the video and a first draft of this post, was built with an AI coding agent (Claude Code). I checked the logic, reviewed the code and changed some flows.
Demo data: UCI Online Retail II, CC BY 4.0 (Chen, 2012, doi:10.24432/C5CG6D).






Top comments (0)