Skip to content
Recipe API

Migrate Food Composition Tables Without Rewriting Yesterday’s Meal Plan

Fresh Ciqual integration work shows how nutrition and recipe APIs can adopt a new food-composition edition without silently changing mappings, mixing calculation bases, or breaking saved meal plans.

nutritionapi-designdata-modelingmigrationmeal-planning

A reference-table update is not a drop-in replacement

On August 20, Open Food Facts opened two related proposals around France’s Ciqual food-composition data. One adds scripts for checking and propagating Ciqual mappings across ingredient and category taxonomies. The other proposes loading the 2025 Ciqual table alongside the 2020 edition rather than replacing it outright.

The counts explain why. The multi-version proposal reports 3,185 records in the 2020 table and 3,484 in the 2025 table. Of those, 3,003 codes occur in both editions, 182 occur only in 2020, and 481 occur only in 2025. Its current implementation loads editions in chronological order, allowing newer data to supersede overlapping records while retaining older-only codes as fallback coverage.

The companion taxonomy-mapping proposal reports 503 Ciqual codes missing from the categories taxonomy under its 2020-based analysis. It also changes mappings, including replacing a wholemeal-flour reference as the default for wheat flour with a white-flour reference. As of publication, both pull requests remain open. Review on the mapping proposal has requested changes after finding automation edge cases, including records with both exact and proxy mappings.

That is not evidence of bad engineering. It is evidence that adopting a food-composition edition is a join migration across datasets, taxonomies, calculations, caches, and product behavior—not a CSV refresh.

The thesis: nutrition APIs should deploy food-composition upgrades as edition-pinned mapping migrations with calculation manifests and cohort diffs, because a sensible fallback can improve coverage while silently producing mixed-edition recipe totals and unexplained changes to saved meals.

Source map and the repeated angle to avoid

Source Freshness What it contributes
Open Food Facts Ciqual 2025 plus 2020 fallback proposal Opened August 20; updated August 21, 2026 Concrete overlap, addition, retirement, load-order, and compatibility behavior across two editions
Open Food Facts Ciqual mapping and audit scripts proposal Opened August 20; reviewed August 21, 2026 Coverage auditing, category-to-ingredient propagation, a materially different flour default, and review-visible automation failure modes
ANSES, The Ciqual nutritional composition table November 19, 2025 Older official context on Ciqual as France’s reference food-composition database and downloadable open data

Earlier Recipe API posts have already argued for source provenance and purpose-bound external-ID crosswalks. Repeating “store the dataset version” would add little. The new angle is release behavior: how to move live calculations between editions without treating code overlap as semantic identity or fallback as invisible compatibility.

Separate three kinds of change

A migration report should classify at least three dimensions.

Identity-set change. New codes expand what can be represented; retired codes remove direct lookup paths. A retired code is not proof that its food disappeared or that a replacement is equivalent. It may have been consolidated, split, renamed, or excluded under a revised table policy.

Mapping change. An internal ingredient or category may point to a different reference food. The flour example matters because “wheat flour,” “white flour,” and “wholemeal flour” can share search ancestry while producing different nutrition. A mapping edit can change thousands of recipes even if both Ciqual editions contain all involved codes.

Value or schema change. A code present in both editions may have changed nutrient values, source evidence, missingness, or columns. Shared identifiers reduce migration work; they do not guarantee byte-for-byte or meaning-for-meaning equivalence.

These dimensions need separate statuses. code_present_in_both must not be interpreted as nutrition_unchanged. fallback_available must not be interpreted as current_edition. mapping_automated must not be interpreted as reviewed_exact_match.

The mixed-edition total is a real output state

Loading 2020 first and 2025 second is a practical compatibility strategy. For overlapping codes, the newer record can win. Foods whose codes disappeared can still resolve through 2020. Coverage does not collapse on launch day.

But consider a saved lentil soup with five mapped ingredients. Four resolve against 2025; one legacy stock or prepared-food code resolves only against 2020. The total may be perfectly useful, but it is no longer a “Ciqual 2025 calculation” in the singular. It is a composite assembled under a selection policy.

That distinction matters when clients:

  • compare a saved meal with a newly computed one;
  • enforce sodium, protein, or calorie thresholds;
  • rank recipes by nutrient density;
  • explain why a recommendation moved;
  • export records for a coach, researcher, or regulated workflow;
  • invalidate caches after a reference-data release.

Do not hide the mixture behind one top-level source label. Store a calculation manifest:

{
  "nutritionCalculationId": "nutcalc_01K3...",
  "recipeVersion": "recipe_842@17",
  "selectionPolicy": "ciqual-migration-2026-08",
  "computedAt": "2026-08-21T13:30:00Z",
  "components": [
    {
      "ingredientId": "ingredient:lentil-cooked",
      "reference": {"dataset": "ciqual", "edition": "2025", "code": "example-a"},
      "mappingRelation": "exact",
      "mappingVersion": "food-crosswalk-2026-08-21"
    },
    {
      "ingredientId": "ingredient:prepared-stock",
      "reference": {"dataset": "ciqual", "edition": "2020", "code": "example-b"},
      "mappingRelation": "proxy",
      "fallbackReason": "code_absent_from_2025"
    }
  ],
  "editionCoverage": {"2025": 4, "2020_fallback": 1},
  "status": "mixed_reference_editions"
}

The codes are illustrative. The contract is the useful part: edition belongs on each selected row, while the top level reports the policy and mixture.

A migration pipeline that preserves product truth

Stage Required output Failure it prevents
Inventory Added, retired, shared, and changed records by edition Assuming a larger table is a strict superset
Namespace Dataset plus edition plus code Treating ciqual:12345 as timeless
Mapping audit Exact, proxy, ambiguous, retired, and unmapped edges Bulk propagation turning a category default into an ingredient fact
Shadow calculation Old-policy and candidate-policy results for the same recipes Discovering changes only after users see them
Cohort diff Changes by ingredient, recipe type, nutrient, locale, and mapping relation A good average hiding regressions in one food class
Publication New immutable calculation IDs and an explicit activation policy Rewriting stored historical totals in place
Monitoring Fallback rate, unmapped rate, threshold crossings, and support-visible reasons Permanent compatibility mode becoming invisible debt

Shadow calculations should use the same recipe versions, quantities, serving yields, and unit conversions. Otherwise the diff cannot attribute changes to the reference-table migration. Compare both absolute nutrient deltas and product consequences: filter eligibility, warning thresholds, recommendation order, and meal-plan target status.

Prioritize review where impact is high, not merely where the nutrient delta is large. A modest sodium change that crosses a product threshold can matter more than a large vitamin change that no client uses. Likewise, an automated exact mapping deserves more scrutiny if it touches a common ingredient than an unresolved mapping for a rare catalog item.

Edge cases to put in the release gate

Same code, revised meaning. Compare names, food states, edible portions, units, nutrient columns, and values. Do not diff numeric arrays by position unless column identity has also been validated.

Retired code with no successor. Keep the historical row available for reproducibility, but return legacy_reference for new calculations or require a reviewed proxy. Never silently redirect by nearest name.

One old food split into several new foods. Return candidates based on preparation state and source text. A one-to-many split cannot be resolved by preserving the old code.

Exact and proxy edges on one taxon. This is one of the review concerns visible in the fresh mapping proposal. Define precedence and intended use, or reject the ambiguous mapping during publication.

Category and ingredient disagreement. A product category can provide a fallback, while parsed ingredients provide more specific evidence. Record which layer won; do not allow file order to decide.

Missing versus zero. A newer edition may improve coverage or change missingness. A missing value must remain unknown, not become zero during cross-edition merging.

Cached saved meals. Decide whether saved totals are immutable snapshots, lazily recomputed, or actively migrated. Whatever the policy, expose computedAt, calculation version, and a reason when the displayed value changes.

API decisions for builders and buyers

A simple nutrition response can remain simple, but an audit expansion should answer:

  • Which edition supplied each ingredient’s values?
  • Was the mapping exact, proxy, inherited, or fallback?
  • Did the current result mix editions?
  • Which calculation and mapping policies were applied?
  • Is a previous calculation still retrievable?
  • What changed between two calculation IDs?
  • Did the change alter a user-visible threshold or ranking?
  • Can clients request current, legacy, or a pinned edition without changing endpoint shape?

A useful comparison endpoint might be GET /nutrition-calculations/{old}/diff/{new}. It should return component-level causes, not only changed totals. “Protein increased by 2.1 g” is less actionable than “the wheat-flour mapping changed from a wholemeal proxy to a white-flour reference under crosswalk version X.”

Fallback is valuable during migration, but it needs an exit strategy. Track the share of calculated ingredient mass—not only ingredient count—coming from legacy rows. Set review queues for common fallback mappings. Publish deprecation criteria based on coverage and impact rather than an arbitrary date.

The practical takeaway

The fresh Ciqual work exposes two separate migration pressures at once: the reference table changed, and the joins into the food taxonomy need repair and review. Loading both editions can preserve service continuity. Audit scripts can expose missing mappings. Neither mechanism, by itself, proves that a recipe total is current, comparable, or safe to overwrite.

For recipe, meal-planning, and nutrition APIs, the durable design is to pin every selected reference row to an edition, version mappings independently, run old and new calculations side by side, and publish a manifest for composite results. That turns a food-table upgrade from an unexplained numerical event into a controlled data release—and keeps yesterday’s meal plan reproducible after tomorrow’s nutrition data arrives.

Sources

Start Building

One consistent schema on every response. Get a free key and ship in minutes.