5.4 KiB
Nutrition data — the contract between the agent team, Fiesta and the app
The customer app shows a nutrition panel on the product screen. The data comes from the global catalogue, which the agent team fills. This is what each side has to produce and can rely on.
1. What the app receives
GET /live/api/v1/mob/products/getproductbyvariant?tenantid=&productid=&variantid=
Each product in details[] carries:
"nutrition": {
"per": "100g",
"servingsize": "30g",
"items": [
{ "name": "Energy", "value": 520, "unit": "kcal" },
{ "name": "Protein", "value": 6.5, "unit": "g" },
{ "name": "Total Sugars", "value": 22, "unit": "g" },
{ "name": "Sodium", "value": 310, "unit": "mg" }
]
}
Rules the app can build on:
- The key is ABSENT when nothing is known. Not
null, not{}. A missing key means "we do not know", never "this food has no nutrition". itemsis never empty whennutritionis present. A panel with no rows is not sent, because an empty box on a product page reads as a claim.perandservingsizeare each optional and often absent — see §3. Show the basis only when it is there. Do not default it to100g: a wrong basis makes every figure beneath it a misstatement rather than an unknown.valueis a number, and may be a decimal. Saturated fat is 11.5 g as often as 11 g.unitis free text and may be absent. "Servings per pack 4" has a figure and no unit.- A row may have a
nameand avalueof 0 with no unit. That is real label text with no number in it — "Contains permitted natural colour". Render the name and leave the figure column blank; do not print0. - Every member of a variant group carries its own panel, so switching from 500 ml to 1 L does not blank the screen.
2. What the agent team fills
A nutrition column of type jsonb on each brand_* table in the
catalogue database, holding exactly the object above.
ALTER TABLE brand_patanjali ADD COLUMN IF NOT EXISTS nutrition jsonb;
Notes:
- The column is optional and Fiesta already handles its absence. Catalogue reads discover columns per table and substitute NULL for any that are missing, so brands can be filled one at a time and a table without the column keeps working. Nothing has to be co-ordinated with a deploy.
- Spelling is
servingsize, one word, lowercase. It is the shape the app was written against. - Write what the label says. If the pack states per 100 g,
peris"100g". If it states per serving, say so. If it states neither, omit the field rather than assuming. nutritionand the oldernutrientsmay both exist on a row. Keep both — the console shows the lines, the app shows the panel, and they are different readers with different needs.
3. Where today's data comes from, and why it is thinner
Every catalogue row currently holds nutrition as nutrients, a text[] of
display lines:
{"Energy 350kcal", "Protein 7g"}
Fiesta parses those into the same panel shape when no structured nutrition
exists, so the app shows figures for products the agent team has not reached
yet. That derived panel has no per and no servingsize — the strings
never carried them — which is the visible difference between a filled brand and
one still waiting.
Parsing rules, in models/nutrition.go:
| line | becomes |
|---|---|
Energy 350kcal |
{Energy, 350, kcal} |
Protein: 6.5 g |
{Protein, 6.5, g} |
Energy - 520 kcal |
{Energy, 520, kcal} |
Vitamin B12 1.2µg |
{Vitamin B12, 1.2, µg} |
Servings per pack 4 |
{Servings per pack, 4, ""} |
Contains permitted colour |
{Contains permitted colour, 0, ""} — kept whole |
100 |
dropped — a figure with no label is noise |
Structured always wins over derived where both exist.
4. How it reaches a shop's product
The catalogue is the source; a tenant's product is a snapshot taken at import, because the catalogue is re-scraped and rows retire. Chain:
brand_*.nutrition (agent team)
└─ catalogue read ─ repositories/catalogueRepository.go: nutritionOf
└─ import ─ services/productService.go: catalogueFactsOf
└─ products.cataloguefacts → {"nutrition": {...}, "nutrients": [...]}
└─ endpoint ─ services/productService.go: decorateNutrition
getproductbyvariant does not query the catalogue database. That would be a
cross-database lookup per product on a screen a shopper is waiting on. It reads
the snapshot only.
5. Products imported before the snapshot existed
This is the one thing that must be done before any of it shows.
cataloguefacts is empty on every product imported before that column existed —
measured on tenant 1147: 17 products, 15 catalogue-imported, 0 with facts.
Those products will show no nutrition no matter what the catalogue holds.
The fix is a one-off backfill, which also restores their FSSAI licence, highlights and provider list:
go run ./scratch/cataloguefactsbackfill # dry run — prints every change
go run ./scratch/cataloguefactsbackfill apply # writes, then prints the undo
It touches only products with an imageid and a NULL cataloguefacts, so
re-running it is a no-op rather than a second opinion. Run it after the agent
team fills a brand to pick up that brand's structured panels; it is safe to run
repeatedly as more brands are filled.