Skip to content

Commit

Permalink
fix: Nutri-Score v2 fixes and improvements to knowledge panels (#9795)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephanegigandet authored and john-gom committed May 24, 2024
1 parent b7a6c53 commit c00ebb6
Show file tree
Hide file tree
Showing 142 changed files with 590 additions and 362 deletions.
8 changes: 8 additions & 0 deletions docs/api/ref/schemas/product_ingredients.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ properties:
type: string
ingredients_percent_analysis:
type: integer
ingredients_sweeteners_n:
type: integer
description: |
Number of sweeteners additives in the ingredients. Undefined if ingredients are not specified.
ingredients_non_nutritive_sweeteners_n:
type: integer
description: |
Number of non-nutritive sweeteners additives (as specified in the Nutri-Score formula) in the ingredients. Undefined if ingredients are not specified.
ingredients_tags:
type: array
items:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions lib/ProductOpener/Display.pm
Original file line number Diff line number Diff line change
Expand Up @@ -7394,8 +7394,6 @@ sub display_page ($request_ref) {

sub display_image_box ($product_ref, $id, $minheight_ref) {

# print STDERR "display_image_box : $id\n";

my $img = display_image($product_ref, $id, $small_size);
if ($img ne '') {
my $code = $product_ref->{code};
Expand Down
15 changes: 6 additions & 9 deletions lib/ProductOpener/Food.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1390,6 +1390,10 @@ Differences with the 2021 version:
=head4 $prepared - string contains either "" or "-prepared"
=head3 Return values
Return undef if no value could be computed or estimated.
=cut

sub compute_nutriscore_2023_fruits_vegetables_legumes ($product_ref, $prepared) {
Expand Down Expand Up @@ -1601,11 +1605,7 @@ sub compute_nutriscore_data ($product_ref, $prepared, $nutriments_field, $versio
salt => $nutriments_ref->{"salt" . $prepared . "_100g"},

fruits_vegetables_legumes => $fruits_vegetables_legumes,
fiber => (
(defined $nutriments_ref->{"fiber" . $prepared . "_100g"})
? $nutriments_ref->{"fiber" . $prepared . "_100g"}
: 0
),
fiber => $nutriments_ref->{"fiber" . $prepared . "_100g"},
proteins => $nutriments_ref->{"proteins" . $prepared . "_100g"},
};

Expand All @@ -1621,9 +1621,7 @@ sub compute_nutriscore_data ($product_ref, $prepared, $nutriments_field, $versio
}

if ($is_beverage) {
if (defined $product_ref->{with_non_nutritive_sweeteners}) {
$nutriscore_data_ref->{with_non_nutritive_sweeteners} = $product_ref->{with_non_nutritive_sweeteners};
}
$nutriscore_data_ref->{non_nutritive_sweeteners} = $product_ref->{ingredients_non_nutritive_sweeteners_n};
}
}

Expand Down Expand Up @@ -2227,7 +2225,6 @@ sub compute_serving_size_data ($product_ref) {

my $unit = get_property("nutrients", "zz:$nid", "unit:en")
; # $unit will be undef if the nutrient is not in the taxonomy
print STDERR "nid: $nid - unit: $unit\n";

# If the nutrient has no unit (e.g. pH), or is a % (e.g. "% vol" for alcohol), it is the same regardless of quantity
# otherwise we adjust the value for 100g
Expand Down
61 changes: 40 additions & 21 deletions lib/ProductOpener/Ingredients.pm
Original file line number Diff line number Diff line change
Expand Up @@ -6425,7 +6425,9 @@ sub preparse_ingredients_text ($ingredients_lc, $text) {

sub extract_ingredients_classes_from_text ($product_ref) {

not defined $product_ref->{ingredients_text} and return;
if (not defined $product_ref->{ingredients_text}) {
return;
}
my $ingredients_lc = $product_ref->{ingredients_lc} || $product_ref->{lc};
my $text = preparse_ingredients_text($ingredients_lc, $product_ref->{ingredients_text});
# do not match anything if we don't have a translation for "and"
Expand Down Expand Up @@ -7060,43 +7062,60 @@ sub extract_ingredients_classes_from_text ($product_ref) {
}

# Determine if the product has sweeteners, and non nutritive sweeteners
determine_if_the_product_contains_sweeteners($product_ref);
count_sweeteners_and_non_nutritive_sweeteners($product_ref);

return;
}

=head2 determine_if_the_product_contains_sweeteners
=head2 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.
=head3 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.
=head4 ingredients_sweeteners_n
=head4 ingredients_non_nutritive_sweeteners_n
=cut

sub determine_if_the_product_contains_sweeteners ($product_ref) {
sub count_sweeteners_and_non_nutritive_sweeteners ($product_ref) {

# Delete old fields, can be removed once all products have been reprocessed
delete $product_ref->{with_sweeteners};
delete $product_ref->{with_non_nutritive_sweeteners};
delete $product_ref->{without_non_nutritive_sweeteners};

if (
get_matching_regexp_property_from_tags(
'additives', $product_ref->{'additives_tags'},
'additives_classes:en', 'sweetener'
)
)
{
$product_ref->{with_sweeteners} = 1;
# Set the number of sweeteners only if the product has specified ingredients
if (not $product_ref->{ingredients_n}) {
delete $product_ref->{ingredients_sweeteners_n};
delete $product_ref->{ingredients_non_nutritive_sweeteners_n};
}
else {

if (
get_matching_regexp_property_from_tags(
'additives', $product_ref->{'additives_tags'},
'non_nutritive_sweetener:en', 'yes'
)
)
{
$product_ref->{with_non_nutritive_sweeteners} = 1;
$product_ref->{ingredients_sweeteners_n} = 0;
$product_ref->{ingredients_non_nutritive_sweeteners_n} = 0;

# Go through additives and check if the product contains sweeteners and non-nutritive sweeteners
if (defined $product_ref->{additives_tags}) {
foreach my $additive (@{$product_ref->{additives_tags}}) {
my $sweetener_property = get_inherited_property("additives", $additive, "sweetener:en") // "";
if ($sweetener_property eq "yes") {
$product_ref->{ingredients_sweeteners_n}++;
}
my $non_nutritive_sweetener_property
= get_inherited_property("additives", $additive, "non_nutritive_sweetener:en") // "";
if ($non_nutritive_sweetener_property eq "yes") {
$product_ref->{ingredients_non_nutritive_sweeteners_n}++;
}
}
}
}

return;
Expand Down
99 changes: 70 additions & 29 deletions lib/ProductOpener/KnowledgePanels.pm
Original file line number Diff line number Diff line change
Expand Up @@ -900,51 +900,92 @@ sub create_nutriscore_2023_panel ($product_ref, $target_lc, $target_cc, $options
# Nutri-Score panel
my $grade = deep_get($product_ref, "nutriscore", $version, "grade");

# Title
if ($grade eq "not-applicable") {
$panel_data_ref->{title} = lang_in_other_lc($target_lc, "attribute_nutriscore_not_applicable_title");
}
else {
$panel_data_ref->{title}
= sprintf(lang_in_other_lc($target_lc, "attribute_nutriscore_grade_title"), uc($grade));
if ($panel_data_ref->{nutriscore_unknown_reason_short}) {
$panel_data_ref->{subtitle} = $panel_data_ref->{nutriscore_unknown_reason_short};
}
else {
$panel_data_ref->{subtitle} = lang_in_other_lc($target_lc,
"attribute_nutriscore_" . $panel_data_ref->{nutriscore_grade} . "_description_short");
}
}

# Nutri-Score sub-panels for each positive or negative component
foreach my $type (qw/positive negative/) {
my $components_ref = deep_get($product_ref, "nutriscore", $version, "data", "components", $type) // [];
foreach my $component_ref (@$components_ref) {

my $component_panel_data_ref = {
"type" => $type,
"id" => $component_ref->{id},
"value" => $component_ref->{value},
"unit" => $component_ref->{unit},
"points" => $component_ref->{points},
"points_max" => $component_ref->{points_max},
};
create_panel_from_json_template(
"nutriscore_component_" . $component_ref->{id},
"api/knowledge-panels/health/nutriscore/nutriscore_component.tt.json",
$component_panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref
);
# Subtitle
if ($panel_data_ref->{nutriscore_unknown_reason_short}) {
$panel_data_ref->{subtitle} = $panel_data_ref->{nutriscore_unknown_reason_short};
}
else {
$panel_data_ref->{subtitle} = lang_in_other_lc($target_lc,
"attribute_nutriscore_" . $panel_data_ref->{nutriscore_grade} . "_description_short");
}

# Nutri-Score computed
if (($grade ne "not-applicable") and ($grade ne "unknown")) {

# Nutri-Score sub-panels for each positive or negative component
foreach my $type (qw/positive negative/) {
my $components_ref = deep_get($product_ref, "nutriscore", $version, "data", "components", $type) // [];
foreach my $component_ref (@$components_ref) {

my $value = $component_ref->{value};

# Specify if there is a space between the value and the unit
my $space_before_unit = '';

my $unit = $component_ref->{unit};

# If the value is not defined (e.g. fiber or fruits_vegetables_legumes), display "unknown" with no unit
if (not defined $value) {
$value = lc(lang_in_other_lc($target_lc, "unknown"));
$unit = '';
}
else {
# Localize the unit for the number of non-nutritive sweeteners
if ($component_ref->{id} eq "non_nutritive_sweeteners") {
$space_before_unit = ' ';
if ($value > 1) {
$unit = lang_in_other_lc($target_lc, "sweeteners");
}
else {
$unit = lang_in_other_lc($target_lc, "sweetener");
}
}
}

my $component_panel_data_ref = {
"type" => $type,
"id" => $component_ref->{id},
"value" => $value,
"unit" => $unit,
"space_before_unit" => $space_before_unit,
"points" => $component_ref->{points},
"points_max" => $component_ref->{points_max},
};
create_panel_from_json_template(
"nutriscore_component_" . $component_ref->{id},
"api/knowledge-panels/health/nutriscore/nutriscore_component.tt.json",
$component_panel_data_ref,
$product_ref,
$target_lc,
$target_cc,
$options_ref
);
}

create_panel_from_json_template("nutriscore_details",
"api/knowledge-panels/health/nutriscore/nutriscore_details.tt.json",
$panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref);
}
}

# Nutri-Score panel: score
# Nutri-Score panel: parent panel
create_panel_from_json_template("nutriscore_2023", "api/knowledge-panels/health/nutriscore/nutriscore_2023.tt.json",
$panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref);

# Nutri-Score description
create_panel_from_json_template("nutriscore_description",
"api/knowledge-panels/health/nutriscore/nutriscore_description.tt.json",
$panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref);
create_panel_from_json_template("nutriscore_details",
"api/knowledge-panels/health/nutriscore/nutriscore_details.tt.json",
$panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref);

return;
}

Expand Down
13 changes: 10 additions & 3 deletions lib/ProductOpener/Numbers.pm
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,21 @@ sub convert_string_to_number ($value) {
Round a number to a maximum number of decimal places.
Return undef if passed an undefined value.
=cut

sub round_to_max_decimal_places ($value, $max_decimal_places) {

# Round to the maximum number of decimal places
my $rounded_value = sprintf("%.${max_decimal_places}f", $value);
my $return_value = undef;

if (defined $value) {

# Round to the maximum number of decimal places
$return_value = sprintf("%.${max_decimal_places}f", $value) + 0;
}

return $rounded_value + 0;
return $return_value;
}

1;
Expand Down
Loading

0 comments on commit c00ebb6

Please sign in to comment.