make-mp-data 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/defaults.js +11662 -0
- package/index.js +171 -126
- package/package.json +1 -1
- package/schemas/complex.js +14 -14
- package/schemas/funnels.js +66 -67
- package/schemas/simple.js +4 -4
- package/scratch.mjs +6 -4
- package/tests/unit.test.js +108 -13
- package/types.d.ts +9 -2
- package/utils.js +105 -29
package/schemas/simple.js
CHANGED
|
@@ -37,7 +37,7 @@ const config = {
|
|
|
37
37
|
event: "checkout",
|
|
38
38
|
weight: 2,
|
|
39
39
|
properties: {
|
|
40
|
-
amount: weightedRange(5, 500, .25
|
|
40
|
+
amount: weightedRange(5, 500, .25),
|
|
41
41
|
currency: ["USD", "CAD", "EUR", "BTC", "ETH", "JPY"],
|
|
42
42
|
coupon: ["none", "none", "none", "none", "10%OFF", "20%OFF", "10%OFF", "20%OFF", "30%OFF", "40%OFF", "50%OFF"],
|
|
43
43
|
numItems: weightedRange(1, 10),
|
|
@@ -48,7 +48,7 @@ const config = {
|
|
|
48
48
|
event: "add to cart",
|
|
49
49
|
weight: 4,
|
|
50
50
|
properties: {
|
|
51
|
-
amount: weightedRange(5, 500, .25
|
|
51
|
+
amount: weightedRange(5, 500, .25),
|
|
52
52
|
rating: weightedRange(1, 5),
|
|
53
53
|
reviews: weightedRange(0, 35),
|
|
54
54
|
isFeaturedItem: [true, false, false],
|
|
@@ -71,7 +71,7 @@ const config = {
|
|
|
71
71
|
properties: {
|
|
72
72
|
videoCategory: pickAWinner(videoCategories, integer(0, 9)),
|
|
73
73
|
isFeaturedItem: [true, false, false],
|
|
74
|
-
watchTimeSec: weightedRange(10, 600, .25
|
|
74
|
+
watchTimeSec: weightedRange(10, 600, .25),
|
|
75
75
|
quality: ["2160p", "1440p", "1080p", "720p", "480p", "360p", "240p"],
|
|
76
76
|
format: ["mp4", "avi", "mov", "mpg"],
|
|
77
77
|
uploader_id: chance.guid.bind(chance)
|
|
@@ -133,7 +133,7 @@ const config = {
|
|
|
133
133
|
profit: { events: ["checkout"], values: [4, 2, 42, 420] },
|
|
134
134
|
watchTimeSec: {
|
|
135
135
|
events: ["watch video"],
|
|
136
|
-
values: weightedRange(50, 1200, 6
|
|
136
|
+
values: weightedRange(50, 1200, 6)
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
},
|
package/scratch.mjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import main from "./index.js";
|
|
2
2
|
import amir from './customers/amir.js';
|
|
3
3
|
import simple from './schemas/simple.js';
|
|
4
|
+
import funnels from './schemas/funnels.js';
|
|
5
|
+
import foobar from './schemas/foobar.js';
|
|
6
|
+
import complex from './schemas/complex.js';
|
|
7
|
+
import deepNest from './schemas/deepNest.js';
|
|
4
8
|
import execSync from 'child_process';
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
/** @type {main.Config} */
|
|
8
12
|
const spec = {
|
|
9
|
-
...
|
|
10
|
-
numUsers: 1000,
|
|
11
|
-
numEvents: 100000,
|
|
13
|
+
...funnels,
|
|
12
14
|
writeToDisk: false,
|
|
13
15
|
verbose: true,
|
|
14
|
-
makeChart:
|
|
16
|
+
makeChart: false,
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
|
package/tests/unit.test.js
CHANGED
|
@@ -39,7 +39,11 @@ const { applySkew,
|
|
|
39
39
|
buildFileNames,
|
|
40
40
|
TimeSoup,
|
|
41
41
|
getChance,
|
|
42
|
-
initChance
|
|
42
|
+
initChance,
|
|
43
|
+
validateEventConfig,
|
|
44
|
+
validateTime,
|
|
45
|
+
interruptArray,
|
|
46
|
+
optimizedBoxMuller
|
|
43
47
|
} = require('../utils');
|
|
44
48
|
|
|
45
49
|
|
|
@@ -59,7 +63,7 @@ describe('timesoup', () => {
|
|
|
59
63
|
});
|
|
60
64
|
|
|
61
65
|
|
|
62
|
-
describe('
|
|
66
|
+
describe('names', () => {
|
|
63
67
|
|
|
64
68
|
test('default config', () => {
|
|
65
69
|
const config = { simulationName: 'testSim' };
|
|
@@ -182,7 +186,7 @@ describe('naming things', () => {
|
|
|
182
186
|
});
|
|
183
187
|
|
|
184
188
|
|
|
185
|
-
describe('
|
|
189
|
+
describe('determinism', () => {
|
|
186
190
|
test('initializes RNG with seed from environment variable', () => {
|
|
187
191
|
process.env.SEED = 'test-seed';
|
|
188
192
|
// @ts-ignore
|
|
@@ -206,8 +210,8 @@ describe('determined random', () => {
|
|
|
206
210
|
});
|
|
207
211
|
|
|
208
212
|
|
|
209
|
-
describe('
|
|
210
|
-
test('
|
|
213
|
+
describe('generation', () => {
|
|
214
|
+
test('users: can make', () => {
|
|
211
215
|
const numDays = 30;
|
|
212
216
|
const user = generateUser('uuid-123', numDays);
|
|
213
217
|
expect(user).toHaveProperty('distinct_id');
|
|
@@ -219,7 +223,7 @@ describe('generateUser', () => {
|
|
|
219
223
|
expect(user).toHaveProperty('sessionIds');
|
|
220
224
|
});
|
|
221
225
|
|
|
222
|
-
test('
|
|
226
|
+
test('user: in time range', () => {
|
|
223
227
|
const numDays = 30;
|
|
224
228
|
const user = generateUser('uuid-123', numDays);
|
|
225
229
|
const createdDate = dayjs(user.created, 'YYYY-MM-DD');
|
|
@@ -228,9 +232,86 @@ describe('generateUser', () => {
|
|
|
228
232
|
});
|
|
229
233
|
});
|
|
230
234
|
|
|
235
|
+
describe('validation', () => {
|
|
231
236
|
|
|
237
|
+
beforeAll(() => {
|
|
238
|
+
global.NOW = 1672531200; // fixed point in time for testing
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('events: throws non array', () => {
|
|
242
|
+
// @ts-ignore
|
|
243
|
+
expect(() => validateEventConfig("not an array")).toThrow("events must be an array");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('events: strings', () => {
|
|
247
|
+
const events = ["event1", "event2"];
|
|
248
|
+
const result = validateEventConfig(events);
|
|
249
|
+
|
|
250
|
+
expect(result).toEqual([
|
|
251
|
+
{ event: "event1", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
|
|
252
|
+
{ event: "event2", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
result.forEach(event => {
|
|
256
|
+
expect(event.weight).toBeGreaterThanOrEqual(1);
|
|
257
|
+
expect(event.weight).toBeLessThanOrEqual(5);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('events: objects', () => {
|
|
262
|
+
const events = [{ event: "event1", properties: { a: 1 } }, { event: "event2", properties: { b: 2 } }];
|
|
263
|
+
const result = validateEventConfig(events);
|
|
232
264
|
|
|
233
|
-
|
|
265
|
+
expect(result).toEqual(events);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('events: mix', () => {
|
|
269
|
+
const events = ["event1", { event: "event2", properties: { b: 2 } }];
|
|
270
|
+
// @ts-ignore
|
|
271
|
+
const result = validateEventConfig(events);
|
|
272
|
+
|
|
273
|
+
expect(result).toEqual([
|
|
274
|
+
{ event: "event1", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
|
|
275
|
+
{ event: "event2", properties: { b: 2 } }
|
|
276
|
+
]);
|
|
277
|
+
|
|
278
|
+
expect(result[0].weight).toBeGreaterThanOrEqual(1);
|
|
279
|
+
expect(result[0].weight).toBeLessThanOrEqual(5);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('dates: between', () => {
|
|
283
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 15); // 15 days ago
|
|
284
|
+
const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
285
|
+
const latestTime = global.NOW;
|
|
286
|
+
expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test('dates: outside earliest', () => {
|
|
290
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 31); // 31 days ago
|
|
291
|
+
const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
292
|
+
const latestTime = global.NOW;
|
|
293
|
+
expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(false);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('dates: outside latest', () => {
|
|
297
|
+
const chosenTime = -1;
|
|
298
|
+
const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
299
|
+
const latestTime = global.NOW;
|
|
300
|
+
expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(false);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('dates: inference in', () => {
|
|
304
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 15); // 15 days ago
|
|
305
|
+
expect(validateTime(chosenTime)).toBe(true);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('dates: inference out', () => {
|
|
309
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 31); // 31 days ago
|
|
310
|
+
expect(validateTime(chosenTime)).toBe(false);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('enrichment', () => {
|
|
234
315
|
test('hook works', () => {
|
|
235
316
|
const arr = [];
|
|
236
317
|
const hook = (item) => item * 2;
|
|
@@ -238,7 +319,7 @@ describe('enrich array', () => {
|
|
|
238
319
|
enrichedArray.hookPush(1);
|
|
239
320
|
enrichedArray.hookPush(2);
|
|
240
321
|
expect(enrichedArray.includes(2)).toBeTruthy();
|
|
241
|
-
expect(enrichedArray.includes(4)).toBeTruthy();
|
|
322
|
+
expect(enrichedArray.includes(4)).toBeTruthy();
|
|
242
323
|
});
|
|
243
324
|
|
|
244
325
|
test('filter empties', () => {
|
|
@@ -247,7 +328,7 @@ describe('enrich array', () => {
|
|
|
247
328
|
const enrichedArray = enrichArray(arr, { hook });
|
|
248
329
|
enrichedArray.hookPush(null);
|
|
249
330
|
enrichedArray.hookPush(undefined);
|
|
250
|
-
enrichedArray.hookPush({});
|
|
331
|
+
enrichedArray.hookPush({});
|
|
251
332
|
enrichedArray.hookPush({ a: 1 });
|
|
252
333
|
enrichedArray.hookPush([1, 2]);
|
|
253
334
|
expect(enrichedArray).toHaveLength(3);
|
|
@@ -256,12 +337,14 @@ describe('enrich array', () => {
|
|
|
256
337
|
expect(enrichedArray.includes('[object Object]')).toBeTruthy();
|
|
257
338
|
expect(enrichedArray.includes('1')).toBeTruthy();
|
|
258
339
|
expect(enrichedArray.includes('2')).toBeTruthy();
|
|
259
|
-
|
|
340
|
+
|
|
260
341
|
});
|
|
342
|
+
|
|
343
|
+
|
|
261
344
|
});
|
|
262
345
|
|
|
263
346
|
|
|
264
|
-
describe('
|
|
347
|
+
describe('utilities', () => {
|
|
265
348
|
|
|
266
349
|
test('pick: works', () => {
|
|
267
350
|
const array = [1, 2, 3];
|
|
@@ -343,13 +426,13 @@ describe('utils', () => {
|
|
|
343
426
|
test('weightedRange: within range', () => {
|
|
344
427
|
const values = weightedRange(5, 15);
|
|
345
428
|
expect(values.every(v => v >= 5 && v <= 15)).toBe(true);
|
|
346
|
-
expect(values.length).toBe(
|
|
429
|
+
expect(values.length).toBe(50);
|
|
347
430
|
});
|
|
348
431
|
|
|
349
432
|
test('applySkew: skews', () => {
|
|
350
433
|
const value = boxMullerRandom();
|
|
351
434
|
const skewedValue = applySkew(value, .25);
|
|
352
|
-
expect(Math.abs(skewedValue)).
|
|
435
|
+
expect(Math.abs(skewedValue)).toBeLessThanOrEqual(Math.abs(value));
|
|
353
436
|
});
|
|
354
437
|
|
|
355
438
|
test('mapToRange: works', () => {
|
|
@@ -537,5 +620,17 @@ describe('utils', () => {
|
|
|
537
620
|
expect(stdDev).toBeCloseTo(1, 1);
|
|
538
621
|
});
|
|
539
622
|
|
|
623
|
+
test('optimized box normal distribution', () => {
|
|
624
|
+
const values = [];
|
|
625
|
+
for (let i = 0; i < 10000; i++) {
|
|
626
|
+
values.push(optimizedBoxMuller());
|
|
627
|
+
}
|
|
628
|
+
const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
629
|
+
const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
|
|
630
|
+
const stdDev = Math.sqrt(variance);
|
|
631
|
+
expect(mean).toBeCloseTo(0, 1);
|
|
632
|
+
expect(stdDev).toBeCloseTo(1, 1);
|
|
633
|
+
});
|
|
634
|
+
|
|
540
635
|
|
|
541
636
|
});
|
package/types.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ declare namespace main {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export interface EnrichedArray<T> extends Array<T> {
|
|
63
|
-
hookPush: (item: T) =>
|
|
63
|
+
hookPush: (item: T) => boolean;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export interface EventConfig {
|
|
@@ -85,13 +85,20 @@ declare namespace main {
|
|
|
85
85
|
sequence: string[];
|
|
86
86
|
weight?: number;
|
|
87
87
|
isFirstFunnel?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* If true, the funnel will require the user to repeat the sequence of events in order to convert
|
|
90
|
+
* If false, the user does not need to repeat the sequence of events in order to convert
|
|
91
|
+
* ^ when false, users who repeat the repetitive steps are more likely to convert
|
|
92
|
+
*/
|
|
93
|
+
requireRepeats?: boolean;
|
|
88
94
|
order?:
|
|
89
95
|
| "sequential"
|
|
90
96
|
| "first-fixed"
|
|
91
97
|
| "last-fixed"
|
|
92
98
|
| "random"
|
|
93
99
|
| "first-and-last-fixed"
|
|
94
|
-
| "middle-fixed"
|
|
100
|
+
| "middle-fixed"
|
|
101
|
+
| "interrupted";
|
|
95
102
|
conversionRate?: number;
|
|
96
103
|
timeToConvert?: number;
|
|
97
104
|
props?: Record<string, ValueValid>;
|
package/utils.js
CHANGED
|
@@ -11,10 +11,12 @@ dayjs.extend(utc);
|
|
|
11
11
|
require('dotenv').config();
|
|
12
12
|
|
|
13
13
|
/** @typedef {import('./types').Config} Config */
|
|
14
|
+
/** @typedef {import('./types').EventConfig} EventConfig */
|
|
14
15
|
/** @typedef {import('./types').ValueValid} ValueValid */
|
|
15
16
|
/** @typedef {import('./types').EnrichedArray} EnrichArray */
|
|
16
17
|
/** @typedef {import('./types').EnrichArrayOptions} EnrichArrayOptions */
|
|
17
18
|
/** @typedef {import('./types').Person} Person */
|
|
19
|
+
/** @typedef {import('./types').Funnel} Funnel */
|
|
18
20
|
|
|
19
21
|
let globalChance;
|
|
20
22
|
let chanceInitialized = false;
|
|
@@ -268,6 +270,14 @@ function boxMullerRandom() {
|
|
|
268
270
|
return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
|
|
269
271
|
};
|
|
270
272
|
|
|
273
|
+
function optimizedBoxMuller() {
|
|
274
|
+
const chance = getChance();
|
|
275
|
+
const u = Math.max(Math.min(chance.normal({ mean: .5, dev: .25 }), 1), 0);
|
|
276
|
+
const v = Math.max(Math.min(chance.normal({ mean: .5, dev: .25 }), 1), 0);
|
|
277
|
+
return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
|
|
278
|
+
|
|
279
|
+
}
|
|
280
|
+
|
|
271
281
|
/**
|
|
272
282
|
* applies a skew to a value;
|
|
273
283
|
* Skew=0.5: When the skew is 0.5, the distribution becomes more compressed, with values clustering closer to the mean.
|
|
@@ -377,12 +387,14 @@ function weighFunnels(acc, funnel) {
|
|
|
377
387
|
* @param {number} skew=1
|
|
378
388
|
* @param {number} size=100
|
|
379
389
|
*/
|
|
380
|
-
function weightedRange(min, max, skew = 1, size =
|
|
390
|
+
function weightedRange(min, max, skew = 1, size = 50) {
|
|
391
|
+
if (size > 2000) size = 2000;
|
|
381
392
|
const mean = (max + min) / 2;
|
|
382
393
|
const sd = (max - min) / 4;
|
|
383
394
|
const array = [];
|
|
384
395
|
while (array.length < size) {
|
|
385
|
-
const normalValue = boxMullerRandom();
|
|
396
|
+
// const normalValue = boxMullerRandom();
|
|
397
|
+
const normalValue = optimizedBoxMuller();
|
|
386
398
|
const skewedValue = applySkew(normalValue, skew);
|
|
387
399
|
const mappedValue = mapToRange(skewedValue, mean, sd);
|
|
388
400
|
if (mappedValue >= min && mappedValue <= max) {
|
|
@@ -458,6 +470,76 @@ function shuffleOutside(array) {
|
|
|
458
470
|
return [outsideShuffled[0], ...middleFixed, outsideShuffled[1]];
|
|
459
471
|
}
|
|
460
472
|
|
|
473
|
+
/**
|
|
474
|
+
* @param {EventConfig[]} funnel
|
|
475
|
+
* @param {EventConfig[]} possibles
|
|
476
|
+
*/
|
|
477
|
+
function interruptArray(funnel, possibles, percent = 50) {
|
|
478
|
+
if (!Array.isArray(funnel)) return funnel;
|
|
479
|
+
if (!Array.isArray(possibles)) return funnel;
|
|
480
|
+
if (!funnel.length) return funnel;
|
|
481
|
+
if (!possibles.length) return funnel;
|
|
482
|
+
const ignorePositions = [0, funnel.length - 1];
|
|
483
|
+
const chance = getChance();
|
|
484
|
+
loopSteps: for (const [index, event] of funnel.entries()) {
|
|
485
|
+
if (ignorePositions.includes(index)) continue loopSteps;
|
|
486
|
+
if (chance.bool({ likelihood: percent })) {
|
|
487
|
+
funnel[index] = chance.pickone(possibles);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return funnel;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/*
|
|
495
|
+
----
|
|
496
|
+
VALIDATORS
|
|
497
|
+
----
|
|
498
|
+
*/
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* @param {EventConfig[] | string[]} events
|
|
503
|
+
*/
|
|
504
|
+
function validateEventConfig(events) {
|
|
505
|
+
if (!Array.isArray(events)) throw new Error("events must be an array");
|
|
506
|
+
const cleanEventConfig = [];
|
|
507
|
+
for (const event of events) {
|
|
508
|
+
if (typeof event === "string") {
|
|
509
|
+
/** @type {EventConfig} */
|
|
510
|
+
const eventTemplate = {
|
|
511
|
+
event,
|
|
512
|
+
isFirstEvent: false,
|
|
513
|
+
properties: {},
|
|
514
|
+
weight: integer(1, 5)
|
|
515
|
+
};
|
|
516
|
+
cleanEventConfig.push(eventTemplate);
|
|
517
|
+
}
|
|
518
|
+
if (typeof event === "object") {
|
|
519
|
+
cleanEventConfig.push(event);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return cleanEventConfig;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function validateTime(chosenTime, earliestTime, latestTime) {
|
|
526
|
+
if (!earliestTime) earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
527
|
+
if (!latestTime) latestTime = global.NOW;
|
|
528
|
+
|
|
529
|
+
if (typeof chosenTime === 'number') {
|
|
530
|
+
if (chosenTime > 0) {
|
|
531
|
+
if (chosenTime > earliestTime) {
|
|
532
|
+
if (chosenTime < latestTime) {
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
|
|
461
543
|
/*
|
|
462
544
|
----
|
|
463
545
|
META
|
|
@@ -474,33 +556,43 @@ function enrichArray(arr = [], opts = {}) {
|
|
|
474
556
|
const { hook = a => a, type = "", ...rest } = opts;
|
|
475
557
|
|
|
476
558
|
function transformThenPush(item) {
|
|
477
|
-
if (item === null) return
|
|
478
|
-
if (item === undefined) return
|
|
559
|
+
if (item === null) return false;
|
|
560
|
+
if (item === undefined) return false;
|
|
479
561
|
if (typeof item === 'object') {
|
|
480
|
-
if (Object.keys(item).length === 0) return
|
|
562
|
+
if (Object.keys(item).length === 0) return false;
|
|
481
563
|
}
|
|
564
|
+
|
|
565
|
+
//hook is passed an array
|
|
482
566
|
if (Array.isArray(item)) {
|
|
483
567
|
for (const i of item) {
|
|
484
568
|
try {
|
|
485
569
|
const enriched = hook(i, type, rest);
|
|
486
|
-
arr.push(
|
|
570
|
+
if (Array.isArray(enriched)) enriched.forEach(e => arr.push(e));
|
|
571
|
+
else arr.push(enriched);
|
|
572
|
+
|
|
487
573
|
}
|
|
488
574
|
catch (e) {
|
|
489
575
|
console.error(`\n\nyour hook had an error\n\n`, e);
|
|
490
576
|
arr.push(i);
|
|
577
|
+
return false;
|
|
491
578
|
}
|
|
492
579
|
|
|
493
580
|
}
|
|
494
|
-
return
|
|
581
|
+
return true;
|
|
495
582
|
}
|
|
583
|
+
|
|
584
|
+
//hook is passed a single item
|
|
496
585
|
else {
|
|
497
586
|
try {
|
|
498
587
|
const enriched = hook(item, type, rest);
|
|
499
|
-
|
|
588
|
+
if (Array.isArray(enriched)) enriched.forEach(e => arr.push(e));
|
|
589
|
+
else arr.push(enriched);
|
|
590
|
+
return true;
|
|
500
591
|
}
|
|
501
592
|
catch (e) {
|
|
502
593
|
console.error(`\n\nyour hook had an error\n\n`, e);
|
|
503
|
-
|
|
594
|
+
arr.push(item);
|
|
595
|
+
return false;
|
|
504
596
|
}
|
|
505
597
|
}
|
|
506
598
|
|
|
@@ -674,22 +766,6 @@ function TimeSoup(earliestTime, latestTime, peaks = 5, deviation = 2, mean = 0)
|
|
|
674
766
|
}
|
|
675
767
|
|
|
676
768
|
|
|
677
|
-
function validateTime(chosenTime, earliestTime, latestTime) {
|
|
678
|
-
if (!earliestTime) earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
679
|
-
if (!latestTime) latestTime = global.NOW;
|
|
680
|
-
|
|
681
|
-
if (typeof chosenTime === 'number') {
|
|
682
|
-
if (chosenTime > 0) {
|
|
683
|
-
if (chosenTime > earliestTime) {
|
|
684
|
-
if (chosenTime < latestTime) {
|
|
685
|
-
return true;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
return false;
|
|
692
|
-
}
|
|
693
769
|
|
|
694
770
|
|
|
695
771
|
/**
|
|
@@ -819,7 +895,7 @@ module.exports = {
|
|
|
819
895
|
|
|
820
896
|
initChance,
|
|
821
897
|
getChance,
|
|
822
|
-
|
|
898
|
+
validateTime,
|
|
823
899
|
boxMullerRandom,
|
|
824
900
|
applySkew,
|
|
825
901
|
mapToRange,
|
|
@@ -832,17 +908,17 @@ module.exports = {
|
|
|
832
908
|
pickAWinner,
|
|
833
909
|
weighArray,
|
|
834
910
|
weighFunnels,
|
|
835
|
-
|
|
911
|
+
validateEventConfig,
|
|
836
912
|
shuffleArray,
|
|
837
913
|
shuffleExceptFirst,
|
|
838
914
|
shuffleExceptLast,
|
|
839
915
|
fixFirstAndLast,
|
|
840
916
|
shuffleMiddle,
|
|
841
917
|
shuffleOutside,
|
|
842
|
-
|
|
918
|
+
interruptArray,
|
|
843
919
|
generateUser,
|
|
844
920
|
enrichArray,
|
|
845
|
-
|
|
921
|
+
optimizedBoxMuller,
|
|
846
922
|
buildFileNames,
|
|
847
923
|
streamJSON,
|
|
848
924
|
streamCSV
|