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