make-mp-data 1.4.4 → 1.4.5
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/.vscode/settings.json +1 -0
- package/core/index.js +24 -19
- package/core/utils.js +134 -77
- package/dungeons/.gitkeep +0 -0
- package/package.json +5 -5
- package/schemas/anon.js +2 -2
- package/schemas/complex.js +22 -22
- package/schemas/foobar.js +2 -2
- package/schemas/funnels.js +10 -10
- package/schemas/simple.js +16 -30
- package/scratch.mjs +4 -7
- package/scripts/new.sh +52 -0
- package/tests/coverage/.gitkeep +0 -0
- package/tests/e2e.test.js +4 -22
- package/tests/jest.config.js +3 -2
- package/tests/unit.test.js +327 -207
- package/types.d.ts +157 -80
- package/schemas/deepNest.js +0 -106
package/.vscode/settings.json
CHANGED
package/core/index.js
CHANGED
|
@@ -6,18 +6,23 @@ by AK
|
|
|
6
6
|
ak@mixpanel.com
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
|
|
10
|
+
//!feature: mirror strategies
|
|
11
|
+
//!feature: fixedTimeFunnel? if set this funnel will occur for all users at the same time ['cards charged', 'charge complete']
|
|
12
|
+
//!feature: churn ... is churnFunnel, possible to return, etc
|
|
13
|
+
//!bug: not writing adspend CSV
|
|
14
|
+
//!bug: using --mc flag reverts to --complex for some reason
|
|
15
|
+
|
|
16
|
+
//todo: send SCD data to mixpanel (blocked on dev)
|
|
17
|
+
//todo: send and map lookup tables to mixpanel (also blocked on dev)
|
|
13
18
|
|
|
14
19
|
/** @typedef {import('../types').Config} Config */
|
|
15
20
|
/** @typedef {import('../types').EventConfig} EventConfig */
|
|
16
21
|
/** @typedef {import('../types').Funnel} Funnel */
|
|
17
22
|
/** @typedef {import('../types').Person} Person */
|
|
18
|
-
/** @typedef {import('../types').
|
|
23
|
+
/** @typedef {import('../types').SCDSchema} SCDTableRow */
|
|
19
24
|
/** @typedef {import('../types').UserProfile} UserProfile */
|
|
20
|
-
/** @typedef {import('../types').
|
|
25
|
+
/** @typedef {import('../types').EventSchema} EventSpec */
|
|
21
26
|
|
|
22
27
|
const dayjs = require("dayjs");
|
|
23
28
|
const utc = require("dayjs/plugin/utc");
|
|
@@ -177,16 +182,16 @@ async function main(config) {
|
|
|
177
182
|
log(`------------------SETUP------------------`, "\n");
|
|
178
183
|
|
|
179
184
|
//setup all the data structures we will push into
|
|
180
|
-
const eventData = u.
|
|
181
|
-
const userProfilesData = u.
|
|
182
|
-
const adSpendData = u.
|
|
185
|
+
const eventData = u.hookArray([], { hook, type: "event", config });
|
|
186
|
+
const userProfilesData = u.hookArray([], { hook, type: "user", config });
|
|
187
|
+
const adSpendData = u.hookArray([], { hook, type: "ad-spend", config });
|
|
183
188
|
const scdTableKeys = Object.keys(scdProps);
|
|
184
189
|
const scdTableData = [];
|
|
185
190
|
for (const [index, key] of scdTableKeys.entries()) {
|
|
186
|
-
scdTableData[index] = u.
|
|
191
|
+
scdTableData[index] = u.hookArray([], { hook, type: "scd", config, scdKey: key });
|
|
187
192
|
}
|
|
188
|
-
const groupProfilesData = u.
|
|
189
|
-
const lookupTableData = u.
|
|
193
|
+
const groupProfilesData = u.hookArray([], { hook, type: "group", config });
|
|
194
|
+
const lookupTableData = u.hookArray([], { hook, type: "lookup", config });
|
|
190
195
|
const avgEvPerUser = Math.ceil(numEvents / numUsers);
|
|
191
196
|
|
|
192
197
|
// if no funnels, make some out of events...
|
|
@@ -331,7 +336,7 @@ async function main(config) {
|
|
|
331
336
|
const newTime = dayjs(event.time).add(timeShift, "second");
|
|
332
337
|
event.time = newTime.toISOString();
|
|
333
338
|
if (epochStart && newTime.unix() < epochStart) event = {};
|
|
334
|
-
if (epochEnd && newTime.unix() > epochEnd) event = {};
|
|
339
|
+
if (epochEnd && newTime.unix() > (epochEnd - 60 * 60)) event = {};
|
|
335
340
|
}
|
|
336
341
|
catch (e) {
|
|
337
342
|
//noop
|
|
@@ -453,7 +458,7 @@ async function main(config) {
|
|
|
453
458
|
log(`\tsent ${comma(imported.success)} events\n`);
|
|
454
459
|
importResults.events = imported;
|
|
455
460
|
}
|
|
456
|
-
if (userProfilesData) {
|
|
461
|
+
if (userProfilesData && userProfilesData.length) {
|
|
457
462
|
log(`importing user profiles to mixpanel...\n`);
|
|
458
463
|
const imported = await mp(creds, clone(userProfilesData), {
|
|
459
464
|
recordType: "user",
|
|
@@ -462,7 +467,7 @@ async function main(config) {
|
|
|
462
467
|
log(`\tsent ${comma(imported.success)} user profiles\n`);
|
|
463
468
|
importResults.users = imported;
|
|
464
469
|
}
|
|
465
|
-
if (adSpendData) {
|
|
470
|
+
if (adSpendData && adSpendData.length) {
|
|
466
471
|
log(`importing ad spend data to mixpanel...\n`);
|
|
467
472
|
const imported = await mp(creds, clone(adSpendData), {
|
|
468
473
|
recordType: "event",
|
|
@@ -616,8 +621,8 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
|
|
|
616
621
|
const groupEvents = groupPair[2] || [];
|
|
617
622
|
|
|
618
623
|
// empty array for group events means all events
|
|
619
|
-
if (!groupEvents.length) eventTemplate[groupKey] = u.pick(u.
|
|
620
|
-
if (groupEvents.includes(eventTemplate.event)) eventTemplate[groupKey] = u.pick(u.
|
|
624
|
+
if (!groupEvents.length) eventTemplate[groupKey] = u.pick(u.weighNumRange(1, groupCardinality));
|
|
625
|
+
if (groupEvents.includes(eventTemplate.event)) eventTemplate[groupKey] = u.pick(u.weighNumRange(1, groupCardinality));
|
|
621
626
|
}
|
|
622
627
|
|
|
623
628
|
//make $insert_id
|
|
@@ -668,7 +673,7 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
|
668
673
|
.map((eventName) => {
|
|
669
674
|
const foundEvent = config.events.find((e) => e.event === eventName);
|
|
670
675
|
/** @type {EventConfig} */
|
|
671
|
-
const eventSpec = foundEvent || { event: eventName, properties: {} };
|
|
676
|
+
const eventSpec = clone(foundEvent) || { event: eventName, properties: {} };
|
|
672
677
|
for (const key in eventSpec.properties) {
|
|
673
678
|
try {
|
|
674
679
|
eventSpec.properties[key] = u.choose(eventSpec.properties[key]);
|
|
@@ -877,7 +882,7 @@ function makeAdSpend(day) {
|
|
|
877
882
|
const utm_term = u.choose(u.pickAWinner(network.utm_term)());
|
|
878
883
|
//each of these is a campaign
|
|
879
884
|
const adSpendEvent = {
|
|
880
|
-
event: "
|
|
885
|
+
event: "$ad_spend",
|
|
881
886
|
time: day,
|
|
882
887
|
source: 'dm4',
|
|
883
888
|
utm_campaign: campaign,
|
package/core/utils.js
CHANGED
|
@@ -15,8 +15,8 @@ const { domainSuffix, domainPrefix } = require('./defaults');
|
|
|
15
15
|
/** @typedef {import('../types').Config} Config */
|
|
16
16
|
/** @typedef {import('../types').EventConfig} EventConfig */
|
|
17
17
|
/** @typedef {import('../types').ValueValid} ValueValid */
|
|
18
|
-
/** @typedef {import('../types').EnrichedArray}
|
|
19
|
-
/** @typedef {import('../types').
|
|
18
|
+
/** @typedef {import('../types').EnrichedArray} hookArray */
|
|
19
|
+
/** @typedef {import('../types').hookArrayOptions} hookArrayOptions */
|
|
20
20
|
/** @typedef {import('../types').Person} Person */
|
|
21
21
|
/** @typedef {import('../types').Funnel} Funnel */
|
|
22
22
|
|
|
@@ -34,6 +34,7 @@ RNG
|
|
|
34
34
|
/**
|
|
35
35
|
* the random number generator initialization function
|
|
36
36
|
* @param {string} seed
|
|
37
|
+
* @returns {Chance}
|
|
37
38
|
*/
|
|
38
39
|
function initChance(seed) {
|
|
39
40
|
if (process.env.SEED) seed = process.env.SEED; // Override seed with environment variable if available
|
|
@@ -42,6 +43,7 @@ function initChance(seed) {
|
|
|
42
43
|
if (global.MP_SIMULATION_CONFIG) global.MP_SIMULATION_CONFIG.chance = globalChance;
|
|
43
44
|
chanceInitialized = true;
|
|
44
45
|
}
|
|
46
|
+
return globalChance;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
/**
|
|
@@ -54,8 +56,7 @@ function getChance() {
|
|
|
54
56
|
if (!seed) {
|
|
55
57
|
return new Chance();
|
|
56
58
|
}
|
|
57
|
-
initChance(seed);
|
|
58
|
-
return globalChance;
|
|
59
|
+
return initChance(seed);
|
|
59
60
|
}
|
|
60
61
|
return globalChance;
|
|
61
62
|
}
|
|
@@ -242,64 +243,6 @@ function integer(min = 1, max = 100) {
|
|
|
242
243
|
};
|
|
243
244
|
|
|
244
245
|
|
|
245
|
-
/**
|
|
246
|
-
* Creates a function that generates a weighted list of items
|
|
247
|
-
* with a higher likelihood of picking a specified index and clear second and third place indices.
|
|
248
|
-
*
|
|
249
|
-
* @param {Array} items - The list of items to pick from.
|
|
250
|
-
* @param {number} [mostChosenIndex] - The index of the item to be most favored.
|
|
251
|
-
* @returns {function} - A function that returns a weighted list of items.
|
|
252
|
-
*/
|
|
253
|
-
function pickAWinner(items, mostChosenIndex) {
|
|
254
|
-
const chance = getChance();
|
|
255
|
-
|
|
256
|
-
// Ensure mostChosenIndex is within the bounds of the items array
|
|
257
|
-
if (!items) return () => { return ""; };
|
|
258
|
-
if (!items.length) return () => { return ""; };
|
|
259
|
-
if (!mostChosenIndex) mostChosenIndex = chance.integer({ min: 0, max: items.length - 1 });
|
|
260
|
-
if (mostChosenIndex >= items.length) mostChosenIndex = items.length - 1;
|
|
261
|
-
|
|
262
|
-
// Calculate second and third most chosen indices
|
|
263
|
-
const secondMostChosenIndex = (mostChosenIndex + 1) % items.length;
|
|
264
|
-
const thirdMostChosenIndex = (mostChosenIndex + 2) % items.length;
|
|
265
|
-
|
|
266
|
-
// Return a function that generates a weighted list
|
|
267
|
-
return function () {
|
|
268
|
-
const weighted = [];
|
|
269
|
-
for (let i = 0; i < 10; i++) {
|
|
270
|
-
const rand = chance.d10(); // Random number between 1 and 10
|
|
271
|
-
|
|
272
|
-
// 35% chance to favor the most chosen index
|
|
273
|
-
if (chance.bool({ likelihood: 35 })) {
|
|
274
|
-
// 50% chance to slightly alter the index
|
|
275
|
-
if (chance.bool({ likelihood: 50 })) {
|
|
276
|
-
weighted.push(items[mostChosenIndex]);
|
|
277
|
-
} else {
|
|
278
|
-
const addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;
|
|
279
|
-
let newIndex = mostChosenIndex + addOrSubtract;
|
|
280
|
-
|
|
281
|
-
// Ensure newIndex is within bounds
|
|
282
|
-
if (newIndex < 0) newIndex = 0;
|
|
283
|
-
if (newIndex >= items.length) newIndex = items.length - 1;
|
|
284
|
-
weighted.push(items[newIndex]);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
// 25% chance to favor the second most chosen index
|
|
288
|
-
else if (chance.bool({ likelihood: 25 })) {
|
|
289
|
-
weighted.push(items[secondMostChosenIndex]);
|
|
290
|
-
}
|
|
291
|
-
// 15% chance to favor the third most chosen index
|
|
292
|
-
else if (chance.bool({ likelihood: 15 })) {
|
|
293
|
-
weighted.push(items[thirdMostChosenIndex]);
|
|
294
|
-
}
|
|
295
|
-
// Otherwise, pick a random item from the list
|
|
296
|
-
else {
|
|
297
|
-
weighted.push(chance.pickone(items));
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
return weighted;
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
246
|
|
|
304
247
|
|
|
305
248
|
|
|
@@ -359,12 +302,13 @@ function mapToRange(value, mean, sd) {
|
|
|
359
302
|
* @param {number} step=1
|
|
360
303
|
*/
|
|
361
304
|
function range(a, b, step = 1) {
|
|
305
|
+
const arr = [];
|
|
362
306
|
step = !step ? 1 : step;
|
|
363
307
|
b = b / step;
|
|
364
308
|
for (var i = a; i <= b; i++) {
|
|
365
|
-
|
|
309
|
+
arr.push(i * step);
|
|
366
310
|
}
|
|
367
|
-
return
|
|
311
|
+
return arr;
|
|
368
312
|
};
|
|
369
313
|
|
|
370
314
|
|
|
@@ -491,7 +435,7 @@ function weighFunnels(acc, funnel) {
|
|
|
491
435
|
* @param {number} skew=1
|
|
492
436
|
* @param {number} size=100
|
|
493
437
|
*/
|
|
494
|
-
function
|
|
438
|
+
function weighNumRange(min, max, skew = 1, size = 50) {
|
|
495
439
|
if (size > 2000) size = 2000;
|
|
496
440
|
const mean = (max + min) / 2;
|
|
497
441
|
const sd = (max - min) / 4;
|
|
@@ -508,13 +452,16 @@ function weightedRange(min, max, skew = 1, size = 50) {
|
|
|
508
452
|
return array;
|
|
509
453
|
}
|
|
510
454
|
|
|
455
|
+
/**
|
|
456
|
+
* arbitrarily weigh an array of values to create repeats
|
|
457
|
+
* @param {Array<any>} arr
|
|
458
|
+
*/
|
|
511
459
|
function weighArray(arr) {
|
|
512
|
-
|
|
513
460
|
// Calculate the upper bound based on the size of the array with added noise
|
|
514
461
|
const maxCopies = arr.length + integer(1, arr.length);
|
|
515
462
|
|
|
516
463
|
// Create an empty array to store the weighted elements
|
|
517
|
-
|
|
464
|
+
const weightedArray = [];
|
|
518
465
|
|
|
519
466
|
// Iterate over the input array and copy each element a random number of times
|
|
520
467
|
arr.forEach(element => {
|
|
@@ -527,6 +474,106 @@ function weighArray(arr) {
|
|
|
527
474
|
return weightedArray;
|
|
528
475
|
}
|
|
529
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Creates a function that generates a weighted array of values.
|
|
479
|
+
*
|
|
480
|
+
* @overload
|
|
481
|
+
* @param {Array<{value: string, weight: number}>} items - An array of weighted objects or an array of strings.
|
|
482
|
+
* @returns {function(): Array<string>} A function that returns a weighted array of values when called.
|
|
483
|
+
*
|
|
484
|
+
* @overload
|
|
485
|
+
* @param {Array<string>} items - An array of strings.
|
|
486
|
+
* @returns {function(): Array<string>} A function that returns a weighted array with automatically assigned random weights to each string.
|
|
487
|
+
*/
|
|
488
|
+
|
|
489
|
+
function weighChoices(items) {
|
|
490
|
+
let weightedItems;
|
|
491
|
+
|
|
492
|
+
// If items are strings, assign unique random weights
|
|
493
|
+
if (items.every(item => typeof item === 'string')) {
|
|
494
|
+
const weights = shuffleArray(range(1, items.length));
|
|
495
|
+
weightedItems = items.map((item, index) => ({
|
|
496
|
+
value: item,
|
|
497
|
+
weight: weights[index]
|
|
498
|
+
}));
|
|
499
|
+
} else {
|
|
500
|
+
weightedItems = items;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return function generateWeightedArray() {
|
|
504
|
+
const weightedArray = [];
|
|
505
|
+
|
|
506
|
+
// Add each value to the array the number of times specified by its weight
|
|
507
|
+
weightedItems.forEach(({ value, weight }) => {
|
|
508
|
+
if (!weight) weight = 1;
|
|
509
|
+
for (let i = 0; i < weight; i++) {
|
|
510
|
+
weightedArray.push(value);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
return weightedArray;
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Creates a function that generates a weighted list of items
|
|
520
|
+
* with a higher likelihood of picking a specified index and clear second and third place indices.
|
|
521
|
+
*
|
|
522
|
+
* @param {Array} items - The list of items to pick from.
|
|
523
|
+
* @param {number} [mostChosenIndex] - The index of the item to be most favored.
|
|
524
|
+
* @returns {function} - A function that returns a weighted list of items.
|
|
525
|
+
*/
|
|
526
|
+
function pickAWinner(items, mostChosenIndex) {
|
|
527
|
+
const chance = getChance();
|
|
528
|
+
|
|
529
|
+
// Ensure mostChosenIndex is within the bounds of the items array
|
|
530
|
+
if (!items) return () => { return ""; };
|
|
531
|
+
if (!items.length) return () => { return ""; };
|
|
532
|
+
if (!mostChosenIndex) mostChosenIndex = chance.integer({ min: 0, max: items.length - 1 });
|
|
533
|
+
if (mostChosenIndex >= items.length) mostChosenIndex = items.length - 1;
|
|
534
|
+
|
|
535
|
+
// Calculate second and third most chosen indices
|
|
536
|
+
const secondMostChosenIndex = (mostChosenIndex + 1) % items.length;
|
|
537
|
+
const thirdMostChosenIndex = (mostChosenIndex + 2) % items.length;
|
|
538
|
+
|
|
539
|
+
// Return a function that generates a weighted list
|
|
540
|
+
return function () {
|
|
541
|
+
const weighted = [];
|
|
542
|
+
for (let i = 0; i < 10; i++) {
|
|
543
|
+
const rand = chance.d10(); // Random number between 1 and 10
|
|
544
|
+
|
|
545
|
+
// 35% chance to favor the most chosen index
|
|
546
|
+
if (chance.bool({ likelihood: 35 })) {
|
|
547
|
+
// 50% chance to slightly alter the index
|
|
548
|
+
if (chance.bool({ likelihood: 50 })) {
|
|
549
|
+
weighted.push(items[mostChosenIndex]);
|
|
550
|
+
} else {
|
|
551
|
+
const addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;
|
|
552
|
+
let newIndex = mostChosenIndex + addOrSubtract;
|
|
553
|
+
|
|
554
|
+
// Ensure newIndex is within bounds
|
|
555
|
+
if (newIndex < 0) newIndex = 0;
|
|
556
|
+
if (newIndex >= items.length) newIndex = items.length - 1;
|
|
557
|
+
weighted.push(items[newIndex]);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// 25% chance to favor the second most chosen index
|
|
561
|
+
else if (chance.bool({ likelihood: 25 })) {
|
|
562
|
+
weighted.push(items[secondMostChosenIndex]);
|
|
563
|
+
}
|
|
564
|
+
// 15% chance to favor the third most chosen index
|
|
565
|
+
else if (chance.bool({ likelihood: 15 })) {
|
|
566
|
+
weighted.push(items[thirdMostChosenIndex]);
|
|
567
|
+
}
|
|
568
|
+
// Otherwise, pick a random item from the list
|
|
569
|
+
else {
|
|
570
|
+
weighted.push(chance.pickone(items));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return weighted;
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
530
577
|
/*
|
|
531
578
|
----
|
|
532
579
|
SHUFFLERS
|
|
@@ -654,10 +701,10 @@ META
|
|
|
654
701
|
/**
|
|
655
702
|
* our meta programming function which lets you mutate items as they are pushed into an array
|
|
656
703
|
* @param {any[]} arr
|
|
657
|
-
* @param {
|
|
658
|
-
* @returns {
|
|
704
|
+
* @param {hookArrayOptions} opts
|
|
705
|
+
* @returns {hookArray}}
|
|
659
706
|
*/
|
|
660
|
-
function
|
|
707
|
+
function hookArray(arr = [], opts = {}) {
|
|
661
708
|
const { hook = a => a, type = "", ...rest } = opts;
|
|
662
709
|
|
|
663
710
|
function transformThenPush(item) {
|
|
@@ -703,7 +750,7 @@ function enrichArray(arr = [], opts = {}) {
|
|
|
703
750
|
|
|
704
751
|
}
|
|
705
752
|
|
|
706
|
-
/** @type {
|
|
753
|
+
/** @type {hookArray} */
|
|
707
754
|
// @ts-ignore
|
|
708
755
|
const enrichedArray = arr;
|
|
709
756
|
|
|
@@ -714,12 +761,15 @@ function enrichArray(arr = [], opts = {}) {
|
|
|
714
761
|
return enrichedArray;
|
|
715
762
|
};
|
|
716
763
|
|
|
764
|
+
/**
|
|
765
|
+
* @param {Config} config
|
|
766
|
+
*/
|
|
717
767
|
function buildFileNames(config) {
|
|
718
768
|
const { format = "csv", groupKeys = [], lookupTables = [] } = config;
|
|
719
769
|
let extension = "";
|
|
720
770
|
extension = format === "csv" ? "csv" : "json";
|
|
721
771
|
// const current = dayjs.utc().format("MM-DD-HH");
|
|
722
|
-
|
|
772
|
+
let simName = config.simulationName;
|
|
723
773
|
let writeDir = "./";
|
|
724
774
|
if (config.writeToDisk) {
|
|
725
775
|
const dataFolder = path.resolve("./data");
|
|
@@ -732,13 +782,17 @@ function buildFileNames(config) {
|
|
|
732
782
|
const writePaths = {
|
|
733
783
|
eventFiles: [path.join(writeDir, `${simName}-EVENTS.${extension}`)],
|
|
734
784
|
userFiles: [path.join(writeDir, `${simName}-USERS.${extension}`)],
|
|
735
|
-
adSpendFiles: [
|
|
785
|
+
adSpendFiles: [],
|
|
736
786
|
scdFiles: [],
|
|
737
787
|
mirrorFiles: [],
|
|
738
788
|
groupFiles: [],
|
|
739
789
|
lookupFiles: [],
|
|
740
790
|
folder: writeDir,
|
|
741
791
|
};
|
|
792
|
+
//add ad spend files
|
|
793
|
+
if (config?.hasAdSpend) {
|
|
794
|
+
writePaths.adSpendFiles.push(path.join(writeDir, `${simName}-AD-SPEND.${extension}`));
|
|
795
|
+
}
|
|
742
796
|
|
|
743
797
|
//add SCD files
|
|
744
798
|
const scdKeys = Object.keys(config?.scdProps || {});
|
|
@@ -869,7 +923,9 @@ function TimeSoup(earliestTime, latestTime, peaks = 5, deviation = 2, mean = 0)
|
|
|
869
923
|
iterations++;
|
|
870
924
|
offset = chance.normal({ mean: mean, dev: chunkSize / deviation });
|
|
871
925
|
isValidTime = validateTime(chunkMid + offset, earliestTime, latestTime);
|
|
872
|
-
if (iterations >
|
|
926
|
+
if (iterations > 25000) {
|
|
927
|
+
throw `${iterations} iterations... exceeded`;
|
|
928
|
+
}
|
|
873
929
|
} while (chunkMid + offset < chunkStart || chunkMid + offset > chunkEnd);
|
|
874
930
|
|
|
875
931
|
try {
|
|
@@ -1026,7 +1082,7 @@ module.exports = {
|
|
|
1026
1082
|
boxMullerRandom,
|
|
1027
1083
|
applySkew,
|
|
1028
1084
|
mapToRange,
|
|
1029
|
-
|
|
1085
|
+
weighNumRange,
|
|
1030
1086
|
progress,
|
|
1031
1087
|
range,
|
|
1032
1088
|
openFinder,
|
|
@@ -1044,11 +1100,12 @@ module.exports = {
|
|
|
1044
1100
|
shuffleOutside,
|
|
1045
1101
|
interruptArray,
|
|
1046
1102
|
generateUser,
|
|
1047
|
-
|
|
1103
|
+
hookArray,
|
|
1048
1104
|
optimizedBoxMuller,
|
|
1049
1105
|
buildFileNames,
|
|
1050
1106
|
streamJSON,
|
|
1051
1107
|
streamCSV,
|
|
1052
1108
|
inferFunnels,
|
|
1053
|
-
datesBetween
|
|
1109
|
+
datesBetween,
|
|
1110
|
+
weighChoices
|
|
1054
1111
|
};
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "make-mp-data",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.05",
|
|
4
4
|
"description": "builds all mixpanel primitives for a given project",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "types.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"start": "node ./core/index.js",
|
|
9
|
-
"dev": "nodemon scratch.mjs --ignore /data",
|
|
10
|
-
"complex": "nodemon ./core/index.js --complex --e 10000 --u 100",
|
|
11
|
-
"simple": "nodemon ./core/index.js --simple --e 10000 --u 100",
|
|
9
|
+
"dev": "nodemon scratch.mjs --ignore /data",
|
|
12
10
|
"prune": "rm -f ./data/* && rm -f ./tmp/*",
|
|
13
11
|
"post": "npm publish",
|
|
14
12
|
"test": "NODE_ENV=test jest --runInBand",
|
|
15
|
-
"
|
|
13
|
+
"coverage": "node --no-warnings --experimental-vm-modules node_modules/jest/bin/jest.js --coverage && open ./tests/coverage/lcov-report/index.html",
|
|
14
|
+
"deps": "sh ./scripts/deps.sh",
|
|
15
|
+
"new": "sh ./scripts/new.sh"
|
|
16
16
|
},
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
package/schemas/anon.js
CHANGED
|
@@ -14,7 +14,7 @@ const dayjs = require("dayjs");
|
|
|
14
14
|
const utc = require("dayjs/plugin/utc");
|
|
15
15
|
dayjs.extend(utc);
|
|
16
16
|
const { uid, comma } = require('ak-tools');
|
|
17
|
-
const { pickAWinner,
|
|
17
|
+
const { pickAWinner, weighNumRange, date, integer } = require('../core/utils');
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
|
|
@@ -86,7 +86,7 @@ const config = {
|
|
|
86
86
|
superProps: {},
|
|
87
87
|
userProps: {
|
|
88
88
|
title: chance.profession.bind(chance),
|
|
89
|
-
luckyNumber:
|
|
89
|
+
luckyNumber: weighNumRange(42, 420),
|
|
90
90
|
spiritAnimal: ["duck", "dog", "otter", "penguin", "cat", "elephant", "lion", "cheetah", "giraffe", "zebra", "rhino", "hippo", "whale", "dolphin", "shark", "octopus", "squid", "jellyfish", "starfish", "seahorse", "crab", "lobster", "shrimp", "clam", "snail", "slug", "butterfly", "moth", "bee", "wasp", "ant", "beetle", "ladybug", "caterpillar", "centipede", "millipede", "scorpion", "spider", "tarantula", "tick", "mite", "mosquito", "fly", "dragonfly", "damselfly", "grasshopper", "cricket", "locust", "mantis", "cockroach", "termite", "praying mantis", "walking stick", "stick bug", "leaf insect", "lacewing", "aphid", "cicada", "thrips", "psyllid", "scale insect", "whitefly", "mealybug", "planthopper", "leafhopper", "treehopper", "flea", "louse", "bedbug", "flea beetle", "weevil", "longhorn beetle", "leaf beetle", "tiger beetle", "ground beetle", "lady beetle", "firefly", "click beetle", "rove beetle", "scarab beetle", "dung beetle", "stag beetle", "rhinoceros beetle", "hercules beetle", "goliath beetle", "jewel beetle", "tortoise beetle"]
|
|
91
91
|
},
|
|
92
92
|
scdProps: {},
|
package/schemas/complex.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const Chance = require('chance');
|
|
10
10
|
const chance = new Chance();
|
|
11
|
-
const {
|
|
11
|
+
const { weighNumRange, date, integer } = require('../core/utils.js');
|
|
12
12
|
const u = require('ak-tools');
|
|
13
13
|
|
|
14
14
|
/** @type {import('../types.js').Config} */
|
|
@@ -37,7 +37,7 @@ const config = {
|
|
|
37
37
|
"event": "checkout",
|
|
38
38
|
"weight": 2,
|
|
39
39
|
"properties": {
|
|
40
|
-
amount:
|
|
40
|
+
amount: weighNumRange(5, 500, .25),
|
|
41
41
|
currency: ["USD", "USD", "USD", "CAD", "EUR", "EUR", "BTC", "BTC", "ETH", "JPY"],
|
|
42
42
|
cart: makeProducts(12),
|
|
43
43
|
}
|
|
@@ -46,9 +46,9 @@ const config = {
|
|
|
46
46
|
"event": "add to cart",
|
|
47
47
|
"weight": 4,
|
|
48
48
|
"properties": {
|
|
49
|
-
amount:
|
|
49
|
+
amount: weighNumRange(5, 500, .25),
|
|
50
50
|
qty: integer(1, 5),
|
|
51
|
-
product_id:
|
|
51
|
+
product_id: weighNumRange(1, 1000, 1.4)
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
{
|
|
@@ -65,10 +65,10 @@ const config = {
|
|
|
65
65
|
"properties": {
|
|
66
66
|
category: ["funny", "educational", "inspirational", "music", "news", "sports", "cooking", "DIY", "travel", "gaming"],
|
|
67
67
|
hashTags: makeHashTags,
|
|
68
|
-
watchTimeSec:
|
|
68
|
+
watchTimeSec: weighNumRange(10, 600, .25,),
|
|
69
69
|
quality: ["2160p", "1440p", "1080p", "720p", "480p", "360p", "240p"],
|
|
70
70
|
format: ["mp4", "avi", "mov", "mpg"],
|
|
71
|
-
video_id:
|
|
71
|
+
video_id: weighNumRange(1, 50000, 1.4),
|
|
72
72
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
@@ -76,8 +76,8 @@ const config = {
|
|
|
76
76
|
"event": "comment",
|
|
77
77
|
"weight": 2,
|
|
78
78
|
"properties": {
|
|
79
|
-
length:
|
|
80
|
-
video_id:
|
|
79
|
+
length: weighNumRange(1, 500, .25),
|
|
80
|
+
video_id: weighNumRange(1, 50000, 1.4),
|
|
81
81
|
has_replies: [true, false, false, false, false],
|
|
82
82
|
has_photo: [true, false, false, false, false],
|
|
83
83
|
|
|
@@ -87,7 +87,7 @@ const config = {
|
|
|
87
87
|
"event": "save video",
|
|
88
88
|
"weight": 4,
|
|
89
89
|
"properties": {
|
|
90
|
-
video_id:
|
|
90
|
+
video_id: weighNumRange(1, 50000, 1.4),
|
|
91
91
|
ui_control: ["toolbar", "menu", "keyboard"]
|
|
92
92
|
|
|
93
93
|
|
|
@@ -97,7 +97,7 @@ const config = {
|
|
|
97
97
|
"event": "view item",
|
|
98
98
|
"weight": 8,
|
|
99
99
|
"properties": {
|
|
100
|
-
product_id:
|
|
100
|
+
product_id: weighNumRange(1, 24, 3),
|
|
101
101
|
colors: ["light", "dark", "custom", "dark"]
|
|
102
102
|
}
|
|
103
103
|
},
|
|
@@ -105,7 +105,7 @@ const config = {
|
|
|
105
105
|
"event": "save item",
|
|
106
106
|
"weight": 5,
|
|
107
107
|
"properties": {
|
|
108
|
-
product_id:
|
|
108
|
+
product_id: weighNumRange(1, 1000, 12),
|
|
109
109
|
colors: ["light", "dark", "custom", "dark"]
|
|
110
110
|
}
|
|
111
111
|
},
|
|
@@ -113,7 +113,7 @@ const config = {
|
|
|
113
113
|
"event": "support ticket",
|
|
114
114
|
"weight": 2,
|
|
115
115
|
"properties": {
|
|
116
|
-
product_id:
|
|
116
|
+
product_id: weighNumRange(1, 1000, .6),
|
|
117
117
|
description: chance.sentence.bind(chance),
|
|
118
118
|
severity: ["low", "medium", "high"],
|
|
119
119
|
ticket_id: chance.guid.bind(chance)
|
|
@@ -141,7 +141,7 @@ const config = {
|
|
|
141
141
|
*/
|
|
142
142
|
userProps: {
|
|
143
143
|
title: chance.profession.bind(chance),
|
|
144
|
-
luckyNumber:
|
|
144
|
+
luckyNumber: weighNumRange(42, 420),
|
|
145
145
|
experiment: designExperiment(),
|
|
146
146
|
spiritAnimal: ["unicorn", "dragon", "phoenix", "sasquatch", "yeti", "kraken", "jackalope", "thunderbird", "mothman", "nessie", "chupacabra", "jersey devil", "bigfoot", "weindgo", "bunyip", "mokele-mbembe", "tatzelwurm", "megalodon"],
|
|
147
147
|
timezone: chance.timezone.bind(chance), // ["America/New_York", "America/Los_Angeles", "America/Chicago", "America/Denver", "America/Phoenix", "America/Anchorage", "Pacific/Honolulu"]
|
|
@@ -153,8 +153,8 @@ const config = {
|
|
|
153
153
|
/** each generates it's own table */
|
|
154
154
|
scdProps: {
|
|
155
155
|
plan: ["free", "free", "free", "free", "basic", "basic", "basic", "premium", "premium", "enterprise"],
|
|
156
|
-
MRR:
|
|
157
|
-
NPS:
|
|
156
|
+
MRR: weighNumRange(0, 10000, .15),
|
|
157
|
+
NPS: weighNumRange(0, 10, 2, 150),
|
|
158
158
|
subscribed: [true, true, true, true, true, true, false, false, false, false, "it's complicated"],
|
|
159
159
|
renewalDate: date(100, false),
|
|
160
160
|
},
|
|
@@ -164,7 +164,7 @@ const config = {
|
|
|
164
164
|
profit: { events: ["checkout"], values: [4, 2, 42, 420] },
|
|
165
165
|
watchTimeSec: {
|
|
166
166
|
events: ["watch video"],
|
|
167
|
-
values:
|
|
167
|
+
values: weighNumRange(50, 1200, 6)
|
|
168
168
|
}
|
|
169
169
|
},
|
|
170
170
|
|
|
@@ -181,7 +181,7 @@ const config = {
|
|
|
181
181
|
company_id: {
|
|
182
182
|
name: () => { return chance.company(); },
|
|
183
183
|
email: () => { return `CSM: ${chance.pickone(["AK", "Jessica", "Michelle", "Dana", "Brian", "Dave"])}`; },
|
|
184
|
-
"# of employees":
|
|
184
|
+
"# of employees": weighNumRange(3, 10000),
|
|
185
185
|
"industry": ["tech", "finance", "healthcare", "education", "government", "non-profit"],
|
|
186
186
|
"segment": ["enterprise", "SMB", "mid-market"],
|
|
187
187
|
"products": [["core"], ["core"], ["core", "add-ons"], ["core", "pro-serve"], ["core", "add-ons", "pro-serve"], ["core", "BAA", "enterprise"], ["free"], ["free"], ["free", "addons"]],
|
|
@@ -190,7 +190,7 @@ const config = {
|
|
|
190
190
|
name: () => { return `#${chance.word({ length: integer(4, 24), capitalize: true })}`; },
|
|
191
191
|
email: ["public", "private"],
|
|
192
192
|
"room provider": ["partner", "core", "core", "core"],
|
|
193
|
-
"room capacity":
|
|
193
|
+
"room capacity": weighNumRange(3, 1000000),
|
|
194
194
|
"isPublic": [true, false, false, false, false],
|
|
195
195
|
"country": chance.country.bind(chance),
|
|
196
196
|
"isVerified": [true, true, false, false, false],
|
|
@@ -208,9 +208,9 @@ const config = {
|
|
|
208
208
|
"demand": ["high", "medium", "medium", "low"],
|
|
209
209
|
"supply": ["high", "medium", "medium", "low"],
|
|
210
210
|
"manufacturer": chance.company.bind(chance),
|
|
211
|
-
"price":
|
|
212
|
-
"rating":
|
|
213
|
-
"reviews":
|
|
211
|
+
"price": weighNumRange(5, 500, 1000, .25),
|
|
212
|
+
"rating": weighNumRange(1, 5),
|
|
213
|
+
"reviews": weighNumRange(0, 35)
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
},
|
|
@@ -222,7 +222,7 @@ const config = {
|
|
|
222
222
|
copyright: ["all rights reserved", "creative commons", "creative commons", "public domain", "fair use"],
|
|
223
223
|
uploader_id: chance.guid.bind(chance),
|
|
224
224
|
"uploader influence": ["low", "low", "low", "medium", "medium", "high"],
|
|
225
|
-
thumbs:
|
|
225
|
+
thumbs: weighNumRange(0, 35),
|
|
226
226
|
rating: ["G", "PG", "PG-13", "R", "NC-17", "PG-13", "R", "NC-17", "R", "PG", "PG"]
|
|
227
227
|
}
|
|
228
228
|
|