diff --git a/cgi/product_multilingual.pl b/cgi/product_multilingual.pl index 517702ccd609c..3651b8d774190 100755 --- a/cgi/product_multilingual.pl +++ b/cgi/product_multilingual.pl @@ -35,6 +35,7 @@ use ProductOpener::Mail qw/:all/; use ProductOpener::Products qw/:all/; use ProductOpener::Food qw/:all/; +use ProductOpener::Units qw/:all/; use ProductOpener::Ingredients qw/:all/; use ProductOpener::Images qw/:all/; use ProductOpener::URL qw/:all/; diff --git a/docs/reference/schemas/packagings/packaging_component-write.yaml b/docs/reference/schemas/packagings/packaging_component-write.yaml index 3586471f8102d..86196b522f116 100644 --- a/docs/reference/schemas/packagings/packaging_component-write.yaml +++ b/docs/reference/schemas/packagings/packaging_component-write.yaml @@ -10,6 +10,8 @@ description: |- For input, clients can either pass the id of a corresponding taxonomy entry (e.g. "en:pizza-box"), or a free text value prefixed with the language code of the text (e.g. "en:Pizza box", "fr:boite à pizza"). If the language code prefix is missing, the value of the "lc" field of the query will be used. The resulting structure will contain the id of the canonical entry in the taxonomy if it good be matched, or the free text value prefixed with the language code otherwise. + + For weights, the API is expecting a number with the number of grams. If a string is passed instead of a number, we will attempt to convert it to grams. The string may contain units (e.g. "6.9 g"), and use . or , as the decimal separator. Conversion may not work for all inputs. If a string was converted to a number, the API response will include a warning and specify the converted value. examples: - number_of_units: 6 shape: @@ -34,11 +36,15 @@ properties: type: string description: Quantity (weight or volume) of food product contained in the packaging component. (e.g. 75cl for a wine bottle) weight_specified: - type: number - description: 'Weight (as specified by the manufacturer) of one unit of the empty packaging component (in grams). (e.g. for a 6 pack of 1.5l water bottles, it might be 30, the weight in grams of 1 empty water bottle without its cap which is a different packaging component).' + type: + - number + - string + description: 'Weight (as specified by the manufacturer) of one unit of the empty packaging component (in grams). (e.g. for a 6 pack of 1.5l water bottles, it might be 30, the weight in grams of 1 empty water bottle without its cap which is a different packaging component). If passed a string - possibly with an unit - it will be converted to a number.' weight_measured: - type: number - description: 'Weight (as measured by one or more users) of one unit of the empty packaging component (in grams). (e.g. for a 6 pack of 1.5l water bottles, it might be 30, the weight in grams of 1 empty water bottle without its cap which is a different packaging component).' + type: + - number + - string + description: 'Weight (as measured by one or more users) of one unit of the empty packaging component (in grams). (e.g. for a 6 pack of 1.5l water bottles, it might be 30, the weight in grams of 1 empty water bottle without its cap which is a different packaging component). If passed a string - possibly with an unit - it will be converted to a number.' brands: type: string description: 'A comma separated list of brands / product names for the packaging component (e.g. "Tetra Pak", Tetra Brik"' diff --git a/lib/ProductOpener/Display.pm b/lib/ProductOpener/Display.pm index 595c175c14729..8198d07cffe9f 100644 --- a/lib/ProductOpener/Display.pm +++ b/lib/ProductOpener/Display.pm @@ -168,6 +168,7 @@ use ProductOpener::Recipes qw(:all); use ProductOpener::PackagerCodes qw(:all); use ProductOpener::Export qw(:all); use ProductOpener::API qw(:all); +use ProductOpener::Units qw/:all/; use Cache::Memcached::Fast; use Encode; diff --git a/lib/ProductOpener/Food.pm b/lib/ProductOpener/Food.pm index 60c2a6868bbb6..ef80993f60f93 100644 --- a/lib/ProductOpener/Food.pm +++ b/lib/ProductOpener/Food.pm @@ -55,17 +55,6 @@ BEGIN { &normalize_nutriment_value_and_modifier &assign_nid_modifier_value_and_unit - &unit_to_g - &g_to_unit - - &unit_to_kcal - - &unit_to_mmoll - &mmoll_to_unit - - &normalize_serving_size - &normalize_quantity - &canonicalize_nutriment &fix_salt_equivalent @@ -113,6 +102,7 @@ use ProductOpener::Numbers qw/:all/; use ProductOpener::Ingredients qw/:all/; use ProductOpener::Text qw/:all/; use ProductOpener::FoodGroups qw/:all/; +use ProductOpener::Units qw/:all/; use ProductOpener::Products qw(&remove_fields); use ProductOpener::Display qw/single_param/; @@ -323,183 +313,6 @@ sub assign_nid_modifier_value_and_unit ($product_ref, $nid, $modifier, $value, $ return; } -=head2 unit_to_kcal($value, $unit) - -Converts into kcal. - -=cut - -sub unit_to_kcal ($value, $unit) { - $unit = lc($unit); - - (not defined $value) and return $value; - - ($unit eq 'kj') and return int($value / 4.184 + 0.5); - - # return value without modification if it's already in kcal - return $value + 0; # + 0 to make sure the value is treated as number -} - -=head2 unit_to_g($value, $unit) - -Converts into grams. Eg.: -unit_to_g(2,kg) => returns 2000 -unit_to_g(520,mg) => returns 0.52 - -=cut - -# This is a key:value pairs -# The keys are the unit names and the values are the multipliers we can use to convert to a standard unit. -# We can divide by these values to do the reverse ie, Convert from standard to non standard -my %unit_conversion_map = ( - # kg = 公斤 - gōngjīn = кг - "\N{U+516C}\N{U+65A4}" => 1000, - # l = 公升 - gōngshēng = л = liter - "\N{U+516C}\N{U+5347}" => 1000, - 'kg' => 1000, - 'кг' => 1000, - 'l' => 1000, - 'л' => 1000, - # mg = 毫克 - háokè = мг - "\N{U+6BEB}\N{U+514B}" => 0.001, - 'mg' => 0.001, - 'мг' => 0.001, - 'mcg' => 0.000001, - 'µg' => 0.000001, - 'oz' => 28.349523125, - 'fl oz' => 30, - 'dl' => 100, - 'дл' => 100, - 'cl' => 10, - 'кл' => 10, - # 斤 - jīn = 500 Grams - "\N{U+65A4}" => 500, - # Standard units: No conversion units - # Value without modification if it's already grams or 克 (kè) or 公克 (gōngkè) or г - 'g' => 1, - '' => 1, - ' ' => 1, - 'kj' => 1, - '克' => 1, - '公克' => 1, - 'г' => 1, - 'мл' => 1, - 'ml' => 1, - 'mmol/l' => 1, - "\N{U+6BEB}\N{U+5347}" => 1, - '% vol' => 1, - 'ph' => 1, - '%' => 1, - '% dv' => 1, - '% vol (alcohol)' => 1, - 'iu' => 1, - # Division factors for "non standard unit" to mmoll conversions - 'mol/l' => 0.001, - 'mval/l' => 2, - 'ppm' => 100, - "\N{U+00B0}rh" => 40.080, - "\N{U+00B0}fh" => 10.00, - "\N{U+00B0}e" => 7.02, - "\N{U+00B0}dh" => 5.6, - 'gpg' => 5.847 -); - -sub unit_to_g ($value, $unit) { - $unit = lc($unit); - - if ($unit =~ /^(fl|fluid)(\.| )*(oz|once|ounce)/) { - $unit = "fl oz"; - } - - (not defined $value) and return $value; - - $value =~ s/,/\./; - $value =~ s/^(<|environ|max|maximum|min|minimum)( )?//; - $value eq '' and return $value; - - if (exists($unit_conversion_map{$unit})) { - return $value * $unit_conversion_map{$unit}; - } - - (($unit eq 'kcal') or ($unit eq 'ккал')) and return int($value * 4.184 + 0.5); - - # We return with + 0 to make sure the value is treated as number (needed when outputting json and to store in mongodb as a number) - # lets not assume that we have a valid unit - return; -} - -=head2 g_to_unit($value, $unit) - -Converts grams into . Eg.: -g_to_unit(2000,kg) => returns 2 -g_to_unit(0.52,mg) => returns 520 - -=cut - -sub g_to_unit ($value, $unit) { - $unit = lc($unit); - - if ((not defined $value) or ($value eq '')) { - return ""; - } - - $unit eq 'fl. oz' and $unit = 'fl oz'; - $unit eq 'fl.oz' and $unit = 'fl oz'; - - $value =~ s/,/\./; - $value =~ s/^(<|environ|max|maximum|min|minimum)( )?//; - - $value eq '' and return $value; - - # Divide with the values in the hash - if (exists($unit_conversion_map{$unit})) { - return $value / $unit_conversion_map{$unit}; - } - - (($unit eq 'kcal') or ($unit eq 'ккал')) and return int($value / 4.184 + 0.5); - - # return value without modification if unit is already grams or 克 (kè) or 公克 (gōngkè) or г - return $value + 0; - # + 0 to make sure the value is treated as number - # (needed when outputting json and to store in mongodb as a number) -} - -sub unit_to_mmoll ($value, $unit) { - $unit = lc($unit); - - if ((not defined $value) or ($value eq '')) { - return ''; - } - - $value =~ s/,/\./; - $value =~ s/^(<|environ|max|maximum|min|minimum)( )?//; - - # Divide with the values in the hash - if (exists($unit_conversion_map{$unit})) { - return $value / $unit_conversion_map{$unit}; - } - - return $value + 0; -} - -sub mmoll_to_unit ($value, $unit) { - $unit = lc($unit); - - if ((not defined $value) or ($value eq '')) { - return ''; - } - - $value =~ s/,/\./; - $value =~ s/^(<|environ|max|maximum|min|minimum)( )?//; - - # Multiply with the values in the hash - if (exists($unit_conversion_map{$unit})) { - return $value * $unit_conversion_map{$unit}; - } - - return $value + 0; -} - # For fat, saturated fat, sugars, salt: http://www.diw.de/sixcms/media.php/73/diw_wr_2010-19.pdf @nutrient_levels = (['fat', 3, 20], ['saturated-fat', 1.5, 5], ['sugars', 5, 12.5], ['salt', 0.3, 1.5],); @@ -926,103 +739,6 @@ sub canonicalize_nutriment ($target_lc, $nutrient) { return $nid; } -my $international_units = qr/kg|g|mg|µg|oz|l|dl|cl|ml|(fl(\.?)(\s)?oz)/i; -# Chinese units: a good start is https://en.wikipedia.org/wiki/Chinese_units_of_measurement#Mass -my $chinese_units = qr/ - (?:[\N{U+6BEB}\N{U+516C}]?\N{U+514B})| # 毫克 or 公克 or 克 or (克 kè is the Chinese word for gram) - # (公克 gōngkè is for "metric gram") - (?:\N{U+516C}?\N{U+65A4})| # 公斤 or 斤 or (公斤 gōngjīn is a "metric kg") - (?:[\N{U+6BEB}\N{U+516C}]?\N{U+5347})| # 毫升 or 公升 or 升 (升 is liter) - \N{U+5428} # 吨 (ton?) - /ix; -my $russian_units = qr/г|мг|кг|л|дл|кл|мл/i; -my $units = qr/$international_units|$chinese_units|$russian_units/i; - -=head2 normalize_quantity($quantity) - -Return the size in g or ml for the whole product. Eg.: -normalize_quantity(1 barquette de 40g) returns 40 -normalize_quantity(20 tranches 500g) returns 500 -normalize_quantity(6x90g) returns 540 -normalize_quantity(2kg) returns 2000 - -=cut - -sub normalize_quantity ($quantity) { - - my $q = undef; - my $u = undef; - - # 12 pots x125 g - # 6 bouteilles de 33 cl - # 6 bricks de 1 l - # 10 unités, 170 g - # 4 bouteilles en verre de 20cl - if ($quantity =~ /(\d+)(\s(\p{Letter}| )+)?(\s)?( de | of |x|\*)(\s)?((\d+)(\.|,)?(\d+)?)(\s)?($units)/i) { - my $m = $1; - $q = lc($7); - $u = $12; - $q = convert_string_to_number($q); - $q = unit_to_g($q * $m, $u); - } - elsif ($quantity =~ /((\d+)(\.|,)?(\d+)?)(\s)?($units)/i) { - $q = lc($1); - $u = $6; - $q = convert_string_to_number($q); - $q = unit_to_g($q, $u); - } - - return $q; -} - -=head2 normalize_serving_size($serving) - -Returns the size in g or ml for the serving. Eg.: -normalize_serving_size(1 barquette de 40g)->returns 40 -normalize_serving_size(2.5kg)->returns 2500 - -=cut - -sub normalize_serving_size ($serving) { - - # Regex captures any ( )? group, but leaves allowances for a preceding - # token to allow for patterns like "One bag (32g)", "1 small bottle (180ml)" etc - if ($serving =~ /^(.*[ \(])?(?(\d+)(\.|,)?(\d+)?)( )?(?\w+)\b/i) { - my $q = $+{quantity}; - my $u = normalize_unit($+{unit}); - $q = convert_string_to_number($q); - - return unit_to_g($q, $u); - } - - #$log->trace("serving size normalized", { serving => $serving, q => $q, u => $u }) if $log->is_trace(); - return 0; -} - -# @todo we should have equivalences for more units if we are supporting this -my @unit_equivalences_list = ( - ['g', qr/gram(s)?/], - ['g', qr/gramme(s)?/], # French -); - -=head2 normalize_unit ( $unit ) - -Normalizes units to their standard symbolic forms so that we can support unit names and alternative -representations in our normalization logic. - -=cut - -sub normalize_unit ($originalUnit) { - - foreach my $unit_name (@unit_equivalences_list) { - if ($originalUnit =~ $unit_name->[1]) { - return $unit_name->[0]; - } - } - - return $originalUnit; -} - =head2 is_beverage_for_nutrition_score( $product_ref ) Determines if a product should be considered as a beverage for Nutri-Score computations, diff --git a/lib/ProductOpener/ImportConvert.pm b/lib/ProductOpener/ImportConvert.pm index 1358ea6e93070..3782eba9e9471 100644 --- a/lib/ProductOpener/ImportConvert.pm +++ b/lib/ProductOpener/ImportConvert.pm @@ -102,6 +102,7 @@ use ProductOpener::Tags qw/:all/; use ProductOpener::Products qw/:all/; use ProductOpener::Ingredients qw/:all/; use ProductOpener::Food qw/:all/; +use ProductOpener::Units qw/:all/; use CGI qw/:cgi :form escapeHTML/; use URI::Escape::XS; diff --git a/lib/ProductOpener/Packaging.pm b/lib/ProductOpener/Packaging.pm index 10053059e7b40..ae7dacb353c41 100644 --- a/lib/ProductOpener/Packaging.pm +++ b/lib/ProductOpener/Packaging.pm @@ -63,6 +63,7 @@ use ProductOpener::Tags qw/:all/; use ProductOpener::Store qw/:all/; use ProductOpener::API qw/:all/; use ProductOpener::Numbers qw/:all/; +use ProductOpener::Units qw/:all/; =head1 FUNCTIONS @@ -422,6 +423,23 @@ sub get_checked_and_taxonomized_packaging_component_data ($tags_lc, $input_packa $packaging_ref->{$weight} = convert_string_to_number($input_packaging_ref->{$weight}); $has_data = 1; } + elsif (defined normalize_quantity($input_packaging_ref->{$weight})) { + $packaging_ref->{$weight} + = convert_string_to_number(normalize_quantity($input_packaging_ref->{$weight})); + $has_data = 1; + add_warning( + $response_ref, + { + message => {id => "invalid_type_must_be_number"}, + field => { + id => $weight, + value => $input_packaging_ref->{$weight}, + valued_converted => $packaging_ref->{$weight} + }, + impact => {id => "value_converted"}, + } + ); + } else { add_warning( $response_ref, diff --git a/lib/ProductOpener/Products.pm b/lib/ProductOpener/Products.pm index 248b5b06f7e60..6da9d686c99cd 100644 --- a/lib/ProductOpener/Products.pm +++ b/lib/ProductOpener/Products.pm @@ -1751,7 +1751,7 @@ we can rename it to a generic user account like openfoodfacts-contributors. =cut # Fields that contain usernames -my @users_fields = qw(editors_tags photographers_tags informers_tags correctors_tags checkers_tags weighters_tags); +my @users_fields = qw(editors_tags photographers_tags informers_tags correctors_tags checkers_tags weighers_tags); sub replace_user_id_in_product ($product_id, $user_id, $new_user_id) { @@ -1919,7 +1919,7 @@ Record that a user has made a change of a specific type to the product. For each type, there is a "list" array, and a "seen" hash -=head4 $user_type e.g. editors, photographers, weighters +=head4 $user_type e.g. editors, photographers, weighers =head4 $user_id @@ -2119,11 +2119,16 @@ sub compute_product_history_and_completeness ($product_data_root, $current_produ my $packagings_data_signature = ""; my $packagings_weights_signature = ""; foreach my $packagings_ref (@{$product_ref->{packagings}}) { - foreach my $property (qw(shape material recycling number_of_units quantity_per_unit)) { - $packagings_data_signature .= $property . ":" . ($packagings_ref->{$property} || '') . ","; + # We make a copy of numeric values so that Perl does not turn the value to a string when we concatenate it in the signature + my $number_of_units = $packagings_ref->{number_of_units}; + my $weight_measured = $packagings_ref->{weight_measured}; + + $packagings_data_signature .= "number_of_units:" . $number_of_units . ','; + foreach my $property (qw(shape material recycling quantity_per_unit)) { + $packagings_data_signature .= $property . ":" . ($packagings_ref->{$property} || '') . ','; } $packagings_data_signature .= "\n"; - $packagings_weights_signature .= ($packagings_ref->{weight_measured} || '') . "\n"; + $packagings_weights_signature .= ($weight_measured || '') . "\n"; } # If the signature is empty or contains only line feeds, we don't have data if ($packagings_data_signature !~ /^\s*$/) { @@ -2296,7 +2301,7 @@ sub compute_product_history_and_completeness ($product_data_root, $current_produ and ($id eq 'weights_measured') and (($diff eq 'add') or ($diff eq 'change'))) { - record_user_edit_type($users_ref, "weighters", $userid); + record_user_edit_type($users_ref, "weighers", $userid); } # Uploaded photos + all fields @@ -2343,7 +2348,7 @@ sub compute_product_history_and_completeness ($product_data_root, $current_produ $current_product_ref->{informers_tags} = $users_ref->{informers}{list}; $current_product_ref->{correctors_tags} = $users_ref->{correctors}{list}; $current_product_ref->{checkers_tags} = $users_ref->{checkers}{list}; - $current_product_ref->{weighters_tags} = $users_ref->{weighters}{list}; + $current_product_ref->{weighers_tags} = $users_ref->{weighers}{list}; compute_completeness_and_missing_tags($current_product_ref, \%current, \%last); diff --git a/lib/ProductOpener/Tags.pm b/lib/ProductOpener/Tags.pm index bf9d3c527c22a..16145e5c9998c 100644 --- a/lib/ProductOpener/Tags.pm +++ b/lib/ProductOpener/Tags.pm @@ -220,7 +220,7 @@ To this initial list, taxonomized fields will be added by retrieve_tags_taxonomy informers => 1, checkers => 1, correctors => 1, - weighters => 1, + weighers => 1, ); # Fields that are tags related to users @@ -230,7 +230,7 @@ To this initial list, taxonomized fields will be added by retrieve_tags_taxonomy informers => 1, checkers => 1, correctors => 1, - weighters => 1, + weighers => 1, ); # Fields that have an associated taxonomy diff --git a/lib/ProductOpener/Units.pm b/lib/ProductOpener/Units.pm new file mode 100644 index 0000000000000..ac829e21e01e1 --- /dev/null +++ b/lib/ProductOpener/Units.pm @@ -0,0 +1,338 @@ +# 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 . + +=head1 NAME + +ProductOpener::Units - functions to convert units + +=head1 DESCRIPTION + +=cut + +package ProductOpener::Units; + +use ProductOpener::PerlStandards; +use Exporter qw< import >; + +use Log::Any qw($log); + +BEGIN { + use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS); + @EXPORT_OK = qw( + + &unit_to_g + &g_to_unit + + &unit_to_kcal + + &unit_to_mmoll + &mmoll_to_unit + + &normalize_serving_size + &normalize_quantity + + ); # symbols to export on request + %EXPORT_TAGS = (all => [@EXPORT_OK]); +} + +use vars @EXPORT_OK; + +use ProductOpener::Numbers qw/:all/; + +=head1 FUNCTIONS + +=head2 unit_to_kcal($value, $unit) + +Converts into kcal. + +=cut + +sub unit_to_kcal ($value, $unit) { + $unit = lc($unit); + + (not defined $value) and return $value; + + ($unit eq 'kj') and return int($value / 4.184 + 0.5); + + # return value without modification if it's already in kcal + return $value + 0; # + 0 to make sure the value is treated as number +} + +=head2 unit_to_g($value, $unit) + +Converts into grams. Eg.: +unit_to_g(2,kg) => returns 2000 +unit_to_g(520,mg) => returns 0.52 + +=cut + +# This is a key:value pairs +# The keys are the unit names and the values are the multipliers we can use to convert to a standard unit. +# We can divide by these values to do the reverse ie, Convert from standard to non standard +my %unit_conversion_map = ( + # kg = 公斤 - gōngjīn = кг + "\N{U+516C}\N{U+65A4}" => 1000, + # l = 公升 - gōngshēng = л = liter + "\N{U+516C}\N{U+5347}" => 1000, + 'kg' => 1000, + 'кг' => 1000, + 'l' => 1000, + 'л' => 1000, + # mg = 毫克 - háokè = мг + "\N{U+6BEB}\N{U+514B}" => 0.001, + 'mg' => 0.001, + 'мг' => 0.001, + 'mcg' => 0.000001, + 'µg' => 0.000001, + 'oz' => 28.349523125, + 'fl oz' => 30, + 'dl' => 100, + 'дл' => 100, + 'cl' => 10, + 'кл' => 10, + # 斤 - jīn = 500 Grams + "\N{U+65A4}" => 500, + # Standard units: No conversion units + # Value without modification if it's already grams or 克 (kè) or 公克 (gōngkè) or г + 'g' => 1, + '' => 1, + ' ' => 1, + 'kj' => 1, + '克' => 1, + '公克' => 1, + 'г' => 1, + 'мл' => 1, + 'ml' => 1, + 'mmol/l' => 1, + "\N{U+6BEB}\N{U+5347}" => 1, + '% vol' => 1, + 'ph' => 1, + '%' => 1, + '% dv' => 1, + '% vol (alcohol)' => 1, + 'iu' => 1, + # Division factors for "non standard unit" to mmoll conversions + 'mol/l' => 0.001, + 'mval/l' => 2, + 'ppm' => 100, + "\N{U+00B0}rh" => 40.080, + "\N{U+00B0}fh" => 10.00, + "\N{U+00B0}e" => 7.02, + "\N{U+00B0}dh" => 5.6, + 'gpg' => 5.847 +); + +sub unit_to_g ($value, $unit) { + $unit = lc($unit); + + if ($unit =~ /^(fl|fluid)(\.| )*(oz|once|ounce)/) { + $unit = "fl oz"; + } + + (not defined $value) and return $value; + + $value =~ s/,/\./; + $value =~ s/^(<|environ|max|maximum|min|minimum)( )?//; + $value eq '' and return $value; + + if (exists($unit_conversion_map{$unit})) { + return $value * $unit_conversion_map{$unit}; + } + + (($unit eq 'kcal') or ($unit eq 'ккал')) and return int($value * 4.184 + 0.5); + + # We return with + 0 to make sure the value is treated as number (needed when outputting json and to store in mongodb as a number) + # lets not assume that we have a valid unit + return; +} + +=head2 g_to_unit($value, $unit) + +Converts grams into . Eg.: +g_to_unit(2000,kg) => returns 2 +g_to_unit(0.52,mg) => returns 520 + +=cut + +sub g_to_unit ($value, $unit) { + $unit = lc($unit); + + if ((not defined $value) or ($value eq '')) { + return ""; + } + + $unit eq 'fl. oz' and $unit = 'fl oz'; + $unit eq 'fl.oz' and $unit = 'fl oz'; + + $value =~ s/,/\./; + $value =~ s/^(<|environ|max|maximum|min|minimum)( )?//; + + $value eq '' and return $value; + + # Divide with the values in the hash + if (exists($unit_conversion_map{$unit})) { + return $value / $unit_conversion_map{$unit}; + } + + (($unit eq 'kcal') or ($unit eq 'ккал')) and return int($value / 4.184 + 0.5); + + # return value without modification if unit is already grams or 克 (kè) or 公克 (gōngkè) or г + return $value + 0; + # + 0 to make sure the value is treated as number + # (needed when outputting json and to store in mongodb as a number) +} + +sub unit_to_mmoll ($value, $unit) { + $unit = lc($unit); + + if ((not defined $value) or ($value eq '')) { + return ''; + } + + $value =~ s/,/\./; + $value =~ s/^(<|environ|max|maximum|min|minimum)( )?//; + + # Divide with the values in the hash + if (exists($unit_conversion_map{$unit})) { + return $value / $unit_conversion_map{$unit}; + } + + return $value + 0; +} + +sub mmoll_to_unit ($value, $unit) { + $unit = lc($unit); + + if ((not defined $value) or ($value eq '')) { + return ''; + } + + $value =~ s/,/\./; + $value =~ s/^(<|environ|max|maximum|min|minimum)( )?//; + + # Multiply with the values in the hash + if (exists($unit_conversion_map{$unit})) { + return $value * $unit_conversion_map{$unit}; + } + + return $value + 0; +} + +my $international_units = qr/kg|g|mg|µg|oz|l|dl|cl|ml|(fl(\.?)(\s)?oz)/i; +# Chinese units: a good start is https://en.wikipedia.org/wiki/Chinese_units_of_measurement#Mass +my $chinese_units = qr/ + (?:[\N{U+6BEB}\N{U+516C}]?\N{U+514B})| # 毫克 or 公克 or 克 or (克 kè is the Chinese word for gram) + # (公克 gōngkè is for "metric gram") + (?:\N{U+516C}?\N{U+65A4})| # 公斤 or 斤 or (公斤 gōngjīn is a "metric kg") + (?:[\N{U+6BEB}\N{U+516C}]?\N{U+5347})| # 毫升 or 公升 or 升 (升 is liter) + \N{U+5428} # 吨 (ton?) + /ix; +my $russian_units = qr/г|мг|кг|л|дл|кл|мл/i; +my $units = qr/$international_units|$chinese_units|$russian_units/i; + +=head2 normalize_quantity($quantity) + +Returns the size in g or ml for the whole product. Eg.: +normalize_quantity(1 barquette de 40g) returns 40 +normalize_quantity(20 tranches 500g) returns 500 +normalize_quantity(6x90g) returns 540 +normalize_quantity(2kg) returns 2000 + +Returns undef if no quantity was detected. + +=cut + +sub normalize_quantity ($quantity) { + + my $q = undef; + my $u = undef; + + # 12 pots x125 g + # 6 bouteilles de 33 cl + # 6 bricks de 1 l + # 10 unités, 170 g + # 4 bouteilles en verre de 20cl + if ($quantity =~ /(\d+)(\s(\p{Letter}| )+)?(\s)?( de | of |x|\*)(\s)?((\d+)(\.|,)?(\d+)?)(\s)?($units)/i) { + my $m = $1; + $q = lc($7); + $u = $12; + $q = convert_string_to_number($q); + $q = unit_to_g($q * $m, $u); + } + elsif ($quantity =~ /((\d+)(\.|,)?(\d+)?)(\s)?($units)/i) { + $q = lc($1); + $u = $6; + $q = convert_string_to_number($q); + $q = unit_to_g($q, $u); + } + + return $q; +} + +=head2 normalize_serving_size($serving) + +Returns the size in g or ml for the serving. Eg.: +normalize_serving_size(1 barquette de 40g)->returns 40 +normalize_serving_size(2.5kg)->returns 2500 + +=cut + +sub normalize_serving_size ($serving) { + + # Regex captures any ( )? group, but leaves allowances for a preceding + # token to allow for patterns like "One bag (32g)", "1 small bottle (180ml)" etc + if ($serving =~ /^(.*[ \(])?(?(\d+)(\.|,)?(\d+)?)( )?(?\w+)\b/i) { + my $q = $+{quantity}; + my $u = normalize_unit($+{unit}); + $q = convert_string_to_number($q); + + return unit_to_g($q, $u); + } + + #$log->trace("serving size normalized", { serving => $serving, q => $q, u => $u }) if $log->is_trace(); + return 0; +} + +# @todo we should have equivalences for more units if we are supporting this +my @unit_equivalences_list = ( + ['g', qr/gram(s)?/], + ['g', qr/gramme(s)?/], # French +); + +=head2 normalize_unit ( $unit ) + +Normalizes units to their standard symbolic forms so that we can support unit names and alternative +representations in our normalization logic. + +=cut + +sub normalize_unit ($originalUnit) { + + foreach my $unit_name (@unit_equivalences_list) { + if ($originalUnit =~ $unit_name->[1]) { + return $unit_name->[0]; + } + } + + return $originalUnit; +} + +1; + diff --git a/lib/startup_apache2.pl b/lib/startup_apache2.pl index c539da982a128..3415e75ebfd3f 100755 --- a/lib/startup_apache2.pl +++ b/lib/startup_apache2.pl @@ -72,6 +72,7 @@ use ProductOpener::Display qw/:all/; use ProductOpener::Products qw/:all/; use ProductOpener::Food qw/:all/; +use ProductOpener::Units qw/:all/; use ProductOpener::Images qw/:all/; use ProductOpener::Index qw/:all/; use ProductOpener::Tags qw/:all/; diff --git a/po/common/common.pot b/po/common/common.pot index 2fb475de7fbbc..83dfc5765fd7c 100644 --- a/po/common/common.pot +++ b/po/common/common.pot @@ -6468,6 +6468,10 @@ msgctxt "api_message_invalid_type_must_be_integer" msgid "Invalid type: must be an integer" msgstr "Invalid type: must be an integer" +msgctxt "api_message_invalid_type_must_be_number" +msgid "Invalid type: must be a number" +msgstr "Invalid type: must be a number" + msgctxt "api_message_missing_field" msgid "Missing field" msgstr "Missing field" @@ -6492,6 +6496,10 @@ msgctxt "api_impact_field_ignored" msgid "Field ignored" msgstr "Field ignored" +msgctxt "api_impact_value_converted" +msgid "Value converted" +msgstr "Value converted" + # Unit = element, not unit of measure msgctxt "packaging_number_of_units" msgid "Number of units" diff --git a/po/common/en.po b/po/common/en.po index 80411de5832de..d89f80c2644b6 100644 --- a/po/common/en.po +++ b/po/common/en.po @@ -6475,6 +6475,10 @@ msgctxt "api_message_invalid_type_must_be_integer" msgid "Invalid type: must be an integer" msgstr "Invalid type: must be an integer" +msgctxt "api_message_invalid_type_must_be_number" +msgid "Invalid type: must be a number" +msgstr "Invalid type: must be a number" + msgctxt "api_message_missing_field" msgid "Missing field" msgstr "Missing field" @@ -6499,6 +6503,10 @@ msgctxt "api_impact_field_ignored" msgid "Field ignored" msgstr "Field ignored" +msgctxt "api_impact_value_converted" +msgid "Value converted" +msgstr "Value converted" + # Unit = element, not unit of measure msgctxt "packaging_number_of_units" msgid "Number of units" diff --git a/po/tags/en.po b/po/tags/en.po index 710b8ab07bcd3..0ffc3859d5562 100644 --- a/po/tags/en.po +++ b/po/tags/en.po @@ -551,12 +551,11 @@ msgid "food-group" msgstr "food-group" #. part of an url, put here the plural in your language, without caps, and by using hyphens instead of spaces -msgctxt "weighters:plural" -msgid "weighters" -msgstr "weighters" +msgctxt "weighers:plural" +msgid "weighers" +msgstr "weighers" #. part of an url, put here the singular in your language, without caps, and by using hyphens instead of spaces -msgctxt "weighters:singular" -msgid "weighter" -msgstr "weighter" - +msgctxt "weighers:singular" +msgid "weigher" +msgstr "weigher" \ No newline at end of file diff --git a/po/tags/fr.po b/po/tags/fr.po index 9a9f0644121fb..0839435645bfc 100644 --- a/po/tags/fr.po +++ b/po/tags/fr.po @@ -614,5 +614,15 @@ msgid "food-groups" msgstr "groupes-alimentaires" msgctxt "food_groups:singular" -msgid "groupe-alimentaire" -msgstr "groupe-alimentaire" \ No newline at end of file +msgid "food-group" +msgstr "groupe-alimentaire" + +#. part of an url, put here the plural in your language, without caps, and by using hyphens instead of spaces +msgctxt "weigher:plural" +msgid "weighers" +msgstr "peseurs" + +#. part of an url, put here the singular in your language, without caps, and by using hyphens instead of spaces +msgctxt "weigher:singular" +msgid "weigher" +msgstr "peseur" \ No newline at end of file diff --git a/po/tags/tags.pot b/po/tags/tags.pot index 3ef57b091c8a0..b3c84685f888b 100644 --- a/po/tags/tags.pot +++ b/po/tags/tags.pot @@ -645,12 +645,11 @@ msgid "food-group" msgstr "food-group" #. part of an url, put here the plural in your language, without caps, and by using hyphens instead of spaces -msgctxt "weighters:plural" -msgid "weighters" -msgstr "" +msgctxt "weighers:plural" +msgid "weighers" +msgstr "" #. part of an url, put here the singular in your language, without caps, and by using hyphens instead of spaces -msgctxt "weighters:singular" -msgid "weighter" -msgstr "" - +msgctxt "weighers:singular" +msgid "weigher" +msgstr "" \ No newline at end of file diff --git a/scripts/import_fleurymichon.pl b/scripts/import_fleurymichon.pl index 5c9b4ea5291e6..fbc398ecf47da 100755 --- a/scripts/import_fleurymichon.pl +++ b/scripts/import_fleurymichon.pl @@ -40,6 +40,7 @@ use ProductOpener::Mail qw/:all/; use ProductOpener::Products qw/:all/; use ProductOpener::Food qw/:all/; +use ProductOpener::Units qw/:all/; use ProductOpener::Ingredients qw/:all/; use ProductOpener::Images qw/:all/; use ProductOpener::PackagerCodes qw/:all/; diff --git a/scripts/import_systemeu.pl b/scripts/import_systemeu.pl index fd0d8eb4ce021..eba2dcd860808 100755 --- a/scripts/import_systemeu.pl +++ b/scripts/import_systemeu.pl @@ -38,6 +38,7 @@ use ProductOpener::Mail qw/:all/; use ProductOpener::Products qw/:all/; use ProductOpener::Food qw/:all/; +use ProductOpener::Units qw/:all/; use ProductOpener::Ingredients qw/:all/; use ProductOpener::Images qw/:all/; use ProductOpener::DataQuality qw/:all/; diff --git a/scripts/refresh_products_tags.js b/scripts/refresh_products_tags.js index 7cecc7eb3e7e8..efc09e5fe7676 100644 --- a/scripts/refresh_products_tags.js +++ b/scripts/refresh_products_tags.js @@ -68,7 +68,7 @@ db.products.aggregate( [ ecoscore_tags:1, owners_tags:1, food_groups_tags:1, - weighters_tags:1, + weighers_tags:1, }}, {"$out": "products_tags"} ]); diff --git a/tests/integration/api_v3_product_write.t b/tests/integration/api_v3_product_write.t index 5067495800e42..f2c3e7659ffc6 100644 --- a/tests/integration/api_v3_product_write.t +++ b/tests/integration/api_v3_product_write.t @@ -520,6 +520,72 @@ my $tests_ref = [ } }' }, + # Weights sent as strings (with dot or comma) + { + test_case => 'patch-packagings-weights-as-strings', + method => 'PATCH', + path => '/api/v3/product/1234567890017', + body => '{ + "fields": "updated,misc_tags,weighters_tags", + "tags_lc": "en", + "product": { + "packagings_add": [ + { + "number_of_units": 6, + "shape": {"lc_name": "bottle"}, + "material": {"lc_name": "PET"}, + "quantity_per_unit": "25cl", + "weight_measured": "10" + }, + { + "number_of_units": 1, + "shape": {"lc_name": "box"}, + "material": {"lc_name": "wood"}, + "weight_specified": "25.5" + }, + { + "number_of_units": 1, + "shape": {"lc_name": "film"}, + "material": {"lc_name": "plastic"}, + "weight_specified": "2,01" + } + ] + } + }' + }, + # Weights sent as strings with units + { + test_case => 'patch-packagings-weights-as-strings-with-units', + method => 'PATCH', + path => '/api/v3/product/1234567890018', + body => '{ + "fields": "updated,misc_tags,weighters_tags", + "tags_lc": "en", + "product": { + "packagings_add": [ + { + "number_of_units": 6, + "shape": {"lc_name": "bottle"}, + "material": {"lc_name": "PET"}, + "quantity_per_unit": "25cl", + "weight_measured": "10 g" + }, + { + "number_of_units": 1, + "shape": {"lc_name": "box"}, + "material": {"lc_name": "wood"}, + "weight_specified": "25.5g" + }, + { + "number_of_units": 1, + "shape": {"lc_name": "film"}, + "material": {"lc_name": "plastic"}, + "weight_specified": "2,01 grams" + } + ] + } + }' + }, ]; execute_api_tests(__FILE__, $tests_ref); diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-existing-product.json b/tests/integration/expected_test_results/api_v2_product_read/get-existing-product.json index 3d72f805c9418..0f7d457a7d617 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-existing-product.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-existing-product.json @@ -775,13 +775,13 @@ "packagings" : [ { "material" : "en:wood", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:recycle", "shape" : "en:box" }, { "material" : "en:glass", - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -790,13 +790,13 @@ }, { "material" : "en:steel", - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : "en:recycle", "shape" : "en:lid" }, { "material" : "en:plastic", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:discard", "shape" : "en:film" } @@ -861,7 +861,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "status" : 1, "status_verbose" : "product found" diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-fields-all-knowledge-panels.json b/tests/integration/expected_test_results/api_v2_product_read/get-fields-all-knowledge-panels.json index 57b44c735f203..2863a17510565 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-fields-all-knowledge-panels.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-fields-all-knowledge-panels.json @@ -1926,13 +1926,13 @@ "packagings" : [ { "material" : "en:wood", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:recycle", "shape" : "en:box" }, { "material" : "en:glass", - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -1941,13 +1941,13 @@ }, { "material" : "en:steel", - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : "en:recycle", "shape" : "en:lid" }, { "material" : "en:plastic", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:discard", "shape" : "en:film" } @@ -2012,7 +2012,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "status" : 1, "status_verbose" : "product found" diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-fields-all.json b/tests/integration/expected_test_results/api_v2_product_read/get-fields-all.json index 3d72f805c9418..0f7d457a7d617 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-fields-all.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-fields-all.json @@ -775,13 +775,13 @@ "packagings" : [ { "material" : "en:wood", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:recycle", "shape" : "en:box" }, { "material" : "en:glass", - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -790,13 +790,13 @@ }, { "material" : "en:steel", - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : "en:recycle", "shape" : "en:lid" }, { "material" : "en:plastic", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:discard", "shape" : "en:film" } @@ -861,7 +861,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "status" : 1, "status_verbose" : "product found" 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 f10967c88e310..26939fb408753 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 @@ -2250,13 +2250,13 @@ "packagings" : [ { "material" : "en:wood", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:recycle", "shape" : "en:box" }, { "material" : "en:glass", - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -2265,13 +2265,13 @@ }, { "material" : "en:steel", - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : "en:recycle", "shape" : "en:lid" }, { "material" : "en:plastic", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:discard", "shape" : "en:film" } @@ -2336,7 +2336,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "status" : 1, "status_verbose" : "product found" diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-fields-raw.json b/tests/integration/expected_test_results/api_v2_product_read/get-fields-raw.json index fcc523264371b..c768e70da8ed5 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-fields-raw.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-fields-raw.json @@ -770,13 +770,13 @@ "packagings" : [ { "material" : "en:wood", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:recycle", "shape" : "en:box" }, { "material" : "en:glass", - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -785,13 +785,13 @@ }, { "material" : "en:steel", - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : "en:recycle", "shape" : "en:lid" }, { "material" : "en:plastic", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:discard", "shape" : "en:film" } @@ -856,7 +856,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "status" : 1, "status_verbose" : "product found" diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-packagings-fr.json b/tests/integration/expected_test_results/api_v2_product_read/get-packagings-fr.json index cc41e8915d912..709f0ee58e6dc 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-packagings-fr.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-packagings-fr.json @@ -4,13 +4,13 @@ "packagings" : [ { "material" : "en:wood", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:recycle", "shape" : "en:box" }, { "material" : "en:glass", - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -19,13 +19,13 @@ }, { "material" : "en:steel", - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : "en:recycle", "shape" : "en:lid" }, { "material" : "en:plastic", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:discard", "shape" : "en:film" } diff --git a/tests/integration/expected_test_results/api_v2_product_read/get-packagings.json b/tests/integration/expected_test_results/api_v2_product_read/get-packagings.json index cc41e8915d912..709f0ee58e6dc 100644 --- a/tests/integration/expected_test_results/api_v2_product_read/get-packagings.json +++ b/tests/integration/expected_test_results/api_v2_product_read/get-packagings.json @@ -4,13 +4,13 @@ "packagings" : [ { "material" : "en:wood", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:recycle", "shape" : "en:box" }, { "material" : "en:glass", - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -19,13 +19,13 @@ }, { "material" : "en:steel", - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : "en:recycle", "shape" : "en:lid" }, { "material" : "en:plastic", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:discard", "shape" : "en:film" } diff --git a/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json b/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json index 9a2423bc10462..653e461f4519c 100644 --- a/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json +++ b/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json @@ -797,7 +797,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "status" : 1, "status_verbose" : "product found" diff --git a/tests/integration/expected_test_results/api_v2_product_write/get-product.json b/tests/integration/expected_test_results/api_v2_product_write/get-product.json index fbaaf896b338f..8765543c4b94d 100644 --- a/tests/integration/expected_test_results/api_v2_product_write/get-product.json +++ b/tests/integration/expected_test_results/api_v2_product_write/get-product.json @@ -795,7 +795,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "status" : 1, "status_verbose" : "product found" diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-existing-product.json b/tests/integration/expected_test_results/api_v3_product_read/get-existing-product.json index 0dadb4c397819..386361fea5a58 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-existing-product.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-existing-product.json @@ -778,7 +778,7 @@ "material" : { "id" : "en:wood" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle" }, @@ -790,7 +790,7 @@ "material" : { "id" : "en:glass" }, - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -805,7 +805,7 @@ "material" : { "id" : "en:steel" }, - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : { "id" : "en:recycle" }, @@ -817,7 +817,7 @@ "material" : { "id" : "en:plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:discard" }, @@ -886,7 +886,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "result" : { "id" : "product_found", 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 d7c2034402fbf..5ebad13c7c3b4 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 @@ -1929,7 +1929,7 @@ "material" : { "id" : "en:wood" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle" }, @@ -1941,7 +1941,7 @@ "material" : { "id" : "en:glass" }, - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -1956,7 +1956,7 @@ "material" : { "id" : "en:steel" }, - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : { "id" : "en:recycle" }, @@ -1968,7 +1968,7 @@ "material" : { "id" : "en:plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:discard" }, @@ -2037,7 +2037,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "result" : { "id" : "product_found", diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-fields-all.json b/tests/integration/expected_test_results/api_v3_product_read/get-fields-all.json index 27965c30bb7fc..30a354d6ffb35 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-fields-all.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-fields-all.json @@ -778,7 +778,7 @@ "material" : { "id" : "en:wood" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle" }, @@ -790,7 +790,7 @@ "material" : { "id" : "en:glass" }, - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -805,7 +805,7 @@ "material" : { "id" : "en:steel" }, - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : { "id" : "en:recycle" }, @@ -817,7 +817,7 @@ "material" : { "id" : "en:plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:discard" }, @@ -886,7 +886,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "result" : { "id" : "product_found", 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 c37a9119cf3b7..4764cd4ec7fc5 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 @@ -2253,7 +2253,7 @@ "material" : { "id" : "en:wood" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle" }, @@ -2265,7 +2265,7 @@ "material" : { "id" : "en:glass" }, - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -2280,7 +2280,7 @@ "material" : { "id" : "en:steel" }, - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : { "id" : "en:recycle" }, @@ -2292,7 +2292,7 @@ "material" : { "id" : "en:plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:discard" }, @@ -2361,7 +2361,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "result" : { "id" : "product_found", diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-fields-raw.json b/tests/integration/expected_test_results/api_v3_product_read/get-fields-raw.json index f0f3fdbf74077..4c88087cd1dbf 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-fields-raw.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-fields-raw.json @@ -771,13 +771,13 @@ "packagings" : [ { "material" : "en:wood", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:recycle", "shape" : "en:box" }, { "material" : "en:glass", - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -786,13 +786,13 @@ }, { "material" : "en:steel", - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : "en:recycle", "shape" : "en:lid" }, { "material" : "en:plastic", - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : "en:discard", "shape" : "en:film" } @@ -857,7 +857,7 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "result" : { "id" : "product_found", diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-packagings-fr.json b/tests/integration/expected_test_results/api_v3_product_read/get-packagings-fr.json index 910a9b61da904..316bf9faed97d 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-packagings-fr.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-packagings-fr.json @@ -8,7 +8,7 @@ "id" : "en:wood", "lc_name" : "Bois" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycler" @@ -23,7 +23,7 @@ "id" : "en:glass", "lc_name" : "Verre" }, - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -41,7 +41,7 @@ "id" : "en:steel", "lc_name" : "Acier" }, - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycler" @@ -56,7 +56,7 @@ "id" : "en:plastic", "lc_name" : "Plastique" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:discard", "lc_name" : "Jeter" diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-packagings.json b/tests/integration/expected_test_results/api_v3_product_read/get-packagings.json index 34d3bcd1b1ed0..527151e72f788 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-packagings.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-packagings.json @@ -7,7 +7,7 @@ "material" : { "id" : "en:wood" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle" }, @@ -19,7 +19,7 @@ "material" : { "id" : "en:glass" }, - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -34,7 +34,7 @@ "material" : { "id" : "en:steel" }, - "number_of_units" : "3", + "number_of_units" : 3, "recycling" : { "id" : "en:recycle" }, @@ -46,7 +46,7 @@ "material" : { "id" : "en:plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:discard" }, diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-auth-good-password.json b/tests/integration/expected_test_results/api_v3_product_write/patch-auth-good-password.json index 6a1122c584863..5c21dfcdba740 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-auth-good-password.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-auth-good-password.json @@ -8,7 +8,7 @@ ], "packagings" : [ { - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycle" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-add-components-to-existing-product.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-add-components-to-existing-product.json index 8e6d61cc997ce..aca76c7013a1b 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-add-components-to-existing-product.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-add-components-to-existing-product.json @@ -8,7 +8,7 @@ "id" : "en:plastic", "lc_name" : "Plastic" }, - "number_of_units" : "2", + "number_of_units" : 2, "recycling" : { "id" : "en:strange value", "lc_name" : "Strange value" @@ -23,7 +23,7 @@ "id" : "en:cardboard", "lc_name" : "Cardboard" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycle" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-0.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-0.json index c00f33450b30b..076905303d149 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-0.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-0.json @@ -4,7 +4,7 @@ "product" : { "packagings" : [ { - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycle" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-1.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-1.json index 68bf82173f393..401122b8327af 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-1.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-1.json @@ -4,7 +4,7 @@ "product" : { "packagings" : [ { - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycle" @@ -15,7 +15,7 @@ } }, { - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycle" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-2.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-2.json index f91df29e18c55..34409847fca07 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-2.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-2.json @@ -20,7 +20,7 @@ "product" : { "packagings" : [ { - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycle" @@ -31,7 +31,7 @@ } }, { - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycle" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-fr-fields.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-fr-fields.json index c6006aa32b164..2575c6c990f07 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-fr-fields.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-fr-fields.json @@ -27,7 +27,7 @@ "id" : "en:plastic", "lc_name" : "Plastique" }, - "number_of_units" : "2", + "number_of_units" : 2, "recycling" : { "id" : "en:strange value", "lc_name" : "en:strange value" @@ -42,7 +42,7 @@ "id" : "en:cardboard", "lc_name" : "Carton" }, - "number_of_units" : "1", + "number_of_units" : 1, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycler" @@ -57,7 +57,7 @@ "id" : "en:plastic", "lc_name" : "Plastique" }, - "number_of_units" : "3", + "number_of_units" : 3, "shape" : { "id" : "en:bottle", "lc_name" : "Bouteille" @@ -68,7 +68,7 @@ "id" : "en:glass", "lc_name" : "Verre" }, - "number_of_units" : "4", + "number_of_units" : 4, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycler" @@ -78,8 +78,7 @@ "lc_name" : "Pot" } } - ], - "weighters_tags" : [] + ] }, "status" : "success_with_warnings", "warnings" : [ diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-quantity-and-weight.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-quantity-and-weight.json index dcf3f04f2627a..5c8a3aa5450a5 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-quantity-and-weight.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-quantity-and-weight.json @@ -30,7 +30,7 @@ "id" : "en:pet-polyethylene-terephthalate", "lc_name" : "PET - Polyethylene terephthalate" }, - "number_of_units" : "6", + "number_of_units" : 6, "quantity_per_unit" : "25cl", "quantity_per_unit_unit" : "cl", "quantity_per_unit_value" : 25, @@ -38,23 +38,20 @@ "id" : "en:bottle", "lc_name" : "Bottle" }, - "weight_measured" : "10" + "weight_measured" : 10 }, { "material" : { "id" : "en:wood", "lc_name" : "Wood" }, - "number_of_units" : "1", + "number_of_units" : 1, "shape" : { "id" : "en:box", "lc_name" : "Box" }, "weight_specified" : 25.5 } - ], - "weighters_tags" : [ - "openfoodfacts-contributors" ] }, "status" : "success_with_warnings", diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-weights-as-strings-with-units.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-weights-as-strings-with-units.json new file mode 100644 index 0000000000000..b2f03c89a2f9c --- /dev/null +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-weights-as-strings-with-units.json @@ -0,0 +1,171 @@ +{ + "code" : "1234567890018", + "errors" : [], + "product" : { + "misc_tags" : [ + "en:nutriscore-not-computed", + "en:nutriscore-missing-category", + "en:nutrition-not-enough-data-to-compute-nutrition-score", + "en:nutriscore-missing-nutrition-data", + "en:nutriscore-missing-nutrition-data-energy", + "en:nutriscore-missing-nutrition-data-fat", + "en:nutriscore-missing-nutrition-data-saturated-fat", + "en:nutriscore-missing-nutrition-data-sugars", + "en:nutriscore-missing-nutrition-data-sodium", + "en:nutriscore-missing-nutrition-data-proteins", + "en:nutrition-no-fiber", + "en:packagings-not-complete", + "en:packagings-not-empty-but-not-complete", + "en:packagings-not-empty", + "en:packagings-with-weights", + "en:packagings-with-all-weights", + "en:packagings-with-all-weights-not-complete", + "en:ecoscore-extended-data-not-computed", + "en:ecoscore-not-computed", + "en:main-countries-new-product" + ], + "packagings" : [ + { + "material" : { + "id" : "en:pet-polyethylene-terephthalate", + "lc_name" : "PET - Polyethylene terephthalate" + }, + "number_of_units" : 6, + "quantity_per_unit" : "25cl", + "quantity_per_unit_unit" : "cl", + "quantity_per_unit_value" : 25, + "shape" : { + "id" : "en:bottle", + "lc_name" : "Bottle" + }, + "weight_measured" : 10 + }, + { + "material" : { + "id" : "en:wood", + "lc_name" : "Wood" + }, + "number_of_units" : 1, + "shape" : { + "id" : "en:box", + "lc_name" : "Box" + }, + "weight_specified" : 25.5 + }, + { + "material" : { + "id" : "en:plastic", + "lc_name" : "Plastic" + }, + "number_of_units" : 1, + "shape" : { + "id" : "en:film", + "lc_name" : "Film" + }, + "weight_specified" : 2.01 + } + ] + }, + "status" : "success_with_warnings", + "warnings" : [ + { + "field" : { + "id" : "weight_measured", + "value" : "10 g", + "valued_converted" : 10 + }, + "impact" : { + "id" : "value_converted", + "lc_name" : "Value converted", + "name" : "Value converted" + }, + "message" : { + "id" : "invalid_type_must_be_number", + "lc_name" : "Invalid type: must be a number", + "name" : "Invalid type: must be a number" + } + }, + { + "field" : { + "id" : "recycling", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + }, + { + "field" : { + "id" : "weight_specified", + "value" : "25.5g", + "valued_converted" : 25.5 + }, + "impact" : { + "id" : "value_converted", + "lc_name" : "Value converted", + "name" : "Value converted" + }, + "message" : { + "id" : "invalid_type_must_be_number", + "lc_name" : "Invalid type: must be a number", + "name" : "Invalid type: must be a number" + } + }, + { + "field" : { + "id" : "recycling", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + }, + { + "field" : { + "id" : "weight_specified", + "value" : "2,01 grams", + "valued_converted" : 2.01 + }, + "impact" : { + "id" : "value_converted", + "lc_name" : "Value converted", + "name" : "Value converted" + }, + "message" : { + "id" : "invalid_type_must_be_number", + "lc_name" : "Invalid type: must be a number", + "name" : "Invalid type: must be a number" + } + }, + { + "field" : { + "id" : "recycling", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + } + ] +} diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-weights-as-strings.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-weights-as-strings.json new file mode 100644 index 0000000000000..19b0664ad6ea9 --- /dev/null +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-weights-as-strings.json @@ -0,0 +1,120 @@ +{ + "code" : "1234567890017", + "errors" : [], + "product" : { + "misc_tags" : [ + "en:nutriscore-not-computed", + "en:nutriscore-missing-category", + "en:nutrition-not-enough-data-to-compute-nutrition-score", + "en:nutriscore-missing-nutrition-data", + "en:nutriscore-missing-nutrition-data-energy", + "en:nutriscore-missing-nutrition-data-fat", + "en:nutriscore-missing-nutrition-data-saturated-fat", + "en:nutriscore-missing-nutrition-data-sugars", + "en:nutriscore-missing-nutrition-data-sodium", + "en:nutriscore-missing-nutrition-data-proteins", + "en:nutrition-no-fiber", + "en:packagings-not-complete", + "en:packagings-not-empty-but-not-complete", + "en:packagings-not-empty", + "en:packagings-with-weights", + "en:packagings-with-all-weights", + "en:packagings-with-all-weights-not-complete", + "en:ecoscore-extended-data-not-computed", + "en:ecoscore-not-computed", + "en:main-countries-new-product" + ], + "packagings" : [ + { + "material" : { + "id" : "en:pet-polyethylene-terephthalate", + "lc_name" : "PET - Polyethylene terephthalate" + }, + "number_of_units" : 6, + "quantity_per_unit" : "25cl", + "quantity_per_unit_unit" : "cl", + "quantity_per_unit_value" : 25, + "shape" : { + "id" : "en:bottle", + "lc_name" : "Bottle" + }, + "weight_measured" : 10 + }, + { + "material" : { + "id" : "en:wood", + "lc_name" : "Wood" + }, + "number_of_units" : 1, + "shape" : { + "id" : "en:box", + "lc_name" : "Box" + }, + "weight_specified" : 25.5 + }, + { + "material" : { + "id" : "en:plastic", + "lc_name" : "Plastic" + }, + "number_of_units" : 1, + "shape" : { + "id" : "en:film", + "lc_name" : "Film" + }, + "weight_specified" : 2.01 + } + ] + }, + "status" : "success_with_warnings", + "warnings" : [ + { + "field" : { + "id" : "recycling", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + }, + { + "field" : { + "id" : "recycling", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + }, + { + "field" : { + "id" : "recycling", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + } + ] +} diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr-and-spanish.json b/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr-and-spanish.json index 8465b57c7af24..7a52c8b713d75 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr-and-spanish.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr-and-spanish.json @@ -8,7 +8,7 @@ "id" : "en:paper", "lc_name" : "Papier" }, - "number_of_units" : "2", + "number_of_units" : 2, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycler" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr-and-unrecognized-spanish.json b/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr-and-unrecognized-spanish.json index 1872b48a76831..674d77f28917f 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr-and-unrecognized-spanish.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr-and-unrecognized-spanish.json @@ -8,7 +8,7 @@ "id" : "en:paper", "lc_name" : "Papier" }, - "number_of_units" : "2", + "number_of_units" : 2, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycler" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr.json b/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr.json index 7b49a7fe98753..2375da7f4b56a 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name-fr.json @@ -8,7 +8,7 @@ "id" : "en:paper", "lc_name" : "Papier" }, - "number_of_units" : "2", + "number_of_units" : 2, "recycling" : { "id" : "en:recycle", "lc_name" : "Recycler" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name.json b/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name.json index 534e1626faa28..c6d26f97363c5 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-properties-with-lc-name.json @@ -8,7 +8,7 @@ "id" : "en:pet-polyethylene-terephthalate", "lc_name" : "PET - Polyethylene terephthalate" }, - "number_of_units" : "2", + "number_of_units" : 2, "recycling" : { "id" : "en:discard", "lc_name" : "Discard" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-replace-packagings.json b/tests/integration/expected_test_results/api_v3_product_write/patch-replace-packagings.json index 6e4e1476db469..a440fc9ca39e3 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-replace-packagings.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-replace-packagings.json @@ -8,7 +8,7 @@ "id" : "en:plastic", "lc_name" : "Plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "shape" : { "id" : "en:bag", "lc_name" : "Bag" @@ -20,7 +20,7 @@ "id" : "en:paper", "lc_name" : "Paper" }, - "number_of_units" : "1", + "number_of_units" : 1, "shape" : { "id" : "en:label", "lc_name" : "Label" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-all.json b/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-all.json index 7fde928c7934a..e2f6ccd8f568a 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-all.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-all.json @@ -383,7 +383,7 @@ "id" : "en:plastic", "lc_name" : "Plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "shape" : { "id" : "en:bag", "lc_name" : "Bag" @@ -443,7 +443,7 @@ "traces_hierarchy" : [], "traces_tags" : [], "unknown_nutrients_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, "status" : "success_with_warnings", "warnings" : [ diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-packagings.json b/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-packagings.json index 3ea48b00f5823..14ac9d017c1fe 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-packagings.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-packagings.json @@ -8,7 +8,7 @@ "id" : "en:plastic", "lc_name" : "Plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "shape" : { "id" : "en:bag", "lc_name" : "Bag" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-undef.json b/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-undef.json index 3ea48b00f5823..14ac9d017c1fe 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-undef.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-undef.json @@ -8,7 +8,7 @@ "id" : "en:plastic", "lc_name" : "Plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "shape" : { "id" : "en:bag", "lc_name" : "Bag" 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 be8453517283a..0a9da8c17d518 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 @@ -745,7 +745,7 @@ "id" : "en:plastic", "lc_name" : "Plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "shape" : { "id" : "en:bag", "lc_name" : "Bag" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-updated.json b/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-updated.json index 3ea48b00f5823..14ac9d017c1fe 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-updated.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-request-fields-updated.json @@ -8,7 +8,7 @@ "id" : "en:plastic", "lc_name" : "Plastic" }, - "number_of_units" : "1", + "number_of_units" : 1, "shape" : { "id" : "en:bag", "lc_name" : "Bag" diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-weight-as-number-or-string.json b/tests/integration/expected_test_results/api_v3_product_write/patch-weight-as-number-or-string.json index 44f5fa33212bf..3e885309040f2 100644 --- a/tests/integration/expected_test_results/api_v3_product_write/patch-weight-as-number-or-string.json +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-weight-as-number-or-string.json @@ -26,7 +26,7 @@ ], "packagings" : [ { - "number_of_units" : "1", + "number_of_units" : 1, "shape" : { "id" : "en:bottle", "lc_name" : "Bottle" @@ -34,7 +34,7 @@ "weight_measured" : 0.43 }, { - "number_of_units" : "2", + "number_of_units" : 2, "shape" : { "id" : "en:box", "lc_name" : "Box" @@ -42,7 +42,7 @@ "weight_measured" : 0.43 }, { - "number_of_units" : "3", + "number_of_units" : 3, "shape" : { "id" : "en:lid", "lc_name" : "Lid" diff --git a/tests/integration/expected_test_results/import_csv_file/3270190128403.json b/tests/integration/expected_test_results/import_csv_file/3270190128403.json index 481fe850e3122..a98b716fd2215 100644 --- a/tests/integration/expected_test_results/import_csv_file/3270190128403.json +++ b/tests/integration/expected_test_results/import_csv_file/3270190128403.json @@ -884,5 +884,5 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] } diff --git a/tests/integration/expected_test_results/import_csv_file/4270190128403.json b/tests/integration/expected_test_results/import_csv_file/4270190128403.json index b769ec79b5053..4c1e6e1a238b4 100644 --- a/tests/integration/expected_test_results/import_csv_file/4270190128403.json +++ b/tests/integration/expected_test_results/import_csv_file/4270190128403.json @@ -494,5 +494,5 @@ "traces_hierarchy" : [], "traces_tags" : [], "unknown_nutrients_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] } diff --git a/tests/integration/expected_test_results/import_csv_file/5270190128403.json b/tests/integration/expected_test_results/import_csv_file/5270190128403.json index 53ec95db32b24..ba197b343126f 100644 --- a/tests/integration/expected_test_results/import_csv_file/5270190128403.json +++ b/tests/integration/expected_test_results/import_csv_file/5270190128403.json @@ -544,5 +544,5 @@ "unknown_ingredients_n" : 0, "unknown_nutrients_tags" : [], "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] } diff --git a/tests/integration/expected_test_results/search_v1/search-no-filter.json b/tests/integration/expected_test_results/search_v1/search-no-filter.json index 3d2c7b76ea8c0..2e71abfacc9ca 100644 --- a/tests/integration/expected_test_results/search_v1/search-no-filter.json +++ b/tests/integration/expected_test_results/search_v1/search-no-filter.json @@ -657,7 +657,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000034/test-1", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, { "_id" : "200000000037", @@ -1192,7 +1192,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000037/vegan-palm-oil-free", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, { "_id" : "200000000038", @@ -1778,7 +1778,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000038/palm-oil-free-non-vegan", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, { "_id" : "200000000039", @@ -2330,7 +2330,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000039/vegan-test-snack", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, { "_id" : "200000000045", @@ -3053,7 +3053,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000045/vegan-test-snack", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] } ], "skip" : 0 diff --git a/tests/integration/expected_test_results/search_v1/search-tags-categories-without-ingredients-from-palm-oil.json b/tests/integration/expected_test_results/search_v1/search-tags-categories-without-ingredients-from-palm-oil.json index 8dcb06e63c89e..0244ed8a0b6d0 100644 --- a/tests/integration/expected_test_results/search_v1/search-tags-categories-without-ingredients-from-palm-oil.json +++ b/tests/integration/expected_test_results/search_v1/search-tags-categories-without-ingredients-from-palm-oil.json @@ -725,7 +725,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000045/vegan-test-snack", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] } ], "skip" : 0 diff --git a/tests/integration/expected_test_results/search_v1/search-without-ingredients-from-palm-oil.json b/tests/integration/expected_test_results/search_v1/search-without-ingredients-from-palm-oil.json index 3d2c7b76ea8c0..2e71abfacc9ca 100644 --- a/tests/integration/expected_test_results/search_v1/search-without-ingredients-from-palm-oil.json +++ b/tests/integration/expected_test_results/search_v1/search-without-ingredients-from-palm-oil.json @@ -657,7 +657,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000034/test-1", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, { "_id" : "200000000037", @@ -1192,7 +1192,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000037/vegan-palm-oil-free", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, { "_id" : "200000000038", @@ -1778,7 +1778,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000038/palm-oil-free-non-vegan", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, { "_id" : "200000000039", @@ -2330,7 +2330,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000039/vegan-test-snack", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] }, { "_id" : "200000000045", @@ -3053,7 +3053,7 @@ "unknown_nutrients_tags" : [], "url" : "http://world.openfoodfacts.localhost/product/200000000045/vegan-test-snack", "vitamins_tags" : [], - "weighters_tags" : [] + "weighers_tags" : [] } ], "skip" : 0 diff --git a/tests/unit/food.t b/tests/unit/food.t index c86623d652632..413ec26ac5e40 100644 --- a/tests/unit/food.t +++ b/tests/unit/food.t @@ -4,123 +4,11 @@ use Modern::Perl '2017'; use utf8; use Test::More; -use Test::Number::Delta relative => 1.001; use Log::Any::Adapter 'TAP'; use ProductOpener::Tags qw/:all/; use ProductOpener::Food qw/:all/; -# Based on https://de.wikipedia.org/w/index.php?title=Wasserh%C3%A4rte&oldid=160348959#Einheiten_und_Umrechnung -is(mmoll_to_unit(1, 'mol/l'), 0.001); -is(mmoll_to_unit('1', 'moll/l'), 1); -is(mmoll_to_unit(1, 'mmol/l'), 1); -is(mmoll_to_unit(1, 'mval/l'), 2); -is(mmoll_to_unit(1, 'ppm'), 100); -is(mmoll_to_unit(1, "\N{U+00B0}rH"), 40.080); -is(mmoll_to_unit(1, "\N{U+00B0}fH"), 10.00); -is(mmoll_to_unit(1, "\N{U+00B0}e"), 7.02); -is(mmoll_to_unit(1, "\N{U+00B0}dH"), 5.6); -is(mmoll_to_unit(1, 'gpg'), 5.847); - -is(unit_to_mmoll(1, 'mol/l'), 1000); -is(unit_to_mmoll('1', 'mmol/l'), 1); -is(unit_to_mmoll(1, 'mmol/l'), 1); -is(unit_to_mmoll(1, 'mval/l'), 0.5); -is(unit_to_mmoll(1, 'ppm'), 0.01); -delta_ok(unit_to_mmoll(1, "\N{U+00B0}rH"), 0.025); -delta_ok(unit_to_mmoll(1, "\N{U+00B0}fH"), 0.1); -delta_ok(unit_to_mmoll(1, "\N{U+00B0}e"), 0.142); -delta_ok(unit_to_mmoll(1, "\N{U+00B0}dH"), 0.1783); -delta_ok(unit_to_mmoll(1, 'gpg'), 0.171); - -is(mmoll_to_unit(unit_to_mmoll(1, 'ppm'), "\N{U+00B0}dH"), 0.056); - -# Chinese Measurements Source: http://www.new-chinese.org/lernwortschatz-chinesisch-masseinheiten.html -# kè - gram - 克 -is(normalize_quantity("42\N{U+514B}"), 42); -is(normalize_serving_size("42\N{U+514B}"), 42); -is(unit_to_g(42, "\N{U+514B}"), 42); -is(g_to_unit(42, "\N{U+514B}"), 42); -# gōngkè - gram - 公克 (in use at least in Taïwan) -is(normalize_quantity("42\N{U+516C}\N{U+514B}"), 42); -is(normalize_serving_size("42\N{U+516C}\N{U+514B}"), 42); -is(unit_to_g(42, "\N{U+516C}\N{U+514B}"), 42); -is(g_to_unit(42, "\N{U+516C}\N{U+514B}"), 42); -# héokè - milligram - 毫克 -is(normalize_quantity("42000\N{U+6BEB}\N{U+514B}"), 42); -is(normalize_serving_size("42000\N{U+6BEB}\N{U+514B}"), 42); -is(unit_to_g(42000, "\N{U+6BEB}\N{U+514B}"), 42); -is(g_to_unit(42, "\N{U+6BEB}\N{U+514B}"), 42000); -# jīn - pound 500 g - 斤 -is(normalize_quantity("84\N{U+65A4}"), 42000); -is(normalize_serving_size("84\N{U+65A4}"), 42000); -is(unit_to_g(84, "\N{U+65A4}"), 42000); -is(g_to_unit(42000, "\N{U+65A4}"), 84); -# gōngjīn - kg - 公斤 -is(normalize_quantity("42\N{U+516C}\N{U+65A4}"), 42000); -is(normalize_serving_size("42\N{U+516C}\N{U+65A4}"), 42000); -is(unit_to_g(42, "\N{U+516C}\N{U+65A4}"), 42000); -is(g_to_unit(42000, "\N{U+516C}\N{U+65A4}"), 42); -# háoshēng - milliliter - 毫升 -is(normalize_quantity("42\N{U+6BEB}\N{U+5347}"), 42); -is(normalize_serving_size("42\N{U+6BEB}\N{U+5347}"), 42); -is(unit_to_g(42, "\N{U+6BEB}\N{U+5347}"), 42); -is(g_to_unit(42, "\N{U+6BEB}\N{U+5347}"), 42); -# gōngshēng - liter - 公升 -is(normalize_quantity("42\N{U+516C}\N{U+5347}"), 42000); -is(normalize_serving_size("42\N{U+516C}\N{U+5347}"), 42000); -is(unit_to_g(42, "\N{U+516C}\N{U+5347}"), 42000); -is(g_to_unit(42000, "\N{U+516C}\N{U+5347}"), 42); - -# Russian units - -is(unit_to_g(1, "г"), 1); -is(unit_to_g(1, "мг"), 0.001); - -# unit conversion tests -# TODO -# if (!defined(unit_to_g(1, "unknown"))) -# { -# return 1; -# } -is(unit_to_g(1, "kj"), 1); -is(unit_to_g(1, "kcal"), 4); -is(unit_to_g(1000, "kcal"), 4184); -is(unit_to_g(1.2345, "kg"), 1234.5); -is(unit_to_g(1, "kJ"), 1); -is(unit_to_g(10, ""), 10); -is(unit_to_g(10, " "), 10); -is(unit_to_g(10, "% vol"), 10); -is(unit_to_g(10, "%"), 10); -is(unit_to_g(10, "% vol"), 10); -is(unit_to_g(10, "% DV"), 10); -is(unit_to_g(11, "mL"), 11); -is(g_to_unit(42000, "kg"), 42); -is(g_to_unit(28.349523125, "oz"), 1); -is(g_to_unit(30, "fl oz"), 1); -is(g_to_unit(1, "mcg"), 1000000); - -is(normalize_quantity("1 г"), 1); -is(normalize_quantity("1 мг"), 0.001); -is(normalize_quantity("1 кг"), 1000); -is(normalize_quantity("1 л"), 1000); -is(normalize_quantity("1 дл"), 100); -is(normalize_quantity("1 кл"), 10); -is(normalize_quantity("1 мл"), 1); - -is(normalize_quantity("250G"), 250); -is(normalize_quantity("4 x 25g"), 100); -is(normalize_quantity("4 x25g"), 100); -is(normalize_quantity("4 * 25g"), 100); -is(normalize_quantity("4X2,5L"), 10000); -is(normalize_quantity("1 barquette de 40g"), 40); -is(normalize_quantity("2 barquettes de 40g"), 80); -is(normalize_quantity("6 bouteilles de 33cl"), 6 * 33 * 10); -is(normalize_quantity("10 unités de 170g"), 1700); -is(normalize_quantity("10 unites, 170g"), 170); -is(normalize_quantity("4 bouteilles en verre de 20cl"), 800); -is(normalize_quantity("5 bottles of 20cl"), 100 * 10); - my $product_ref = { lc => "en", categories_tags => ["en:beverages"], diff --git a/tests/unit/food_normalize_serving_size.t b/tests/unit/food_normalize_serving_size.t deleted file mode 100644 index 97aa701b0a5c2..0000000000000 --- a/tests/unit/food_normalize_serving_size.t +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/perl -w - -use Modern::Perl '2017'; -use utf8; - -use Test::More; -use Test::Number::Delta relative => 1.001; -use Log::Any::Adapter 'TAP'; - -use ProductOpener::Tags qw/:all/; -use ProductOpener::Food qw/:all/; - -my @serving_sizes = ( - ["100g", "100"], - ["250 g", "250"], - ["1.5kg", "1500"], - ["2,5g", "2.5"], - ["1 plate (25g)", "25"], - ["1 grilled link (82g)", "82"], - ["2 buns = 20g", "20"], - ["43 someinvalidunit (430g)", "430"], - ["1500ml", "1500"], -); - -foreach my $test_ref (@serving_sizes) { - is(normalize_serving_size($test_ref->[0]), $test_ref->[1]); -} - -#TODO -# if (!defined(normalize_serving_size("20 someinvalidunit"))) -# { -# return 1; -# } - -# if (!defined(normalize_serving_size("15aug"))) -# { -# return 1; -# } - -done_testing(); diff --git a/tests/unit/units.t b/tests/unit/units.t new file mode 100644 index 0000000000000..483af5e8fd48c --- /dev/null +++ b/tests/unit/units.t @@ -0,0 +1,139 @@ +#!/usr/bin/perl -w + +use Modern::Perl '2017'; +use utf8; + +use Test::More; +use Test::Number::Delta relative => 1.001; +use Log::Any::Adapter 'TAP'; + +use ProductOpener::Units qw/:all/; + +# Based on https://de.wikipedia.org/w/index.php?title=Wasserh%C3%A4rte&oldid=160348959#Einheiten_und_Umrechnung +is(mmoll_to_unit(1, 'mol/l'), 0.001); +is(mmoll_to_unit('1', 'moll/l'), 1); +is(mmoll_to_unit(1, 'mmol/l'), 1); +is(mmoll_to_unit(1, 'mval/l'), 2); +is(mmoll_to_unit(1, 'ppm'), 100); +is(mmoll_to_unit(1, "\N{U+00B0}rH"), 40.080); +is(mmoll_to_unit(1, "\N{U+00B0}fH"), 10.00); +is(mmoll_to_unit(1, "\N{U+00B0}e"), 7.02); +is(mmoll_to_unit(1, "\N{U+00B0}dH"), 5.6); +is(mmoll_to_unit(1, 'gpg'), 5.847); + +is(unit_to_mmoll(1, 'mol/l'), 1000); +is(unit_to_mmoll('1', 'mmol/l'), 1); +is(unit_to_mmoll(1, 'mmol/l'), 1); +is(unit_to_mmoll(1, 'mval/l'), 0.5); +is(unit_to_mmoll(1, 'ppm'), 0.01); +delta_ok(unit_to_mmoll(1, "\N{U+00B0}rH"), 0.025); +delta_ok(unit_to_mmoll(1, "\N{U+00B0}fH"), 0.1); +delta_ok(unit_to_mmoll(1, "\N{U+00B0}e"), 0.142); +delta_ok(unit_to_mmoll(1, "\N{U+00B0}dH"), 0.1783); +delta_ok(unit_to_mmoll(1, 'gpg'), 0.171); + +is(mmoll_to_unit(unit_to_mmoll(1, 'ppm'), "\N{U+00B0}dH"), 0.056); + +# Chinese Measurements Source: http://www.new-chinese.org/lernwortschatz-chinesisch-masseinheiten.html +# kè - gram - 克 +is(normalize_quantity("42\N{U+514B}"), 42); +is(normalize_serving_size("42\N{U+514B}"), 42); +is(unit_to_g(42, "\N{U+514B}"), 42); +is(g_to_unit(42, "\N{U+514B}"), 42); +# gōngkè - gram - 公克 (in use at least in Taïwan) +is(normalize_quantity("42\N{U+516C}\N{U+514B}"), 42); +is(normalize_serving_size("42\N{U+516C}\N{U+514B}"), 42); +is(unit_to_g(42, "\N{U+516C}\N{U+514B}"), 42); +is(g_to_unit(42, "\N{U+516C}\N{U+514B}"), 42); +# héokè - milligram - 毫克 +is(normalize_quantity("42000\N{U+6BEB}\N{U+514B}"), 42); +is(normalize_serving_size("42000\N{U+6BEB}\N{U+514B}"), 42); +is(unit_to_g(42000, "\N{U+6BEB}\N{U+514B}"), 42); +is(g_to_unit(42, "\N{U+6BEB}\N{U+514B}"), 42000); +# jīn - pound 500 g - 斤 +is(normalize_quantity("84\N{U+65A4}"), 42000); +is(normalize_serving_size("84\N{U+65A4}"), 42000); +is(unit_to_g(84, "\N{U+65A4}"), 42000); +is(g_to_unit(42000, "\N{U+65A4}"), 84); +# gōngjīn - kg - 公斤 +is(normalize_quantity("42\N{U+516C}\N{U+65A4}"), 42000); +is(normalize_serving_size("42\N{U+516C}\N{U+65A4}"), 42000); +is(unit_to_g(42, "\N{U+516C}\N{U+65A4}"), 42000); +is(g_to_unit(42000, "\N{U+516C}\N{U+65A4}"), 42); +# háoshēng - milliliter - 毫升 +is(normalize_quantity("42\N{U+6BEB}\N{U+5347}"), 42); +is(normalize_serving_size("42\N{U+6BEB}\N{U+5347}"), 42); +is(unit_to_g(42, "\N{U+6BEB}\N{U+5347}"), 42); +is(g_to_unit(42, "\N{U+6BEB}\N{U+5347}"), 42); +# gōngshēng - liter - 公升 +is(normalize_quantity("42\N{U+516C}\N{U+5347}"), 42000); +is(normalize_serving_size("42\N{U+516C}\N{U+5347}"), 42000); +is(unit_to_g(42, "\N{U+516C}\N{U+5347}"), 42000); +is(g_to_unit(42000, "\N{U+516C}\N{U+5347}"), 42); + +# Russian units + +is(unit_to_g(1, "г"), 1); +is(unit_to_g(1, "мг"), 0.001); + +# unit conversion tests +# TODO +# if (!defined(unit_to_g(1, "unknown"))) +# { +# return 1; +# } +is(unit_to_g(1, "kj"), 1); +is(unit_to_g(1, "kcal"), 4); +is(unit_to_g(1000, "kcal"), 4184); +is(unit_to_g(1.2345, "kg"), 1234.5); +is(unit_to_g(1, "kJ"), 1); +is(unit_to_g(10, ""), 10); +is(unit_to_g(10, " "), 10); +is(unit_to_g(10, "% vol"), 10); +is(unit_to_g(10, "%"), 10); +is(unit_to_g(10, "% vol"), 10); +is(unit_to_g(10, "% DV"), 10); +is(unit_to_g(11, "mL"), 11); +is(g_to_unit(42000, "kg"), 42); +is(g_to_unit(28.349523125, "oz"), 1); +is(g_to_unit(30, "fl oz"), 1); +is(g_to_unit(1, "mcg"), 1000000); + +is(normalize_quantity("1 г"), 1); +is(normalize_quantity("1 мг"), 0.001); +is(normalize_quantity("1 кг"), 1000); +is(normalize_quantity("1 л"), 1000); +is(normalize_quantity("1 дл"), 100); +is(normalize_quantity("1 кл"), 10); +is(normalize_quantity("1 мл"), 1); + +is(normalize_quantity("250G"), 250); +is(normalize_quantity("4 x 25g"), 100); +is(normalize_quantity("4 x25g"), 100); +is(normalize_quantity("4 * 25g"), 100); +is(normalize_quantity("4X2,5L"), 10000); +is(normalize_quantity("1 barquette de 40g"), 40); +is(normalize_quantity("2 barquettes de 40g"), 80); +is(normalize_quantity("6 bouteilles de 33cl"), 6 * 33 * 10); +is(normalize_quantity("10 unités de 170g"), 1700); +is(normalize_quantity("10 unites, 170g"), 170); +is(normalize_quantity("4 bouteilles en verre de 20cl"), 800); +is(normalize_quantity("5 bottles of 20cl"), 100 * 10); + +my @serving_sizes = ( + ["100g", "100"], + ["250 g", "250"], + ["1.5kg", "1500"], + ["2,5g", "2.5"], + ["1 plate (25g)", "25"], + ["1 grilled link (82g)", "82"], + ["2 buns = 20g", "20"], + ["43 someinvalidunit (430g)", "430"], + ["1500ml", "1500"], +); + +foreach my $test_ref (@serving_sizes) { + is(normalize_serving_size($test_ref->[0]), $test_ref->[1]); +} + +done_testing();