DEV Community

Cover image for Pricor: local AI reads the supplier spreadsheet, an exact engine does the math
Teo Furlan
Teo Furlan

Posted on

Pricor: local AI reads the supplier spreadsheet, an exact engine does the math

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.

What I Built

Two suppliers send you their price lists. Supplier A's spreadsheet says 12.100,00 under Precio Bulto c/IVA (price per box, VAT included), 10+5 under Bonificación, and 100 under Unid x Bulto. Supplier B's says 92,00 under Costo Unit. s/IVA (unit cost, before VAT) and 8% under Dto.

Both are selling the same screw. Which one is cheaper?

You cannot tell until both lists speak the same language. Supplier A's screw costs 85.50 per unit: two chained discounts (not 15%), VAT removed, the box split into 100 units. Supplier B's costs 84.64. In many small Argentine shops, and at least one distributor I have talked to, someone does this translation by hand, for every supplier, in every supplier's own format, every time a list arrives. And next week, one of the columns will have moved.

Pricor (price + order) is a local app that turns those files into consistent, importable product records: supplier code, description, net unit cost and currency, with row-level traceability. It reads Excel or CSV, proposes what each column means, asks about whatever the file does not say, and shows every number from the original cell to the final cost before you approve it. Rejected rows stay visible and exportable; nothing is dropped silently.

Pricor prepares the file; it does not replace the shop's management system. Those systems already import price updates, but only in their own fixed formats.

Who would pay? The side hustle I have in mind is setting up and supporting a shop's recurring supplier imports, built on this tool. That is a hypothesis, not validated demand: I have not had a paying customer or measured time saved yet.

Demo

The video was built from screens recorded in the running app with Laya and Gemma 4 E4B running locally, using two small fictional lists ("Ferretería Norte" and "Distribuidora Sur") written for the recording. The narration is a local Kokoro voice.

A longer browser rehearsal on two lists from a real public supplier price page covered the full loop:

  1. First import. Gemma's suggestions arrive with no answer preselected. I confirmed the columns and assumptions, inspected the calculation trace, approved, and downloaded the CSV/XLSX exports.
  2. Next week's list. The same supplier's saved plan was reused. Pricor noticed that the price column had moved and followed the saved mapping instead of reading the wrong column.
  3. Price report. Comparing the two approved lists: 514 increases, 118 decreases, 420 unchanged, 8 new products, 5 discontinued, and one +200% anomaly, across 1,057 and 1,060 accepted products.

To be clear about that run: the second list was simulated by a tool that perturbs a real list and records every change in a manifest. The exports were checked against every changed price and every added or removed code in that manifest, not just the totals. It demonstrates the behavior; it is not evidence of a real supplier's price update.

The arithmetic behind the example at the top, as the receipts in the demo show it:

12,100.00   per box, 21% VAT included
 × 0.90 × 0.95   chained discounts of 10% and 5%
 ÷ 1.21          remove VAT
 ÷ 100           units per box
=    85.50 net per unit

92.00       per unit, before VAT
 × 0.92          8% discount
=    84.64 net per unit
Enter fullscreen mode Exit fullscreen mode

No model calculates those results.

Code

Repository: github.com/teofurlan/pricor

The application is Apache-2.0: backend, frontend, tests, the corpus download tooling (supplier files are linked from their public pages, not redistributed), the Laya fine-tuning tooling and the benchmark. The fine-tuned Laya weights are not published yet; the training script reproduces them.

How I Built It

The whole design is one rule: models interpret, the engine executes, the user decides.

Pricor as a local filter: supplier files go through a deterministic profile, Laya, Gemma 4 and the user, then an exact engine produces one common format for the system the shop already uses

Pricor sits in front of the system a shop already uses (or the one it is moving to), as a local filter with three layers: Laya, then Gemma, then you.

  • A deterministic profiler finds the header row, row types, apparent column types and number formats (12.100,00 is not 12.1).
  • A fine-tuned multilingual Laya model classifies each column into a closed set of roles. Columns it is unsure about go to Gemma 4 E4B, running locally through Ollama. Whatever is still uncertain goes to the user.
  • Questions are generated deterministically from what the plan is missing: does the price include VAT? Is it per pack or per unit? Which currency? Gemma may suggest an answer and show its (uncalibrated) confidence, but nothing is accepted on the user's behalf.
  • The executor only understands a closed catalog of operations: parse numbers, apply chained discounts, remove VAT, split packs into units, map values, normalize text and, optionally, convert currency. It never runs model-generated code.
  • FastAPI serves a React frontend. SQLite stores approved plan versions and price history. Exports come with a separate rejected-rows report.

Plans are saved per supplier. Before reusing one, a fingerprint checks each column's position, normalized header and apparent type. If a column the plan needs is missing, the affected rule is blocked instead of quietly producing a cost. Currency conversion keeps the original value and currency, and snapshots the exchange rate used for that run.

Memory is explicit, local and cited

Pricor can remember things like "this supplier's prices always include VAT". Ollama produces bge-m3 embeddings, SQLite stores them, and Python ranks matches by cosine similarity.

A memory is created only when the user writes one, or accepts a Gemma suggestion and clicks Remember this. Never automatically. The most relevant memories are passed to Gemma as numbered, untrusted data; Gemma must cite any memory a suggestion relies on, and the UI shows the citation. A reply citing a memory that was not supplied is discarded entirely. If memory is unavailable, the import continues with a notice. A Memory panel lets the user view, edit and delete everything.

In one manual check (not a benchmark), with one note about one supplier and one about another, Gemma's VAT and per-pack suggestions for the first supplier cited the right note and not the other.

The benchmark did not justify automating everything

The benchmark in the repo has only 29 held-out columns from two distributors, so read it as a sanity check, not a leaderboard:

Classifier Correct Accuracy
Keyword/type heuristics 24/29 82.8%
Laya zero-shot 18/29 62.1%
Laya fine-tuned 22/29 75.9%
Gemma prompt-only 25/29 86.2%

In the whole-sheet cascade, 11 columns were resolved automatically (10 correctly) and 18 of 29 still went to the user. That is the honest result: on this data, the app should ask. Laya took about 35 ms per column on GPU; Gemma averaged about 11.4 s per sheet (RTX 4060 Laptop GPU). Those are different units, not a speedup claim.

The caveats are documented next to the numbers: thresholds were chosen on partly synthetic validation data, the test set lacks some roles, the heuristics were written after their held-out headers had been seen, and two labels were corrected after the first run. A heuristics-plus-Laya agreement stage I tried afterwards showed no advantage, so the shipped cascade does not use it.

Bugs worth telling

Percentages. Excel stores a cell displayed as 21% as 0.21. Lose the number format and 21% VAT becomes 0.21% VAT, which still produces a plausible-looking cost. The reader now keeps that meaning at the spreadsheet boundary, without touching the engine's percentage-point contract.

A second discount column. While recording the demo, I found that when a list has two separate discount columns, the plan builder uses only the first one. Chained discounts in one cell (10+5) are handled correctly, which is what the demo uses, but a second column is currently ignored without a question, which breaks the app's own "nothing silent" rule. Fixing that is next on the list.

The backend suite passes 1,219 tests (three live-model tests deselected) and the frontend suite 194. None of that proves compatibility with every Excel file a supplier can produce.

Where I'm Starting From

I did not start from a model looking for a problem. I started from what is publicly visible:

  • Supplier price lists change often in Argentina. Press reports describe kiosks receiving price-change notices from two or three suppliers a week. Inflation has slowed in 2026, so this is less urgent than in 2023–24, but lists still change.
  • Management systems already import prices, but only in their own format. The documentation of several local systems (Dux, ADM Global, Flexxus) describes re-importing a file whose columns match their fields, or filling a fixed template. None documents per-supplier saved mappings, pack-to-unit conversion, or chained discounts with VAT included. That gap is my inference from their docs, not a survey.

What people told me so far

After publishing the first version of this post, I talked to two people. Two conversations are not validation, but they changed how I describe the project:

  • An IT colleague at a distributor confirmed that the product data suppliers send is loaded into the distributor's system by hand, and said a tool like this would help.
  • A business owner told me Pricor was not very useful for their specific case, but could be very useful for automating data entry into new or existing systems: shops and companies moving from paper records to a digital system. They offered to ask colleagues whether it would help them.

That second idea is broader than supplier price lists, and I have not tested it. The next step is still the same: a supervised trial with a real list someone already handles by hand.

Why Does Open Innovation Matter?

The open models are in the import path, not bolted on as a chatbot. Laya and Gemma read the supplier's data, and their limits directly decide when the app has to ask the user.

  • Privacy. A shop's supplier lists and costs never leave its own computer to reach a hosted model provider.
  • Cost. A shop imports the same suppliers every week. With local inference, reusing a plan has no per-call fee. Electricity, hardware and support are not free, but there is no meter running.
  • Offline. Imports work without internet. Only the optional online exchange-rate refresh needs a connection; a cached or manually entered rate works offline.
  • Fine-tuning and measurement. Open weights let me fine-tune Laya for this narrow task and publish exactly how I measured it. It improved over its zero-shot baseline but did not beat Gemma or the heuristics on this small set, and I can say so because everything is inspectable and replaceable.
  • Licensing. Gemma 4 is Apache-2.0, so it can ship inside something a shop pays for.

The next step is not more features: it is a supervised trial with a shop or distributor, using a list they already load by hand. Compare Pricor's output against their current import, watch where the confirmation steps confuse them, and ask what recurring support would be worth paying for. Until then, the business case stays provisional.

Prize Categories

  • Best Use of Gemma: Gemma 4 E4B runs locally through Ollama. It classifies the columns Laya is unsure about, suggests answers to the plan's questions, and uses explicitly saved memories as cited context. It is not fine-tuned, and it never does price arithmetic.

I am also entering for the overall prize. I do not claim any other partner integration.

Top comments (4)

Collapse
 
vasyl_kyryliuk profile image
Vasyl Kyryliuk •

The explicit supplier memories caught my eye. If someone later edits a remembered VAT-inclusive assumption, does an earlier approved import keep the assumption and trace it originally used, or pick up the new memory? I'm building VAT Engine and thinking about how to keep past calculations explainable after settings change.

Collapse
 
teo_furlan profile image
Teo Furlan •

Hi Vasyl! An approved import keeps its confirmed plan, results, and calculation trace (per-row). Supplier memories inform suggestions rather than acting as live settings, so editing a remembered VAT-inclusive assumption doesn’t retroactively change an approved calculation.
There is an important limitation, though: memory references currently retain IDs, not versioned copies of the original note text. The calculation remains explainable, but I wouldn’t claim complete historical provenance of the memory behind the suggestion yet.
For a VAT Engine, I’d separate mutable defaults from the evidence attached to each approved calculation, including the exact assumption and memory version used. Hope this helps!

Collapse
 
vasyl_kyryliuk profile image
Vasyl Kyryliuk •

Thanks, Teo, that answers it. Especially useful to know that the calculation is preserved but the original memory text isn't. Appreciate you being clear about that limitation.

Thread Thread
 
teo_furlan profile image
Teo Furlan •

You’re welcome! Thanks for asking in the first place haha. If you ever want to compare notes on VAT Engine or need a second pair of eyes on the design, feel free to reach out. I’d be happy to help ;)