blob: 51cc5885cc88f98123c6aa3d9a65f9934c938bf6 [file] [log] [blame]
Ali Ghassemi5b408882015-01-16 14:58:00 -08001var mercury = require('mercury');
Alex Fandrianto94f155c2015-02-06 14:53:13 -08002var _ = require('lodash');
Ali Ghassemi5b408882015-01-16 14:58:00 -08003var EventEmitter = require('events').EventEmitter;
4
Alex Fandrianto94f155c2015-02-06 14:53:13 -08005var arraySet = require('../../lib/arraySet');
Ali Ghassemi5b408882015-01-16 14:58:00 -08006var freeze = require('../../lib/mercury/freeze');
7
8var namespaceService = require('../namespace/service');
9var smartService = require('../smart/service');
10
11var log = require('../../lib/log')('services:recommendations:service');
12
13var LEARNER_KEY = 'learner-shortcut';
14var MAX_NUM_RECOMMENDATIONS = 10;
15
16module.exports = {
Alex Fandrianto94f155c2015-02-06 14:53:13 -080017 getAll: getAll,
18 getRecommendationScore: getRecommendationScore,
19 setRecommendationScore: setRecommendationScore
Ali Ghassemi5b408882015-01-16 14:58:00 -080020};
21
22// Singleton state for all the bookmarks.
23var recommendationsObs = mercury.array([]);
24
25smartService.loadOrCreate(
26 LEARNER_KEY,
27 smartService.constants.LEARNER_SHORTCUT, {
28 k: MAX_NUM_RECOMMENDATIONS
29 }
30).catch(function(err) {
31 log.error(err);
32});
33
34/*
35 * Gets all the namespace items that are recommended based on our learning agent
36 * As new recommendations become available/removed the observable array will
37 * change to reflect the changes.
38 *
39 * The observable result has an events property which is an EventEmitter
40 * and emits 'end', 'itemError' events.
41 *
42 * @return {Promise.<mercury.array>} Promise of an observable array
43 * of recommended items
44 */
45function getAll() {
46
47 // Empty out the array
48 recommendationsObs.splice(0, recommendationsObs.getLength());
49 var immutableRecommendationsObs = freeze(recommendationsObs);
50 immutableRecommendationsObs.events = new EventEmitter();
51
52 return smartService.predict(LEARNER_KEY).then(getRecommendationItems);
53
54 function getRecommendationItems(recs) {
55 var allItems = recs.map(function(rec) {
56 var name = rec.item;
57 return addNamespaceItem(name).catch(function(err) {
58 immutableRecommendationsObs.events.emit('itemError', {
59 name: name,
60 error: err
61 });
62 log.error('Failed to create item for "' + name + '"', err);
63 });
64 });
65
66 Promise.all(allItems).then(function() {
67 immutableRecommendationsObs.events.emit('end');
68 }).catch(function() {
69 immutableRecommendationsObs.events.emit('end');
70 });
71
72 return immutableRecommendationsObs;
73 }
74}
75
76/*
77 * Gets the namespace items for a name and adds it to the observable array
78 */
79function addNamespaceItem(name) {
80 return namespaceService.getNamespaceItem(name)
81 .then(function(item) {
82 recommendationsObs.push(item);
83 });
Alex Fandrianto94f155c2015-02-06 14:53:13 -080084}
85
86/*
87 * Get the score of a particular recommended object name.
88 */
89function getRecommendationScore(name) {
90 return smartService.predict(LEARNER_KEY, {
91 name: name,
92 penalize: false
93 }).then(function(res) {
94 var match = res.filter(function(rec) {
95 return rec.item === name;
96 })[0];
97 return match ? match.score : 0;
98 });
99}
100
101/*
102 * Set the score of a particular object name.
103 * Note: This will penalize (or boost) parents of the given name.
104 */
105function setRecommendationScore(name, newScore) {
106 if (newScore > 0) {
107 addNamespaceItem(name);
108 } else {
109 arraySet.set(recommendationsObs, null, false, indexOf.bind(null, name));
110 }
111
112 return getRecommendationScore(name).then(function(curScore) {
113 var delta = newScore - curScore;
114 return smartService.update(LEARNER_KEY, {
115 name: name,
116 weight: delta
117 }).then(function() {
118 return curScore;
119 });
120 }).catch(function(err) {
121 log.error('Failed to set the recommendation score of', name, 'to', newScore,
122 err);
123 });
124}
125
126/*
127 * Check the observe array for the index of the given item. -1 if not present.
128 */
129function indexOf(name) {
130 return _.findIndex(recommendationsObs(), function(rec) {
131 // Since recommendations can be assigned out of order, check for undefined.
132 return rec !== undefined && name === rec.objectName;
133 });
Ali Ghassemi5b408882015-01-16 14:58:00 -0800134}