We tested Jev in our calorie tracker. Here’s why we didn’t ship it.
Jev picked better food records than our code and ran faster than Gemini Flash Lite. The full calorie pipeline gave us a much less convincing reason to switch.
Our food matcher could turn “sugar-free syrup” into caramel popcorn. It could choose raw blueberries for dried blueberries, or barbecue beef for bolognese sauce. Those are real bugs, and a fast model that understands food names seemed like a promising way to fix them.
So we tested Jev, TypeSafe’s model for structured decisions, against our code and Gemini 3.5 Flash Lite. This follows our first journal on AI calorie estimates, but asks a different question: once we have identified a food, can a model choose a better nutrition record?
We kept Jev out of production and shipped four targeted matcher fixes instead. The experiment made a good case for better matching. It did not establish that adding another model would give users better calorie estimates.
Where Jev would fit
Jev accepts text, so it was choosing between food records after the photo had already been read. We gave it a food query, every usable candidate returned by the USDA search, and a “none fits” option. Calories and macros were still calculated in code.
Identify food and grams
Gemini 3.1 Flash Lite stays fixed in the paired photo test.
Choose a food record
Our code, Jev or Gemini 3.5 Flash Lite picks from the same USDA candidates.
Calculate the meal
Use the chosen record, or the existing AI nutrition estimator when no match fits.
The first result looked compelling
We built 60 labelled queries across 20 food families: 20 queries from our existing photo fixtures and 40 variations designed to catch mistakes. Models saw the full usable candidate list and made three passes. The code made one deterministic decision per query.
This was deliberately heavy on known traps. Its score describes this test set, not the proportion of real meals that KcalSnap gets right.
Better choices on the matcher test
| Selector | Correct decisions | Median lookup time |
|---|---|---|
| Code before the fixes | 44/60 (73.3%) | No model request |
| Jev 1.13, full list | 168/180 (93.3%) | 333 ms |
| Gemini 3.5 Flash Lite, full list | 164/180 (91.1%) | 585 ms |
Correct means an acceptable food record or a correct “none fits” decision under our labels. The 180 model decisions repeat the same 60 queries three times. Timing covers the selector request, not a whole scan.
Jev was faster. The accuracy difference between the two models was much less persuasive: on the 21 held-out queries, their entire gap came from one beef query repeated three times. Accepting a reasonable cooked-ground-beef approximation made them tie.
There were weaknesses too. When no candidate fit, the code correctly declined 13 of 14 cases. Jev did so in 33 of 42 repeated decisions, and Flash Lite in 36 of 42. Reordering candidates while changing their option IDs changed each model’s chosen record on 12 of 60 queries. Some were equally acceptable alternatives, but a few changed correctness.
Why we dropped the shortlist and tried a code veto
Our top-eight ranking retained an acceptable record in only 12 of the 16 held-out queries that had one. A model cannot choose an answer it never sees, so the photo test used full lists.
We also tried keeping every “none fits” decision from the code, then asking the model to choose from the full list otherwise. That protected against some bad picks, but the code veto blocked nine queries with acceptable matches in the 60-case set. The model behind it could never rescue them.
Then we tested the calorie total
We ran nine public meal photos three times each. For every scan, all five alternatives shared the same recognized foods, estimated grams and USDA candidates. Each then ran its own nutrition selection and actual AI fallback. Model selection had a 1.5-second deadline, falling back to the code’s choice on timeout.
No clear calorie win
| Nutrition selection | Within 25% of estimated kcal reference | Median absolute reference deviation | Median added time for the same scan |
|---|---|---|---|
| Code before the fixes | 21/27 | 10.9% | Baseline |
| Jev, full list | 22/27 | 15.2% | +330 ms |
| Flash Lite 3.5, full list | 23/27 | 18.4% | +579 ms |
| Code veto + Jev | 22/27 | 14.5% | +450 ms |
| Code veto + Flash Lite 3.5 | 22/27 | 16.3% | +630 ms |
Local paired pipeline measurements, including vision, USDA retrieval and nutrition. Most USDA searches used a warm local cache. Phone upload, server startup and screen rendering are excluded. Added time is the median of paired differences, not the difference between two overall medians.
A model moved one or two more scans inside a broad tolerance band, while median deviation increased. Mean deviations stayed close, around 15% to 16%. Those mixed results, against visually estimated references, gave us no defensible accuracy winner.
The missing piece in the first headline was our fallback: a rejected USDA match already goes to an AI nutrition estimator. It does not become zero calories or a failed scan.
AI-estimated food items with the old code versus full-list Jev, across the same 151 detected items. Much more database coverage, without a clear improvement in final calorie estimates.
That is worth investigating with independently documented meal references. It does not prove the AI estimator and USDA are equally accurate, or that we should remove the database. Portions were uncertain, fallback estimates varied, and changing one component can compensate for an error elsewhere.
Jev’s speed advantage was real in this test
Full-list Jev was faster than full-list Flash Lite on 21 of 27 paired scans, saving a median 275 ms. It was still a median 330 ms slower than the code baseline. We have not measured whether that difference is noticeable in the actual app.
Across both photo-test variants, Jev hit the 1.5-second deadline three times in 264 requests; Flash Lite hit it zero times in 264. Every timeout recovered through the code fallback. An earlier Jev shortlist request also reached a separate 20-second timeout. These are observations from a small run, not a provider reliability ranking.
Both models were inexpensive at this scale. Jev’s full-list selector cost about $0.000112 per lookup in the first test, versus $0.000486 for Flash Lite. Cost was not the deciding factor. Adding another model needs a benefit that survives the whole workflow.
What we actually shipped
The four concrete bugs were worth fixing regardless of which model won. These matcher changes are now live:
- Keep sugar-free meaningful. Sugar-free syrup cannot become caramel popcorn, and sugar-free is not treated as interchangeable with reduced sugar or no added sugar.
- Respect dried food. Dried blueberries cannot use the nutrition of raw, fresh or rehydrated berries. A suitable dried record can still be selected.
- Keep bolognese away from barbecue beef. A shared “beef” or “sauce” word cannot override an incompatible food identity. When no defensible match remains, use the existing AI estimator.
- Preserve lean-beef percentages. A 93% lean query keeps that constraint. It cannot quietly become 85% lean or unspecified beef.
We added regression tests using the captured candidate lists and refreshed the matching cache version. Existing saved meals keep their recorded values. We did not add Jev or change the production vision model.
The next benchmark starts with better source data
The next step is to find published meal photos or datasets that already pair the pictured portions with measured weights and documented calorie and macro references. After checking the source and reuse terms, we can use those references to compare portion estimates, nutrition matches and whole-meal totals.
For now, the four matcher fixes are live and the existing pipeline stays in place. Another model comparison should use a stronger reference set.
Method, model versions and public results
The experiment completed on September 18, 2026. We tested Jev 1.13 against Gemini 3.5 Flash Lite through OpenRouter. The photo test kept Gemini 3.1 Flash Lite as the vision model. All reported comparisons use the matcher before the four fixes.
The first test split whole food families into 39 development and 21 held-out queries. Labels accepted practical approximations such as generic meat pasta sauce for bolognese. The completed shuffle also renamed positional IDs and moved “none” to the front, so it does not isolate order alone.
The photos are public Wikimedia Commons fixtures, with examples and attribution in the first journal. No private user scans were used. Download the method and results notes or aggregate results as JSON. This is our own development experiment, not an independent evaluation.
Read field note 001: Your AI recognizes lunch. Can you trust its calorie count?