Product recommendation implementation

This section explains how the "Product recommendation" is implemented in the Accelerator based on Elasticsearch.

Elasticsearch provides the possibility to recommend products to customers based on what other customers who bought the same product purchased along with it.

There are some classes that are implementing the "Product recommendation" functional in Accelerator.

Purchase History Document

The PurchaseHistoryDocument is used for creating an index and searching for recommended products.

public class PurchaseHistoryDocument : IDocument
    {
        [Keyword(Ignore = true)]
        public string Id { get; set; }
        [Keyword]
        public Guid User { get; set; } // Person systemId
        [Keyword]
        public List<string> Products { get; set; } // Article numbers
    }

Purchase History Event Listener

The PurchaseHistoryEventListener is responsible for adding customer products from an order to the PurchaseHistoryDocument index when the order has it's status complete.

 public class PurchaseHistoryEventListener : IIndexQueueHandlerRegistration
    {
        private readonly ModuleECommerce _moduleECommerce;
        private readonly IndexQueueService _indexQueueService;

        public PurchaseHistoryEventListener(
            ModuleECommerce moduleECommerce,
            IndexQueueService indexQueueService)
        {
            _moduleECommerce = moduleECommerce;
            _indexQueueService = indexQueueService;

            _moduleECommerce.EventManager.OrderCreated += OnUpdate;
            _moduleECommerce.EventManager.OrderUpdated += OnUpdate;
            _moduleECommerce.EventManager.OrderDeleted += OnUpdate;
        }

        private void OnUpdate(Guid orderId)
        {
            var order = _moduleECommerce.Orders.GetOrder(orderId, Solution.Instance.SystemToken);
            if (order.OrderStatus == (short)OrderState.Completed)
            {
                var customerId = order.CustomerInfo.PersonID != null ? order.CustomerInfo.PersonID : order.CustomerInfo.OrganizationID;
                if (customerId == Guid.Empty)
                {
                    return;
                }
                _indexQueueService.Enqueue(new IndexQueueItem<PurchaseHistoryDocument>(customerId) { Action = IndexAction.Index });
            }
        }
    }

Product Service Decorator

The ProductServiceDecorator implements the GetMostSoldProducts method to get recommended products for a product.
GetMostSoldProducts method is a place where more complex algorithms can be used to get recommended products.
In the current implementation, the GetMostSoldProducts method uses the "significant_terms" aggregation to get the recommended products.


The "significant_terms" aggregation analyzes purchase data and returns recommend products based on what other customers who bought the same product also purchased.
The "min_doc_count" field has 2 in value. This means that at least two customers must purchase the same product before the product can be treated as recommended.

public override List<Variant> GetMostSoldProducts(Guid webSiteId, Guid channelId, string articleNumber = null, Guid[] productGroupIds = null, int numberOfProducts = 4, string ignoreVariantId = "")
        {
            if (!_searchClientService.IsConfigured)
            {
                return _parent.GetMostSoldProducts(webSiteId, channelId, articleNumber, productGroupIds, numberOfProducts, ignoreVariantId);
            }

            var excludeArticleNumbers = new string[] { articleNumber, ignoreVariantId }.Where(n => !string.IsNullOrEmpty(n)).Distinct();
            var rawSearchResponse = _searchClientService
                .Search<PurchaseHistoryDocument>(selector => selector
                    .Size(0)
                    .Query(queryContainerDescriptor => queryContainerDescriptor.Term(t => t.Field(x => x.Products).Value(articleNumber)))
                    .Aggregations(aggregationDescriptor => aggregationDescriptor.SignificantTerms("product_recommendations", selector => selector
                                                                                    .Field(x => x.Products)
                                                                                    .Exclude(excludeArticleNumbers)
                                                                                    .MinimumDocumentCount(2)))
                );
            var articleNumbers = rawSearchResponse.Aggregations.SignificantTerms("product_recommendations")
                .Buckets
                .OrderByDescending(i => i.Score)
                .Select(i => i.Key)
                .ToList();
            ...
        }