The Missing Edit Ledger Between “2 Eggs” and a Green Score
Fresh work on recipe parsing and environmental-score coverage shows why food APIs need reversible, typed edits that preserve user wording, quantity evidence, canonical identity, and scoring mappings.
A better-covered score can describe a different recipe
An environmental calculator cannot score an ingredient when its food taxonomy has no usable reference-data row. A tempting repair is to offer a more specific, scoreable ingredient. If meat has no row but beef and pork do, the user can choose one and the score can continue.
Open Food Facts' Score My Recipe project added exactly this kind of API on September 4. Its new scoreable-ingredient suggestion endpoint walks down the ingredient taxonomy and returns descendants that resolve to distinct Agribalyse rows. It suppresses redundant descendants that lead to the same row, including duplicates encountered through multiple paths in the taxonomy graph.
The implementation solves a real coverage problem, but the surrounding work reveals a deeper product boundary. A same-day language fix makes recipe parsing use the interface language. New issues propose retaining the user's original ingredient name, retaining original units such as millilitres and counts, and giving users a way to repair ingredients omitted from the environmental calculation.
Together, these changes show that “fix this ingredient” is not one operation. It can mean preserving display text, reinterpreting a language, converting a quantity, narrowing an identity, selecting a scoring proxy, or actually substituting one food for another.
The thesis: a scoring repair flow should represent every proposed change as a typed, reversible revision because improving reference-data coverage can also change the recipe's meaning, quantity basis, and score denominator. Without that edit ledger, a more complete score may simply measure a recipe the user never entered.
Source map and the repeated angle to avoid
Fresh primary evidence from the last seven days:
| Source | Date | Contribution |
|---|---|---|
| Score My Recipe scoreable-ingredient endpoint | Merged September 4, 2026 | Descendant traversal, distinct Agribalyse-row suggestions, DAG deduplication, localized labels, and an OpenAPI contract |
| Score My Recipe interface-language parsing fix | Merged September 4, 2026 | Connects the UI locale to the parser's explicit language parameter |
| Score My Recipe unit-field proposal | Opened September 4, 2026 | Separates original quantity and unit from normalized grams, with count and density examples |
| Score My Recipe original-name proposal | Opened September 4, 2026 | Identifies the need to retain user-entered synonyms while keeping taxonomy identity under the hood |
Recent posts here have already covered parser candidates, ingredient trees, taxonomy-to-reference crosswalks, quantity dimensions, and environmental-score fallbacks. Repeating “preserve provenance” or “taxonomies are not flat” would add little. The narrower issue is edit authority: which transformations may happen automatically, which require confirmation, and how clients can distinguish a changed interpretation from a changed recipe.
One ingredient line has at least four representations
Consider the issue's example 2 œufs. The Open Food Facts parser can return the observed quantity 2, a normalized mass of 120 g, and the taxonomy identity en:egg. Those values serve different purposes:
- Source representation: what the user supplied, including spelling, language, number, and unit.
- Parsed representation: the extracted count and ingredient mention, tied to source spans and a parser version.
- Normalized representation: a canonical ingredient identity and a mass suitable for calculation.
- Method binding: the particular Agribalyse row used by the environmental method.
Collapsing them into name, weight, and ingredientId makes updates destructive. If the UI shows only egg and 120 g, it can no longer round-trip the user's plural French wording or let them change two eggs to three in the unit they understand. If a scoring repair replaces a generic identity with a child, the system cannot tell whether the user clarified the original ingredient or accepted a substitute.
An illustrative record keeps the layers separate; its internal identifiers are placeholders:
{
"ingredientRevisionId": "ingrev_018",
"source": {
"text": "2 œufs",
"language": "fr",
"span": [0, 6],
"immutable": true
},
"quantity": {
"observed": {"value": 2, "unit": "count"},
"normalized": {
"value": 120,
"unit": "g",
"method": "count_times_reference_mass",
"referenceMassG": 60,
"estimated": true
}
},
"identity": {
"conceptId": "en:egg",
"displayText": "œufs",
"selection": "parser"
},
"scoreBinding": {
"dataset": "agribalyse",
"rowCode": "example:egg-reference",
"relation": "taxonomy_mapping",
"status": "matched"
}
}
The source is evidence, not a field to keep rewriting. A user's accepted edit creates a new revision and records the old one as its parent.
Classify the change before applying it
A client should not present every candidate under one “alternatives” label. The same target ingredient can imply very different authority.
| Change type | Example | May run automatically? | Effect on recipe meaning |
|---|---|---|---|
| Display preservation | Keep œufs while storing en:egg |
Yes | None |
| Parser reinterpretation | Reparse French text after correcting lang=en to fr |
Preview automatically | May change structure; require review when material |
| Quantity normalization | Derive grams from 2 eggs |
Yes if policy allows estimates | Does not change observed count, but changes calculation input |
| Identity refinement | Confirm that meat means beef |
No | Narrows an ambiguous ingredient |
| Method proxy selection | Score a generic food using an approved reference row | Policy-dependent | Keeps recipe identity but changes estimate basis |
| Recipe substitution | Replace beef with lentils | No | Changes the actual recipe |
A taxonomy descendant is a candidate refinement, not evidence that the user intended that child. Beef is below meat, but selecting it asserts information absent from the original line. Conversely, an environmental proxy can leave the recipe identity untouched while choosing an approximate calculation row. Those operations should not share an endpoint response or audit verb.
For each candidate, return the taxonomy path, reference row, mapping basis, method version, and whether accepting it changes canonical identity. Let the user choose between “clarify ingredient,” “use estimate only,” and “replace ingredient.”
Show coverage and score as separate outputs
The current calculator reports ingredient IDs omitted from computation, and its interface calculates omitted weight as a percentage of total recipe weight. That distinction is essential because the environmental score is a weighted average over ingredients with matched rows.
Suppose an unscored 300 g ingredient is excluded. Selecting a scoreable descendant does two things at once:
- adds that ingredient's environmental factor to the numerator;
- adds 300 g to the score's effective denominator.
A changed grade therefore does not isolate an improved estimate from a changed measurement boundary. APIs should return both:
{
"score": 61.4,
"grade": "B",
"coverage": {
"byMass": 0.82,
"matchedIngredientCount": 7,
"totalIngredientCount": 9
},
"comparison": {
"previousScore": 74.1,
"previousCoverageByMass": 0.55,
"changeReasons": ["ingredient_refined", "coverage_expanded"]
}
}
Do not rank repair candidates by the grade they produce. That invites score shopping: users or automated optimizers can choose the most favorable descendant rather than the most accurate one. Rank by semantic fit, evidence quality, and mapping specificity, then preview the score effect as a consequence.
Locale is a hint, not ownership of the text
Using interface language for parsing is a sensible immediate fix when the API requires a language. It is not proof that the pasted recipe uses that language. A French-speaking user can paste an English recipe; a grocery product can contain several label languages; a browser locale can differ from both market and source text.
Keep three fields independent:
inputLanguage: declared, detected, or user-confirmed language of the recipe text;displayLocale: language used for labels and explanations;market: jurisdiction used for products, units, and policy.
Changing display locale should not silently reparse the source. Changing input language should create a new parse candidate with a structured diff: ingredient boundaries changed, taxonomy identity changed, quantity changed, or only labels changed. Low-impact label changes can be accepted automatically; changed allergens, quantities, identities, or score coverage need explicit review.
Failure modes an edit ledger makes visible
Synonym erasure. A user selects a familiar synonym, but a later round trip displays only the canonical label. Keeping displayText separate from conceptId prevents the taxonomy from rewriting authorship.
Count-to-mass drift. 2 eggs becomes 120 g, then a user edits 120 without knowing it was derived. Preserve count, reference item mass, derivation method, and which field the user actually edited.
Taxonomy-graph ambiguity. One node can have multiple parents, and several descendants can map to the same reference row. Deduplication is useful for the picker, but the accepted proposal still needs the path and relation used.
Stale proposals. Taxonomy mappings and reference datasets change. Include versions and reject acceptance when a proposal was generated against stale data; regenerate rather than applying an old row code.
Score gaming. A candidate that improves the grade is not necessarily the best identity match. Keep ranking criteria independent from score outcome.
Concurrent edits. A delayed scoring response or suggestion can target an ingredient revision that no longer exists. Require basedOnRevision and use optimistic concurrency or idempotent proposal IDs.
Zero masquerading as missing. A failed quantity conversion should not become 0 g. Return unresolved with a reason, because zero removes the ingredient from the weighted calculation without making the omission obvious.
Implementation checklist
Before shipping an ingredient-repair workflow, verify that it can:
- retain immutable source text, spans, input language, and original units;
- store observed quantities separately from normalized or estimated mass;
- keep user-facing wording separate from canonical taxonomy identity;
- distinguish refinement, proxy selection, parser correction, and substitution;
- require confirmation whenever a proposal changes recipe meaning;
- return taxonomy path, match basis, dataset row, and version for scoring suggestions;
- preview score and mass coverage before and after a proposal;
- rank candidates by semantic evidence rather than favorable score movement;
- create append-only ingredient revisions with actor, timestamp, and reason;
- invalidate stale proposals and reject responses for superseded revisions;
- test multilingual paste, synonyms, counts, volumes, unresolved conversions, and DAG duplicates;
- expose enough audit data to reproduce both the original and revised score.
For developers and technical buyers evaluating a Recipe API, ingredient coverage is only half the question. The more revealing test is what happens when coverage fails. A durable API does not force a client to choose between an unscored ingredient and an unexplained rewrite. It supplies the candidates, preserves the evidence, separates calculation mappings from recipe edits, and lets the product assign authority deliberately.
Sources
- Open Food Facts, Score My Recipe pull request: “feat: suggesting scored ingredients”, merged September 4, 2026.
- Open Food Facts, Score My Recipe pull request: “fix: use interface language to parse recipe”, merged September 4, 2026.
- Open Food Facts, Score My Recipe issue: “Add a ‘unit’ field on ingredients line”, opened September 4, 2026.
- Open Food Facts, Score My Recipe issue: “Keep original input of ingredient name”, opened September 4, 2026.
- Open Food Facts, Score My Recipe issue: “change the way we show an ingredient does not have valid environmental impact”, opened September 4, 2026.
Start Building
One consistent schema on every response. Get a free key and ship in minutes.