Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: packaging stats per parent materials #8594

Merged
merged 12 commits into from
Jun 26, 2023
8 changes: 7 additions & 1 deletion lib/ProductOpener/KnowledgePanels.pm
Original file line number Diff line number Diff line change
Expand Up @@ -703,10 +703,16 @@ sub create_environment_card_panel ($product_ref, $target_lc, $target_cc, $option
$panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref);
}

# Create panel for packaging recycling
# Create panel for packaging components, and packaging materials
create_panel_from_json_template("packaging_recycling",
"api/knowledge-panels/environment/packaging_recycling.tt.json",
$panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref);
create_panel_from_json_template("packaging_materials",
"api/knowledge-panels/environment/packaging_materials.tt.json",
$panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref);
create_panel_from_json_template("packaging_components",
"api/knowledge-panels/environment/packaging_components.tt.json",
$panel_data_ref, $product_ref, $target_lc, $target_cc, $options_ref);

# Create panel for manufacturing place
create_manufacturing_place_panel($product_ref, $target_lc, $target_cc, $options_ref);
Expand Down
107 changes: 90 additions & 17 deletions lib/ProductOpener/Packaging.pm
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ BEGIN {
&parse_packaging_component_data_from_text_phrase
&guess_language_of_packaging_text
&apply_rules_to_augment_packaging_component_data
&aggregate_packaging_by_parent_materials

%packaging_taxonomies
); # symbols to export on request
Expand All @@ -66,6 +67,8 @@ use ProductOpener::Numbers qw/:all/;
use ProductOpener::Units qw/:all/;
use ProductOpener::ImportConvert qw/:all/;

use Data::DeepAccess qw(deep_get deep_val);

=head1 FUNCTIONS

=head2 extract_packagings_from_image( $product_ref $id $ocr_engine $results_ref )
Expand Down Expand Up @@ -776,27 +779,29 @@ Set some tags in the /misc/ facet so that we can track the products that have

sub set_packaging_misc_tags ($product_ref) {

remove_tag($product_ref, "misc", "en:packagings-complete");
remove_tag($product_ref, "misc", "en:packagings-not-complete");
remove_tag($product_ref, "misc", "en:packagings-empty");
remove_tag($product_ref, "misc", "en:packagings-not-empty");
remove_tag($product_ref, "misc", "en:packagings-not-empty-but-not-complete");
remove_tag($product_ref, "misc", "en:packagings-with-weights");
remove_tag($product_ref, "misc", "en:packagings-with-all-weights");
remove_tag($product_ref, "misc", "en:packagings-with-all-weights-complete");
remove_tag($product_ref, "misc", "en:packagings-with-all-weights-not-complete");
remove_tag($product_ref, "misc", "en:packagings-with-some-but-not-all-weights");
if (defined $product_ref->{misc_tags}) {
remove_tag($product_ref, "misc", "en:packagings-complete");
remove_tag($product_ref, "misc", "en:packagings-not-complete");
remove_tag($product_ref, "misc", "en:packagings-empty");
remove_tag($product_ref, "misc", "en:packagings-not-empty");
remove_tag($product_ref, "misc", "en:packagings-not-empty-but-not-complete");
remove_tag($product_ref, "misc", "en:packagings-with-weights");
remove_tag($product_ref, "misc", "en:packagings-with-all-weights");
remove_tag($product_ref, "misc", "en:packagings-with-all-weights-complete");
remove_tag($product_ref, "misc", "en:packagings-with-all-weights-not-complete");
remove_tag($product_ref, "misc", "en:packagings-with-some-but-not-all-weights");

# Remove previous misc tag for the number of components
foreach my $tag ($product_ref->{misc_tags}) {
if ($tag =~ /^en:packagings-number-of-components-/) {
remove_tag($product_ref, "misc", $tag);
}
}
}

# Number of packaging components
my $number_of_packaging_components
= (defined $product_ref->{packagings} ? scalar @{$product_ref->{packagings}} : 0);

# Remove previous misc tag for the number of components
foreach my $tag ($product_ref->{misc_tags}) {
if ($tag =~ /^en:packagings-number-of-components-/) {
remove_tag($product_ref, "misc", $tag);
}
}
# Add the tag for the new number of components
add_tag($product_ref, "misc", "en:packagings-number-of-components-" . $number_of_packaging_components);

Expand Down Expand Up @@ -846,6 +851,71 @@ sub set_packaging_misc_tags ($product_ref) {
return;
}

=head2 aggregate_packaging_by_parent_materials ($product_ref)

Aggregate the weights of each packaging component by parent material (glass, plastics, metal, paper or cardboard)

=cut

sub aggregate_packaging_by_parent_materials ($product_ref) {

my $packagings_materials_ref = {"all" => {}};

if (defined $product_ref->{packagings}) {

# Iterate over each packaging component
foreach my $packaging_ref (@{$product_ref->{packagings}}) {

# Determine what is the parent material for the component
my $material = $packaging_ref->{material};
my $parent_material = "en:unknown";
if (defined $material) {
foreach my $parent ("en:paper-or-cardboard", "en:plastic", "en:glass", "en:metal") {
if (is_a("packaging_materials", $material, $parent)) {
$parent_material = $parent;
last;
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's okay, but don't we have a more efficient way to do this ? (like fetching all parents and then computing intersection with our values ?). Maybe with something like Set::Tiny.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know enough of the source code to suggest an entirely different approach to the logic itself, but a common pattern for getting the first item in a list that matches a certain criteria is using List::Util's first:

use List::Util qw(first);

$parent_material = first {
      is_a("packaging_materials", $material, $_)
} ("en:paper-or-cardboard", "en:plastic", "en:glass", "en:metal");

but if this code is hot (i.e. running several times per call) maybe it could be worth turning is_a() into a hash lookup.

Either way, the current implementation is okay and I wouldn't change it unless it was a bottleneck.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestions. I changed to @garu 's suggestion as I find it more readable with first indeed.

It certainly could be done more efficiently, but this code is executed only when writing products, and given the shallowness of the packaging materials taxonomy, it's just a dozen operations, so I'd prefer not to spend time on it. If we want to optimize things, there are targets that are much more likely to make a difference (e.g. for read operations of products, where we compute attributes and knowledge panels for 100 products...)


# Initialize the entry for the parent material if needed (even if we have no weight,
# it is useful to know that there is some parent material used)
if (not defined $packagings_materials_ref->{$parent_material}) {
$packagings_materials_ref->{$parent_material} = {};
}

# Weight per unit
my $weight = $packaging_ref->{weight_specified} || $packaging_ref->{weight_measured};
stephanegigandet marked this conversation as resolved.
Show resolved Hide resolved
if (defined $weight) {
# Assume we have 1 unit if not specified
my $total_weight = ($packaging_ref->{number_of_units} || 1) * $weight;

# Add the weight to the parent material, and to a special "all" entry for all materials
deep_val($packagings_materials_ref, $parent_material, "weight") += $total_weight;
deep_val($packagings_materials_ref, "all", "weight") += $total_weight;
}
}

# Iterate over each parent material to compute weight statistics
my $total_weight = deep_get($packagings_materials_ref, "all", "weight");
foreach my $parent_material_ref (values %$packagings_materials_ref) {
if (defined $parent_material_ref->{weight}) {
if ($total_weight) {
$parent_material_ref->{weight_percent} = $parent_material_ref->{weight} / $total_weight * 100;
}
if ($product_ref->{product_quantity}) {
$parent_material_ref->{weight_100g}
= $parent_material_ref->{weight} / $product_ref->{product_quantity} * 100;
}
}
}
}

$product_ref->{packagings_materials} = $packagings_materials_ref;

return;
}

=head2 initialize_packagings_structure_with_data_from_packaging_text ($product_ref, $response_ref)

This function populates the packagings structure with data extracted from the packaging_text field.
Expand Down Expand Up @@ -962,6 +1032,9 @@ sub analyze_and_combine_packaging_data ($product_ref, $response_ref) {
# Set packaging facets tags for shape, material and recycling
set_packaging_facets_tags($product_ref);

# Aggregate data per parent material
aggregate_packaging_by_parent_materials($product_ref);

$log->debug("analyze_and_combine_packaging_data - done",
{packagings => $product_ref->{packagings}, response => $response_ref})
if $log->is_debug();
Expand Down
16 changes: 16 additions & 0 deletions po/common/common.pot
Original file line number Diff line number Diff line change
Expand Up @@ -6698,3 +6698,19 @@ msgstr "Sugars should not be higher than carbohydrates."
msgctxt "product_js_saturated_fat_warning"
msgid "Saturated fat should not be higher than fat."
msgstr "Saturated fat should not be higher than fat."

msgctxt "packaging_materials"
msgid "Packaging materials"
msgstr "Packaging materials"

msgctxt "packaging_weight_total"
msgid "Packaging weight"
msgstr "Packaging weight"

msgctxt "packaging_weight_100g"
msgid "Packaging weight per 100 g of product"
msgstr "Packaging weight per 100 g of product"

msgctxt "total"
msgid "Total"
msgstr "Total"
16 changes: 16 additions & 0 deletions po/common/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -6708,3 +6708,19 @@ msgstr "Sugars should not be higher than carbohydrates."
msgctxt "product_js_saturated_fat_warning"
msgid "Saturated fat should not be higher than fat."
msgstr "Saturated fat should not be higher than fat."

msgctxt "packaging_materials"
msgid "Packaging materials"
msgstr "Packaging materials"

msgctxt "packaging_weight_total"
msgid "Packaging weight"
msgstr "Packaging weight"

msgctxt "packaging_weight_100g"
msgid "Packaging weight per 100 g of product"
msgstr "Packaging weight per 100 g of product"

msgctxt "total"
msgid "Total"
msgstr "Total"
15 changes: 15 additions & 0 deletions po/common/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -6534,3 +6534,18 @@ msgctxt "join_the_next_pros_meet_up"
msgid "Join the next Pros' Meet-up"
msgstr "Rejoignez la prochaine rencontre des professionnels"

msgctxt "packaging_materials"
msgid "Packaging materials"
msgstr "Matières d'emballage"

msgctxt "packaging_weight_total"
msgid "Packaging weight"
msgstr "Poids de l'emballage"

msgctxt "packaging_weight_100g"
msgid "Packaging weight per 100 g of product"
msgstr "Poids de l'emballage pour 100 g de produit"

msgctxt "total"
msgid "Total"
msgstr "Total"
5 changes: 5 additions & 0 deletions taxonomies/packaging_materials.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,10 @@ pt:Bateria de zinco-carbono, pilha de zinco-carbono
xx:14 CZ, CZ 14
wikidata:en:Q28765

en:Paper or cardboard
fr:Papier ou carton

<en:Paper or cardboard
en:Cardboard, fiberboard, fibreboard
bg:Картон
ca:cartó
Expand Down Expand Up @@ -1152,6 +1156,7 @@ pt:Cartão FSC
en:Grass carton
de:Graskarton

<en:Paper or cardboard
en:Paper
bg:Хартия
da:Papir
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"title": "[% lang('ecoscore_packaging') %]",
"panel_group_id": "packaging_recycling",
"panel_ids": [
"packaging_recycling"
"packaging_recycling",
],
[% IF panel.packaging_image.defined %]
"image": [% encode_json(panel.packaging_image) %],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Create a structure so that we can display the packaging components for each recycling type
[% SET recycling_types = {} %]
[% SET unknown = "en:unknown" %]
[% IF product.packagings %]
[% FOREACH packaging IN product.packagings %]
[% IF packaging.recycling.defined AND ((packaging.recycling == "en:discard") OR (packaging.recycling == "en:recycle")) %]
[% SET recycling_types.${packaging.recycling} = 1 %]
[% ELSE %]
[% SET recycling_types.$unknown = 1 %]
[% END %]
[% END %]
stephanegigandet marked this conversation as resolved.
Show resolved Hide resolved
[% END %]

{
"level" :"info",
"topics": [
"environment"
],
"title_element": {
"title": "[% lang('packaging_parts') %]",
},
"expanded": true,
"elements": [
[% FOREACH recycling_type IN ["en:recycle", "en:discard", "en:unknown"] %]
[% IF recycling_types.$recycling_type.defined %]
{
"element_type": "text",
"text_element": {
"type": "summary",
"icon_color_from_evaluation": true,
[% IF recycling_type == "en:recycle" %]
"evaluation": "good",
"icon_url": "[% static_subdomain %]/images/icons/dist/recycle-variant.svg",
"icon_alt": "[% display_taxonomy_tag_name("packaging_recycling",recycling_type) %]",
[% ELSIF recycling_type == "en:discard" %]
"evaluation": "bad",
"icon_url": "[% static_subdomain %]/images/icons/dist/delete.svg",
"icon_alt": "[% display_taxonomy_tag_name("packaging_recycling",recycling_type) %]",
[% ELSE %]
"evaluation": "neutral",
"icon_url": "[% static_subdomain %]/images/icons/dist/help.svg",
"icon_alt": "[% lang('unknown') %]",
[% END %]
"html": `
[% FOREACH packaging IN product.packagings %]
[% IF packaging.recycling == recycling_type OR (recycling_type == "en:unknown" AND ((NOT packaging.recycling.defined) OR ((packaging.recycling != "en:discard") AND (packaging.recycling != "en:recycle")))) %]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better way to do that, would have been to dispatch packagings in arrays in recycling_types instead of putting => 1. So that you just have to iterate over the hash, and elements in array.

Because this condition is not trivial to read !

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but it's likely that we will change how we display packaging components in the future. This code did not change, I just renamed the template for clarity.

[% IF packaging.number_of_units %][% packaging.number_of_units %] x [% END %]
<strong>
[% display_taxonomy_tag_name('packaging_shapes',packaging.shape) %]
[% IF packaging.quantity_per_unit %][% packaging.quantity_per_unit %] [% END %]
</strong>
[% 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 %]
<br>
[% END %]
[% END %]
`
}
},
[% END %]
[% END %]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"level" :"info",
"topics": [
"environment"
],
"title_element": {
"title": "[% lang('packaging_materials') %]",
},
"expanded": true,
"elements": [
{
"element_type": "table",
"table_element": {
"id": "packaging_materials",
"title": "[% lang('packaging_materials') %]",
"columns": [
{
"text": "[% lang('packaging_material') %]",
"type": "text",
},
{
"text": "%",
"type": "text",
},
{
"text": "[% lang('packaging_weight_total') %]",
"type": "text",
},
// packaging weight per 100g of product is computed only if we have a quantity
[% IF product.product_quantity %]
{
"text": "[% lang('packaging_weight_100g') %]",
"type": "text",
},
[% END %]
],
"rows": [
// keep a counter of materials so that we don't display the "all" raw if there's only one material
[% SET materials = 0 %]
[% FOREACH parent_material IN ["en:paper-or-cardboard", "en:plastic", "en:glass", "en:metal", "all"] %]
[% IF product.packagings_materials.$parent_material.defined %]
[% IF parent_material != 'all' OR materials > 1 %]
[% SET materials = materials + 1 %]
[% SET parent_material_data = product.packagings_materials.$parent_material %]
{
[% IF parent_material == 'all' %]
"style": "font-weight: bold",
[% END %]
"values": [
{
[% IF parent_material == 'all' %]
"text": "[% lang('total') %]",
[% ELSE %]
"text": "[% display_taxonomy_tag_name('packaging_materials',parent_material) %]"
[% END %]
},
{
"text": "[% IF parent_material_data.weight_percent %][% round(parent_material_data.weight_percent) %]%[% END %]",
},
{
"text": "[% IF parent_material_data.weight %][% parent_material_data.weight %] g[% END %]"
},
[% IF product.product_quantity %]
{
"text": "[% IF parent_material_data.weight_100g %][% round(parent_material_data.weight_100g) %] g[% END %]"
},
[% END %]
]
},
[% END %]
[% END %]
[% END %]
]
}
},
]
}
Loading