From 80bcc29b997ad292823288674b36ffdd5c358ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Thu, 16 Nov 2023 11:20:08 +0100 Subject: [PATCH] feat: set max percent of sugar and salt ingredients based on nutrition facts (#9276) --- lib/ProductOpener/DataQualityFood.pm | 2 +- lib/ProductOpener/Ingredients.pm | 55 ++++++++++++ .../attributes/en-attributes.json | 70 +++++++-------- .../max-sugar-salt-nutrition-facts.json | 23 +++++ .../beverage-with-80-percent-milk.json | 38 ++++---- ...-drink-with-less-than-80-percent-milk.json | 87 ++++++++++--------- .../nutriscore/dairy-drinks-without-milk.json | 46 +++++----- ...orange-juice-category-and-ingredients.json | 8 +- .../nutriscore/en-sweeteners-erythritol.json | 36 ++++---- .../nutriscore/en-sweeteners.json | 36 ++++---- .../nutriscore/fr-gaspacho.json | 6 +- .../nutriscore/fr-ice-tea-with-sweetener.json | 75 +++++++++++++--- .../nutriscore/fr-orange-nectar-0-fat.json | 8 +- tests/unit/ingredients_percent.t | 19 ++-- tests/unit/nutriscore.t | 4 +- 15 files changed, 324 insertions(+), 189 deletions(-) create mode 100644 tests/unit/expected_test_results/ingredients_percent/max-sugar-salt-nutrition-facts.json diff --git a/lib/ProductOpener/DataQualityFood.pm b/lib/ProductOpener/DataQualityFood.pm index 1fbdad9f34a10..e21fa0c21056d 100644 --- a/lib/ProductOpener/DataQualityFood.pm +++ b/lib/ProductOpener/DataQualityFood.pm @@ -940,7 +940,7 @@ sub check_nutrition_data ($product_ref) { # some categories have an expected ingredient - push data quality error if ingredient differs from expected ingredient # note: we currently support only 1 expected ingredient - my ($expected_ingredients, $category_id) + my ($expected_ingredients, $category_id2) = get_inherited_property_from_categories_tags($product_ref, "expected_ingredients:en"); if ((defined $expected_ingredients)) { diff --git a/lib/ProductOpener/Ingredients.pm b/lib/ProductOpener/Ingredients.pm index 38b67ad721fa7..e62e65a6937c4 100644 --- a/lib/ProductOpener/Ingredients.pm +++ b/lib/ProductOpener/Ingredients.pm @@ -131,6 +131,7 @@ use JSON::PP; use Log::Any qw($log); use List::MoreUtils qw(uniq); use Test::More; +use Data::DeepAccess qw(deep_get deep_exists); # MIDDLE DOT with common substitutes (BULLET variants, BULLET OPERATOR and DOT OPERATOR (multiplication)) # U+00B7 "·" (Middle Dot). Is a common character in Catalan. To avoid to break ingredients, @@ -887,6 +888,57 @@ sub add_properties_from_specific_ingredients ($product_ref) { return; } +=head2 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. + +=cut + +sub add_percent_max_for_ingredients_from_nutrition_facts ($product_ref) { + + # Check if we have values for salt and sugar in the nutrition facts + my @ingredient_max_values = (); + my $sugars_100g = deep_get($product_ref, qw(nutriments sugars_100g)); + if (defined $sugars_100g) { + push @ingredient_max_values, {ingredientid => "en:sugar", value => $sugars_100g}; + } + my $salt_100g = deep_get($product_ref, qw(nutriments salt_100g)); + if (defined $salt_100g) { + push @ingredient_max_values, {ingredientid => "en:salt", value => $salt_100g}; + } + + if (scalar @ingredient_max_values) { + + # Traverse the ingredients tree, depth first + + my @ingredients = @{$product_ref->{ingredients}}; + + while (@ingredients) { + + # Remove and process the first ingredient + my $ingredient_ref = shift @ingredients; + my $ingredientid = $ingredient_ref->{id}; + + # Add sub-ingredients at the beginning of the ingredients array + if (defined $ingredient_ref->{ingredients}) { + + unshift @ingredients, @{$ingredient_ref->{ingredients}}; + } + + foreach my $ingredient_max_value_ref (@ingredient_max_values) { + my $value = $ingredient_max_value_ref->{value}; + if (is_a("ingredients", $ingredient_ref->{id}, $ingredient_max_value_ref->{ingredientid})) { + if (not defined $ingredient_ref->{percent_max}) { + $ingredient_ref->{percent_max} = $value; + } + } + + } + } + } + return; +} + =head2 add_specific_ingredients_from_labels ( product_ref ) Check if the product has labels that indicate properties (e.g. origins) for specific ingredients. @@ -2913,6 +2965,9 @@ reference to a hash of product fields that have been created or updated sub estimate_ingredients_percent_service ($product_ref, $updated_product_fields_ref) { + # Add a percent_max value for salt and sugar ingredients, based on the nutrition facts. + add_percent_max_for_ingredients_from_nutrition_facts($product_ref); + if (compute_ingredients_percent_min_max_values(100, 100, $product_ref->{ingredients}) < 0) { # The computation yielded seemingly impossible values, delete the values diff --git a/tests/unit/expected_test_results/attributes/en-attributes.json b/tests/unit/expected_test_results/attributes/en-attributes.json index 8250c826da46b..466f6f7c4c8c3 100644 --- a/tests/unit/expected_test_results/attributes/en-attributes.json +++ b/tests/unit/expected_test_results/attributes/en-attributes.json @@ -313,7 +313,7 @@ "grade" : "a", "icon_url" : "https://server_domain/images/attributes/forest-footprint-a.svg", "id" : "forest_footprint", - "match" : 99.9292929292929, + "match" : 99.9222222222222, "name" : "Forest footprint", "status" : "known", "title" : "Very small forest footprint" @@ -412,12 +412,12 @@ "percent" : 54.5454545454545 }, { - "origin" : "en:paraguay", - "percent" : 22.7272727272727 + "origin" : "en:unknown", + "percent" : 32.9545454545455 }, { - "origin" : "en:unknown", - "percent" : 22.7272727272727 + "origin" : "en:paraguay", + "percent" : 12.5 } ], "epi_score" : 52.9090909090909, @@ -818,7 +818,7 @@ ], "food_groups_tags" : [], "forest_footprint_data" : { - "footprint_per_kg" : 0.00176767676767677, + "footprint_per_kg" : 0.00194444444444444, "grade" : "a", "ingredients" : [ { @@ -828,10 +828,10 @@ "en:organic" ] ], - "footprint_per_kg" : 0.00176767676767677, + "footprint_per_kg" : 0.00194444444444444, "matching_tag_id" : "en:egg", - "percent" : 11.3636363636364, - "percent_estimate" : 11.3636363636364, + "percent" : 12.5, + "percent_estimate" : 12.5, "processing_factor" : 1, "tag_id" : "en:egg", "tag_type" : "ingredients", @@ -858,8 +858,8 @@ { "id" : "en:sugar", "origins" : "en:paraguay", - "percent_estimate" : 22.7272727272727, - "percent_max" : 50, + "percent_estimate" : 12.5, + "percent_max" : 25, "percent_min" : 0, "text" : "sugar", "vegan" : "yes", @@ -868,8 +868,8 @@ { "ciqual_food_code" : "22000", "id" : "en:egg", - "percent_estimate" : 11.3636363636364, - "percent_max" : 33.3333333333333, + "percent_estimate" : 12.5, + "percent_max" : 25, "percent_min" : 0, "text" : "eggs", "vegan" : "no", @@ -878,7 +878,7 @@ { "ciqual_food_code" : "13014", "id" : "en:strawberry", - "percent_estimate" : 5.68181818181818, + "percent_estimate" : 10.2272727272727, "percent_max" : 25, "percent_min" : 0, "text" : "strawberries", @@ -888,7 +888,7 @@ { "ciqual_food_code" : "31077", "id" : "en:high-fructose-corn-syrup", - "percent_estimate" : 2.84090909090909, + "percent_estimate" : 5.11363636363636, "percent_max" : 20, "percent_min" : 0, "text" : "high fructose corn syrup", @@ -898,7 +898,7 @@ { "from_palm_oil" : "no", "id" : "en:rapeseed-oil", - "percent_estimate" : 1.42045454545455, + "percent_estimate" : 2.55681818181818, "percent_max" : 16.6666666666667, "percent_min" : 0, "text" : "rapeseed oil", @@ -908,7 +908,7 @@ { "ciqual_food_code" : "15027", "id" : "en:macadamia-nut", - "percent_estimate" : 0.710227272727273, + "percent_estimate" : 1.27840909090909, "percent_max" : 14.2857142857143, "percent_min" : 0, "text" : "macadamia nuts", @@ -917,7 +917,7 @@ }, { "id" : "en:milk-proteins", - "percent_estimate" : 0.35511363636364, + "percent_estimate" : 0.639204545454547, "percent_max" : 12.5, "percent_min" : 0, "text" : "milk proteins", @@ -927,8 +927,8 @@ { "ciqual_food_code" : "11058", "id" : "en:salt", - "percent_estimate" : 0.17755681818182, - "percent_max" : 11.1111111111111, + "percent_estimate" : 0.125, + "percent_max" : 0.25, "percent_min" : 0, "text" : "salt", "vegan" : "yes", @@ -936,8 +936,8 @@ }, { "id" : "en:e102", - "percent_estimate" : 0.0887784090909065, - "percent_max" : 10, + "percent_estimate" : 0.125, + "percent_max" : 0.25, "percent_min" : 0, "text" : "e102", "vegan" : "yes", @@ -945,8 +945,8 @@ }, { "id" : "en:e120", - "percent_estimate" : 0.0887784090909065, - "percent_max" : 9.09090909090909, + "percent_estimate" : 0.389204545454547, + "percent_max" : 0.25, "percent_min" : 0, "text" : "e120", "vegan" : "no", @@ -1163,10 +1163,10 @@ "energy_100g" : 800, "fat_100g" : 12, "fiber_100g" : 3, - "fruits-vegetables-legumes-estimate-from-ingredients_100g" : 5.68181818181818, - "fruits-vegetables-legumes-estimate-from-ingredients_serving" : 5.68181818181818, - "fruits-vegetables-nuts-estimate-from-ingredients_100g" : 7.8125, - "fruits-vegetables-nuts-estimate-from-ingredients_serving" : 7.8125, + "fruits-vegetables-legumes-estimate-from-ingredients_100g" : 10.2272727272727, + "fruits-vegetables-legumes-estimate-from-ingredients_serving" : 10.2272727272727, + "fruits-vegetables-nuts-estimate-from-ingredients_100g" : 14.0625, + "fruits-vegetables-nuts-estimate-from-ingredients_serving" : 14.0625, "nova-group" : 4, "nova-group_100g" : 4, "nova-group_serving" : 4, @@ -1188,9 +1188,9 @@ "fiber" : 3, "fiber_points" : 3, "fiber_value" : 3, - "fruits_vegetables_nuts_colza_walnut_olive_oils" : 7.8125, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 14.0625, "fruits_vegetables_nuts_colza_walnut_olive_oils_points" : 0, - "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 7.8, + "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 14.1, "is_beverage" : 0, "is_cheese" : 0, "is_fat" : 0, @@ -1225,7 +1225,7 @@ "energy_points" : 2, "fiber" : 3, "fiber_points" : 0, - "fruits_vegetables_legumes" : 5.68181818181818, + "fruits_vegetables_legumes" : 10.2272727272727, "fruits_vegetables_legumes_points" : 0, "is_beverage" : 0, "is_cheese" : 0, @@ -1274,9 +1274,9 @@ "fiber" : 3, "fiber_points" : 3, "fiber_value" : 3, - "fruits_vegetables_nuts_colza_walnut_olive_oils" : 7.8125, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 14.0625, "fruits_vegetables_nuts_colza_walnut_olive_oils_points" : 0, - "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 7.8, + "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 14.1, "grade" : "c", "is_beverage" : 0, "is_cheese" : 0, @@ -1315,9 +1315,9 @@ "nutrition_score_beverage" : 0, "nutrition_score_debug" : "", "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients" : 1, - "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients_value" : 5.68181818181818, + "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients_value" : 10.2272727272727, "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients" : 1, - "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value" : 7.8125, + "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value" : 14.0625, "other_nutritional_substances_tags" : [], "packaging_materials_tags" : [ "en:cardboard" diff --git a/tests/unit/expected_test_results/ingredients_percent/max-sugar-salt-nutrition-facts.json b/tests/unit/expected_test_results/ingredients_percent/max-sugar-salt-nutrition-facts.json new file mode 100644 index 0000000000000..7ba4ad4fa2cea --- /dev/null +++ b/tests/unit/expected_test_results/ingredients_percent/max-sugar-salt-nutrition-facts.json @@ -0,0 +1,23 @@ +[ + { + "id" : "en:water", + "percent_estimate" : 92.5, + "percent_max" : 100, + "percent_min" : 85, + "text" : "water" + }, + { + "id" : "en:sugar", + "percent_estimate" : 3.75, + "percent_max" : 10, + "percent_min" : 0, + "text" : "sugar" + }, + { + "id" : "en:salt", + "percent_estimate" : 3.75, + "percent_max" : 5, + "percent_min" : 0, + "text" : "salt" + } +] diff --git a/tests/unit/expected_test_results/nutriscore/beverage-with-80-percent-milk.json b/tests/unit/expected_test_results/nutriscore/beverage-with-80-percent-milk.json index 5b2306c204e96..5778925a1443f 100644 --- a/tests/unit/expected_test_results/nutriscore/beverage-with-80-percent-milk.json +++ b/tests/unit/expected_test_results/nutriscore/beverage-with-80-percent-milk.json @@ -116,8 +116,8 @@ "fruits-vegetables-legumes-estimate-from-ingredients_serving" : 0, "fruits-vegetables-nuts-estimate-from-ingredients_100g" : 0, "fruits-vegetables-nuts-estimate-from-ingredients_serving" : 0, - "nutrition-score-fr" : 14, - "nutrition-score-fr_100g" : 14, + "nutrition-score-fr" : 16, + "nutrition-score-fr_100g" : 16, "proteins_100g" : 5, "salt" : 0, "salt_100g" : 0, @@ -126,7 +126,7 @@ "saturated-fat_100g" : 5, "sodium_100g" : 0, "sodium_value" : "0", - "sugars_100g" : 10 + "sugars_100g" : 20 }, "nutriscore" : { "2021" : { @@ -145,7 +145,7 @@ "is_cheese" : 0, "is_fat" : 0, "is_water" : 0, - "negative_points" : 16, + "negative_points" : 18, "positive_points" : 2, "proteins" : 5, "proteins_points" : 3, @@ -156,15 +156,15 @@ "sodium" : 0, "sodium_points" : 0, "sodium_value" : 0, - "sugars" : 10, - "sugars_points" : 2, - "sugars_value" : 10 + "sugars" : 20, + "sugars_points" : 4, + "sugars_value" : 20 }, "grade" : "d", "nutrients_available" : 1, "nutriscore_applicable" : 1, "nutriscore_computed" : 1, - "score" : 14 + "score" : 16 }, "2023" : { "category_available" : 1, @@ -189,7 +189,7 @@ "salt", "non_nutritive_sweeteners" ], - "negative_points" : 22, + "negative_points" : 24, "non_nutritive_sweeteners_points" : 0, "positive_nutrients" : [ "fruits_vegetables_legumes", @@ -203,14 +203,14 @@ "salt_points" : 0, "saturated_fat" : 5, "saturated_fat_points" : 4, - "sugars" : 10, - "sugars_points" : 8 + "sugars" : 20, + "sugars_points" : 10 }, "grade" : "e", "nutrients_available" : 1, "nutriscore_applicable" : 1, "nutriscore_computed" : 1, - "score" : 15 + "score" : 17 } }, "nutriscore_2021_tags" : [ @@ -234,7 +234,7 @@ "is_cheese" : 0, "is_fat" : 0, "is_water" : 0, - "negative_points" : 16, + "negative_points" : 18, "positive_points" : 2, "proteins" : 5, "proteins_points" : 3, @@ -242,17 +242,17 @@ "saturated_fat" : 5, "saturated_fat_points" : 4, "saturated_fat_value" : 5, - "score" : 14, + "score" : 16, "sodium" : 0, "sodium_points" : 0, "sodium_value" : 0, - "sugars" : 10, - "sugars_points" : 2, - "sugars_value" : 10 + "sugars" : 20, + "sugars_points" : 4, + "sugars_value" : 20 }, "nutriscore_grade" : "d", - "nutriscore_score" : 14, - "nutriscore_score_opposite" : -14, + "nutriscore_score" : 16, + "nutriscore_score_opposite" : -16, "nutriscore_tags" : [ "d" ], diff --git a/tests/unit/expected_test_results/nutriscore/dairy-drink-with-less-than-80-percent-milk.json b/tests/unit/expected_test_results/nutriscore/dairy-drink-with-less-than-80-percent-milk.json index 981c9cafcf8fc..85e655ef1c758 100644 --- a/tests/unit/expected_test_results/nutriscore/dairy-drink-with-less-than-80-percent-milk.json +++ b/tests/unit/expected_test_results/nutriscore/dairy-drink-with-less-than-80-percent-milk.json @@ -26,25 +26,25 @@ "en:dairies", "en:dairy-drinks" ], - "food_groups" : "en:sweetened-beverages", + "food_groups" : "en:milk-and-yogurt", "food_groups_tags" : [ - "en:beverages", - "en:sweetened-beverages" + "en:milk-and-dairy-products", + "en:milk-and-yogurt" ], "ingredients" : [ { "id" : "en:milk", - "percent_estimate" : 75, + "percent_estimate" : 90, "percent_max" : 100, - "percent_min" : 50, + "percent_min" : 80, "text" : "Milk", "vegan" : "no", "vegetarian" : "yes" }, { "id" : "en:sugar", - "percent_estimate" : 25, - "percent_max" : 50, + "percent_estimate" : 10, + "percent_max" : 20, "percent_min" : 0, "text" : "sugar", "vegan" : "yes", @@ -108,8 +108,9 @@ "en:nutrition-fruits-vegetables-nuts-estimate-from-ingredients", "en:nutrition-all-nutriscore-values-known", "en:nutrition-fruits-vegetables-legumes-estimate-from-ingredients", - "en:nutriscore-2021-same-as-2023", - "en:nutriscore-2021-e-2023-e" + "en:nutriscore-2021-different-from-2023", + "en:nutriscore-2021-better-than-2023", + "en:nutriscore-2021-d-2023-e" ], "nucleotides_tags" : [], "nutriments" : { @@ -120,8 +121,8 @@ "fruits-vegetables-legumes-estimate-from-ingredients_serving" : 0, "fruits-vegetables-nuts-estimate-from-ingredients_100g" : 0, "fruits-vegetables-nuts-estimate-from-ingredients_serving" : 0, - "nutrition-score-fr" : 19, - "nutrition-score-fr_100g" : 19, + "nutrition-score-fr" : 16, + "nutrition-score-fr_100g" : 16, "proteins_100g" : 5, "salt" : 0, "salt_100g" : 0, @@ -130,7 +131,7 @@ "saturated-fat_100g" : 5, "sodium_100g" : 0, "sodium_value" : "0", - "sugars_100g" : 10 + "sugars_100g" : 20 }, "nutriscore" : { "2021" : { @@ -145,11 +146,11 @@ "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, "fruits_vegetables_nuts_colza_walnut_olive_oils_points" : 0, "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 0, - "is_beverage" : 1, + "is_beverage" : 0, "is_cheese" : 0, "is_fat" : 0, "is_water" : 0, - "negative_points" : 21, + "negative_points" : 18, "positive_points" : 2, "proteins" : 5, "proteins_points" : 3, @@ -160,15 +161,15 @@ "sodium" : 0, "sodium_points" : 0, "sodium_value" : 0, - "sugars" : 10, - "sugars_points" : 7, - "sugars_value" : 10 + "sugars" : 20, + "sugars_points" : 4, + "sugars_value" : 20 }, - "grade" : "e", + "grade" : "d", "nutrients_available" : 1, "nutriscore_applicable" : 1, "nutriscore_computed" : 1, - "score" : 19 + "score" : 16 }, "2023" : { "category_available" : 1, @@ -193,7 +194,7 @@ "salt", "non_nutritive_sweeteners" ], - "negative_points" : 22, + "negative_points" : 24, "non_nutritive_sweeteners_points" : 0, "positive_nutrients" : [ "fruits_vegetables_legumes", @@ -207,18 +208,18 @@ "salt_points" : 0, "saturated_fat" : 5, "saturated_fat_points" : 4, - "sugars" : 10, - "sugars_points" : 8 + "sugars" : 20, + "sugars_points" : 10 }, "grade" : "e", "nutrients_available" : 1, "nutriscore_applicable" : 1, "nutriscore_computed" : 1, - "score" : 15 + "score" : 17 } }, "nutriscore_2021_tags" : [ - "e" + "d" ], "nutriscore_2023_tags" : [ "e" @@ -233,12 +234,12 @@ "fruits_vegetables_nuts_colza_walnut_olive_oils" : 0, "fruits_vegetables_nuts_colza_walnut_olive_oils_points" : 0, "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 0, - "grade" : "e", - "is_beverage" : 1, + "grade" : "d", + "is_beverage" : 0, "is_cheese" : 0, "is_fat" : 0, "is_water" : 0, - "negative_points" : 21, + "negative_points" : 18, "positive_points" : 2, "proteins" : 5, "proteins_points" : 3, @@ -246,43 +247,43 @@ "saturated_fat" : 5, "saturated_fat_points" : 4, "saturated_fat_value" : 5, - "score" : 19, + "score" : 16, "sodium" : 0, "sodium_points" : 0, "sodium_value" : 0, - "sugars" : 10, - "sugars_points" : 7, - "sugars_value" : 10 + "sugars" : 20, + "sugars_points" : 4, + "sugars_value" : 20 }, - "nutriscore_grade" : "e", - "nutriscore_score" : 19, - "nutriscore_score_opposite" : -19, + "nutriscore_grade" : "d", + "nutriscore_score" : 16, + "nutriscore_score_opposite" : -16, "nutriscore_tags" : [ - "e" + "d" ], "nutriscore_version" : "2021", "nutrition_data_per" : "100g", "nutrition_data_prepared_per" : "100g", - "nutrition_grade_fr" : "e", - "nutrition_grades" : "e", + "nutrition_grade_fr" : "d", + "nutrition_grades" : "d", "nutrition_grades_tags" : [ - "e" + "d" ], - "nutrition_score_beverage" : 1, + "nutrition_score_beverage" : 0, "nutrition_score_debug" : "", "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients" : 1, "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients_value" : 0, "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients" : 1, "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value" : 0, "other_nutritional_substances_tags" : [], - "pnns_groups_1" : "Beverages", + "pnns_groups_1" : "Milk and dairy products", "pnns_groups_1_tags" : [ - "beverages", + "milk-and-dairy-products", "known" ], - "pnns_groups_2" : "Sweetened beverages", + "pnns_groups_2" : "Milk and yogurt", "pnns_groups_2_tags" : [ - "sweetened-beverages", + "milk-and-yogurt", "known" ], "unknown_ingredients_n" : 0, diff --git a/tests/unit/expected_test_results/nutriscore/dairy-drinks-without-milk.json b/tests/unit/expected_test_results/nutriscore/dairy-drinks-without-milk.json index 823b0c7aaa98b..a9438f1c88ce4 100644 --- a/tests/unit/expected_test_results/nutriscore/dairy-drinks-without-milk.json +++ b/tests/unit/expected_test_results/nutriscore/dairy-drinks-without-milk.json @@ -35,17 +35,17 @@ { "ciqual_food_code" : "18066", "id" : "en:water", - "percent_estimate" : 75, + "percent_estimate" : 95, "percent_max" : 100, - "percent_min" : 50, + "percent_min" : 90, "text" : "Water", "vegan" : "yes", "vegetarian" : "yes" }, { "id" : "en:sugar", - "percent_estimate" : 25, - "percent_max" : 50, + "percent_estimate" : 5, + "percent_max" : 10, "percent_min" : 0, "text" : "sugar", "vegan" : "yes", @@ -129,49 +129,49 @@ "nutriments_estimated" : { "alcohol_100g" : 0, "beta-carotene_100g" : 0, - "calcium_100g" : 0.00627, - "carbohydrates_100g" : 24.95, + "calcium_100g" : 0.006958, + "carbohydrates_100g" : 4.99, "cholesterol_100g" : 0, - "copper_100g" : 1.4125e-05, - "energy-kcal_100g" : 99.75, - "energy-kj_100g" : 425, - "energy_100g" : 425, + "copper_100g" : 1.6425e-05, + "energy-kcal_100g" : 19.95, + "energy-kj_100g" : 85, + "energy_100g" : 85, "fat_100g" : 0, "fiber_100g" : 0, "fructose_100g" : 0, "galactose_100g" : 0, "glucose_100g" : 0, - "iodine_100g" : 7.5e-08, - "iron_100g" : 4.175e-05, + "iodine_100g" : 1.5e-08, + "iron_100g" : 3.235e-05, "lactose_100g" : 0, - "magnesium_100g" : 0.001155, + "magnesium_100g" : 0.001023, "maltose_100g" : 0, - "manganese_100g" : 2.19e-06, + "manganese_100g" : 1.094e-06, "pantothenic-acid_100g" : 0, "phosphorus_100g" : 0, "phylloquinone_100g" : 0, "polyols_100g" : 0, - "potassium_100g" : 0.0039225, + "potassium_100g" : 0.0013685, "proteins_100g" : 0, - "salt_100g" : 0.00705, + "salt_100g" : 0.00749, "saturated-fat_100g" : 0, - "selenium_100g" : 1.625e-06, - "sodium_100g" : 0.0023425, + "selenium_100g" : 7.25e-07, + "sodium_100g" : 0.0023885, "starch_100g" : 0, - "sucrose_100g" : 24.95, - "sugars_100g" : 24.95, + "sucrose_100g" : 4.99, + "sugars_100g" : 4.99, "vitamin-a_100g" : 0, "vitamin-b12_100g" : 0, "vitamin-b1_100g" : 0, - "vitamin-b2_100g" : 4.75e-06, + "vitamin-b2_100g" : 9.5e-07, "vitamin-b6_100g" : 0, "vitamin-b9_100g" : 0, "vitamin-c_100g" : 0, "vitamin-d_100g" : 0, "vitamin-e_100g" : 0, "vitamin-pp_100g" : 0, - "water_100g" : 75.0425, - "zinc_100g" : 1.325e-05 + "water_100g" : 95.0085, + "zinc_100g" : 1.145e-05 }, "nutriscore" : { "2021" : { diff --git a/tests/unit/expected_test_results/nutriscore/en-orange-juice-category-and-ingredients.json b/tests/unit/expected_test_results/nutriscore/en-orange-juice-category-and-ingredients.json index c78f02a6690d4..8a0b16da270dc 100644 --- a/tests/unit/expected_test_results/nutriscore/en-orange-juice-category-and-ingredients.json +++ b/tests/unit/expected_test_results/nutriscore/en-orange-juice-category-and-ingredients.json @@ -59,17 +59,17 @@ { "ciqual_food_code" : "18066", "id" : "en:water", - "percent_estimate" : 37.5, + "percent_estimate" : 45.55, "percent_max" : 50, - "percent_min" : 25, + "percent_min" : 41.1, "text" : "water", "vegan" : "yes", "vegetarian" : "yes" }, { "id" : "en:sugar", - "percent_estimate" : 12.5, - "percent_max" : 25, + "percent_estimate" : 4.45, + "percent_max" : 8.9, "percent_min" : 0, "text" : "sugar", "vegan" : "yes", diff --git a/tests/unit/expected_test_results/nutriscore/en-sweeteners-erythritol.json b/tests/unit/expected_test_results/nutriscore/en-sweeteners-erythritol.json index 7c82c04f3b268..ee033650e8587 100644 --- a/tests/unit/expected_test_results/nutriscore/en-sweeteners-erythritol.json +++ b/tests/unit/expected_test_results/nutriscore/en-sweeteners-erythritol.json @@ -45,9 +45,9 @@ { "ciqual_food_code" : "2074", "id" : "en:apple-juice", - "percent_estimate" : 62.5, + "percent_estimate" : 70.5, "percent_max" : 100, - "percent_min" : 25, + "percent_min" : 41, "text" : "apple juice", "vegan" : "yes", "vegetarian" : "yes" @@ -55,7 +55,7 @@ { "ciqual_food_code" : "18066", "id" : "en:water", - "percent_estimate" : 18.75, + "percent_estimate" : 14.75, "percent_max" : 50, "percent_min" : 0, "text" : "water", @@ -64,8 +64,8 @@ }, { "id" : "en:sugar", - "percent_estimate" : 9.375, - "percent_max" : 33.3333333333333, + "percent_estimate" : 2.25, + "percent_max" : 4.5, "percent_min" : 0, "text" : "sugar", "vegan" : "yes", @@ -73,8 +73,8 @@ }, { "id" : "en:e968", - "percent_estimate" : 9.375, - "percent_max" : 25, + "percent_estimate" : 12.5, + "percent_max" : 4.5, "percent_min" : 0, "text" : "erythritol", "vegan" : "yes", @@ -156,10 +156,10 @@ "nutriments" : { "energy_100g" : 82, "fat_100g" : 0, - "fruits-vegetables-legumes-estimate-from-ingredients_100g" : 62.5, - "fruits-vegetables-legumes-estimate-from-ingredients_serving" : 62.5, - "fruits-vegetables-nuts-estimate-from-ingredients_100g" : 62.5, - "fruits-vegetables-nuts-estimate-from-ingredients_serving" : 62.5, + "fruits-vegetables-legumes-estimate-from-ingredients_100g" : 70.5, + "fruits-vegetables-legumes-estimate-from-ingredients_serving" : 70.5, + "fruits-vegetables-nuts-estimate-from-ingredients_100g" : 70.5, + "fruits-vegetables-nuts-estimate-from-ingredients_serving" : 70.5, "nutrition-score-fr" : 2, "nutrition-score-fr_100g" : 2, "proteins_100g" : 0, @@ -182,9 +182,9 @@ "fiber" : 0, "fiber_points" : 0, "fiber_value" : 0, - "fruits_vegetables_nuts_colza_walnut_olive_oils" : 62.5, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 70.5, "fruits_vegetables_nuts_colza_walnut_olive_oils_points" : 4, - "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 62.5, + "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 70.5, "is_beverage" : 1, "is_cheese" : 0, "is_fat" : 0, @@ -219,7 +219,7 @@ "energy_points" : 1, "fiber" : 0, "fiber_points" : 0, - "fruits_vegetables_legumes" : 62.5, + "fruits_vegetables_legumes" : 70.5, "fruits_vegetables_legumes_points" : 4, "is_beverage" : 1, "is_cheese" : 0, @@ -270,9 +270,9 @@ "fiber" : 0, "fiber_points" : 0, "fiber_value" : 0, - "fruits_vegetables_nuts_colza_walnut_olive_oils" : 62.5, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 70.5, "fruits_vegetables_nuts_colza_walnut_olive_oils_points" : 4, - "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 62.5, + "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 70.5, "grade" : "c", "is_beverage" : 1, "is_cheese" : 0, @@ -311,9 +311,9 @@ "nutrition_score_beverage" : 1, "nutrition_score_debug" : "", "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients" : 1, - "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients_value" : 62.5, + "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients_value" : 70.5, "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients" : 1, - "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value" : 62.5, + "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value" : 70.5, "other_nutritional_substances_tags" : [], "pnns_groups_1" : "Beverages", "pnns_groups_1_tags" : [ diff --git a/tests/unit/expected_test_results/nutriscore/en-sweeteners.json b/tests/unit/expected_test_results/nutriscore/en-sweeteners.json index d61f7e5489928..190fa420335a5 100644 --- a/tests/unit/expected_test_results/nutriscore/en-sweeteners.json +++ b/tests/unit/expected_test_results/nutriscore/en-sweeteners.json @@ -45,9 +45,9 @@ { "ciqual_food_code" : "2074", "id" : "en:apple-juice", - "percent_estimate" : 62.5, + "percent_estimate" : 70.5, "percent_max" : 100, - "percent_min" : 25, + "percent_min" : 41, "text" : "apple juice", "vegan" : "yes", "vegetarian" : "yes" @@ -55,7 +55,7 @@ { "ciqual_food_code" : "18066", "id" : "en:water", - "percent_estimate" : 18.75, + "percent_estimate" : 14.75, "percent_max" : 50, "percent_min" : 0, "text" : "water", @@ -64,8 +64,8 @@ }, { "id" : "en:sugar", - "percent_estimate" : 9.375, - "percent_max" : 33.3333333333333, + "percent_estimate" : 2.25, + "percent_max" : 4.5, "percent_min" : 0, "text" : "sugar", "vegan" : "yes", @@ -73,8 +73,8 @@ }, { "id" : "en:e951", - "percent_estimate" : 9.375, - "percent_max" : 25, + "percent_estimate" : 12.5, + "percent_max" : 4.5, "percent_min" : 0, "text" : "aspartame", "vegan" : "yes", @@ -155,10 +155,10 @@ "nutriments" : { "energy_100g" : 82, "fat_100g" : 0, - "fruits-vegetables-legumes-estimate-from-ingredients_100g" : 62.5, - "fruits-vegetables-legumes-estimate-from-ingredients_serving" : 62.5, - "fruits-vegetables-nuts-estimate-from-ingredients_100g" : 62.5, - "fruits-vegetables-nuts-estimate-from-ingredients_serving" : 62.5, + "fruits-vegetables-legumes-estimate-from-ingredients_100g" : 70.5, + "fruits-vegetables-legumes-estimate-from-ingredients_serving" : 70.5, + "fruits-vegetables-nuts-estimate-from-ingredients_100g" : 70.5, + "fruits-vegetables-nuts-estimate-from-ingredients_serving" : 70.5, "nutrition-score-fr" : 2, "nutrition-score-fr_100g" : 2, "proteins_100g" : 0, @@ -181,9 +181,9 @@ "fiber" : 0, "fiber_points" : 0, "fiber_value" : 0, - "fruits_vegetables_nuts_colza_walnut_olive_oils" : 62.5, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 70.5, "fruits_vegetables_nuts_colza_walnut_olive_oils_points" : 4, - "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 62.5, + "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 70.5, "is_beverage" : 1, "is_cheese" : 0, "is_fat" : 0, @@ -218,7 +218,7 @@ "energy_points" : 1, "fiber" : 0, "fiber_points" : 0, - "fruits_vegetables_legumes" : 62.5, + "fruits_vegetables_legumes" : 70.5, "fruits_vegetables_legumes_points" : 4, "is_beverage" : 1, "is_cheese" : 0, @@ -270,9 +270,9 @@ "fiber" : 0, "fiber_points" : 0, "fiber_value" : 0, - "fruits_vegetables_nuts_colza_walnut_olive_oils" : 62.5, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 70.5, "fruits_vegetables_nuts_colza_walnut_olive_oils_points" : 4, - "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 62.5, + "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 70.5, "grade" : "c", "is_beverage" : 1, "is_cheese" : 0, @@ -311,9 +311,9 @@ "nutrition_score_beverage" : 1, "nutrition_score_debug" : "", "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients" : 1, - "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients_value" : 62.5, + "nutrition_score_warning_fruits_vegetables_legumes_estimate_from_ingredients_value" : 70.5, "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients" : 1, - "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value" : 62.5, + "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value" : 70.5, "other_nutritional_substances_tags" : [], "pnns_groups_1" : "Beverages", "pnns_groups_1_tags" : [ diff --git a/tests/unit/expected_test_results/nutriscore/fr-gaspacho.json b/tests/unit/expected_test_results/nutriscore/fr-gaspacho.json index 7bb90158f042d..8c94686e29dc8 100644 --- a/tests/unit/expected_test_results/nutriscore/fr-gaspacho.json +++ b/tests/unit/expected_test_results/nutriscore/fr-gaspacho.json @@ -132,7 +132,7 @@ "ciqual_food_code" : "11058", "id" : "en:salt", "percent_estimate" : 0.113352272727269, - "percent_max" : 1.1, + "percent_max" : 0.5, "percent_min" : 0, "text" : "sel", "vegan" : "yes", @@ -142,7 +142,7 @@ "ciqual_food_code" : "11000", "id" : "en:garlic", "percent_estimate" : 0.0566761363636346, - "percent_max" : 1.1, + "percent_max" : 0.5, "percent_min" : 0, "text" : "ail", "vegan" : "yes", @@ -152,7 +152,7 @@ "ciqual_food_code" : "2028", "id" : "en:lemon-juice", "percent_estimate" : 0.0566761363636346, - "percent_max" : 1.1, + "percent_max" : 0.5, "percent_min" : 0, "text" : "jus de citron", "vegan" : "yes", diff --git a/tests/unit/expected_test_results/nutriscore/fr-ice-tea-with-sweetener.json b/tests/unit/expected_test_results/nutriscore/fr-ice-tea-with-sweetener.json index 74232a11fb1ba..13e3842f193b2 100644 --- a/tests/unit/expected_test_results/nutriscore/fr-ice-tea-with-sweetener.json +++ b/tests/unit/expected_test_results/nutriscore/fr-ice-tea-with-sweetener.json @@ -60,17 +60,17 @@ { "ciqual_food_code" : "18066", "id" : "en:water", - "percent_estimate" : 52.55, + "percent_estimate" : 89.95, "percent_max" : 95.1, - "percent_min" : 10, + "percent_min" : 84.8, "text" : "Eau", "vegan" : "yes", "vegetarian" : "yes" }, { "id" : "en:sugar", - "percent_estimate" : 24.325, - "percent_max" : 48.15, + "percent_estimate" : 2.85, + "percent_max" : 4.5, "percent_min" : 1.2, "text" : "sucre", "vegan" : "yes", @@ -79,8 +79,8 @@ { "ciqual_food_code" : "31077", "id" : "en:fructose", - "percent_estimate" : 12.1625, - "percent_max" : 32.5, + "percent_estimate" : 2.85, + "percent_max" : 4.5, "percent_min" : 1.2, "text" : "fructose", "vegan" : "yes", @@ -91,8 +91,8 @@ "ingredients" : [ { "id" : "en:e330", - "percent_estimate" : 3.340625, - "percent_max" : 24.675, + "percent_estimate" : 1.68750000000001, + "percent_max" : 4.5, "percent_min" : 0.6, "text" : "acide citrique", "vegan" : "yes", @@ -100,16 +100,16 @@ }, { "id" : "en:e296", - "percent_estimate" : 2.740625, - "percent_max" : 12.3375, + "percent_estimate" : 1.08750000000001, + "percent_max" : 2.25, "percent_min" : 0, "text" : "acide malique", "vegan" : "yes", "vegetarian" : "yes" } ], - "percent_estimate" : 6.08125, - "percent_max" : 24.675, + "percent_estimate" : 2.77500000000001, + "percent_max" : 4.5, "percent_min" : 1.2, "text" : "acidifiants" }, @@ -191,7 +191,7 @@ "ingredients" : [ { "id" : "en:e960", - "percent_estimate" : 3.43125000000002, + "percent_estimate" : 0.125000000000028, "percent_max" : 0.1, "percent_min" : 0, "text" : "glycosides de steviol", @@ -199,7 +199,7 @@ "vegetarian" : "yes" } ], - "percent_estimate" : 3.43125000000002, + "percent_estimate" : 0.125000000000028, "percent_max" : 0.1, "percent_min" : 0, "text" : "édulcorant" @@ -357,6 +357,53 @@ "sodium_value" : 0.01, "sugars_100g" : 4.5 }, + "nutriments_estimated" : { + "alcohol_100g" : 0, + "beta-carotene_100g" : 0, + "calcium_100g" : 0.00652145, + "carbohydrates_100g" : 5.6922, + "cholesterol_100g" : 0, + "copper_100g" : 1.585325e-05, + "energy-kcal_100g" : 20.42112, + "energy-kj_100g" : 86.70432, + "energy_100g" : 86.70432, + "fat_100g" : 8.4e-05, + "fiber_100g" : 0, + "fructose_100g" : 2.7075, + "galactose_100g" : 0, + "glucose_100g" : 0, + "iodine_100g" : 2.4465e-07, + "iron_100g" : 3.14145e-05, + "lactose_100g" : 0, + "magnesium_100g" : 0.00097353, + "maltose_100g" : 0, + "manganese_100g" : 3.84214e-06, + "pantothenic-acid_100g" : 1.32e-07, + "phosphorus_100g" : 1.485e-05, + "phylloquinone_100g" : 0, + "polyols_100g" : 1.46775, + "potassium_100g" : 0.001493935, + "proteins_100g" : 0, + "salt_100g" : 0.007087225, + "saturated-fat_100g" : 2.4e-05, + "selenium_100g" : 6.665e-07, + "sodium_100g" : 0.002259495, + "starch_100g" : 0, + "sucrose_100g" : 2.8443, + "sugars_100g" : 4.22085, + "vitamin-a_100g" : 0, + "vitamin-b12_100g" : 0, + "vitamin-b1_100g" : 0, + "vitamin-b2_100g" : 7.095e-07, + "vitamin-b6_100g" : 0, + "vitamin-b9_100g" : 6e-08, + "vitamin-c_100g" : 0, + "vitamin-d_100g" : 0, + "vitamin-e_100g" : 0, + "vitamin-pp_100g" : 0, + "water_100g" : 91.156945, + "zinc_100g" : 1.09895e-05 + }, "nutriscore" : { "2021" : { "category_available" : 1, diff --git a/tests/unit/expected_test_results/nutriscore/fr-orange-nectar-0-fat.json b/tests/unit/expected_test_results/nutriscore/fr-orange-nectar-0-fat.json index a1fcfd9c1ff67..e54238d454caa 100644 --- a/tests/unit/expected_test_results/nutriscore/fr-orange-nectar-0-fat.json +++ b/tests/unit/expected_test_results/nutriscore/fr-orange-nectar-0-fat.json @@ -55,17 +55,17 @@ { "ciqual_food_code" : "18066", "id" : "en:water", - "percent_estimate" : 25.3333333333333, + "percent_estimate" : 32, "percent_max" : 33, - "percent_min" : 17.6666666666667, + "percent_min" : 31, "text" : "Water", "vegan" : "yes", "vegetarian" : "yes" }, { "id" : "en:sugar", - "percent_estimate" : 17.6666666666667, - "percent_max" : 25.3333333333333, + "percent_estimate" : 11, + "percent_max" : 12, "percent_min" : 10, "text" : "Sugar", "vegan" : "yes", diff --git a/tests/unit/ingredients_percent.t b/tests/unit/ingredients_percent.t index 0d5a3c23fd453..5beea5e043537 100755 --- a/tests/unit/ingredients_percent.t +++ b/tests/unit/ingredients_percent.t @@ -238,6 +238,19 @@ my @tests = ( "Lingon 50%*, socker*, vatten, förtjockningsmedel (pektin), surhetsreglerande medel (citronsyra). *KRAV-certifierad ekologisk ingrediens. Fruktmängd: 50g per 100g. Total mängd socker är 35 g per 100 g sylt. Fruktmängd: 52g per 100 g sylt. Bärmängd: 40 g bär per 100g. Total mängd socker: 45g per 100g sylt. Total mängd socker 44 g, varav tillsatt socker 41g per 100g sylt." }, ], + # max sugar and salt from nutrition facts + [ + 'max-sugar-salt-nutrition-facts', + { + lc => "en", + ingredients_text => "water, sugar, salt", + nutrition_data_per => "100g", + nutriments => { + sugars_100g => 10, + salt_100g => 5, + }, + }, + ], ); foreach my $test_ref (@tests) { @@ -246,11 +259,7 @@ foreach my $test_ref (@tests) { my $product_ref = $test_ref->[1]; parse_ingredients_text_service($product_ref, {}); - if (compute_ingredients_percent_min_max_values(100, 100, $product_ref->{ingredients}) < 0) { - delete_ingredients_percent_values($product_ref->{ingredients}); - } - - compute_ingredients_percent_estimates(100, $product_ref->{ingredients}); + estimate_ingredients_percent_service($product_ref, {}); compare_to_expected_results( $product_ref->{ingredients}, diff --git a/tests/unit/nutriscore.t b/tests/unit/nutriscore.t index c4eea3e74eb13..93fe603421201 100644 --- a/tests/unit/nutriscore.t +++ b/tests/unit/nutriscore.t @@ -224,7 +224,7 @@ my @tests = ( energy_100g => 3378, fat_100g => 10, "saturated-fat_100g" => 5, - sugars_100g => 10, + sugars_100g => 20, sodium_100g => 0, fiber_100g => 2, proteins_100g => 5 @@ -242,7 +242,7 @@ my @tests = ( energy_100g => 3378, fat_100g => 10, "saturated-fat_100g" => 5, - sugars_100g => 10, + sugars_100g => 20, sodium_100g => 0, fiber_100g => 2, proteins_100g => 5