Skip to content

fix(add-meal): convert Open Food Facts micronutrients into the app's units - #775

Open
jordan-lee-code wants to merge 1 commit into
developfrom
fix/off-micronutrient-units-716
Open

fix(add-meal): convert Open Food Facts micronutrients into the app's units#775
jordan-lee-code wants to merge 1 commit into
developfrom
fix/off-micronutrient-units-716

Conversation

@jordan-lee-code

Copy link
Copy Markdown
Collaborator

Summary

Open Food Facts normalises every <nutrient>_100g field to grams, whatever unit the packaging used. MealNutrimentsEntity carries each micronutrient in the unit the app displays it in, milligrams for the minerals and micrograms for vitamins A, D and B12, and fromOffNutriments was copying the values straight across. Every mineral was stored a thousand times too small, and those three vitamins a million times too small.

That is what Genfood reported in #716: vitamin D reading 0.00µg on every Open Food Facts product, in search results and in the Today's nutrients sheet alike, because 5µg had been saved as 0.000005.

Why it is worth more than the sheet it was reported against

NutrientPanelTotals.fromIntakes is deliberately unit-agnostic. It scales the stored per-100 value and hands the result to the panel, and the panel compares it against DriReference amounts in mg and µg. So a food scanned from Open Food Facts was contributing almost nothing to any micronutrient goal, quietly, without ever looking broken. ComputeRecipeNutritionUseCase sums the same fields, so a recipe mixing an Open Food Facts ingredient with a Supabase one was adding grams to milligrams.

Changes

  • Two small private helpers on the factory convert grams to milligrams (cholesterol, the seven minerals, vitamin C, vitamin B6, niacin) and grams to micrograms (vitamins A, D and B12). Both null-pass, matching the surrounding asDoubleOrNull() shape.
  • Macros and the mono/poly/trans lipids are grams on both sides and are untouched.

The other two sources were already correct, which is what made the mismatch legible once you looked at them side by side: fromSpFoodSummary pivots into the app's units and fromFdcNutriments reads FDC values that are natively mg and µg. The same field meant milligrams from one database and grams from another.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / tooling
  • Localization
  • Other

Related issues

Closes #716

Test plan

  • Unit tests added or updated
  • flutter analyze clean

Detail:

  • The existing micronutrient fixtures now carry the grams Open Food Facts actually sends, so the test reads the conversion in both directions at once. Two of the expectations had been pinning the bug (sodium100 == 0.12, against the FDC path's 120 for the same food).
  • New: values copied from live API responses rather than invented, so the fixture cannot drift from what the API sends. Iron for 3168930010265 and sodium for 3017620422003, both reported with _unit: "g".
  • New: the same food mapped from Open Food Facts and from FDC now comes out identical, field for field. That is [Bug]: Micro grams (μg) are displayed wrong #716 in one assertion.
  • New: a declared zero survives as zero rather than becoming a null the panel ignores.
  • flutter test: 1093 passing. flutter analyze: no issues.

What this does not do

Meals logged before this keep the values they were saved with, so old entries read as they do today and nothing gets worse. Repairing that history means rewriting someone's intake records on upgrade, and that deserves its own change and its own review, so it is filed separately rather than folded in here.

Checklist

  • Code follows project style
  • No new interactive widgets were added
  • No user-facing strings changed
  • No DBOs, DTOs, or environment fields changed
  • No secrets or .env values committed
  • PR title follows conventional commit style

…units

Open Food Facts normalises every `<nutrient>_100g` field to grams, whatever
unit the packaging used. This entity carries each micronutrient in the unit
the app displays it in: milligrams for the minerals, micrograms for vitamins
A, D and B12. `fromOffNutriments` copied the values across untouched, so
every mineral was stored a thousand times too small and those three vitamins
a million times too small.

That is what Genfood saw in #716. Vitamin D read 0.00µg on every Open Food
Facts product, in search results and in the Today's nutrients sheet alike,
because 5µg had been stored as 0.000005. The daily micronutrient panel sums
these same fields and compares them against reference intakes in mg and µg,
so a food scanned from Open Food Facts was contributing almost nothing to
any micronutrient goal, quietly, without ever looking broken.

The other two sources were already right, which is what made the mismatch
easy to see once you looked: the Supabase view pivots into the app's units,
and FDC publishes these nutrients in mg and µg natively. The same field
therefore meant milligrams from one database and grams from another.

Two small helpers on the factory now do the conversion, and the tests read
it in both directions: the fixtures carry real values from live API
responses, and one case asserts that the same food mapped from Open Food
Facts and from FDC comes out identical.

Meals logged before this keep the values they were saved with. Repairing
that history means rewriting someone's intake records on upgrade, which
deserves its own change and its own review, so it is filed separately.
@Genfood

Genfood commented Aug 24, 2026

Copy link
Copy Markdown

Thanks. 🙏

Maybe a stupid question, or I don't understand the source code well enough, but wouldn't it be much cleaner to work only with grams regardless of the nutrients?
While I understand this might be not the scope of this PR, nor up to you to decide, I can see a lot of problems that can occur because we treat some nutrients differently.

I would propose to only display some nutrients in micro gram on the UI side, and the rest of the calculations, as well as storing them should happen in grams.

What do you think about this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants