diff --git a/lib/ProductOpener/Food.pm b/lib/ProductOpener/Food.pm index 78b055b5083d5..a84e025e41d8a 100644 --- a/lib/ProductOpener/Food.pm +++ b/lib/ProductOpener/Food.pm @@ -1204,6 +1204,199 @@ my @fruits_vegetables_nuts_by_category_sorted = sort {$fruits_vegetables_nuts_by_category{$b} <=> $fruits_vegetables_nuts_by_category{$a}} keys %fruits_vegetables_nuts_by_category; +=head2 compute_fruit_ratio($product_ref, $prepared) + +Compute the fruit % according to the Nutri-Score rules + +Warning Also modifies product_ref->{misc_tags} + +=head3 return + +The fruit ratio + +=cut + +sub compute_fruit_ratio ($product_ref, $prepared) { + + my $fruits = undef; + + if (defined $product_ref->{nutriments}{"fruits-vegetables-nuts-dried" . $prepared . "_100g"}) { + $fruits = 2 * $product_ref->{nutriments}{"fruits-vegetables-nuts-dried" . $prepared . "_100g"}; + push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts-dried"; + + if (defined $product_ref->{nutriments}{"fruits-vegetables-nuts" . $prepared . "_100g"}) { + $fruits += $product_ref->{nutriments}{"fruits-vegetables-nuts" . $prepared . "_100g"}; + push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts"; + } + + $fruits + = $fruits * 100 / (100 + $product_ref->{nutriments}{"fruits-vegetables-nuts-dried" . $prepared . "_100g"}); + } + elsif (defined $product_ref->{nutriments}{"fruits-vegetables-nuts" . $prepared . "_100g"}) { + $fruits = $product_ref->{nutriments}{"fruits-vegetables-nuts" . $prepared . "_100g"}; + push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts"; + } + elsif (defined $product_ref->{nutriments}{"fruits-vegetables-nuts-estimate" . $prepared . "_100g"}) { + $fruits = $product_ref->{nutriments}{"fruits-vegetables-nuts-estimate" . $prepared . "_100g"}; + $product_ref->{nutrition_score_warning_fruits_vegetables_nuts_estimate} = 1; + push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts-estimate"; + } + # Use the estimate from the ingredients list if we have one + elsif ( + (not defined $fruits) + and + (defined $product_ref->{nutriments}{"fruits-vegetables-nuts-estimate-from-ingredients" . $prepared . "_100g"}) + ) + { + $fruits = $product_ref->{nutriments}{"fruits-vegetables-nuts-estimate-from-ingredients" . $prepared . "_100g"}; + $product_ref->{nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients} = 1; + $product_ref->{nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value} = $fruits; + push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts-estimate-from-ingredients"; + } + else { + # estimates by category of products. not exact values. it's important to distinguish only between the thresholds: 40, 60 and 80 + foreach my $category_id (@fruits_vegetables_nuts_by_category_sorted) { + + if (has_tag($product_ref, "categories", $category_id)) { + $fruits = $fruits_vegetables_nuts_by_category{$category_id}; + $product_ref->{nutrition_score_warning_fruits_vegetables_nuts_from_category} = $category_id; + $product_ref->{nutrition_score_warning_fruits_vegetables_nuts_from_category_value} + = $fruits_vegetables_nuts_by_category{$category_id}; + push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts-from-category"; + my $category = $category_id; + $category =~ s/:/-/; + push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts-from-category-$category"; + last; + } + } + + # If we do not have a fruits estimate, use 0 and add a warning + if (not defined $fruits) { + $fruits = 0; + $product_ref->{nutrition_score_warning_no_fruits_vegetables_nuts} = 1; + push @{$product_ref->{misc_tags}}, "en:nutrition-no-fruits-vegetables-nuts"; + } + } + + if ( (defined $product_ref->{nutrition_score_warning_no_fiber}) + or (defined $product_ref->{nutrition_score_warning_no_fruits_vegetables_nuts})) + { + push @{$product_ref->{misc_tags}}, "en:nutrition-no-fiber-or-fruits-vegetables-nuts"; + } + else { + push @{$product_ref->{misc_tags}}, "en:nutrition-all-nutriscore-values-known"; + } + + return $fruits; +} + +=head2 saturated_fat_ratio( $product_ref, $prepared ) + +Compute saturated_fat_ratio as needed for nutriscore + +=cut + +sub saturated_fat_ratio ($product_ref, $prepared) { + + my $saturated_fat = $product_ref->{nutriments}{"saturated-fat" . $prepared . "_100g"}; + my $fat = $product_ref->{nutriments}{"fat" . $prepared . "_100g"}; + my $saturated_fat_ratio = 0; + if ((defined $saturated_fat) and ($saturated_fat > 0)) { + if ($fat <= 0) { + $fat = $saturated_fat; + } + $saturated_fat_ratio = $saturated_fat / $fat * 100; # in % + } + return $saturated_fat_ratio; +} + +=head2 saturated_fat_0_because_of_fat_0($product_ref, $prepared) + +Detect if we are in the special case where we can detect saturated fat is 0 because fat is 0 + +=cut + +sub saturated_fat_0_because_of_fat_0 ($product_ref, $prepared) { + my $fat = $product_ref->{nutriments}{"fat" . $prepared . "_100g"}; + return ( (!defined $product_ref->{nutriments}{"saturated-fat" . $prepared . "_100g"}) + && (defined $fat) + && ($fat == 0)); +} + +=head2 sugar_0_because_of_carbohydrates_0($product_ref, $prepared) { + +Detect if we are in the special case where we can detect sugars are 0 because carbohydrates are 0 + +=cut + +sub sugar_0_because_of_carbohydrates_0 ($product_ref, $prepared) { + my $carbohydrates = $product_ref->{nutriments}{"carbohydrates" . $prepared . "_100g"}; + return ( (!defined $product_ref->{nutriments}{"sugars" . $prepared . "_100g"}) + && (defined $carbohydrates) + && ($carbohydrates == 0)); +} + +=head2 compute_nutriscore_data( $product_ref, $prepared ) + +Compute data for nutriscore computation. + +Warning: it also modifies $product_ref + +=head3 Arguments + +=head4 $product_ref - ref to product + +=head4 $prepared - string contains either "" or "prepared" + +=head4 $fruits - float - fruit % estimation + +=head3 return + +Ref to a mapping suitable to call compute_nutriscore_score_and_grade + +=cut + +sub compute_nutriscore_data ($product_ref, $prepared) { + # compute data + my $saturated_fat_ratio = saturated_fat_ratio($product_ref, $prepared); + my $fruits = compute_fruit_ratio($product_ref, $prepared); + my $nutriscore_data = { + is_beverage => $product_ref->{nutrition_score_beverage}, + is_water => is_water_for_nutrition_score($product_ref), + is_cheese => is_cheese_for_nutrition_score($product_ref), + is_fat => is_fat_for_nutrition_score($product_ref), + + energy => $product_ref->{nutriments}{"energy" . $prepared . "_100g"}, + sugars => $product_ref->{nutriments}{"sugars" . $prepared . "_100g"}, + saturated_fat => $product_ref->{nutriments}{"saturated-fat" . $prepared . "_100g"}, + saturated_fat_ratio => $saturated_fat_ratio, + sodium => $product_ref->{nutriments}{"sodium" . $prepared . "_100g"} * 1000, # in mg, + + fruits_vegetables_nuts_colza_walnut_olive_oils => $fruits, + fiber => ( + (defined $product_ref->{nutriments}{"fiber" . $prepared . "_100g"}) + ? $product_ref->{nutriments}{"fiber" . $prepared . "_100g"} + : 0 + ), + proteins => $product_ref->{nutriments}{"proteins" . $prepared . "_100g"}, + }; + + # tweak data to take into account special cases + + # if sugar is undefined but carbohydrates is 0, set sugars to 0 + if (sugar_0_because_of_carbohydrates_0($product_ref, $prepared)) { + $nutriscore_data->{sugars} = 0; + } + # if saturated_fat is undefined but fat is 0, set saturated_fat to 0 + # as well as saturated_fat_ratio + if (saturated_fat_0_because_of_fat_0($product_ref, $prepared)) { + $nutriscore_data->{saturated_fat} = 0; + $nutriscore_data->{saturated_fat_ratio} = 0; + } + + return $nutriscore_data; +} + =head2 compute_nutrition_score( $product_ref ) Determines if we have enough data to compute the Nutri-Score (category + nutrition facts), @@ -1345,6 +1538,10 @@ sub compute_nutrition_score ($product_ref) { foreach my $nid ("energy", "fat", "saturated-fat", "sugars", "sodium", "proteins") { if (not defined $product_ref->{nutriments}{$nid . $prepared . "_100g"}) { + # we have two special case where we can deduce data + next + if ((($nid eq "saturated-fat") && saturated_fat_0_because_of_fat_0($product_ref, $prepared)) + || (($nid eq "sugars") && sugar_0_because_of_carbohydrates_0($product_ref, $prepared))); $product_ref->{"nutrition_grades_tags"} = ["unknown"]; add_tag($product_ref, "misc", "en:nutrition-not-enough-data-to-compute-nutrition-score"); $product_ref->{nutrition_score_debug} .= "missing " . $nid . $prepared . " - "; @@ -1382,109 +1579,9 @@ sub compute_nutrition_score ($product_ref) { push @{$product_ref->{misc_tags}}, "en:nutrition-grade-computed-for-prepared-product"; } - # Compute the fruit % according to the Nutri-Score rules - - my $fruits = undef; - - if (defined $product_ref->{nutriments}{"fruits-vegetables-nuts-dried" . $prepared . "_100g"}) { - $fruits = 2 * $product_ref->{nutriments}{"fruits-vegetables-nuts-dried" . $prepared . "_100g"}; - push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts-dried"; - - if (defined $product_ref->{nutriments}{"fruits-vegetables-nuts" . $prepared . "_100g"}) { - $fruits += $product_ref->{nutriments}{"fruits-vegetables-nuts" . $prepared . "_100g"}; - push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts"; - } - - $fruits - = $fruits * 100 / (100 + $product_ref->{nutriments}{"fruits-vegetables-nuts-dried" . $prepared . "_100g"}); - } - elsif (defined $product_ref->{nutriments}{"fruits-vegetables-nuts" . $prepared . "_100g"}) { - $fruits = $product_ref->{nutriments}{"fruits-vegetables-nuts" . $prepared . "_100g"}; - push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts"; - } - elsif (defined $product_ref->{nutriments}{"fruits-vegetables-nuts-estimate" . $prepared . "_100g"}) { - $fruits = $product_ref->{nutriments}{"fruits-vegetables-nuts-estimate" . $prepared . "_100g"}; - $product_ref->{nutrition_score_warning_fruits_vegetables_nuts_estimate} = 1; - push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts-estimate"; - } - # Use the estimate from the ingredients list if we have one - elsif ( - (not defined $fruits) - and - (defined $product_ref->{nutriments}{"fruits-vegetables-nuts-estimate-from-ingredients" . $prepared . "_100g"}) - ) - { - $fruits = $product_ref->{nutriments}{"fruits-vegetables-nuts-estimate-from-ingredients" . $prepared . "_100g"}; - $product_ref->{nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients} = 1; - $product_ref->{nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value} = $fruits; - push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts-estimate-from-ingredients"; - } - else { - # estimates by category of products. not exact values. it's important to distinguish only between the thresholds: 40, 60 and 80 - foreach my $category_id (@fruits_vegetables_nuts_by_category_sorted) { - - if (has_tag($product_ref, "categories", $category_id)) { - $fruits = $fruits_vegetables_nuts_by_category{$category_id}; - $product_ref->{nutrition_score_warning_fruits_vegetables_nuts_from_category} = $category_id; - $product_ref->{nutrition_score_warning_fruits_vegetables_nuts_from_category_value} - = $fruits_vegetables_nuts_by_category{$category_id}; - push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts-from-category"; - my $category = $category_id; - $category =~ s/:/-/; - push @{$product_ref->{misc_tags}}, "en:nutrition-fruits-vegetables-nuts-from-category-$category"; - last; - } - } - - # If we do not have a fruits estimate, use 0 and add a warning - if (not defined $fruits) { - $fruits = 0; - $product_ref->{nutrition_score_warning_no_fruits_vegetables_nuts} = 1; - push @{$product_ref->{misc_tags}}, "en:nutrition-no-fruits-vegetables-nuts"; - } - } - - if ( (defined $product_ref->{nutrition_score_warning_no_fiber}) - or (defined $product_ref->{nutrition_score_warning_no_fruits_vegetables_nuts})) - { - push @{$product_ref->{misc_tags}}, "en:nutrition-no-fiber-or-fruits-vegetables-nuts"; - } - else { - push @{$product_ref->{misc_tags}}, "en:nutrition-all-nutriscore-values-known"; - } - - my $saturated_fat = $product_ref->{nutriments}{"saturated-fat" . $prepared . "_100g"}; - my $fat = $product_ref->{nutriments}{"fat" . $prepared . "_100g"}; - my $saturated_fat_ratio = 0; - if ((defined $saturated_fat) and ($saturated_fat > 0)) { - if ($fat <= 0) { - $fat = $saturated_fat; - } - $saturated_fat_ratio = $saturated_fat / $fat * 100; # in % - } - # Populate the data structure that will be passed to Food::Nutriscore - $product_ref->{nutriscore_data} = { - is_beverage => $product_ref->{nutrition_score_beverage}, - is_water => is_water_for_nutrition_score($product_ref), - is_cheese => is_cheese_for_nutrition_score($product_ref), - is_fat => is_fat_for_nutrition_score($product_ref), - - energy => $product_ref->{nutriments}{"energy" . $prepared . "_100g"}, - sugars => $product_ref->{nutriments}{"sugars" . $prepared . "_100g"}, - saturated_fat => $product_ref->{nutriments}{"saturated-fat" . $prepared . "_100g"}, - saturated_fat_ratio => $saturated_fat_ratio, - sodium => $product_ref->{nutriments}{"sodium" . $prepared . "_100g"} * 1000, # in mg, - - fruits_vegetables_nuts_colza_walnut_olive_oils => $fruits, - fiber => ( - (defined $product_ref->{nutriments}{"fiber" . $prepared . "_100g"}) - ? $product_ref->{nutriments}{"fiber" . $prepared . "_100g"} - : 0 - ), - proteins => $product_ref->{nutriments}{"proteins" . $prepared . "_100g"}, - }; + $product_ref->{nutriscore_data} = compute_nutriscore_data($product_ref, $prepared); my ($nutriscore_score, $nutriscore_grade) = ProductOpener::Nutriscore::compute_nutriscore_score_and_grade($product_ref->{nutriscore_data}); 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 new file mode 100644 index 0000000000000..46309cdf5d130 --- /dev/null +++ b/tests/unit/expected_test_results/nutriscore/fr-orange-nectar-0-fat.json @@ -0,0 +1,201 @@ +{ + "categories" : "fruit-nectar", + "categories_hierarchy" : [ + "en:plant-based-foods-and-beverages", + "en:beverages", + "en:plant-based-beverages", + "en:fruit-based-beverages", + "en:juices-and-nectars", + "en:fruit-nectars" + ], + "categories_lc" : "en", + "categories_properties" : { + "agribalyse_proxy_food_code:en" : "2061" + }, + "categories_properties_tags" : [ + "all-products", + "categories-known", + "agribalyse-food-code-unknown", + "agribalyse-proxy-food-code-2061", + "agribalyse-proxy-food-code-known", + "ciqual-food-code-unknown", + "agribalyse-known", + "agribalyse-2061" + ], + "categories_tags" : [ + "en:plant-based-foods-and-beverages", + "en:beverages", + "en:plant-based-beverages", + "en:fruit-based-beverages", + "en:juices-and-nectars", + "en:fruit-nectars" + ], + "food_groups" : "en:sweetened-beverages", + "food_groups_tags" : [ + "en:beverages", + "en:sweetened-beverages" + ], + "ingredients" : [ + { + "id" : "en:orange", + "percent" : 47, + "percent_estimate" : 47, + "percent_max" : 47, + "percent_min" : 47, + "text" : "Orange", + "vegan" : "yes", + "vegetarian" : "yes" + }, + { + "id" : "en:water", + "percent_estimate" : 25.3333333333333, + "percent_max" : 33, + "percent_min" : 17.6666666666667, + "text" : "Water", + "vegan" : "yes", + "vegetarian" : "yes" + }, + { + "id" : "en:sugar", + "percent_estimate" : 17.6666666666667, + "percent_max" : 25.3333333333333, + "percent_min" : 10, + "text" : "Sugar", + "vegan" : "yes", + "vegetarian" : "yes" + }, + { + "id" : "en:carrot", + "percent" : 10, + "percent_estimate" : 10, + "percent_max" : 10, + "percent_min" : 10, + "text" : "Carrots", + "vegan" : "yes", + "vegetarian" : "yes" + } + ], + "ingredients_analysis" : {}, + "ingredients_analysis_tags" : [ + "en:palm-oil-free", + "en:vegan", + "en:vegetarian" + ], + "ingredients_hierarchy" : [ + "en:orange", + "en:fruit", + "en:citrus-fruit", + "en:water", + "en:sugar", + "en:added-sugar", + "en:disaccharide", + "en:carrot", + "en:vegetable", + "en:root-vegetable" + ], + "ingredients_n" : 4, + "ingredients_n_tags" : [ + "4", + "1-10" + ], + "ingredients_original_tags" : [ + "en:orange", + "en:water", + "en:sugar", + "en:carrot" + ], + "ingredients_percent_analysis" : 1, + "ingredients_tags" : [ + "en:orange", + "en:fruit", + "en:citrus-fruit", + "en:water", + "en:sugar", + "en:added-sugar", + "en:disaccharide", + "en:carrot", + "en:vegetable", + "en:root-vegetable" + ], + "ingredients_text" : "Orange 47%, Water, Sugar, Carrots 10%", + "ingredients_with_specified_percent_n" : 2, + "ingredients_with_specified_percent_sum" : 57, + "ingredients_with_unspecified_percent_n" : 2, + "ingredients_with_unspecified_percent_sum" : 43, + "known_ingredients_n" : 10, + "lc" : "en", + "misc_tags" : [ + "en:nutrition-fruits-vegetables-nuts-estimate-from-ingredients", + "en:nutrition-all-nutriscore-values-known", + "en:nutriscore-computed" + ], + "nutriments" : { + "energy_100g" : 250, + "fat_100g" : 0, + "fiber_100g" : 0, + "fruits-vegetables-nuts-estimate-from-ingredients_100g" : 57, + "fruits-vegetables-nuts-estimate-from-ingredients_serving" : 57, + "nutrition-score-fr" : 17, + "nutrition-score-fr_100g" : 17, + "proteins_100g" : 0.5, + "sodium_100g" : 0.2, + "sugars_100g" : 12 + }, + "nutriscore_data" : { + "energy" : 250, + "energy_points" : 9, + "energy_value" : 250, + "fiber" : 0, + "fiber_points" : 0, + "fiber_value" : 0, + "fruits_vegetables_nuts_colza_walnut_olive_oils" : 57, + "fruits_vegetables_nuts_colza_walnut_olive_oils_points" : 2, + "fruits_vegetables_nuts_colza_walnut_olive_oils_value" : 57, + "grade" : "e", + "is_beverage" : 1, + "is_cheese" : 0, + "is_fat" : 0, + "is_water" : 0, + "negative_points" : 19, + "positive_points" : 2, + "proteins" : 0.5, + "proteins_points" : 0, + "proteins_value" : 0.5, + "saturated_fat" : 0, + "saturated_fat_points" : 0, + "saturated_fat_ratio" : 0, + "saturated_fat_ratio_points" : 0, + "saturated_fat_ratio_value" : 0, + "saturated_fat_value" : 0, + "score" : 17, + "sodium" : 200, + "sodium_points" : 2, + "sodium_value" : 200, + "sugars" : 12, + "sugars_points" : 8, + "sugars_value" : 12 + }, + "nutriscore_grade" : "e", + "nutriscore_score" : 17, + "nutriscore_score_opposite" : -17, + "nutrition_grade_fr" : "e", + "nutrition_grades" : "e", + "nutrition_grades_tags" : [ + "e" + ], + "nutrition_score_beverage" : 1, + "nutrition_score_debug" : "", + "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients" : 1, + "nutrition_score_warning_fruits_vegetables_nuts_estimate_from_ingredients_value" : 57, + "pnns_groups_1" : "Beverages", + "pnns_groups_1_tags" : [ + "beverages", + "known" + ], + "pnns_groups_2" : "Sweetened beverages", + "pnns_groups_2_tags" : [ + "sweetened-beverages", + "known" + ], + "unknown_ingredients_n" : 0 +} diff --git a/tests/unit/expected_test_results/nutriscore/sunflower-oil-no-sugar-no-sat-fat.json b/tests/unit/expected_test_results/nutriscore/sunflower-oil-no-sugar-no-sat-fat.json new file mode 100644 index 0000000000000..37e55646a4505 --- /dev/null +++ b/tests/unit/expected_test_results/nutriscore/sunflower-oil-no-sugar-no-sat-fat.json @@ -0,0 +1,72 @@ +{ + "categories" : "sunflower oils", + "categories_hierarchy" : [ + "en:plant-based-foods-and-beverages", + "en:plant-based-foods", + "en:fats", + "en:vegetable-fats", + "en:vegetable-oils", + "en:sunflower-seeds-and-their-products", + "en:sunflower-oils" + ], + "categories_lc" : "en", + "categories_properties" : { + "agribalyse_food_code:en" : "17440", + "ciqual_food_code:en" : "17440" + }, + "categories_properties_tags" : [ + "all-products", + "categories-known", + "agribalyse-food-code-17440", + "agribalyse-food-code-known", + "agribalyse-proxy-food-code-unknown", + "ciqual-food-code-17440", + "ciqual-food-code-known", + "agribalyse-known", + "agribalyse-17440" + ], + "categories_tags" : [ + "en:plant-based-foods-and-beverages", + "en:plant-based-foods", + "en:fats", + "en:vegetable-fats", + "en:vegetable-oils", + "en:sunflower-seeds-and-their-products", + "en:sunflower-oils" + ], + "food_groups" : "en:fats", + "food_groups_tags" : [ + "en:fats-and-sauces", + "en:fats" + ], + "lc" : "en", + "misc_tags" : [ + "en:nutriscore-not-computed", + "en:nutrition-not-enough-data-to-compute-nutrition-score", + "en:nutriscore-missing-nutrition-data", + "en:nutriscore-missing-nutrition-data-saturated-fat" + ], + "nutriments" : { + "carbohydrates_100g" : 0, + "energy_100g" : 3378, + "fat_100g" : 100, + "fiber_100g" : 0, + "proteins_100g" : 0, + "sodium_100g" : 0 + }, + "nutrition_grades_tags" : [ + "unknown" + ], + "nutrition_score_beverage" : 0, + "nutrition_score_debug" : "missing saturated-fat", + "pnns_groups_1" : "Fat and sauces", + "pnns_groups_1_tags" : [ + "fat-and-sauces", + "known" + ], + "pnns_groups_2" : "Fats", + "pnns_groups_2_tags" : [ + "fats", + "known" + ] +} diff --git a/tests/unit/expected_test_results/nutriscore/sunflower-oil-no-sugar.json b/tests/unit/expected_test_results/nutriscore/sunflower-oil-no-sugar.json new file mode 100644 index 0000000000000..4d1406454db9d --- /dev/null +++ b/tests/unit/expected_test_results/nutriscore/sunflower-oil-no-sugar.json @@ -0,0 +1,114 @@ +{ + "categories" : "sunflower oils", + "categories_hierarchy" : [ + "en:plant-based-foods-and-beverages", + "en:plant-based-foods", + "en:fats", + "en:vegetable-fats", + "en:vegetable-oils", + "en:sunflower-seeds-and-their-products", + "en:sunflower-oils" + ], + "categories_lc" : "en", + "categories_properties" : { + "agribalyse_food_code:en" : "17440", + "ciqual_food_code:en" : "17440" + }, + "categories_properties_tags" : [ + "all-products", + "categories-known", + "agribalyse-food-code-17440", + "agribalyse-food-code-known", + "agribalyse-proxy-food-code-unknown", + "ciqual-food-code-17440", + "ciqual-food-code-known", + "agribalyse-known", + "agribalyse-17440" + ], + "categories_tags" : [ + "en:plant-based-foods-and-beverages", + "en:plant-based-foods", + "en:fats", + "en:vegetable-fats", + "en:vegetable-oils", + "en:sunflower-seeds-and-their-products", + "en:sunflower-oils" + ], + "food_groups" : "en:fats", + "food_groups_tags" : [ + "en:fats-and-sauces", + "en:fats" + ], + "lc" : "en", + "misc_tags" : [ + "en:nutrition-no-fruits-vegetables-nuts", + "en:nutrition-no-fiber-or-fruits-vegetables-nuts", + "en:nutriscore-computed" + ], + "nutriments" : { + "carbohydrates_100g" : 0, + "energy_100g" : 3378, + "fat_100g" : 100, + "fiber_100g" : 0, + "nutrition-score-fr" : 11, + "nutrition-score-fr_100g" : 11, + "proteins_100g" : 0, + "saturated-fat_100g" : 10, + "sodium_100g" : 0 + }, + "nutriscore_data" : { + "energy" : 3378, + "energy_points" : 10, + "energy_value" : 3378, + "fiber" : 0, + "fiber_points" : 0, + "fiber_value" : 0, + "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" : "d", + "is_beverage" : 0, + "is_cheese" : 0, + "is_fat" : 1, + "is_water" : 0, + "negative_points" : 11, + "positive_points" : 0, + "proteins" : 0, + "proteins_points" : 0, + "proteins_value" : 0, + "saturated_fat" : 10, + "saturated_fat_points" : 9, + "saturated_fat_ratio" : 10, + "saturated_fat_ratio_points" : 1, + "saturated_fat_ratio_value" : 10, + "saturated_fat_value" : 10, + "score" : 11, + "sodium" : 0, + "sodium_points" : 0, + "sodium_value" : 0, + "sugars" : 0, + "sugars_points" : 0, + "sugars_value" : 0 + }, + "nutriscore_grade" : "d", + "nutriscore_score" : 11, + "nutriscore_score_opposite" : -11, + "nutrition_grade_fr" : "d", + "nutrition_grades" : "d", + "nutrition_grades_tags" : [ + "d" + ], + "nutrition_score_beverage" : 0, + "nutrition_score_debug" : "", + "nutrition_score_warning_no_fruits_vegetables_nuts" : 1, + "pnns_groups_1" : "Fat and sauces", + "pnns_groups_1_tags" : [ + "fat-and-sauces", + "known" + ], + "pnns_groups_2" : "Fats", + "pnns_groups_2_tags" : [ + "fats", + "known" + ] +} diff --git a/tests/unit/nutriscore.t b/tests/unit/nutriscore.t index 10740e77679c4..a251bfef8c760 100644 --- a/tests/unit/nutriscore.t +++ b/tests/unit/nutriscore.t @@ -103,6 +103,42 @@ my @tests = ( } ], + # if no sugar but carbohydrates is 0, consider sugar 0 + [ + "sunflower-oil-no-sugar", + { + lc => "en", + categories => "sunflower oils", + nutriments => { + energy_100g => 3378, + fat_100g => 100, + "saturated-fat_100g" => 10, + carbohydrates_100g => 0, + sodium_100g => 0, + fiber_100g => 0, + proteins_100g => 0 + } + } + ], + + # if no sugar but carbohydrates is 0, consider sugar 0 + # still saturated fat missing will block + [ + "sunflower-oil-no-sugar-no-sat-fat", + { + lc => "en", + categories => "sunflower oils", + nutriments => { + energy_100g => 3378, + fat_100g => 100, + carbohydrates_100g => 0, + sodium_100g => 0, + fiber_100g => 0, + proteins_100g => 0 + } + } + ], + # saturated fat 1.03 should be rounded to 1.0 which is not strictly greater than 1.0 [ "breakfast-cereals", @@ -191,6 +227,7 @@ my @tests = ( ingredients_text => "Fresh milk 80%, sugar" } ], + [ "dairy-drink-with-less-than-80-percent-milk", { @@ -249,6 +286,25 @@ my @tests = ( ], + # if fat is 0 and we have no saturated fat, we consider it 0 + [ + "fr-orange-nectar-0-fat", + { + lc => "en", + categories => "fruit-nectar", + ingredients_text => "Orange 47%, Water, Sugar, Carrots 10%", + nutriments => { + energy_100g => 250, + fat_100g => 0, + sugars_100g => 12, + sodium_100g => 0.2, + fiber_100g => 0, + proteins_100g => 0.5 + }, + } + + ], + # spring waters ["spring-water-no-nutrition", {lc => "en", categories => "spring water", nutriments => {}}], ["flavored-spring-water-no-nutrition", {lc => "en", categories => "flavoured spring water", nutriments => {}}],