Results · synthetic data + a small human check

Measured on practice orders.
Still waiting for a real shop.

The app handled 88.2% of answerable locked-test lines with no review and 0 wrong rows auto-accepted. These are synthetic results; no real shop has used it yet. A small human-written check also had 0 wrong rows auto-accepted.

Read the synthetic data card · Full evaluation and protocol

Current code · locked synthetic test

Four methods. The same safety checks.

A 111-item synthetic catalogue in 61 product families; 320 practice lines; 80 locked test lines, of which 29 need a human.

Current locked synthetic test · 80 lines · 13 October 2026
MethodHandled with no reviewWrong rows auto-acceptedNo typing needed*Model calls
Exact match54.9%054.9%0
Fuzzy match84.3%084.3%0
Model on every line84.3%092.2%80
Hybrid (the app)88.2%092.2%31

*Automatic, or one tap to confirm a correct pre-filled suggestion. Percentages use answerable lines. The model sees the top 8 candidates. Synthetic data card.

During development, the model on every line was worse than fuzzy matching. On this test it ties fuzzy on automatic rows but gives more correct suggestions. Hybrid asks it only about lines fuzzy can’t resolve.

MacBook Air M2, 8 GB, gemma4:e2b-it-qat, temperature 0, thinking off. Hard lines take about 2 s: p50 1.5 s, p95 2.6 s with nothing else running (up to 2.3 s / 4.5 s while other jobs ran).

Model bake-off · practice set

Five local models, one 8 GB laptop

Each model was the hybrid decider on the 320 practice lines. The chosen one was then run once on the locked test. More disk didn't help the biggest model: RAM is the limit.

Practice set (320 lines) · 13 October 2026
ModelMemoryHandled with no reviewWrong rowsNo typing neededModel p50 / p95
gemma4:e2b-it-qat (default)3.6 GB88.7%096.1%3.1 s / 9.2 s
qwen3.5:2b-q4_K_M (fast option)1.6 GB88.7%094.6%2.5 s / 3.3 s
gemma3:4b2.8 GB88.7%095.6%5.3 s / 8.4 s
qwen3.5:4b-q4_K_M (previous default)3.1 GB88.2%095.6%5.2 s / 8.4 s
gemma4:e4b-it-qat (40-line speed check)6.0 GB (2.5 on GPU)—0—5.6 s / 37 s

The top three are within 1–3 lines of each other. Gemma 4 E2B was chosen for its operator-facing numbers; Qwen3.5 2B is the faster alternative. Full bake-off.

Human-written check · 15 October 2026

Typed by people other than the author

Three friends (not shopkeepers) typed orders for 6 catalogue items in their own words. The app’s splitter turned them into 11 order pieces. The owner labelled them, then they were locked and scored once, with no change to the app before or after. A first round was excluded because those replies were AI-written.

Human-written set · 11 pieces, 5 answerable without a question
MethodWrong rows auto-acceptedMissed flagsHandled with no reviewNo typing neededModel calls
Exact match0140% (2/5)40%0
Fuzzy match0160% (3/5)60%0
Model on every line0160% (3/5)80%11
Hybrid (the app)0160% (3/5)80%5
  • No method auto-accepted a wrong row.
  • The one missed flag is the same line for every method: fortune oil 1 ltr ke 1 carton. The app entered exactly what the label says (Fortune oil 1 L, 1 carton), but the label marked it as needing a question, so by the strict definition it counts as dangerous: 1 of 11. The label was not changed after scoring.
  • Gaps synthetic data never showed: items joined by “aur”, or sent with no separator, are not split (the app asks, but its suggestion names only one product); “pouch” is not a unit word yet; and “tata namak kilo” fits Tata Tea about as well as Tata Salt, so the app asks.
  • 11 pieces from 3 people on 6 items: indicative only. Fixes must be built on practice data and checked on a new human round, never on this set.

Human-written set: method and full results.

Read the denominators

What each metric means

Metric definitions used by the evaluation harness
MetricDefinition
Handled with no reviewCorrect automatic rows ÷ answerable lines. Product, quantity and unit must all be right (“auto-accept accuracy”).
Dangerous rowsWrong automatic rows + missed review flags. Rate: divide by all lines. Target: zero.
No typing neededCorrect automatic rows + fully correct flagged suggestions, divided by answerable lines (“zero typing”).
Suggestion accuracyFully correct suggestions ÷ flagged answerable rows.
Flag recallCorrectly flagged rows ÷ all lines needing review.
Review loadRows sent to the operator ÷ all lines.
Recall@kCorrect product inside the k model candidates.
Model calls and latencyCall count and p50 / p95 model response times.

A boundary around the test

Lock first. Tune on practice data.

  1. Label what a careful operator would conclude.
  2. Hash-lock the test before any model runs. Changed locked sets cannot be scored.
  3. Tune prompts, thresholds, aliases and parser words only on development data.
  4. Compare methods on the same locked test. Justify fixes using development examples.

The synthetic test uses a separate seed; near-duplicate practice lines are rejected and leak-checked. Both reflect one author’s assumptions.

On every push, CI recomputes the locked test set’s hash and fails the build if it changed, then runs the automated tests and the no-model baselines.

A GitHub Actions run of the CI job named test, succeeded in 16 seconds. Every step has a green check: set up Node.js 24, run tests with no Ollama and no network model, check that the locked test set is unchanged, run the exact and fuzzy baselines with no model, add them to the job summary, and upload the baseline results.
A CI run on GitHub Actions: every step green, with no model and no secrets.

An evaluation can optionally be traced in Sentry: each line’s fuzzy match, model call, token counts and safety rules. Tracing exists only in the evaluation tool and runs only on synthetic or fixture data. The shop app sends nothing anywhere.

A Sentry trace of one hybrid evaluation run on synthetic practice lines. A waterfall lists invoke_agent spans, each with execute_tool, chat gemma4:e2b and HTTP spans of about two seconds. The selected safety_rules step shows status needs_review, because the order words fit two Red Label pack sizes, 500 g and 250 g, about equally well.
One traced evaluation of the 320 synthetic practice lines. The selected safety-rules step sent a line to review because two pack sizes fit equally well.

Synthetic data protocol and limitations · Continuous integration · Tracing an evaluation.

Try to make it fail

Random model picks still met the checks

The project has 210 automated tests, run by GitHub Actions on every push. In an adversarial test, a fake model made random product picks over 320 synthetic practice lines. Across 1,265 model calls, 0 wrong rows were auto-accepted.

This does not establish real-order accuracy or cover every failure.

External test report · Synthetic data card

Gains and misses · synthetic test examples

Helpful on a few lines. Fallible on others.

Where it helped

Two more automatic lines than fuzzy: thmusup 2ltr pc 6 and Sarf 500gm 2 Ctn. Two correct one-tap suggestions: everest halddi, frooti 10 ka.

Where it missed

It suggested 100 g for amul butter aadha kilo (500 g was right), read पारले जी 20 वाली as Frooti, and gave an unrelated suggestion for 5 sttar 20 wala 5box after parsing found no product words. All three needed review.

Examples from the current synthetic evaluation. Data card. Product names are examples in a synthetic catalogue; no affiliation.

History · synthetic test

The earlier run

The test pass fixed 25 findings. Stricter parsing now asks about 5 sttar…. That locked-test miss was not tuned away.

First locked synthetic test run · before the external test pass
MethodHandled with no reviewDangerous rowsNo typing neededModel callsModel p50 / p95
Exact match52.9%052.9%0—
Fuzzy match86.3%086.3%0—
Model on every line86.3%092.2%808.4 s / 15.3 s
Hybrid (the app)88.2%094.1%304.2 s / 4.8 s

History, not current performance. Synthetic data card.

Development history

Why the design became hybrid

These 40 hand-written practice lines were used for tuning. This is design history, not held-out or real shop performance.

Development evolution · separate from the locked synthetic test
VersionMethodHandled with no reviewDangerous rowsReview loadModel calls
BaselineFuzzy78.1%037.5%0
Model picks product, full cataloguellm-full50.0%352.5%40
Model picks product, top 8llm-cand43.8%260%40
+ code checks, suggestionsllm-cand78.1%1—40
+ fuzzy first, near-tie marginHybrid87.5%030%11

The synthetic data card describes the separate generated benchmark.

What remains unknown

Real orders are the next test

  • No real shop has used it yet; real review rates are unknown. Apart from the 11-piece human check, every result here is on synthetic data.
  • Prices, carton sizes and nicknames are illustrative.
  • One shop, operator and order at a time. Pasted text only; no photos, voice or WhatsApp integration.
  • A comma inside an item’s description, such as maggi, 4 pack 1 ctn, splits it into two rows. Commas inside numbers (1,500) are read correctly.
  • Items joined by “aur”, or sent with no separator, are not split yet (found by the human check).
  • No local-server authentication; a single-user desktop tool.

Synthetic data limitations · Privacy model.