mabel.ink

The ingredient parser was confidently wrong

July 29, 2026

Ingredient lines on recipe pages are free text. "1 unit Onion" and "16 ounce Refried Black Beans" and "teaspoon (tsp) Salt" all have to become a quantity, a unit and an entry in a controlled vocabulary, or the database cannot filter, scale or build a shopping list. The parser I wrote for RecipeBox does that mapping, and it attaches a confidence score to each line. This is about the day I learned what that score does not measure. The overview of the project is in a private recipe database in three repos.

What the score means

High means the name matched the vocabulary outright. Medium means it matched after dropping trailing words. Low means nothing matched and the string is kept as written, for me to resolve.

Low is the honest failure. It says "I do not know," and a human looks. I came to like low scores for exactly that reason.

What it cannot say

High means "I found a match." It does not mean "I found the right one." Those are different claims, and the score only knows the first.

Four ingredients in one early batch scored high, loaded without complaint, and were wrong:

Later batches found more. In one batch I counted nine silent wrong matches, all high, all loading cleanly. A few of them:

The steak seasoning was the worst. Once it resolved to steak it inherited the beef protein group, so a tablespoon of seasoning would have put its recipe on the Beef facet. A wrong facet sends someone to the wrong recipe. I would rather have a null than that.

The evidence was in the leftovers

The parser works by matching a name and treating whatever it could not use as the preparation text, the part of the line like "diced" or "or coconut vinegar." That leftover is where a wrong match shows its work. A correct match leaves something ordinary behind. A wrong match leaves a word that does not belong to the thing it picked: the "vinegar" that white wine did not account for, the "dressing" that oregano never was.

So on 29 July I stopped reading the confidence buckets and started listing what each match left behind in the preparation field. That is how the buttermilk, mushroom and vinegar cases showed up, and it also turned up a long green pepper and a chili pepper as the fifth and sixth casualties of one bare "pepper" alias. The alias exists because "salt and pepper" really is the commonest string in the corpus. A bare "jam" has no such excuse, and the apricot alias I had added for it failed a batch later. I took that alias back out rather than patching around it.

How review changed

Review used to mean reading the confidence summary and then the files. Now the order is:

  1. Ingredients with no name, because those block the load.
  2. Low confidence, which loads and tells me how well the vocabulary was prepared.
  3. Preparation leftovers, ranked, with the ordinary ones suppressed. This is the section that pays for the rest.
  4. Everything else, including files with a low share of high matches.

Ranking came later, once a batch was too big to read. The idea is that a leftover appearing ninety times is structural and needs one decision, while one appearing twice is where the errors are. Reading files in the order they sit on disk presents those identically.

I also pointed the same report at recipes already imported. It found a habanero pepper stored as black pepper. That one has a visible consequence: a fresh chile was filed as a ground spice, so the recipe answered a search for black pepper and not for habanero. Fixing the vocabulary did not fix it, because parsing happens at fetch time and the result is stored. The wrong answer is still in the stored files, and it is on my backlog as a re-parse.

Why fetch, review and import are separate

Nothing reaches the database until I have read the review block. Parsing is accurate perhaps 80 to 90 percent of the time at best, and I cook from these recipes. A wrong quantity is worse than a missing one, because a missing one makes me look, and a wrong one looks fine. The separation puts a person between a confident answer and a permanent one.

What I took from it

A confidence score measures how sure the matcher is, not whether it is right, and a system can be very sure and very wrong. The useful evidence is whatever the answer could not explain, so I look there first now.