1 | ger_tests = (ESM) ->
|
2 | ns = global.default_namespace
|
3 |
|
4 | describe 'recommending for a person', ->
|
5 |
|
6 | it 'should recommend similar things', ->
|
7 | init_ger(ESM)
|
8 | .then (ger) ->
|
9 | bb.all([
|
10 | ger.event(ns, 'p1','view','a', expires_at: tomorrow),
|
11 | ger.event(ns, 'p2','view','a', expires_at: tomorrow),
|
12 | ger.event(ns, 'p2','view','b', expires_at: tomorrow),
|
13 | ])
|
14 | .then(-> ger.recommendations_for_person(ns, 'p1', actions: {view: 1}, filter_previous_actions: ['view']))
|
15 | .then((recs) ->
|
16 | recs = recs.recommendations
|
17 | recs.length.should.equal 1
|
18 | recs[0].thing.should.equal 'b'
|
19 | )
|
20 |
|
21 |
|
22 | it 'should not return a weight of NaN if person similarity is 0', ->
|
23 | init_ger(ESM)
|
24 | .then (ger) ->
|
25 | bb.all([
|
26 | ger.event(ns, 'p1','view','a', expires_at: tomorrow),
|
27 | ger.event(ns, 'p2','view','x', created_at: today, expires_at: tomorrow),
|
28 | ger.event(ns, 'p2','view','a', created_at: yesterday, expires_at: tomorrow),
|
29 | ])
|
30 | .then(-> ger.recommendations_for_person(ns, 'p1', actions: {view: 1}, filter_previous_actions: ['view'], neighbourhood_search_size: 1))
|
31 | .then((recs) ->
|
32 | for r in recs.recommendations
|
33 | throw "BAD WEIGHT #{r.weight}" if not _.isFinite(r.weight)
|
34 |
|
35 | throw "BAD Confidence #{recommendations_object.confidence}" if not _.isFinite(recs.confidence)
|
36 | )
|
37 |
|
38 | describe 'time_until_expiry', ->
|
39 | it 'should not return recommendations that will expire within time_until_expiry seconds', ->
|
40 | one_hour = 60*60
|
41 | one_day = 24*one_hour
|
42 | a1day = moment().add(1, 'days').format()
|
43 | a2days = moment().add(2, 'days').format()
|
44 | a3days = moment().add(3, 'days').format()
|
45 |
|
46 | init_ger(ESM)
|
47 | .then (ger) ->
|
48 | bb.all([
|
49 | ger.event(ns, 'p1','view','a'),
|
50 |
|
51 | ger.event(ns, 'p2','view','a'),
|
52 | ger.event(ns, 'p2','buy','x', expires_at: a1day),
|
53 | ger.event(ns, 'p2','buy','y', expires_at: a2days),
|
54 | ger.event(ns, 'p2','buy','z', expires_at: a3days)
|
55 | ])
|
56 | .then(-> ger.recommendations_for_person(ns, 'p1', time_until_expiry: (one_day + one_hour), actions: {view: 1, buy: 1}))
|
57 | .then((recs) ->
|
58 | recs = recs.recommendations
|
59 | recs.length.should.equal 2
|
60 | sorted_recs = [recs[0].thing, recs[1].thing].sort()
|
61 | sorted_recs[0].should.equal 'y'
|
62 | sorted_recs[1].should.equal 'z'
|
63 | )
|
64 |
|
65 | describe "minimum_history_required", ->
|
66 | it "should not generate recommendations for events ", ->
|
67 | init_ger(ESM)
|
68 | .then (ger) ->
|
69 | bb.all([
|
70 | ger.event(ns, 'p1','view','a', expires_at: tomorrow),
|
71 | ger.event(ns, 'p2','view','a', expires_at: tomorrow),
|
72 | ger.event(ns, 'p2','view','b', expires_at: tomorrow),
|
73 | ])
|
74 | .then(-> ger.recommendations_for_person(ns, 'p1', minimum_history_required: 2, actions: {view: 1}))
|
75 | .then((recs) ->
|
76 | recs.recommendations.length.should.equal 0
|
77 | ger.recommendations_for_person(ns, 'p2', minimum_history_required: 2, actions: {view: 1})
|
78 | ).then((recs) ->
|
79 | recs.recommendations.length.should.equal 2
|
80 | )
|
81 |
|
82 |
|
83 | describe "joining multiple gers", ->
|
84 | it "similar recommendations should return same confidence", ->
|
85 | ns1 = 'ger_1'
|
86 | ns2 = 'ger_2'
|
87 | bb.all([
|
88 | init_ger(ESM, ns1),
|
89 | init_ger(ESM, ns2)
|
90 | ])
|
91 | .spread (ger1, ger2) ->
|
92 | bb.all([
|
93 |
|
94 | ger1.event(ns1, 'p1','view','a', expires_at: tomorrow),
|
95 | ger1.event(ns1, 'p2','view','a', expires_at: tomorrow),
|
96 | ger1.event(ns1, 'p2','buy','b', expires_at: tomorrow),
|
97 |
|
98 | ger2.event(ns2, 'p1','view','a', expires_at: tomorrow),
|
99 | ger2.event(ns2, 'p2','view','a', expires_at: tomorrow),
|
100 | ger2.event(ns2, 'p2','buy','b', expires_at: tomorrow),
|
101 | ])
|
102 | .then( -> bb.all([
|
103 | ger1.recommendations_for_person(ns1, 'p1', {neighbourhood_size: 2, neighbourhood_search_size: 4, actions: {view: 1}}),
|
104 | ger2.recommendations_for_person(ns2, 'p1', {neighbourhood_size: 4, neighbourhood_search_size: 8, actions: {view: 1}})
|
105 | ])
|
106 | )
|
107 | .spread((recs1, recs2) ->
|
108 | recs1.confidence.should.equal recs2.confidence
|
109 | )
|
110 |
|
111 |
|
112 | describe "confidence", ->
|
113 |
|
114 | it 'should return a confidence ', ->
|
115 | init_ger(ESM)
|
116 | .then (ger) ->
|
117 | bb.all([
|
118 | ger.event(ns, 'p1','action1','a', expires_at: tomorrow),
|
119 | ger.event(ns, 'p2','action1','a', expires_at: tomorrow),
|
120 | ])
|
121 | .then(-> ger.recommendations_for_person(ns, 'p1', actions: {action1: 1}))
|
122 | .then((similar_people) ->
|
123 | similar_people.confidence.should.exist
|
124 | )
|
125 |
|
126 | it 'should return a confidence of 0 not NaN', ->
|
127 | init_ger(ESM)
|
128 | .then (ger) ->
|
129 | bb.all([
|
130 | ger.event(ns, 'p1','action1','a', expires_at: tomorrow)
|
131 | ])
|
132 | .then(-> ger.recommendations_for_person(ns, 'p1', actions: {action1: 1}))
|
133 | .then((similar_people) ->
|
134 | similar_people.confidence.should.equal 0
|
135 | )
|
136 |
|
137 | it "higher weighted recommendations should return greater confidence", ->
|
138 | init_ger(ESM)
|
139 | .then (ger) ->
|
140 | bb.all([
|
141 | ger.event(ns, 'p1','view','a', expires_at: tomorrow),
|
142 | ger.event(ns, 'p1','view','b', expires_at: tomorrow),
|
143 | ger.event(ns, 'p2','view','a', expires_at: tomorrow),
|
144 | ger.event(ns, 'p2','view','b', expires_at: tomorrow),
|
145 | ger.event(ns, 'p2','view','c', expires_at: tomorrow),
|
146 |
|
147 | ger.event(ns, 'p3','view','x', expires_at: tomorrow),
|
148 | ger.event(ns, 'p3','view','y', expires_at: tomorrow),
|
149 | ger.event(ns, 'p4','view','x', expires_at: tomorrow),
|
150 | ger.event(ns, 'p4','view','z', expires_at: tomorrow),
|
151 | ])
|
152 | .then(->
|
153 | bb.all([
|
154 | ger.recommendations_for_person(ns, 'p1', actions: {view: 1})
|
155 | ger.recommendations_for_person(ns, 'p3', actions: {view: 1})
|
156 | ])
|
157 | )
|
158 | .spread((recs1, recs2) ->
|
159 | recs1.confidence.should.greaterThan recs2.confidence
|
160 | )
|
161 |
|
162 | it "more similar people should return greater confidence", ->
|
163 | init_ger(ESM)
|
164 | .then (ger) ->
|
165 | bb.all([
|
166 | ger.event(ns, 'p1','view','a', expires_at: tomorrow),
|
167 | ger.event(ns, 'p2','view','a', expires_at: tomorrow),
|
168 |
|
169 | ger.event(ns, 'p3','view','b', expires_at: tomorrow),
|
170 | ger.event(ns, 'p4','view','b', expires_at: tomorrow),
|
171 | ger.event(ns, 'p5','view','b', expires_at: tomorrow),
|
172 | ])
|
173 | .then(->
|
174 | bb.all([
|
175 | ger.recommendations_for_person(ns, 'p1', actions: {view: 1})
|
176 | ger.recommendations_for_person(ns, 'p3', actions: {view: 1})
|
177 | ])
|
178 | )
|
179 | .spread((recs1, recs2) ->
|
180 |
|
181 | recs2.confidence.should.greaterThan recs1.confidence
|
182 | )
|
183 |
|
184 | it "longer history should mean more confidence", ->
|
185 | init_ger(ESM)
|
186 | .then (ger) ->
|
187 | bb.all([
|
188 | ger.event(ns, 'p1','view','a', expires_at: tomorrow),
|
189 | ger.event(ns, 'p2','view','a', expires_at: tomorrow),
|
190 |
|
191 | ger.event(ns, 'p3','view','x', expires_at: tomorrow),
|
192 | ger.event(ns, 'p3','view','b', expires_at: tomorrow),
|
193 | ger.event(ns, 'p4','view','x', expires_at: tomorrow),
|
194 | ger.event(ns, 'p4','view','b', expires_at: tomorrow),
|
195 | ])
|
196 | .then(->
|
197 | bb.all([
|
198 | ger.recommendations_for_person(ns, 'p1', actions: {view: 1})
|
199 | ger.recommendations_for_person(ns, 'p3', actions: {view: 1})
|
200 | ])
|
201 | )
|
202 | .spread((recs1, recs2) ->
|
203 |
|
204 | recs2.confidence.should.greaterThan recs1.confidence
|
205 | )
|
206 |
|
207 | it "should not return NaN as conifdence", ->
|
208 | init_ger(ESM)
|
209 | .then (ger) ->
|
210 | bb.all([
|
211 | ger.event(ns, 'p1','view','a', expires_at: tomorrow),
|
212 | ])
|
213 | .then(-> ger.recommendations_for_person(ns, 'p1', actions: {view: 1}))
|
214 | .then((recs) ->
|
215 | recs.confidence.should.equal 0
|
216 | )
|
217 |
|
218 | describe "weights", ->
|
219 | it "weights should determine the order of the recommendations", ->
|
220 | init_ger(ESM)
|
221 | .then (ger) ->
|
222 | bb.all([
|
223 | ger.event(ns, 'p1','view','a', expires_at: tomorrow),
|
224 | ger.event(ns, 'p1','buy','b', expires_at: tomorrow),
|
225 |
|
226 | ger.event(ns, 'p2','view','a', expires_at: tomorrow),
|
227 | ger.event(ns, 'p2','view','c', expires_at: tomorrow),
|
228 |
|
229 | ger.event(ns, 'p3','buy','b', expires_at: tomorrow),
|
230 | ger.event(ns, 'p3','buy','d', expires_at: tomorrow),
|
231 | ])
|
232 | .then(-> ger.recommendations_for_person(ns, 'p1', actions: {view: 1, buy: 1}, filter_previous_actions: ['buy', 'view'] ))
|
233 | .then((recs) ->
|
234 | item_weights = recs.recommendations
|
235 | item_weights.length.should.equal 2
|
236 | item_weights[0].weight.should.equal item_weights[1].weight
|
237 |
|
238 | ger.recommendations_for_person(ns, 'p1', actions: {view: 1, buy: 2}, filter_previous_actions: ['buy', 'view'])
|
239 | )
|
240 | .then((recs) ->
|
241 | item_weights = recs.recommendations
|
242 | item_weights[0].weight.should.be.greaterThan item_weights[1].weight
|
243 | item_weights[0].thing.should.equal 'd'
|
244 | item_weights[1].thing.should.equal 'c'
|
245 | )
|
246 |
|
247 | it 'should negative weights should reduce recommended item', ->
|
248 | init_ger(ESM)
|
249 | .then (ger) ->
|
250 | bb.all([
|
251 | ger.event(ns, 'p1','likes','a'),
|
252 | ger.event(ns, 'p1','likes','b'),
|
253 |
|
254 | ger.event(ns, 'p2','likes','a'),
|
255 | ger.event(ns, 'p2','hates','b'),
|
256 | ger.event(ns, 'p2','likes','x', expires_at: tomorrow),
|
257 |
|
258 | ger.event(ns, 'p3','likes','a'),
|
259 | ger.event(ns, 'p3','likes','b'),
|
260 | ger.event(ns, 'p3','likes','y', expires_at: tomorrow),
|
261 |
|
262 | ])
|
263 | .then(-> ger.recommendations_for_person(ns, 'p1', actions: {likes: 1, hates: -1}))
|
264 | .then((recs) ->
|
265 |
|
266 | item_weights = recs.recommendations
|
267 | item_weights.length.should.equal 2
|
268 | item_weights[0].thing.should.equal 'y'
|
269 | item_weights[1].thing.should.equal 'x'
|
270 | item_weights[1].weight.should.be.lessThan item_weights[0].weight
|
271 | )
|
272 |
|
273 | describe "person exploits,", ->
|
274 | it 'recommendations_per_neighbour should stop one persons recommendations eliminating the other recommendations', ->
|
275 | init_ger(ESM)
|
276 | .then (ger) ->
|
277 | bb.all([
|
278 | ger.event(ns, 'p1','view','a'),
|
279 | ger.event(ns, 'p1','view','b'),
|
280 |
|
281 | ger.event(ns, 'p2','view','a'),
|
282 | ger.event(ns, 'p2','view','b'),
|
283 | ger.event(ns, 'p2','buy','x', created_at: moment().subtract(2, 'days').toDate(), expires_at: tomorrow),
|
284 |
|
285 | ger.event(ns, 'p3','view','a'),
|
286 | ger.event(ns, 'p3','buy','l', created_at: moment().subtract(3, 'hours').toDate(), expires_at: tomorrow),
|
287 | ger.event(ns, 'p3','buy','m', created_at: moment().subtract(2, 'hours').toDate(), expires_at: tomorrow),
|
288 | ger.event(ns, 'p3','buy','n', created_at: moment().subtract(1, 'hours').toDate(), expires_at: tomorrow)
|
289 | ])
|
290 | .then(-> ger.recommendations_for_person(ns, 'p1', recommendations_per_neighbour: 1, actions: {buy: 5, view: 1}))
|
291 | .then((recs) ->
|
292 | item_weights = recs.recommendations
|
293 | item_weights.length.should.equal 2
|
294 | item_weights[0].thing.should.equal 'x'
|
295 | item_weights[1].thing.should.equal 'n'
|
296 | )
|
297 |
|
298 |
|
299 | it "a single persons mass interaction should not outweigh 'real' interations", ->
|
300 | init_ger(ESM)
|
301 | .then (ger) ->
|
302 | events = []
|
303 | for x in [1..100]
|
304 | events.push ger.event(ns, "bad_person",'view','t1', expires_at: tomorrow)
|
305 | events.push ger.event(ns, "bad_person",'buy','t1', expires_at: tomorrow)
|
306 |
|
307 | bb.all(events)
|
308 | .then( ->
|
309 | bb.all([
|
310 | ger.event(ns, 'real_person', 'view', 't2', expires_at: tomorrow)
|
311 | ger.event(ns, 'real_person', 'buy', 't2', expires_at: tomorrow)
|
312 | ger.event(ns, 'person', 'view', 't1', expires_at: tomorrow)
|
313 | ger.event(ns, 'person', 'view', 't2', expires_at: tomorrow)
|
314 | ])
|
315 | )
|
316 | .then( ->
|
317 | ger.recommendations_for_person(ns, 'person', actions: {buy:1, view:1})
|
318 | )
|
319 | .then((recs) ->
|
320 | item_weights = recs.recommendations
|
321 | temp = {}
|
322 | (temp[tw.thing] = tw.weight for tw in item_weights)
|
323 | temp['t1'].should.equal temp['t2']
|
324 | )
|
325 |
|
326 | module.exports = ger_tests;
|