From c9f319aa96e8ddbf2b3c35bff1b0f59b76292a2c Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 29 Jan 2025 16:15:30 +1100 Subject: [PATCH] Find broader taxon if we don't have a specific one For example, when importing `beef` products associate the type `meat-product` instead because we don't have a specific beef category. --- .../app/services/product_type_importer.rb | 28 ++++++-- .../services/product_type_importer_spec.rb | 67 +++++++++++++++++++ 2 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 engines/dfc_provider/spec/services/product_type_importer_spec.rb diff --git a/engines/dfc_provider/app/services/product_type_importer.rb b/engines/dfc_provider/app/services/product_type_importer.rb index 2852cdbd51..ab20478512 100644 --- a/engines/dfc_provider/app/services/product_type_importer.rb +++ b/engines/dfc_provider/app/services/product_type_importer.rb @@ -1,11 +1,31 @@ # frozen_string_literal: true class ProductTypeImporter < DfcBuilder + # Try to find the taxon closest matching the given product type. + # If we don't find any matching taxon, we return a random one. def self.taxon(product_type) - dfc_id = product_type&.semanticId + priority_list = [product_type, *list_broaders(product_type)].compact - # Every product needs a primary taxon to be valid. So if we don't have - # one or can't find it we just take a random one. - Spree::Taxon.find_by(dfc_id:) || Spree::Taxon.first + # Optimistic querying. + # We could query all broader taxons in one but then we need to still sort + # them locally and use more memory. That would be a pessimistic query. + # Consider caching the result instead. + taxons = priority_list.lazy.map do |type| + Spree::Taxon.find_by(dfc_id: type.semanticId) + end.compact + + taxons.first || Spree::Taxon.first + end + + def self.list_broaders(type) + return [] if type.nil? + + broaders = type.broaders.map do |id| + DataFoodConsortium::Connector::SKOSParser.concepts[id] + end + + broaders + broaders.flat_map do |broader| + list_broaders(broader) + end end end diff --git a/engines/dfc_provider/spec/services/product_type_importer_spec.rb b/engines/dfc_provider/spec/services/product_type_importer_spec.rb new file mode 100644 index 0000000000..915f8eaeb5 --- /dev/null +++ b/engines/dfc_provider/spec/services/product_type_importer_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +RSpec.describe ProductTypeImporter do + let(:drink) { + DfcLoader.connector.PRODUCT_TYPES.DRINK + } + let(:soft_drink) { + DfcLoader.connector.PRODUCT_TYPES.DRINK.SOFT_DRINK + } + let(:lemonade) { + DfcLoader.connector.PRODUCT_TYPES.DRINK.SOFT_DRINK.LEMONADE + } + + describe ".taxon" do + it "finds a linked taxon" do + create(:taxon, dfc_id: soft_drink.semanticId) + lemonade_taxon = create(:taxon, dfc_id: lemonade.semanticId) + expect(described_class.taxon(lemonade)).to eq lemonade_taxon + end + + it "falls back to a broader taxon" do + drink_taxon = create(:taxon, dfc_id: drink.semanticId) + expect(described_class.taxon(lemonade)).to eq drink_taxon + end + + it "returns random taxon when none can be found" do + only_taxon = create(:taxon) + expect(described_class.taxon(lemonade)).to eq only_taxon + end + + it "queries the database only until it found a taxon" do + soft_drink_taxon = create(:taxon, dfc_id: soft_drink.semanticId) + + expect { + expect(described_class.taxon(lemonade)).to eq soft_drink_taxon + }.to query_database [ + "Spree::Taxon Load", # query for lemonade, not found + "Spree::Taxon Load", # query for soft drink, found + # no query for drink + ] + end + end + + describe ".list_broaders" do + it "returns an empty array if no type is given" do + list = described_class.list_broaders(nil) + expect(list).to eq [] + end + + it "can return an empty list for top concepts" do + list = described_class.list_broaders(drink) + expect(list).to eq [] + end + + it "lists the broader concepts of a type" do + list = described_class.list_broaders(soft_drink) + expect(list).to eq [drink] + end + + it "lists all the broader concepts to the top concepts" do + list = described_class.list_broaders(lemonade) + expect(list).to eq [soft_drink, drink] + end + end +end