From c5cda35c36d0e2aaf353d286cf9f40f27ff0dc86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Fri, 17 Jan 2025 09:00:33 +0100 Subject: [PATCH] feat: determine packaging components in contact with food (#11238) Some simple rules (to start) to determine which packaging components are in contact with food. See https://docs.google.com/document/d/1GFWM7TBQwBQ2UJ_WSgmk4VYkHl0W7wmdTPHprwJlsxY/edit?tab=t.0#heading=h.i1wspmuo16w4 for discussion. Exposed as a separate service. Currently computed and displayed only for moderators, for testing. --- lib/ProductOpener/APIProductServices.pm | 2 + lib/ProductOpener/Display.pm | 8 + lib/ProductOpener/PackagingFoodContact.pm | 271 ++++++++++++++++++ po/common/common.pot | 4 + po/common/en.po | 4 + po/common/fr.po | 4 + .../environment/packaging_components.tt.json | 3 + tests/integration/api_v3_product_services.t | 29 ++ .../get-fields-all-knowledge-panels.json | 6 +- ...attribute-groups-all-knowledge-panels.json | 6 +- ...knowledge_panels_excluded-health_card.json | 6 +- ...included-health_card-environment_card.json | 6 +- .../get-knowledge-panels-fr.json | 6 +- .../get-knowledge-panels.json | 6 +- .../get-fields-all-knowledge-panels.json | 6 +- ...attribute-groups-all-knowledge-panels.json | 6 +- ...knowledge_panels_excluded-health_card.json | 6 +- ...included-health_card-environment_card.json | 6 +- .../get-knowledge-panels-fr.json | 6 +- .../get-knowledge-panels.json | 6 +- ...ntact-of-packaging-components-service.json | 42 +++ ...ted-attribute-groups-knowledge-panels.json | 2 +- .../product_read/get-existing-product.html | 4 + .../canned_tomatoes.json | 37 +++ .../coffee_capsule.json | 38 +++ .../empty_packagings.json | 14 + .../hazelnut_paste_glass_jar.json | 58 ++++ .../packaging_food_contact/meat_tray.json | 43 +++ .../packaging_food_contact/wine_bottle.json | 48 ++++ tests/unit/packaging_food_contact.t | 92 ++++++ 30 files changed, 738 insertions(+), 37 deletions(-) create mode 100644 lib/ProductOpener/PackagingFoodContact.pm create mode 100644 tests/integration/expected_test_results/api_v3_product_services/determine-food-contact-of-packaging-components-service.json create mode 100644 tests/unit/expected_test_results/packaging_food_contact/canned_tomatoes.json create mode 100644 tests/unit/expected_test_results/packaging_food_contact/coffee_capsule.json create mode 100644 tests/unit/expected_test_results/packaging_food_contact/empty_packagings.json create mode 100644 tests/unit/expected_test_results/packaging_food_contact/hazelnut_paste_glass_jar.json create mode 100644 tests/unit/expected_test_results/packaging_food_contact/meat_tray.json create mode 100644 tests/unit/expected_test_results/packaging_food_contact/wine_bottle.json create mode 100644 tests/unit/packaging_food_contact.t diff --git a/lib/ProductOpener/APIProductServices.pm b/lib/ProductOpener/APIProductServices.pm index 5e237833b239b..0c9bf634b553d 100644 --- a/lib/ProductOpener/APIProductServices.pm +++ b/lib/ProductOpener/APIProductServices.pm @@ -116,6 +116,8 @@ my %service_functions = ( extend_ingredients => \&ProductOpener::Ingredients::extend_ingredients_service, estimate_ingredients_percent => \&ProductOpener::Ingredients::estimate_ingredients_percent_service, analyze_ingredients => \&ProductOpener::Ingredients::analyze_ingredients_service, + determine_food_contact_of_packaging_components => + \&ProductOpener::PackagingFoodContact::determine_food_contact_of_packaging_components_service, ); sub check_product_services_api_input ($request_ref) { diff --git a/lib/ProductOpener/Display.pm b/lib/ProductOpener/Display.pm index 146eabab55709..184d832e6a3b5 100644 --- a/lib/ProductOpener/Display.pm +++ b/lib/ProductOpener/Display.pm @@ -174,6 +174,7 @@ use ProductOpener::Cache qw/$max_memcached_object_size $memd generate_cache_key/ use ProductOpener::Permissions qw/has_permission/; use ProductOpener::ProductsFeatures qw(feature_enabled); use ProductOpener::RequestStats qw(:all); +use ProductOpener::PackagingFoodContact qw/determine_food_contact_of_packaging_components_service/; use Encode; use URI::Escape::XS; @@ -7978,6 +7979,13 @@ JS $product_ref->{environmental_score_data}); } + # 2025/01 - For moderators, determine which packaging components are in contact with food, so that we can display them + # This is for initial development of the feature, once finalized, we could compute and store this data in the product + + if ($User{moderator}) { + ProductOpener::PackagingFoodContact::determine_food_contact_of_packaging_components_service($product_ref); + } + # Activate knowledge panels for all users initialize_knowledge_panels_options($knowledge_panels_options_ref, $request_ref); diff --git a/lib/ProductOpener/PackagingFoodContact.pm b/lib/ProductOpener/PackagingFoodContact.pm new file mode 100644 index 0000000000000..155fdbbed4d16 --- /dev/null +++ b/lib/ProductOpener/PackagingFoodContact.pm @@ -0,0 +1,271 @@ +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2023 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +=encoding UTF-8 + +=head1 NAME + +ProductOpener::PackagingFoodContact + +=head1 SYNOPSIS + + +=head1 DESCRIPTION + + +=cut + +package ProductOpener::PackagingFoodContact; + +use ProductOpener::PerlStandards; +use Exporter qw< import >; + +use Log::Any qw($log); + +BEGIN { + use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS); + @EXPORT_OK = qw( + &determine_food_contact_of_packaging_components_service + &determine_food_contact_of_packaging_components + ); # symbols to export on request + %EXPORT_TAGS = (all => [@EXPORT_OK]); +} + +use vars @EXPORT_OK; + +use ProductOpener::Config qw/:all/; +use ProductOpener::API qw/add_warning/; +use ProductOpener::Packaging qw/%packaging_taxonomies/; +use ProductOpener::Tags qw/:all/; + +use Data::DeepAccess qw(deep_get deep_val); +use List::Util qw(first); + +=head1 FUNCTIONS + +=head2 determine_food_contact_of_packaging_components_service ( $product_ref, $updated_product_fields_ref, $errors_ref ) + +Determine if packaging components are in contact with the food. + +This function is a product service that can be run through ProductOpener::ApiProductServices + +=head3 Arguments + +=head4 $product_ref + +product object reference + +=head4 $updated_product_fields_ref + +reference to a hash of product fields that have been created or updated + +=head4 $errors_ref + +reference to an array of error messages + +=cut + +sub determine_food_contact_of_packaging_components_service ( + $product_ref, + $updated_product_fields_ref = {}, + $errors_ref = [] + ) +{ + + # Check if we have packaging data in the packagings structure + my $packagings_ref = $product_ref->{packagings}; + + if (not defined $packagings_ref) { + push @{$errors_ref}, + { + message => {id => "missing_field"}, + field => { + id => "packagings", + impact => {id => "skipped_service"}, + service => {id => "determine_food_contact_of_packaging_components"} + } + }; + return; + } + + # indicate that the service is updating the "packagings" structure + $updated_product_fields_ref->{packagings} = 1; + + determine_food_contact_of_packaging_components($packagings_ref); + + return; +} + +=head2 get_matching_and_non_matching_packaging_components ($packagings_ref, $conditions_ref) + +Find packaging components that match specific conditions (e.g material, shape, recycling). +Conditions are matched using the taxonomy, a parent in the condition matches more specific children. +(e.g. "en:plastic" matches "en:pet" and "en:hdpe") + +=head3 Parameters + +=head4 $packaging_ref packaging data + +=head4 $conditions_ref conditions + +Hash reference with conditions like material, shape, recycling as keys, and a value or an array of values as values. + +=head3 Return values + +Array with: +- a reference to an array of packaging components that match the conditions, +- a reference to an array of packaging components that do not match the conditions. + +=cut + +sub get_matching_and_non_matching_packaging_components ($packagings_ref, $conditions_ref) { + + my @matching_packagings = (); + my @non_matching_packagings = (); + + foreach my $packaging_ref (@$packagings_ref) { + + my $matched = 1; + + foreach my $property (keys %$conditions_ref) { + + if (defined $packaging_ref->{$property}) { + + # Check if the component value is a child of one of the condition values + my @values + = ref $conditions_ref->{$property} eq 'ARRAY' + ? @{$conditions_ref->{$property}} + : ($conditions_ref->{$property}); + my $matched_value = 0; + + foreach my $value (@values) { + if (is_a($packaging_taxonomies{$property}, $value, $packaging_ref->{$property})) { + $matched_value = 1; + last; + } + } + if (not $matched_value) { + $matched = 0; + last; + } + } + else { + $matched = 0; + last; + } + } + + if ($matched) { + push @matching_packagings, $packaging_ref; + } + else { + push @non_matching_packagings, $packaging_ref; + } + } + + return \@matching_packagings, \@non_matching_packagings; +} + +sub set_food_contact_property_of_packaging_components ($packagings_ref, $food_contact) { + + foreach my $packaging_ref (@$packagings_ref) { + + $packaging_ref->{food_contact} = $food_contact; + } + + return; +} + +=head2 determine_food_contact_of_packaging_components ($packagings_ref) + +Determine if packaging components are in contact with the food. + +=head3 Parameters + +=head4 $packagings_ref packaging data + +=cut + +sub determine_food_contact_of_packaging_components ($packagings_ref) { + + # Cans: only the can itself is in contact with the food + my ($cans_ref, $non_cans_ref) + = get_matching_and_non_matching_packaging_components($packagings_ref, {shape => "en:can"}); + if (@$cans_ref) { + set_food_contact_property_of_packaging_components($cans_ref, 1); + set_food_contact_property_of_packaging_components($non_cans_ref, 0); + return; + } + + # Bottles, pots, jars: in contact with the food + my ($bottles_ref, $non_bottles_ref) + = get_matching_and_non_matching_packaging_components($packagings_ref, + {shape => ["en:bottle", "en:pot", "en:jar"]}); + if (@$bottles_ref) { + set_food_contact_property_of_packaging_components($bottles_ref, 1); + set_food_contact_property_of_packaging_components($non_bottles_ref, 0); + + # If there is a seal, it is in contact with the food + my ($seals_ref, $non_seals_ref) + = get_matching_and_non_matching_packaging_components($non_bottles_ref, {shape => "en:seal"}); + if (@$seals_ref) { + set_food_contact_property_of_packaging_components($seals_ref, 1); + } + # Otherwise, if there is a lid or a cap, it is in contact wit the food + else { + my ($lids_ref, $non_lids_ref) + = get_matching_and_non_matching_packaging_components($non_bottles_ref, {shape => ["en:lid", "en:cap"]}); + if (@$lids_ref) { + set_food_contact_property_of_packaging_components($lids_ref, 1); + } + } + return; + } + + # Trays: in contact with food, and the film is in contact with the food + my ($trays_ref, $non_trays_ref) + = get_matching_and_non_matching_packaging_components($packagings_ref, {shape => "en:tray"}); + if (@$trays_ref) { + set_food_contact_property_of_packaging_components($trays_ref, 1); + set_food_contact_property_of_packaging_components($non_trays_ref, 0); + + my ($films_ref, $non_films_ref) + = get_matching_and_non_matching_packaging_components($non_trays_ref, {shape => "en:film"}); + if (@$films_ref) { + set_food_contact_property_of_packaging_components($films_ref, 1); + } + return; + } + + # Individual packaging components (dose, bag): in contact with the food + my ($individuals_ref, $non_individuals_ref) + = get_matching_and_non_matching_packaging_components($packagings_ref, + {shape => ["en:individual-dose", "en:individual-bag"]}); + if (@$individuals_ref) { + set_food_contact_property_of_packaging_components($individuals_ref, 1); + set_food_contact_property_of_packaging_components($non_individuals_ref, 0); + return; + } + + return; +} + +1; + diff --git a/po/common/common.pot b/po/common/common.pot index 65d9785c44c34..9a3e749a73f0d 100644 --- a/po/common/common.pot +++ b/po/common/common.pot @@ -7369,3 +7369,7 @@ msgstr "Limiting ultra-processed foods reduces the risk of noncommunicable chron msgctxt "recommendation_limit_ultra_processed_foods_text" msgid "Several studies have found that a lower consumption of ultra-processed foods is associated with a reduced risk of noncommunicable chronic diseases, such as obesity, hypertension and diabetes." msgstr "Several studies have found that a lower consumption of ultra-processed foods is associated with a reduced risk of noncommunicable chronic diseases, such as obesity, hypertension and diabetes." + +msgctxt "in_contact_with_food" +msgid "In contact with food" +msgstr "In contact with food" diff --git a/po/common/en.po b/po/common/en.po index cd921dd5e6a5e..ca82939b4500a 100644 --- a/po/common/en.po +++ b/po/common/en.po @@ -7358,3 +7358,7 @@ msgstr "Limiting ultra-processed foods reduces the risk of noncommunicable chron msgctxt "recommendation_limit_ultra_processed_foods_text" msgid "Several studies have found that a lower consumption of ultra-processed foods is associated with a reduced risk of noncommunicable chronic diseases, such as obesity, hypertension and diabetes." msgstr "Several studies have found that a lower consumption of ultra-processed foods is associated with a reduced risk of noncommunicable chronic diseases, such as obesity, hypertension and diabetes." + +msgctxt "in_contact_with_food" +msgid "In contact with food" +msgstr "In contact with food" diff --git a/po/common/fr.po b/po/common/fr.po index d0a8c67bb92f2..3a35aea0ac827 100644 --- a/po/common/fr.po +++ b/po/common/fr.po @@ -7317,3 +7317,7 @@ msgstr "Limiter les aliments ultra-transformés réduit le risque de maladies ch msgctxt "recommendation_limit_ultra_processed_foods_text" msgid "Several studies have found that a lower consumption of ultra-processed foods is associated with a reduced risk of noncommunicable chronic diseases, such as obesity, hypertension and diabetes." msgstr "Plusieurs études ont montré qu'une consommation plus faible d'aliments ultra-transformés est associée à un risque diminué de maladies chroniques non transmissibles, telles que l'obésité, l'hypertension et le diabète." + +msgctxt "in_contact_with_food" +msgid "In contact with food" +msgstr "En contact avec l'aliment" diff --git a/templates/api/knowledge-panels/environment/packaging_components.tt.json b/templates/api/knowledge-panels/environment/packaging_components.tt.json index f491cae82f403..bfe9315e9466d 100644 --- a/templates/api/knowledge-panels/environment/packaging_components.tt.json +++ b/templates/api/knowledge-panels/environment/packaging_components.tt.json @@ -53,6 +53,9 @@ [% IF packaging.material %] ([% display_taxonomy_tag_name('packaging_materials',packaging.material) %][% IF packaging.weight_specified %][% sep %]: [% packaging.weight_specified %] g[% ELSIF packaging.weight_measured %][% sep %]: [% packaging.weight_measured %] g[% END %]) [% END %] + [% IF packaging.food_contact %] + - [% edq(lang('in_contact_with_food')) %] + [% END %]
[% END %] [% END %] diff --git a/tests/integration/api_v3_product_services.t b/tests/integration/api_v3_product_services.t index eae5f9792a7e2..0b742195a1244 100644 --- a/tests/integration/api_v3_product_services.t +++ b/tests/integration/api_v3_product_services.t @@ -173,6 +173,35 @@ JSON "ingredients_text_fr": "sucre, huile de palme, eau, quelque chose d'inconnu" } } +JSON + }, + + # determine_food_contact_of_packaging_components + { + test_case => 'determine-food-contact-of-packaging-components-service', + method => 'POST', + path => '/api/v3/product_services', + body => <\n Box\n \n \n \n (Wood)\n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycle", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -2453,7 +2453,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Discard", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -2464,7 +2464,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-fields-attribute-groups-all-knowledge-panels.json b/tests/integration/expected_test_results/api_v2_product_read/get-fields-attribute-groups-all-knowledge-panels.json index be6ff7158b515..30c9009a30b64 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-fields-attribute-groups-all-knowledge-panels.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-fields-attribute-groups-all-knowledge-panels.json @@ -3096,7 +3096,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycle", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -3107,7 +3107,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Discard", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -3118,7 +3118,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card-knowledge_panels_excluded-health_card.json b/tests/integration/expected_test_results/api_v2_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card-knowledge_panels_excluded-health_card.json index a6802afbeb851..a89b615a3acf0 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card-knowledge_panels_excluded-health_card.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card-knowledge_panels_excluded-health_card.json @@ -604,7 +604,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycle", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -615,7 +615,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Discard", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -626,7 +626,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card.json b/tests/integration/expected_test_results/api_v2_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card.json index 504e0d66d323b..50f76992c4e91 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card.json @@ -1616,7 +1616,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycle", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -1627,7 +1627,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Discard", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -1638,7 +1638,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-knowledge-panels-fr.json b/tests/integration/expected_test_results/api_v2_product_read/get-knowledge-panels-fr.json index bc03dd1180998..eb8e2f063e107 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-knowledge-panels-fr.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-knowledge-panels-fr.json @@ -1668,7 +1668,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Boîte\n \n \n \n (Bois)\n \n
\n \n \n \n \n \n 3 x \n \n Couvercle\n \n \n \n (Acier)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Boîte\n \n \n \n (Bois)\n \n \n
\n \n \n \n \n \n 3 x \n \n Couvercle\n \n \n \n (Acier)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycler", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -1679,7 +1679,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastique)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastique)\n \n \n
\n \n \n ", "icon_alt" : "Jeter", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -1690,7 +1690,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bouteille\n 25cl \n \n \n (Verre)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bouteille\n 25cl \n \n \n (Verre)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Inconnu", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-knowledge-panels.json b/tests/integration/expected_test_results/api_v2_product_read/get-knowledge-panels.json index 6ab0f9c079b57..54e20a386b30f 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-knowledge-panels.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-knowledge-panels.json @@ -1661,7 +1661,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycle", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -1672,7 +1672,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Discard", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -1683,7 +1683,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-fields-all-knowledge-panels.json b/tests/integration/expected_test_results/api_v3_product_read/get-fields-all-knowledge-panels.json index 099f187a16bbc..50131e18877a8 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-fields-all-knowledge-panels.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-fields-all-knowledge-panels.json @@ -2066,7 +2066,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycle", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -2077,7 +2077,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Discard", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -2088,7 +2088,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-fields-attribute-groups-all-knowledge-panels.json b/tests/integration/expected_test_results/api_v3_product_read/get-fields-attribute-groups-all-knowledge-panels.json index 27a2317beb3a4..eb49a710d9ae2 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-fields-attribute-groups-all-knowledge-panels.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-fields-attribute-groups-all-knowledge-panels.json @@ -2712,7 +2712,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycle", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -2723,7 +2723,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Discard", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -2734,7 +2734,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card-knowledge_panels_excluded-health_card.json b/tests/integration/expected_test_results/api_v3_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card-knowledge_panels_excluded-health_card.json index efd3f3499f037..4f2d7f7c762f2 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card-knowledge_panels_excluded-health_card.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card-knowledge_panels_excluded-health_card.json @@ -605,7 +605,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycle", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -616,7 +616,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Discard", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -627,7 +627,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card.json b/tests/integration/expected_test_results/api_v3_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card.json index 67a2cf08faa18..cf4c1570bd0ec 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-fields-knowledge-panels-knowledge-panels_included-health_card-environment_card.json @@ -1247,7 +1247,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycle", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -1258,7 +1258,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Discard", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -1269,7 +1269,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-knowledge-panels-fr.json b/tests/integration/expected_test_results/api_v3_product_read/get-knowledge-panels-fr.json index 9458f10bc0477..ce5d7e688e8c1 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-knowledge-panels-fr.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-knowledge-panels-fr.json @@ -1299,7 +1299,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Boîte\n \n \n \n (Bois)\n \n
\n \n \n \n \n \n 3 x \n \n Couvercle\n \n \n \n (Acier)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Boîte\n \n \n \n (Bois)\n \n \n
\n \n \n \n \n \n 3 x \n \n Couvercle\n \n \n \n (Acier)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycler", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -1310,7 +1310,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastique)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastique)\n \n \n
\n \n \n ", "icon_alt" : "Jeter", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -1321,7 +1321,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bouteille\n 25cl \n \n \n (Verre)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bouteille\n 25cl \n \n \n (Verre)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Inconnu", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-knowledge-panels.json b/tests/integration/expected_test_results/api_v3_product_read/get-knowledge-panels.json index 3bde53628d86c..189f2c1181961 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-knowledge-panels.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-knowledge-panels.json @@ -1292,7 +1292,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "good", - "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n
\n \n \n \n \n ", + "html" : "\n \n \n 1 x \n \n Box\n \n \n \n (Wood)\n \n \n
\n \n \n \n \n \n 3 x \n \n Lid\n \n \n \n (Steel)\n \n \n
\n \n \n \n \n ", "icon_alt" : "Recycle", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/recycle-variant.svg", @@ -1303,7 +1303,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "bad", - "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n \n \n \n \n \n \n 1 x \n \n Film\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Discard", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/delete.svg", @@ -1314,7 +1314,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n
\n \n \n \n \n \n \n ", + "html" : "\n \n \n \n \n 6 x \n \n Bottle\n 25cl \n \n \n (Glass)\n \n \n
\n \n \n \n \n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/api_v3_product_services/determine-food-contact-of-packaging-components-service.json b/tests/integration/expected_test_results/api_v3_product_services/determine-food-contact-of-packaging-components-service.json new file mode 100644 index 0000000000000..cf58f22d011ba --- /dev/null +++ b/tests/integration/expected_test_results/api_v3_product_services/determine-food-contact-of-packaging-components-service.json @@ -0,0 +1,42 @@ +{ + "errors" : [], + "fields" : [ + "updated" + ], + "product" : { + "packagings" : [ + { + "food_contact" : 1, + "material" : { + "id" : "en:plastic" + }, + "shape" : { + "id" : "en:tray" + } + }, + { + "food_contact" : 1, + "material" : { + "id" : "en:plastic" + }, + "shape" : { + "id" : "en:film" + } + }, + { + "food_contact" : 0, + "material" : { + "id" : "en:paper" + }, + "shape" : { + "id" : "en:label" + } + } + ] + }, + "services" : [ + "determine_food_contact_of_packaging_components" + ], + "status" : "success", + "warnings" : [] +} diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-updated-attribute-groups-knowledge-panels.json b/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-updated-attribute-groups-knowledge-panels.json index 44d339af1c933..78752fc8332d8 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-updated-attribute-groups-knowledge-panels.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-updated-attribute-groups-knowledge-panels.json @@ -837,7 +837,7 @@ "element_type" : "text", "text_element" : { "evaluation" : "neutral", - "html" : "\n \n \n 1 x \n \n Bag\n \n \n \n (Plastic)\n \n
\n \n \n ", + "html" : "\n \n \n 1 x \n \n Bag\n \n \n \n (Plastic)\n \n \n
\n \n \n ", "icon_alt" : "Unknown", "icon_color_from_evaluation" : true, "icon_url" : "http://static.openfoodfacts.localhost/images/icons/dist/help.svg", diff --git a/tests/integration/expected_test_results/product_read/get-existing-product.html b/tests/integration/expected_test_results/product_read/get-existing-product.html index 14e84f003920a..ebda9c3dcc1dd 100644 --- a/tests/integration/expected_test_results/product_read/get-existing-product.html +++ b/tests/integration/expected_test_results/product_read/get-existing-product.html @@ -4023,6 +4023,7 @@

Packaging

(Wood) +
@@ -4037,6 +4038,7 @@

Packaging

(Steel) +
@@ -4084,6 +4086,7 @@

Packaging

(Plastic) +
@@ -4125,6 +4128,7 @@

Packaging

(Glass) +
diff --git a/tests/unit/expected_test_results/packaging_food_contact/canned_tomatoes.json b/tests/unit/expected_test_results/packaging_food_contact/canned_tomatoes.json new file mode 100644 index 0000000000000..931eaee31ec74 --- /dev/null +++ b/tests/unit/expected_test_results/packaging_food_contact/canned_tomatoes.json @@ -0,0 +1,37 @@ +{ + "lc" : "en", + "misc_tags" : [ + "en:packagings-number-of-components-2", + "en:packagings-not-complete", + "en:packagings-not-empty-but-not-complete", + "en:packagings-not-empty" + ], + "packaging_materials_tags" : [ + "en:metal", + "en:paper" + ], + "packaging_recycling_tags" : [], + "packaging_shapes_tags" : [ + "en:can", + "en:label" + ], + "packaging_text" : "can, paper label", + "packagings" : [ + { + "food_contact" : 1, + "material" : "en:metal", + "shape" : "en:can" + }, + { + "food_contact" : 0, + "material" : "en:paper", + "shape" : "en:label" + } + ], + "packagings_materials" : { + "all" : {}, + "en:metal" : {}, + "en:paper-or-cardboard" : {} + }, + "packagings_n" : 2 +} diff --git a/tests/unit/expected_test_results/packaging_food_contact/coffee_capsule.json b/tests/unit/expected_test_results/packaging_food_contact/coffee_capsule.json new file mode 100644 index 0000000000000..45c7f89a3b4d1 --- /dev/null +++ b/tests/unit/expected_test_results/packaging_food_contact/coffee_capsule.json @@ -0,0 +1,38 @@ +{ + "lc" : "en", + "misc_tags" : [ + "en:packagings-number-of-components-3", + "en:packagings-not-complete", + "en:packagings-not-empty-but-not-complete", + "en:packagings-not-empty" + ], + "packaging_materials_tags" : [ + "en:plastic" + ], + "packaging_recycling_tags" : [], + "packaging_shapes_tags" : [ + "en:box", + "en:capsule", + "en:film" + ], + "packaging_text" : "carboard box, plastic capsule, plastic film", + "packagings" : [ + { + "shape" : "en:box" + }, + { + "material" : "en:plastic", + "shape" : "en:capsule" + }, + { + "material" : "en:plastic", + "shape" : "en:film" + } + ], + "packagings_materials" : { + "all" : {}, + "en:plastic" : {}, + "en:unknown" : {} + }, + "packagings_n" : 3 +} diff --git a/tests/unit/expected_test_results/packaging_food_contact/empty_packagings.json b/tests/unit/expected_test_results/packaging_food_contact/empty_packagings.json new file mode 100644 index 0000000000000..b8143031e6d5d --- /dev/null +++ b/tests/unit/expected_test_results/packaging_food_contact/empty_packagings.json @@ -0,0 +1,14 @@ +{ + "lc" : "en", + "misc_tags" : [ + "en:packagings-number-of-components-0", + "en:packagings-not-complete", + "en:packagings-empty" + ], + "packaging_materials_tags" : [], + "packaging_recycling_tags" : [], + "packaging_shapes_tags" : [], + "packaging_text" : "", + "packagings" : [], + "packagings_materials" : {} +} diff --git a/tests/unit/expected_test_results/packaging_food_contact/hazelnut_paste_glass_jar.json b/tests/unit/expected_test_results/packaging_food_contact/hazelnut_paste_glass_jar.json new file mode 100644 index 0000000000000..c53ff616ae277 --- /dev/null +++ b/tests/unit/expected_test_results/packaging_food_contact/hazelnut_paste_glass_jar.json @@ -0,0 +1,58 @@ +{ + "lc" : "en", + "misc_tags" : [ + "en:packagings-number-of-components-5", + "en:packagings-not-complete", + "en:packagings-not-empty-but-not-complete", + "en:packagings-not-empty" + ], + "packaging_materials_tags" : [ + "en:cardboard", + "en:glass", + "en:paper", + "en:plastic" + ], + "packaging_recycling_tags" : [], + "packaging_shapes_tags" : [ + "en:box", + "en:jar", + "en:label", + "en:lid", + "en:seal" + ], + "packaging_text" : "glass jar, plastic lid, paper label, paper seal, cardboard box", + "packagings" : [ + { + "food_contact" : 1, + "material" : "en:glass", + "shape" : "en:jar" + }, + { + "food_contact" : 0, + "material" : "en:plastic", + "shape" : "en:lid" + }, + { + "food_contact" : 0, + "material" : "en:paper", + "shape" : "en:label" + }, + { + "food_contact" : 1, + "material" : "en:paper", + "shape" : "en:seal" + }, + { + "food_contact" : 0, + "material" : "en:cardboard", + "shape" : "en:box" + } + ], + "packagings_materials" : { + "all" : {}, + "en:glass" : {}, + "en:paper-or-cardboard" : {}, + "en:plastic" : {} + }, + "packagings_n" : 5 +} diff --git a/tests/unit/expected_test_results/packaging_food_contact/meat_tray.json b/tests/unit/expected_test_results/packaging_food_contact/meat_tray.json new file mode 100644 index 0000000000000..24e9c0608c8e2 --- /dev/null +++ b/tests/unit/expected_test_results/packaging_food_contact/meat_tray.json @@ -0,0 +1,43 @@ +{ + "lc" : "en", + "misc_tags" : [ + "en:packagings-number-of-components-3", + "en:packagings-not-complete", + "en:packagings-not-empty-but-not-complete", + "en:packagings-not-empty" + ], + "packaging_materials_tags" : [ + "en:paper", + "en:plastic" + ], + "packaging_recycling_tags" : [], + "packaging_shapes_tags" : [ + "en:film", + "en:label", + "en:tray" + ], + "packaging_text" : "plastic tray, plastic film, paper label", + "packagings" : [ + { + "food_contact" : 1, + "material" : "en:plastic", + "shape" : "en:tray" + }, + { + "food_contact" : 1, + "material" : "en:plastic", + "shape" : "en:film" + }, + { + "food_contact" : 0, + "material" : "en:paper", + "shape" : "en:label" + } + ], + "packagings_materials" : { + "all" : {}, + "en:paper-or-cardboard" : {}, + "en:plastic" : {} + }, + "packagings_n" : 3 +} diff --git a/tests/unit/expected_test_results/packaging_food_contact/wine_bottle.json b/tests/unit/expected_test_results/packaging_food_contact/wine_bottle.json new file mode 100644 index 0000000000000..9366aa431eb99 --- /dev/null +++ b/tests/unit/expected_test_results/packaging_food_contact/wine_bottle.json @@ -0,0 +1,48 @@ +{ + "lc" : "en", + "misc_tags" : [ + "en:packagings-number-of-components-3", + "en:packagings-not-complete", + "en:packagings-not-empty-but-not-complete", + "en:packagings-not-empty" + ], + "packaging_materials_tags" : [ + "en:cork", + "en:glass", + "en:paper" + ], + "packaging_recycling_tags" : [ + "en:recycle" + ], + "packaging_shapes_tags" : [ + "en:bottle", + "en:label", + "en:unknown" + ], + "packaging_text" : "glass bottle, cork, paper label", + "packagings" : [ + { + "food_contact" : 1, + "material" : "en:glass", + "recycling" : "en:recycle", + "shape" : "en:bottle" + }, + { + "food_contact" : 0, + "material" : "en:cork", + "shape" : "en:unknown" + }, + { + "food_contact" : 0, + "material" : "en:paper", + "shape" : "en:label" + } + ], + "packagings_materials" : { + "all" : {}, + "en:glass" : {}, + "en:paper-or-cardboard" : {}, + "en:unknown" : {} + }, + "packagings_n" : 3 +} diff --git a/tests/unit/packaging_food_contact.t b/tests/unit/packaging_food_contact.t new file mode 100644 index 0000000000000..074364373d3b3 --- /dev/null +++ b/tests/unit/packaging_food_contact.t @@ -0,0 +1,92 @@ +#!/usr/bin/perl -w + +use Modern::Perl '2017'; +use utf8; + +use Test2::V0; +use Data::Dumper; +$Data::Dumper::Terse = 1; +use Log::Any::Adapter 'TAP'; + +use JSON; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Packaging qw/init_packaging_taxonomies_regexps analyze_and_combine_packaging_data/; +use ProductOpener::PackagingFoodContact qw/determine_food_contact_of_packaging_components/; +use ProductOpener::Test qw/compare_to_expected_results init_expected_results/; +use ProductOpener::API qw/get_initialized_response/; + +my ($test_id, $test_dir, $expected_result_dir, $update_expected_results) = (init_expected_results(__FILE__)); + +init_packaging_taxonomies_regexps(); + +# Tests for determine_food_contact_of_packaging_components() + +my @tests = ( + + [ + 'empty_packagings', + { + lc => "en", + packaging_text => "", + } + ], + [ + 'hazelnut_paste_glass_jar', + { + lc => "en", + packaging_text => "glass jar, plastic lid, paper label, paper seal, cardboard box", + } + ], + [ + 'canned_tomatoes', + { + lc => "en", + packaging_text => "can, paper label", + } + ], + [ + 'coffee_capsule', + { + lc => "en", + packaging_text => "carboard box, plastic capsule, plastic film", + } + ], + [ + 'meat_tray', + { + lc => "en", + packaging_text => "plastic tray, plastic film, paper label", + } + ], + [ + 'wine_bottle', + { + lc => "en", + packaging_text => "glass bottle, cork, paper label", + } + ], + +); + +my $json = JSON->new->allow_nonref->canonical; + +foreach my $test_ref (@tests) { + + my $testid = $test_ref->[0]; + my $product_ref = $test_ref->[1]; + + # Run the test + + # Response structure to keep track of warnings and errors + # Note: currently some warnings and errors are added, + # but we do not yet do anything with them + my $response_ref = get_initialized_response(); + + analyze_and_combine_packaging_data($product_ref, $response_ref); + determine_food_contact_of_packaging_components($product_ref->{packagings}); + + compare_to_expected_results($product_ref, "$expected_result_dir/$testid.json", $update_expected_results); +} + +done_testing();