/* * Seldon -- open source prediction engine * ======================================= * * Copyright 2011-2015 Seldon Technologies Ltd and Rummble Ltd (http://www.seldon.io/) * * ******************************************************************************************** * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ******************************************************************************************** */ package io.seldon.api.resource.service; import io.seldon.api.APIException; import io.seldon.api.Constants; import io.seldon.api.Util; import io.seldon.api.caching.ActionHistoryCache; import io.seldon.api.resource.ActionBean; import io.seldon.api.resource.ConsumerBean; import io.seldon.api.resource.ItemBean; import io.seldon.api.resource.ItemRecommendationsBean; import io.seldon.api.resource.ListBean; import io.seldon.api.resource.RecommendationBean; import io.seldon.api.resource.ResourceBean; import io.seldon.general.RecommendationStorage; import io.seldon.memcache.MemCacheKeys; import io.seldon.recommendation.CFAlgorithm; import io.seldon.recommendation.LastRecommendationBean; import io.seldon.recommendation.Recommendation; import io.seldon.recommendation.RecommendationPeer; import io.seldon.recommendation.RecommendationPeer.RecResultContext; import io.seldon.recommendation.RecommendationResult; import io.seldon.recommendation.explanation.ExplanationPeer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.jdo.JDODataStoreException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author claudio */ @Service public class RecommendationService { private static Logger logger = Logger.getLogger(RecommendationService.class.getName()); private static final String RECOMMENDATION_UUID_ATTR = "recommendationUuid"; private static final String EXPLANATION_ATTR = "_explanation"; public static final String KEYWORD_PAR = "keywords"; @Autowired private RecommendationPeer recommender; @Autowired private RecommendationStorage recommendationStorage; @Autowired private ItemService itemService; @Autowired private UserService userService; @Autowired private ActionHistoryCache actionCache; @Autowired private ExplanationPeer explanationPeer; public static RecommendationBean findRecommendationBean(String itemId,ListBean list) { for(ResourceBean r : list.getList()) { RecommendationBean res = (RecommendationBean) r; if(res.getItem().equals(itemId)) { return res; } } return null; } @SuppressWarnings("unchecked") public ListBean getRecommendedUsers(ConsumerBean c,String userId, String itemId, String linkType, List<String> algorithms, int limit) { return getRecommendedUsers(c, userId, itemId, linkType, null, limit); } public static CFAlgorithm getAlgorithmOptions(ConsumerBean c,String userId,List<String> algorithms,String recTag) { //ALGORITHM CFAlgorithm cfAlgorithm = null; try { if (algorithms != null && !algorithms.isEmpty()) // if algorithms passed in this takes priority { cfAlgorithm = Util.getAlgorithmOptions(c, algorithms,recTag); } else { if (cfAlgorithm == null) // get server side assigned algorithm for client { cfAlgorithm = Util.getAlgorithmService().getAlgorithmOptions(c,recTag); } } } catch (CloneNotSupportedException e) { throw new APIException(APIException.CANNOT_CLONE_CFALGORITHM); } return cfAlgorithm; } public ResourceBean getRecommendedItems(ConsumerBean consumerBean, String userId, Long currentItemId, Set<Integer> dimensions, String lastRecommendationListUuid, int limit, String attributes,List<String> algorithms,String referrer,String recTag, boolean includeCohort,Set<Long> scoreItems,String locale) { List<String> actualAlgorithms = new ArrayList<>(); if(algorithms!=null) { for (String alg : algorithms) { if (alg.startsWith("recommenders")) { actualAlgorithms = Arrays.asList(alg.split(":")[1].split("\\|")); } } } int typeId = 0; boolean full = true; final String shortName = consumerBean.getShort_name(); // ListBean listBean = (ListBean) MemCachePeer.get(recommendedItemsKey(userId, cfAlgorithm, typeId, dimensionId, full, shortName)); // Limit the size of the retrieved bean // listBean = Util.getLimitedBean(listBean, limit); // if (listBean == null) { ListBean listBean = new ListBean(); Long internalUserId; try { internalUserId = userService.getInternalUserId(consumerBean, userId); } catch (APIException e) { internalUserId = Constants.ANONYMOUS_USER; } catch (JDODataStoreException e) { logger.error("Got a datastore exception trying to get userid for "+userId+" client "+shortName,e); internalUserId = Constants.ANONYMOUS_USER; } //Attributes List<String> attributeList = null; if(StringUtils.isNotBlank(attributes)) { attributeList = Arrays.asList(attributes.split(",")); } final ImmutablePair<RecommendationResult, RecResultContext> recResultPair = recommender.getRecommendations( internalUserId, consumerBean.getShort_name(), userId, typeId, dimensions, limit, lastRecommendationListUuid, currentItemId, referrer, recTag,actualAlgorithms, scoreItems); final RecommendationResult recResult = recResultPair.left; final String algKey = recResultPair.right.algKey; final Map<Long,String> item_recommender_lookup = recResultPair.right.item_recommender_lookup; List<Recommendation> recommendations = recResult.getRecs(); List<ItemBean> itemRecs = new ArrayList<>(); final boolean isExplanationNeeded = explanationPeer.isExplanationNeededForClient(shortName); for (Recommendation recommendation : recommendations) { long internalId = recommendation.getContent(); String recExplanation = null; if (isExplanationNeeded) { if (item_recommender_lookup != null) { /// String recommender_used = item_recommender_lookup.get(internalId); if (recommender_used != null) { if (logger.isDebugEnabled()) { String msg = String.format("explanation (per item) using recommender[%s] for item[%d]", recommender_used,internalId); logger.debug(msg); } recExplanation = explanationPeer.explainRecommendationResult(shortName, recommender_used, locale); } } if (recExplanation == null) { if (logger.isDebugEnabled()) { String msg = String.format("explanation (per set) using recommender[%s] for item[%d]", algKey,internalId); logger.debug(msg); } recExplanation = explanationPeer.explainRecommendationResult(shortName, algKey, locale); } } String recommendedItemId = null; try { recommendedItemId = itemService.getClientItemId(consumerBean, internalId); } catch (APIException e) { logger.info("Item with internal ID " + internalId + " not found; ignoring..." , e); } if (recommendedItemId != null) { final ItemBean itemBean = itemService.getItemLocalized(consumerBean, recommendedItemId, full,locale); if (isExplanationNeeded) { addExplanationAttribute(itemBean, recExplanation); } //filter the item ItemBean resItem = ItemService.filter(itemBean, attributeList); addUuidAttribute(resItem, recResult); itemRecs.add(resItem); } } if (includeCohort) { return new ItemRecommendationsBean(itemRecs,recResult.getCohort()); } else { listBean.addAll(itemRecs); listBean.setRequested(limit); listBean.setSize(recommendations.size()); return listBean; } } private static void addUuidAttribute(ItemBean itemBean, RecommendationResult recResult) { Map<String,String> attributesName = itemBean.getAttributesName(); if ( attributesName == null ) { attributesName = new HashMap<>(); } attributesName.put(RECOMMENDATION_UUID_ATTR, recResult.getUuid()); } private static void addExplanationAttribute(ItemBean itemBean, String recExplanation) { Map<String,String> attributesName = itemBean.getAttributesName(); if ( attributesName == null ) { attributesName = new HashMap<>(); } attributesName.put(EXPLANATION_ATTR, recExplanation); } private static String recommendedItemsKey(String userId, CFAlgorithm cfAlgorithm, int typeId, int dimensionId, boolean full, String shortName) { return MemCacheKeys.getRecommendedItemsKey(shortName, cfAlgorithm, userId, typeId, dimensionId, full); } public LastRecommendationBean retrieveLastRecs(ConsumerBean consumerBean, ActionBean actionBean,String recsCounter, String recTag){ return recommendationStorage.retrieveLastRecommendations(consumerBean.getShort_name(), actionBean.getUser(), recsCounter, recTag); } public List<Long> findIgnoredItemsFromLastRecs(ConsumerBean consumerBean, ActionBean actionBean, LastRecommendationBean lastRecs) { Long currentItem = itemService.getInternalItemId(consumerBean, actionBean.getItem()); if(lastRecs!=null) { for (int i = 0; i < lastRecs.getRecs().size(); i++) { if (lastRecs.getRecs().get(i).equals(currentItem)) return lastRecs.getRecs().subList(0, i); } } // not in there return Collections.emptyList(); } }