<<

NAME

ProductOpener::Ingredients - process and analyze ingredients lists

SYNOPSIS

ProductOpener::Ingredients processes, normalize, parses and analyze ingredients lists to extract and recognize individual ingredients, additives and allergens, and to compute product properties related to ingredients (is the product vegetarian, vegan, does it contain palm oil etc.)

    use ProductOpener::Ingredients qw/:all/;

        [..]

        clean_ingredients_text($product_ref);

        extract_ingredients_from_text($product_ref);

        extract_additives_from_text($product_ref);

        detect_allergens_from_text($product_ref);

DESCRIPTION

[..]

FUNCTIONS

init_allergens_regexps () - initialize regular expressions needed for ingredients parsing

This function initializes regular expressions needed to parse traces and allergens in ingredients lists.

FUNCTIONS

init_percent_or_quantity_regexps($ingredients_lc) - initialize regular expressions needed for ingredients parsing

This function creates regular expressions that match quantities or percent of an ingredient, including localized strings like "minimum"

init_labels_regexps () - initialize regular expressions needed for ingredients parsing

This function creates regular expressions that match all variations of labels that we want to recognize in ingredients lists, such as organic and fair trade.

has_specific_ingredient_property ( product_ref, searched_ingredient_id, property )

Check if the specific ingredients structure (extracted from the end of the ingredients list and product labels) contains a property for an ingredient. (e.g. do we have an origin specified for a specific ingredient)

Arguments

product_ref

searched_ingredient_id

If the ingredient_id parameter is undef, then we return the value for any specific ingredient. (useful for products for which we do not have ingredients, but for which we have a label like "French eggs": we can still derive the origin of the ingredients, e.g. for the Environmental-Score)

property

e.g. "origins"

Return values

value

- undef if we don't have a specific ingredient with the requested property matching the requested ingredient - otherwise the value for the matching specific ingredient

add_properties_from_specific_ingredients ( product_ref )

Go through the ingredients structure, and add properties to ingredients that match specific ingredients for which we have extra information (e.g. origins from a label).

add_percent_max_for_ingredients_from_nutrition_facts ( $product_ref )

Add a percent_max value for salt and sugar ingredients, based on the nutrition facts.

add_specific_ingredients_from_labels ( product_ref )

Check if the product has labels that indicate properties (e.g. origins) for specific ingredients.

e.g.

en:French pork fr:Viande Porcine Française, VPF, viande de porc française, Le Porc Français, Porc Origine France, porc français, porc 100% France origins:en: en:france ingredients:en: en:pork

This function extracts those mentions and adds them to the specific_ingredients structure.

Return values

specific_ingredients structure

Array of specific ingredients.

parse_specific_ingredients_from_text ( product_ref, $text, $percent_or_quantity_regexp, $per_100g_regexp )

Lists of ingredients sometime include extra mentions for specific ingredients at the end of the ingredients list. e.g. "Prepared with 50g of fruits for 100g of finished product".

This function extracts those mentions and adds them to the specific_ingredients structure.

This function is also used to parse the origins of ingredients field.

Arguments

product_ref

text $text

percent regular expression $percent_or_quantity_regexp

Used to find % values, language specific.

Pass undef in order to skip % recognition. This is useful if we know the text is only for the origins of ingredients.

per_100g regular expression $per_100g_regexp

Return values

specific_ingredients structure

Array of specific ingredients.

parse_processing_from_ingredient ( $ingredients_lc, $ingredient )

This function extract processing method from one ingredient. If processing methods are found and remaining ingredient text exists without the processing method, then, it returns: - $processing (concatenate if more than one), - $ingredient (without processing) and - $ingredient_id (without processing) If it does not result in known ingredient, then it returns the same but unchanged.

Arguments

ingredients_lc

language abbreviation (en for English, for example)

ingredient

string ("pear", for example)

Return values

processings_ref

reference to an array of processings

ingredient

updated ingredient without processing methods

ingredient_id

English first element for that ingredient (en:pear, for example)

ingredient_recognized

0 or 1

parse_origins_from_text ( product_ref, $text, $ingredients_lc)

This function parses the origins of ingredients field to extract the origins of specific ingredients. The origins are stored in the specific_ingredients structure of the product.

Note: this function is similar to parse_specific_ingredients_from_text() that operates on ingredients lists. The difference is that parse_specific_ingredients_from_text() only extracts and recognizes text that is an extra mention at the end of an ingredient list (e.g. "Origin of strawberries: Spain"), while parse_origins_from_text() will also recognize text like "Strawberries: Spain".

Arguments

product_ref

text $text

$ingredients_lc : language for the origins text

In most cases it is the same as $product_ref->{ingredients_lc}, except if there are no ingredients listed, in which case we can have origins listed in the main language of the product.

Return values

specific_ingredients structure

Array of specific ingredients.

select_ingredients_lc ($product_ref)

Select, set and return the `ingredients_lc` field in $product_ref.

This is the language that will be used to parse ingredients. We first check that ingredients_text_{lang} exists and is non-empty for the product main language (`lc`), and return it if it does. Otherwise we look at all languages defined in `languages_codes` for a non-empty `ingredients_text_lang`.

If we find a language with non empty ingredients in ingredients_text_{lang}: - we copy the value to the `ingredients_text` field in the product - we set the `ingredients_lc` field in the product and return it, otherwise we unset it.

Arguments

$product_ref

Return values

ingredients_lc

Language code for ingredients parsing.

get_or_select_ingredients_lc ($product_ref)

Return the ingredients_lc field if already set, otherwise call select_ingredients_lc() to select it.

This function is used in ingredients related services, to ensure that the ingredients_lc field is set.

get_percent_or_quantity_and_normalized_quantity($percent_or_quantity_value, $percent_or_quantity_unit)

Used to assign percent or quantity for strings parsed with $percent_or_quantity_regexp.

Arguments

percent_or_quantity_value

percent_or_quantity_unit

Return values

If the percent_or_quantity_unit is %, we return a defined value for percent, otherwise we return quantity and quantity_g

percent

quantity

If the unit is not %, quantity is a concatenation of the quantity value and unit

quantity_g

Normalized quantity in grams.

Example

$ingredient = "100% cocoa"; # or "milk 10cl"

if ($ingredient =~ /\s$percent_or_quantity_regexp$/i) { $percent_or_quantity_value = $1; $percent_or_quantity_unit = $2;

        my ($percent, $quantity, $quantity_g)
                = get_percent_or_quantity_and_normalized_quantity($percent_or_quantity_value, $percent_or_quantity_unit);

parse_ingredients_text_service ( $product_ref, $updated_product_fields_ref, $errors_ref )

Parse the ingredients_text field to extract individual ingredients.

This function is a product service that can be run through ProductOpener::ApiProductServices

Arguments

$product_ref

product object reference

$updated_product_fields_ref

reference to a hash of product fields that have been created or updated

$errors_ref

reference to an array of error messages

extend_ingredients_service ( $product_ref, $updated_product_fields_ref, $errors_ref )

After the nested ingredients structure has been built with the parse_ingredients_text_service, this service adds some properties to the ingredients:

- Origins, labels etc. that have been extracted from other fields - Ciqual and Ecobalyse codes

Arguments

$product_ref

product object reference

$updated_product_fields_ref

reference to a hash of product fields that have been created or updated

$errors_ref

reference to an array of error messages

flatten_sub_ingredients ( product_ref )

Flatten the nested list of ingredients.

compute_ingredients_tags ( product_ref )

Go through the nested ingredients and:

Compute ingredients_original_tags and ingredients_tags.

Compute the total % of "leaf" ingredients (without sub-ingredients) with a specified %, and unspecified %.

- ingredients_with_specified_percent_n : number of "leaf" ingredients with a specified % - ingredients_with_specified_percent_sum : % sum of "leaf" ingredients with a specified % - ingredients_with_unspecified_percent_n - ingredients_with_unspecified_percent_sum

extract_ingredients_from_text ( product_ref )

This function calls:

- parse_ingredients_text_service() to parse the ingredients text in the main language of the product to extract individual ingredients and sub-ingredients

- compute_ingredients_percent_min_max_values() to create the ingredients array with nested sub-ingredients arrays

- compute_ingredients_tags() to create a flat array ingredients_original_tags and ingredients_tags (with parents)

- analyze_ingredients_service() to analyze ingredients to see the ones that are vegan, vegetarian, from palm oil etc. and to compute the resulting value for the complete product

get_missing_ciqual_codes ($ingredients_ref)

Assign a ciqual_food_code or a ciqual_proxy_food_code to ingredients and sub ingredients.

Arguments

$ingredients_ref

reference to an array of ingredients

Return values

@ingredients_without_ciqual_codes

get_missing_ecobalyse_ids ($ingredients_ref)

Assign a ecobalyse_code or a ecobalyse_proxy_code to ingredients and sub ingredients. (NOTE : this is a first version that'll soon be improved)

Arguments

$ingredients_ref

reference to an array of ingredients

Return values

@ingredients_without_ecobalyse_ids

get_geographical_area ($originid)

Retrieve the geographical area for ecobalyse. (NOTE : this is a first version that'll soon be improved)

Arguments

$originid

reference to the name of the country

Return values

$ecobalyse_area

estimate_ingredients_percent_service ( $product_ref, $updated_product_fields_ref, $errors_ref )

Compute minimum and maximum percent ranges and percent estimates for each ingredient and sub ingredient.

This function is a product service that can be run through ProductOpener::ApiProductServices

Arguments

$product_ref

product object reference

$updated_product_fields_ref

reference to a hash of product fields that have been created or updated

$errors_ref

reference to an array of error messages

count_ingredients_with_specified_percent($product_ref)

Count ingredients with specified percent, including sub-ingredients.

Return values

$ingredients_n

Number of ingredients.

$ingredients_with_specified_percent_n

Number of ingredients with a specified percent value.

$total_specified_percent

Sum of the specified percent values.

Note: this can be greater than 100 if percent values are specified for ingredients and their sub ingredients.

delete_ingredients_percent_values ( ingredients_ref )

This function deletes the percent_min and percent_max values of all ingredients.

It is called if the compute_ingredients_percent_min_max_values() encountered impossible values (e.g. "Water, Sugar 80%" -> Water % should be greater than 80%, but the total would be more than 100%)

The function is recursive to also delete values for sub-ingredients.

compute_ingredients_percent_min_max_values ( total_min, total_max, ingredients_ref )

This function computes the possible minimum and maximum ranges for the percent values of each ingredient and sub-ingredients.

Ingredients lists sometimes specify the percent value for some ingredients, but usually not all. This functions computes minimum and maximum percent values for all other ingredients.

Ingredients list are ordered by descending order of quantity.

This function is recursive and it calls itself for each ingredients with sub-ingredients.

Arguments

total_min - the minimum percent value of the total of all the ingredients in ingredients_ref

0 when the function is called on all ingredients of a product, but can be different than 0 if called on sub-ingredients of an ingredient that has a minimum value set.

total_max - the maximum percent value of all ingredients passed in ingredients_ref

100 when the function is called on all ingredients of a product, but can be different than 0 if called on sub-ingredients of an ingredient that has a maximum value set.

ingredient_ref : nested array of ingredients and sub-ingredients

Return values

Negative value - analysis error

The analysis encountered an impossible value. e.g. "Flour, Sugar 80%": The % of Flour must be greated to the % of Sugar, but the sum would then be above 100%.

Or there were too many loops to analyze the values.

0 or positive value - analysis ok

The return value is the number of times we adjusted min and max values for ingredients and sub ingredients.

init_percent_values($total_min, $total_max, $ingredients_ref)

Initialize the percent, percent_min and percent_max value for each ingredient in list.

$ingredients_ref is the list of ingredients (as hash), where parsed percent are already set.

$total_min and $total_max might be set if we have a parent ingredient and are parsing a sub list.

When a percent is specifically set, use this value for percent_min and percent_max.

Warning: percent listed for sub-ingredients can be absolute (e.g. "Sugar, fruits 40% (pear 30%, apple 10%)") or they can be relative to the parent ingredient (e.g. "Sugar, fruits 40% (pear 75%, apple 25%)". We try to detect those cases and rescale the percent accordingly.

Otherwise use 0 for percent_min and total_max for percent_max.

set_percent_max_from_taxonomy ( ingredients_ref )

Set the percentage maximum for ingredients like flavouring where this is defined on the Ingredients taxonomy. The percent_max will not be applied in the following cases:

 - if applying the percent_max would mean that it is not possible for the ingredient
   total to add up to 100%
 - If a later ingredient has a higher percentage than the percent_max of the restricted ingredient

compute_ingredients_percent_estimates ( total, ingredients_ref )

This function computes a possible estimate for the percent values of each ingredient and sub-ingredients.

The sum of all estimates must be 100%, and the estimates try to match the min and max constraints computed previously with the compute_ingredients_percent_min_max_values() function.

Arguments

total - the total of all the ingredients in ingredients_ref

100 when the function is called on all ingredients of a product, but can be different than 100 if called on sub-ingredients of an ingredient.

ingredient_ref : nested array of ingredients and sub-ingredients

Return values

analyze_ingredients_service ( $product_ref, $updated_product_fields_ref, $errors_ref )

Analyzes ingredients to see the ones that are vegan, vegetarian, from palm oil etc. and computes the resulting value for the complete product.

The results are overridden by labels like "Vegan", "Vegetarian" or "Palm oil free"

Results are stored in the ingredients_analysis_tags array.

This function is a product service that can be run through ProductOpener::ApiProductServices

Arguments

$product_ref

product object reference

$updated_product_fields_ref

reference to a hash of product fields that have been created or updated

$errors_ref

reference to an array of error messages

normalize_a_of_b ( $lc, $a, $b, $of_bool, $alternate_names_ref = undef )

This function is called by normalize_enumeration()

Given a category ($a) and a type ($b), it will return the ingredient that result from the combination of these two.

English: oil, olive -> olive oil Croatian: ječmeni, slad -> ječmeni slad French: huile, olive -> huile d'olive Russian: масло растительное, пальмовое -> масло растительное оливковое

Arguments

lc

language abbreviation (en for English, for example)

$a

string, category as defined in %ingredients_categories_and_types, example: 'oil' for 'oil (sunflower, olive and palm)'

$b

string, type as defined in %ingredients_categories_and_types, example: 'sunflower' or 'olive' or 'palm' for 'oil (sunflower, olive and palm)'

$of_bool - indicate if we want to construct entries like "<category> of <type>"

e.g. in French we combine "huile" and "olive" to "huile d'olive" but we combine "poivron" and "rouge" to "poivron rouge".

$alternate_names_ref

Reference to an array of alternate names for the category

Return value

combined $a and $b (or $b and $a, depending of the language), that is expected to be an ingredient

string, comma-joined category and type, example: 'palm vegetal oil' or 'sunflower vegetal oil' or 'olive vegetal oil'

normalize_enumeration ($ingredients_lc, $category, $types, $of_bool, $alternate_names_ref = undef, $do_not_output_parent = undef)

This function is called by develop_ingredients_categories_and_types()

Some ingredients are specified by an ingredient "category" (e.g. "oil") and a "types" string (e.g. "sunflower, palm").

This function combines the category to all elements of the types string $category = "Vegetal oil" and $types = "palm, sunflower and olive" will return "vegetal oil (palm vegetal oil, sunflower vegetal oil, olive vegetal oil)"

Arguments

lc

language abbreviation (en for English, for example)

category

string, as matched from definition in %ingredients_categories_and_types, example: 'Vegetal oil' for 'Vegetal oil (sunflower, olive and palm)'

types

string, as matched from definition in %ingredients_categories_and_types, example: 'sunflower, olive and palm' for 'Vegetal oil (sunflower, olive and palm)'

$of_bool - indicate if we want to construct entries like "<category> of <type>"

e.g. in French we combine "huile" and "olive" to "huile d'olive" but we combine "poivron" and "rouge" to "poivron rouge".

$alternate_names_ref

Reference to an array of alternate names for the category

$do_not_output_parent - indicate if we want to output the parent ingredient

e.g. for "carbonates d'ammonium et de sodium", we want only "carbonates d'ammonium, carbonates de sodium" and not "carbonates (carbonates d'ammonium, carbonates de sodium)" as "carbonates" is another additive

Return value

Transformed ingredients list text

string, with the type + a list of comma-joined category with all elements of the types example: 'vegetal oils (sunflower vegetal oil, olive vegetal oil, palm vegetal oil)'

split_generic_name_from_ingredients ( product_ref language_code )

Some producers send us an ingredients list that starts with the generic name followed by the actual ingredients list.

e.g. "Pâtes de fruits aromatisées à la fraise et à la canneberge, contenant de la maltodextrine et de l'acérola. Source de vitamines B1, B6, B12 et C. Ingrédients : Pulpe de fruits 50% (poire William 25%, fraise 15%, canneberge 10%), sucre, sirop de glucose de blé, maltodextrine 5%, stabilisant : glycérol, gélifiant : pectine, acidifiant : acide citrique, arôme naturel de fraise, arôme naturel de canneberge, poudre d'acérola (acérola, maltodextrine) 0,4%, vitamines : B1, B6 et B12. Fabriqué dans un atelier utilisant: GLUTEN*, FRUITS A COQUE*. * Allergènes"

This function splits the list to put the generic name in the generic_name_[lc] field and the ingredients list in the ingredients_text_[lc] field.

If there is already a generic name, it is not overridden.

WARNING: This function should be called only during the import of data from producers. It should not be called on lists that can be the result of an OCR, as there is no guarantee that the text before the ingredients list is the generic name. It should also not be called when we import product data from the producers platform to the public database.

clean_ingredients_text_for_lang ( product_ref language_code )

Perform some cleaning of the ingredients list.

The operations included in the cleaning must be 100% safe.

The function can be applied multiple times on the ingredients list.

cut_ingredients_text_for_lang ( product_ref language_code )

This function should be called once when getting text data from the OCR that includes an ingredients list.

It tries to remove phrases before and after the list that are not ingredients list.

It MUST NOT be applied multiple times on the ingredients list, as it could otherwise remove parts of the ingredients list. (e.g. it looks for "Ingredients: " and remove everything before it. If there are multiple "Ingredients:" listed, it would keep only the last one if called multiple times.

replace_additive ($number, $letter, $variant) - normalize the additive

This function is used inside regular expressions to turn additives to a normalized form.

Using a function to concatenate the E-number, letter and variant makes it possible to deal with undefined $letter or $variant without triggering an undefined warning.

Synopsis

        $text =~ s/(\b)e( |-|\.)?$additivesregexp(\b|\s|,|\.|;|\/|-|\\|$)/replace_additive($3,$6,$9) . $12/ieg;

develop_ingredients_categories_and_types ( $ingredients_lc, $text ) - turn "oil (sunflower, olive and palm)" into "sunflower oil, olive oil, palm oil"

Some ingredients are specified by an ingredient "category" (e.g. "oil", "flavouring") and a "type" (e.g. "sunflower", "palm" or "strawberry", "vanilla").

Sometimes, the category is mentioned only once for several types: "strawberry and vanilla flavourings", "vegetable oil (palm, sunflower)".

This function lists each individual ingredient: "oil (sunflower, olive and palm)" becomes "sunflower oil, olive oil, palm oil"

Arguments

Language

Ingredients list text

Return value

Transformed ingredients list text

%ingredients_categories_and_types

For each language, we list the categories and types of ingredients that can be combined when the ingredient list contains something like "<category> (<type1>, <type2> and <type3>)"

We can also provide a list of alternate_names, so that we can have a category like "oils and fats" and generate entries like "sunflower oil", "cocoa fat" when the ingredients list contains "oils and fats (sunflower, cocoa)".

Alternate names need to contain "<type>" which will be replaced by the type.

This can be especially useful in languages like German where we can create compound words with the type and the category* like "Kokosnussöl" or "Sonnenblumenfett":

        de => [
                {
                        categories => ["pflanzliches Fett", "pflanzliche Öle", "pflanzliche Öle und Fette", "Fett", "Öle"],
                        types => ["Avocado", "Baumwolle", "Distel", "Kokosnuss", "Palm", "Palmkern", "Raps", "Shea", "Sonnenblumen",],
                        # Kokosnussöl, Sonnenblumenfett
                        alternate_names => ["<type>fett", "<type>öl"],
                },
        ],

Simple plural (just an additional "s" at the end) will be added in the regexp.

Note that a "<categories> ([list of types])" enumeration will be developed only if all the types can be matched to the specified types in ingredients_categories_and_types.

preparse_ingredients_text ($ingredients_lc, $text) - normalize the ingredient list to make parsing easier

This function transform the ingredients list in a more normalized list that is easier to parse.

It does the following:

- Normalize quote characters - Replace abbreviations by their full name - Remove extra spaces in compound words width dashes (e.g. céléri - rave -> céléri-rave) - Split vitamins enumerations - Normalize additives and split additives enumerations - Split other enumerations (e.g. oils, some minerals) - Split allergens and traces - Deal with signs like * to indicate labels (e.g. *: Organic)

Arguments

Language

Ingredients list text

Return value

Transformed ingredients list text

extract_additives_from_text ($product_ref) - extract additives from the ingredients text

This function extracts additives from the ingredients text and adds them to the product_ref in the additives_tags array.

TODO: this function is independent of the ingredient parsing, we should combine the two.

Arguments

Product reference

count_sweeteners_and_non_nutritive_sweeteners

Check if the product contains sweeteners and non nutritive sweeteners (used for the Nutri-Score for beverages)

The NNS / Non nutritive sweeteners listed in the Nutri-Score Update report beverages_31 01 2023-voted have been added as a non_nutritive_sweetener:en:yes property in the additives taxonomy.

Return values

The function sets the following fields in the product_ref hash.

If there are no ingredients specified for the product, the fields are not set.

ingredients_sweeteners_n

ingredients_non_nutritive_sweeteners_n

detect_allergens_from_ingredients ( $product_ref )

Detects allergens from the ingredients extracted from the ingredients text, using the "allergens:en" property associated to some ingredients in the ingredients taxonomy.

This functions needs to be run after the $product_ref->{ingredients} array is populated from the ingredients text.

It is called by detect_allergens_from_text(). Allergens are added to $product_ref->{"allergens_from_ingredients"} which is then used by detect_allergens_from_text() to populate the allergens_tags field.

get_allergens_taxonomyid ( $ingredients_lc, $ingredient_or_allergen )

In the allergens provided by users, we may get ingredients that are not in the allergens taxonomy, but that are in the ingredients taxonomy and have an inherited allergens:en property. (e.g. the allergens taxonomy has an en:fish entry, but users may indicate specific fish species)

This function tries to match the ingredient with an allergen in the allergens taxonomy, and otherwise return the taxonomy id for the original ingredient.

Parameters

$ingredients_lc

The language code of $ingredient_or_allergen.

$ingredient_or_allergen

The ingredient or allergen to match. Can also be an ingredient id or allergens id prefixed with a language code.

Return value

The taxonomy id for the allergen, or the original ingredient if no allergen was found.

detect_allergens_from_text ( $product_ref )

This function: - combines all the ways we have to detect allergens in order to populate the allergens_tags and traces_tags fields. - creates the ingredients_text_with_allergens_[lc] fields with added HTML <span class="allergen"> tags

Allergens are recognized in the following ways:

1. using the list of ingredients that have been recognized through ingredients analysis, by looking at the allergens:en property in the ingredients taxonomy. This is done with the function detect_allergens_from_ingredients()

2. when entered in ALL CAPS, or between underscores

3. when matching exact entries o synonyms of the allergens taxonomy

Allergens detected using 2. or 3. are marked with <span class="allergen">

add_ingredients_matching_function ( $ingredients_ref, $match_function_ref )

Recursive function to compute the percentage of ingredients that match a specific function.

The match function takes 2 arguments: - ingredient id - processing (comma separated list of ingredients_processing taxonomy entries)

Used to compute % of fruits and vegetables, % of milk etc. which is needed by some algorithm like the Nutri-Score.

Return values

$percent

Sum of matching ingredients percent.

$water_percent

Percent of water (used to recompute the percentage for categories of products that are consumed after removing water)

estimate_ingredients_matching_function ( $product_ref, $match_function_ref, $nutrient_id = undef )

This function analyzes the ingredients to estimate the percentage of ingredients of a specific type (e.g. fruits/vegetables/legumes for the Nutri-Score).

Parameters

$product_ref

$match_function_ref

Reference to a function that matches specific ingredients (e.g. fruits/vegetables/legumes)

$nutrient_id (optional)

If the $nutrient_id argument is defined, we also store the nutrient value in $product_ref->{nutriments}.

Return value

Estimated percentage of ingredients matching the function.

is_fruits_vegetables_nuts_olive_walnut_rapeseed_oils ( $ingredient_id, $processing = undef )

Determine if an ingredient should be counted as "fruits, vegetables, nuts, olive / walnut / rapeseed oils" in Nutriscore 2021 algorithm.

- we use the nutriscore_fruits_vegetables_nuts:en property to identify qualifying ingredients - we check that the parent of those ingredients is not a flour - we check that the ingredient does not have a processing like en:powder

NUTRI-SCORE FREQUENTLY ASKED QUESTIONS - UPDATED 27/09/2022:

"However, fruits, vegetables and pulses that are subject to further processing (e.g. concentrated fruit juice sugars, powders, freeze-drying, candied fruits, fruits in stick form, flours leading to loss of water) do not count. As an example, corn in the form of popcorn or soy proteins cannot be considered as vegetables. Regarding the frying process, fried vegetables which are thick and only partially dehydrated by the process can be taken into account, whereas crisps which are thin and completely dehydrated are excluded."

estimate_nutriscore_2021_fruits_vegetables_nuts_percent_from_ingredients ( product_ref )

This function analyzes the ingredients to estimate the minimum percentage of fruits, vegetables, nuts, olive / walnut / rapeseed oil, so that we can compute the Nutri-Score fruit points if we don't have a value given by the manufacturer or estimated by users.

Results are stored in $product_ref->{nutriments}{"fruits-vegetables-nuts-estimate-from-ingredients_100g"} (and _serving)

is_fruits_vegetables_legumes ( $ingredient_id, $processing = undef)

Determine if an ingredient should be counted as "fruits, vegetables, legumes" in Nutriscore 2023 algorithm.

- we use the eurocode_2_group_1:en and eurocode_2_group_2:en property to identify qualifying ingredients - we check that the parent of those ingredients is not a flour - we check that the ingredient does not have a processing like en:powder

1.2.2. Ingredients contributing to the "Fruit, vegetables and legumes" component

The list of ingredients qualifying for the "Fruit, vegetables and legumes" component has been revised to include the following Eurocodes: • Vegetables groups o 8.10 (Leaf vegetables); o 8.15 (Brassicas); o 8.20 (Stalk vegetables); o 8.25 (Shoot vegetables); o 8.30 (Onion-family vegetables); o 8.38 (Root vegetables); o 8.40 (Fruit vegetables); o 8.42 (Flower-head vegetables); o 8.45 (Seed vegetables and immature pulses); o 8.50 (Edible fungi); o 8.55 (Seaweeds and algae); o 8.60 (Vegetable mixtures) Fruits groups o 9.10 (Malaceous fruit); o 9.20 (Prunus species fruit); o 9.25 (Other stone fruit); o 9.30 (Berries); o 9.40 (Citrus fruit); o 9.50 (Miscellaneous fruit); o 9.60 (Fruit mixtures). Pulses groups o 7.10 (Pulses).

Additionally, in the fats and oils category specifically, oils derived from ingredients in the list qualify for the component (e.g. olive and avocado).

--

NUTRI-SCORE FREQUENTLY ASKED QUESTIONS - UPDATED 27/09/2022:

"However, fruits, vegetables and pulses that are subject to further processing (e.g. concentrated fruit juice sugars, powders, freeze-drying, candied fruits, fruits in stick form, flours leading to loss of water) do not count. As an example, corn in the form of popcorn or soy proteins cannot be considered as vegetables. Regarding the frying process, fried vegetables which are thick and only partially dehydrated by the process can be taken into account, whereas crisps which are thin and completely dehydrated are excluded."

estimate_nutriscore_2023_fruits_vegetables_legumes_percent_from_ingredients ( product_ref )

This function analyzes the ingredients to estimate the minimum percentage of fruits, vegetables, legumes, so that we can compute the Nutri-Score (2023) fruit points.

Results are stored in $product_ref->{nutriments}{"fruits-vegetables-legumes-estimate-from-ingredients_100g"} (and _serving)

is_milk ( $ingredient_id, $processing = undef )

Determine if an ingredient should be counted as milk in Nutriscore 2021 algorithm

estimate_nutriscore_2021_milk_percent_from_ingredients ( product_ref )

This function analyzes the ingredients to estimate the percentage of milk in a product, in order to know if a dairy drink should be considered as a food (at least 80% of milk) or a beverage.

Return value: estimated % of milk.

is_red_meat ( $ingredient_id )

Determine if an ingredient should be counted as red meat in Nutriscore 2023 algorithm

estimate_nutriscore_2023_red_meat_percent_from_ingredients ( product_ref )

This function analyzes the ingredients to estimate the percentage of red meat, so that we can determine if the maximum limit of 2 points for proteins should be applied in the Nutri-Score 2023 algorithm.

sub get_ingredients_with_property_value ($ingredients_ref, $property, $value)

Returns a list of ingredients that have a specific property value.

<<