Nutrients handling in Open Food Facts: Difference between revisions

From Open Food Facts wiki
(Created page with "== Summary == This page describes how nutrients are stored in Open Food Facts and how they can be read or written through the API. == Why it's more complex that you might th...")
Β 
No edit summary
Β 
(30 intermediate revisions by 4 users not shown)
Line 1: Line 1:
[[Category:ProductOpener]]
[[Category:API]]
[[Category:Documentation]]
== Summary ==
== Summary ==


Line 19: Line 22:
=== Data input ===
=== Data input ===


==== Fields sent by the write API ====
==== Fields sent to the write API ====


Apps (+ the OFF web site edit form) send:
Apps (+ the OFF web site edit form) send:


* nutrition_data_per : "100g" or "serving"
* <code>nutrition_data_per</code>: <code>100g</code> or <code>serving</code>
* serving_size (optional, but very useful, in particular if the nutrition facts are specified per serving)
* <code>serving_size</code>: (optional, but very useful, in particular if the nutrition facts are specified per serving)
* for each nutrient:
* For each nutrient:
** nutrient-id (e.g. saturated-fat, energy-kcal) : value for the nutrient, for the size specified in "nutrition_data_per"
** <code>{nutrient-id}</code> (e.g. <code>saturated-fat</code>, <code>energy-kcal</code>): value for the nutrient, for the size specified in the fieldΒ  <code>nutrition_data_per</code>. This is a string made of the modifier ("<", ">" or "") and the value without unit ("17.8", "34,7", "< 0.1", etc).
** nutrient-id_unit (e.g. saturated-fat_unit, energy-kcal_unit) : the unit of the value passed (e.g. "g", "mg", "kcal", "kJ")
** <code>{nutrient-id}_unit</code> (e.g. saturated-fat_unit, energy-kcal_unit): the unit of the value passed (e.g. "g", "mg", "kcal", "kJ", "%", "")


Notes about the energy fields:
Notes about the energy fields:
* Energy on labels can be written in kcal, kJ or both
* Energy on labels can be written in kcal, kJ or both
* when the energy is written in both, there is no simple conversion ratio between kcal and kJ
* When the energy is written in both, there is no universally defined conversion ratio between kcal and kJ.
** This is because labelling laws in EU, US, CA etc. specify that the energy fields can (or must) be computed by multiplying and summing values for carbohydrates, proteins, fat etc.
** This is because labelling laws in EU, US, CA etc. specify that the energy fields can (or must) be computed by multiplying and summing values for carbohydrates, proteins, fat etc.
** see https://esha.com/calorie-calculation-country/ for details
** That's why we now store 2 energy fields, and not just one
* energy-kj and energy-kcal are the identifiers (nutrient-id) of the "Energy in kJ" and "Energy in kcal" fields.
** See https://esha.com/calorie-calculation-country/ for details
* old apps used to pass energy (as we had only one energy field instead of energy-kcal + energy-kj)
* <code>energy-kj</code> and <code>energy-kcal</code> are the identifiers (<code>{nutrient-id}</code>) of the "Energy in kJ" and "Energy in kcal" fields.
** for those apps, the energy_unit field is used to populate the corresponding energy-kj or energy-kcal field
* Old apps used to pass energy (as we had only one energy field instead of energy-kcal + energy-kj)
** For those apps, the <code>energy_unit</code> field is used to populate the corresponding <code>energy-kj</code> or <code>energy-kcal</code> field


==== Fields stored and computed by Product Opener
==== Fields entered and computed by the OFF backend (Product Opener) ====


* nutrient-id_value : what was passed in the nutrient-id field
* <code>{nutrient-id}_value</code>: what was entered and passed in the nutrient-id field.
* nutrient-id_unit : what was passed in the nutrient-id_unit field
* <code>{nutrient-id}_unit</code>: what was entered and passed in the nutrient-id_unit field.
* nutrient-id : the value converted in the default unit (currently kJ for all energy fields, including energy-kcal)
* <code>{nutrient-id}</code>: the value converted in the default unit (currently kJ for all energy fields (including energy-kcal), otherwise gram).
* nutrient-id_[value of nutrition_data_per) is set to nutrient-id (e.g. if nutrition_data_per is "100g", the value of sugars_100g is set to the value of sugars
* <code>{nutrient-id}_(100g|serving)</code>: is set to <code>{nutrient-id}</code> (e.g. if <code>nutrition_data_per = 100g</code>, the value of <code>sugars_100g</code> is set to the value of sugars.
* if the serving size is known, we compute the value for the other field (nutrient-id_100g or nutrient-id_serving)
* If the serving size is known, we compute the value for the other field (nutrient-id_100g or nutrient-id_serving) in default unit.
* The value of <code>energy_(100g|serving)</code> is calculated from <code>energy-kJ_(100g|serving)</code> if available, otherwise it is calculated from <code>energy-kcal}_(100g|serving)</code>. This field is in <code>kJ</code>. Note however that the kJ and kcal can be different according to the regulation, so do not assume a factor of 4.2 between the values.


=== Data display ===
=== Data display ===


The OFF web site, and apps, should use those fields to display the nutrition facts:
The OFF web site, and apps, '''should only use these OFF processed fields''' to display the nutrition data:


* nutrient-id_100g or nutrient-id_serving (depending on the value of nutrition_data_per) : value in g or in kJ
* <code>{nutrient-id}_100g</code> or <code>{nutrient-id}_serving</code> (depending on the value of the value of <code>nutrition_data_per</code>): value in g or in kJ. The values are passed as float/double???
* <code>{nutrient-id}_modifier</code> to get the value modifier ("<", ">", "~")
* <code>{nutrient-id}_value</code>: to show the original entered/passed data.


The web site and apps must:
The web site and apps '''must''':
* indicate if the nutrition facts are per 100g or per serving.
* Indicate if the nutrition facts are per 100g or per serving.
* convert the value in g or in kJ to the desired unit.
* Convert the value from g or kJ to the desired unit.
Β 
Notes:
* Don't use the <code>{nutrient-id}_value</code> and <code>{nutrient-id}_unit</code> fields
** The <code>{nutrient-id}</code> may be empty as some apps don't pass a unit, and we use a default unit for the field
** The value may be per 100g or per serving


=== Data edit ===
=== Data edit ===
Line 60: Line 72:
The web site and apps that allow editing the nutrition facts must populate the table with the same rules than for data display.
The web site and apps that allow editing the nutrition facts must populate the table with the same rules than for data display.


(What does this mean? only allow editing of the off processed field)???
== Possible improvements to make things (a bit) simpler ==
* In the API, rename the <code>{nutrient-id}</code> fields (such as <code>energy-kj</code>, <code>saturated-fat</code>) to <code>{nutrient-id}_value</code>, so that it matches the name of the field stored. It also makes clearer where the value is coded.
* Store the <code>energy-kcal</code> values in kcal, without converting them to kJ
** This is probably what people intuitively expect
** It removes unnecessary conversions from kcal to kJ and back (which is not trivial, as some people/apps may choose a different conversion factor, such as 4.2 or 4.185)
** This would be a breaking change if some apps use the energy-kcal_100g/serving or energy-kj_100g/serving fields
* Expose only the recommended fields (see [[Nutrients_handling_in_Open_Food_Facts#Data_display|data display]])
== Possible improvements to make things (a bit) more complex ==
The current API does not allow to store values for the prepared product, but it can be done on the OFF web site.
We should extend the API to allow to write nutrition facts for the prepared product.
The fields for the prepared product are the same than for the as-sold product, except "_prepared" is appended to the nutrient-id.


=== Data output ===
e.g. saturated-fat_prepared_100g, saturated-fat_prepared_value etc.
== Documentation ==
* [https://blog.trustwell.com/calorie-calculation-country How calories are calculated in different countries].
* [https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32011R1169&from=FR%20Appendix%20XIV#d1e32-62-1 Energy calculation in the EU]

Latest revision as of 07:29, 7 August 2024

Summary

This page describes how nutrients are stored in Open Food Facts and how they can be read or written through the API.

Why it's more complex that you might think

  • Nutrients lists are different depending on countries
  • Nutrients list are different depending on time (evolutions in the law)
    • e.g. in 2013 in Europe, the main unit for energy became kJ instead of kcal, and salt replaced sodium, with salt = 2.5 x sodium
  • Nutrients on labels may be in different units
    • e.g. sodium could be listed in mg or in g
    • US uses calories (kcal), EU uses kJ + kcal
  • Nutrients may be specified per 100g, per 100ml (which may be different than per 100g), per serving, or both
  • Nutrients may be specified for the product sold as-is, or prepared (with water, milk or something else), or both
  • Some apps, producers, databases etc. may give us values in a specific unit which is not the one that is on the label

How it works

Data input

Fields sent to the write API

Apps (+ the OFF web site edit form) send:

  • nutrition_data_per: 100g or serving
  • serving_size: (optional, but very useful, in particular if the nutrition facts are specified per serving)
  • For each nutrient:
    • {nutrient-id} (e.g. saturated-fat, energy-kcal): value for the nutrient, for the size specified in the field nutrition_data_per. This is a string made of the modifier ("<", ">" or "") and the value without unit ("17.8", "34,7", "< 0.1", etc).
    • {nutrient-id}_unit (e.g. saturated-fat_unit, energy-kcal_unit): the unit of the value passed (e.g. "g", "mg", "kcal", "kJ", "%", "")

Notes about the energy fields:

  • Energy on labels can be written in kcal, kJ or both
  • When the energy is written in both, there is no universally defined conversion ratio between kcal and kJ.
    • This is because labelling laws in EU, US, CA etc. specify that the energy fields can (or must) be computed by multiplying and summing values for carbohydrates, proteins, fat etc.
    • That's why we now store 2 energy fields, and not just one
    • See https://esha.com/calorie-calculation-country/ for details
  • energy-kj and energy-kcal are the identifiers ({nutrient-id}) of the "Energy in kJ" and "Energy in kcal" fields.
  • Old apps used to pass energy (as we had only one energy field instead of energy-kcal + energy-kj)
    • For those apps, the energy_unit field is used to populate the corresponding energy-kj or energy-kcal field

Fields entered and computed by the OFF backend (Product Opener)

  • {nutrient-id}_value: what was entered and passed in the nutrient-id field.
  • {nutrient-id}_unit: what was entered and passed in the nutrient-id_unit field.
  • {nutrient-id}: the value converted in the default unit (currently kJ for all energy fields (including energy-kcal), otherwise gram).
  • {nutrient-id}_(100g|serving): is set to {nutrient-id} (e.g. if nutrition_data_per = 100g, the value of sugars_100g is set to the value of sugars.
  • If the serving size is known, we compute the value for the other field (nutrient-id_100g or nutrient-id_serving) in default unit.
  • The value of energy_(100g|serving) is calculated from energy-kJ_(100g|serving) if available, otherwise it is calculated from energy-kcal}_(100g|serving). This field is in kJ. Note however that the kJ and kcal can be different according to the regulation, so do not assume a factor of 4.2 between the values.

Data display

The OFF web site, and apps, should only use these OFF processed fields to display the nutrition data:

  • {nutrient-id}_100g or {nutrient-id}_serving (depending on the value of the value of nutrition_data_per): value in g or in kJ. The values are passed as float/double???
  • {nutrient-id}_modifier to get the value modifier ("<", ">", "~")
  • {nutrient-id}_value: to show the original entered/passed data.

The web site and apps must:

  • Indicate if the nutrition facts are per 100g or per serving.
  • Convert the value from g or kJ to the desired unit.

Notes:

  • Don't use the {nutrient-id}_value and {nutrient-id}_unit fields
    • The {nutrient-id} may be empty as some apps don't pass a unit, and we use a default unit for the field
    • The value may be per 100g or per serving

Data edit

The web site and apps that allow editing the nutrition facts must populate the table with the same rules than for data display.

(What does this mean? only allow editing of the off processed field)???

Possible improvements to make things (a bit) simpler

  • In the API, rename the {nutrient-id} fields (such as energy-kj, saturated-fat) to {nutrient-id}_value, so that it matches the name of the field stored. It also makes clearer where the value is coded.
  • Store the energy-kcal values in kcal, without converting them to kJ
    • This is probably what people intuitively expect
    • It removes unnecessary conversions from kcal to kJ and back (which is not trivial, as some people/apps may choose a different conversion factor, such as 4.2 or 4.185)
    • This would be a breaking change if some apps use the energy-kcal_100g/serving or energy-kj_100g/serving fields
  • Expose only the recommended fields (see data display)

Possible improvements to make things (a bit) more complex

The current API does not allow to store values for the prepared product, but it can be done on the OFF web site.

We should extend the API to allow to write nutrition facts for the prepared product.

The fields for the prepared product are the same than for the as-sold product, except "_prepared" is appended to the nutrient-id.

e.g. saturated-fat_prepared_100g, saturated-fat_prepared_value etc.

Documentation