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/index.js
CHANGED
|
@@ -6,6 +6,12 @@ by AK
|
|
|
6
6
|
ak@mixpanel.com
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
//todo: churn ... is churnFunnel, possible to return, etc
|
|
10
|
+
//todo: fixedTimeFunnel? if set this funnel will occur for all users at the same time ['cart charged', 'charge complete']
|
|
11
|
+
//todo defaults!!!
|
|
12
|
+
//todo ads-data
|
|
13
|
+
|
|
14
|
+
|
|
9
15
|
const dayjs = require("dayjs");
|
|
10
16
|
const utc = require("dayjs/plugin/utc");
|
|
11
17
|
dayjs.extend(utc);
|
|
@@ -117,9 +123,9 @@ async function main(config) {
|
|
|
117
123
|
config.makeChart = makeChart;
|
|
118
124
|
config.soup = soup;
|
|
119
125
|
config.hook = hook;
|
|
120
|
-
|
|
126
|
+
|
|
121
127
|
//event validation
|
|
122
|
-
const validatedEvents =
|
|
128
|
+
const validatedEvents = u.validateEventConfig(events);
|
|
123
129
|
events = validatedEvents;
|
|
124
130
|
config.events = validatedEvents;
|
|
125
131
|
global.MP_SIMULATION_CONFIG = config;
|
|
@@ -147,46 +153,9 @@ async function main(config) {
|
|
|
147
153
|
|
|
148
154
|
// if no funnels, make some out of events...
|
|
149
155
|
if (!funnels || !funnels.length) {
|
|
150
|
-
|
|
151
|
-
const firstEvents = events.filter((e) => e.isFirstEvent).map((e) => e.event);
|
|
152
|
-
const usageEvents = events.filter((e) => !e.isFirstEvent).map((e) => e.event);
|
|
153
|
-
const numFunnelsToCreate = Math.ceil(usageEvents.length);
|
|
154
|
-
/** @type {Funnel} */
|
|
155
|
-
const funnelTemplate = {
|
|
156
|
-
sequence: [],
|
|
157
|
-
conversionRate: 50,
|
|
158
|
-
order: 'sequential',
|
|
159
|
-
props: {},
|
|
160
|
-
timeToConvert: 1,
|
|
161
|
-
isFirstFunnel: false,
|
|
162
|
-
weight: 1
|
|
163
|
-
};
|
|
164
|
-
if (firstEvents.length) {
|
|
165
|
-
for (const event of firstEvents) {
|
|
166
|
-
createdFunnels.push({ ...clone(funnelTemplate), sequence: [event], isFirstFunnel: true, conversionRate: 100 });
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
//at least one funnel with all usage events
|
|
171
|
-
createdFunnels.push({ ...clone(funnelTemplate), sequence: usageEvents });
|
|
172
|
-
|
|
173
|
-
//for the rest, make random funnels
|
|
174
|
-
followUpFunnels: for (let i = 1; i < numFunnelsToCreate; i++) {
|
|
175
|
-
/** @type {Funnel} */
|
|
176
|
-
const funnel = { ...clone(funnelTemplate) };
|
|
177
|
-
funnel.conversionRate = u.integer(25, 75);
|
|
178
|
-
funnel.timeToConvert = u.integer(1, 10);
|
|
179
|
-
funnel.weight = u.integer(1, 10);
|
|
180
|
-
const sequence = u.shuffleArray(usageEvents).slice(0, u.integer(2, usageEvents.length));
|
|
181
|
-
funnel.sequence = sequence;
|
|
182
|
-
funnel.order = 'random';
|
|
183
|
-
createdFunnels.push(funnel);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
funnels = createdFunnels;
|
|
156
|
+
funnels = inferFunnels(events);
|
|
187
157
|
config.funnels = funnels;
|
|
188
158
|
CONFIG = config;
|
|
189
|
-
|
|
190
159
|
}
|
|
191
160
|
|
|
192
161
|
//user loop
|
|
@@ -271,8 +240,7 @@ async function main(config) {
|
|
|
271
240
|
u.progress("groups", i);
|
|
272
241
|
const group = {
|
|
273
242
|
[groupKey]: i,
|
|
274
|
-
...makeProfile(groupProps[groupKey])
|
|
275
|
-
// $distinct_id: i,
|
|
243
|
+
...makeProfile(groupProps[groupKey])
|
|
276
244
|
};
|
|
277
245
|
group["distinct_id"] = i;
|
|
278
246
|
groupProfiles.push(group);
|
|
@@ -300,18 +268,24 @@ async function main(config) {
|
|
|
300
268
|
const actualNow = dayjs();
|
|
301
269
|
const fixedNow = dayjs.unix(global.NOW);
|
|
302
270
|
const timeShift = actualNow.diff(fixedNow, "second");
|
|
303
|
-
|
|
271
|
+
|
|
304
272
|
eventData.forEach((event) => {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
273
|
+
try {
|
|
274
|
+
const newTime = dayjs(event.time).add(timeShift, "second");
|
|
275
|
+
event.time = newTime.toISOString();
|
|
276
|
+
if (epochStart && newTime.unix() < epochStart) event = {};
|
|
277
|
+
if (epochEnd && newTime.unix() > epochEnd) event = {};
|
|
278
|
+
}
|
|
279
|
+
catch (e) {
|
|
280
|
+
//noop
|
|
281
|
+
}
|
|
309
282
|
});
|
|
310
283
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
284
|
+
// const dayShift = actualNow.diff(global.NOW, "day");
|
|
285
|
+
// userProfilesData.forEach((profile) => {
|
|
286
|
+
// const newTime = dayjs(profile.created).add(dayShift, "day");
|
|
287
|
+
// profile.created = newTime.toISOString();
|
|
288
|
+
// });
|
|
315
289
|
|
|
316
290
|
|
|
317
291
|
// draw charts
|
|
@@ -461,6 +435,11 @@ async function main(config) {
|
|
|
461
435
|
};
|
|
462
436
|
}
|
|
463
437
|
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
|
|
464
443
|
/**
|
|
465
444
|
* creates a random event
|
|
466
445
|
* @param {string} distinct_id
|
|
@@ -528,8 +507,8 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
|
|
|
528
507
|
}
|
|
529
508
|
|
|
530
509
|
/**
|
|
531
|
-
*
|
|
532
|
-
* this is called
|
|
510
|
+
* from a funnel spec to a funnel that a user completes/doesn't complete
|
|
511
|
+
* this is called MANY times per user
|
|
533
512
|
* @param {Funnel} funnel
|
|
534
513
|
* @param {Person} user
|
|
535
514
|
* @param {UserProfile} profile
|
|
@@ -541,7 +520,14 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
|
|
|
541
520
|
function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
542
521
|
const { hook } = config;
|
|
543
522
|
hook(funnel, "funnel-pre", { user, profile, scd, funnel, config });
|
|
544
|
-
|
|
523
|
+
let {
|
|
524
|
+
sequence,
|
|
525
|
+
conversionRate = 50,
|
|
526
|
+
order = 'sequential',
|
|
527
|
+
timeToConvert = 1,
|
|
528
|
+
props,
|
|
529
|
+
requireRepeats = false,
|
|
530
|
+
} = funnel;
|
|
545
531
|
const { distinct_id, created, anonymousIds, sessionIds } = user;
|
|
546
532
|
const { superProps, groupKeys } = config;
|
|
547
533
|
const { name, email } = profile;
|
|
@@ -556,32 +542,90 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
|
556
542
|
}
|
|
557
543
|
}
|
|
558
544
|
|
|
559
|
-
const funnelPossibleEvents = sequence
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
545
|
+
const funnelPossibleEvents = sequence
|
|
546
|
+
.map((eventName) => {
|
|
547
|
+
const foundEvent = config.events.find((e) => e.event === eventName);
|
|
548
|
+
/** @type {EventConfig} */
|
|
549
|
+
const eventSpec = foundEvent || { event: eventName, properties: {} };
|
|
550
|
+
for (const key in eventSpec.properties) {
|
|
551
|
+
try {
|
|
552
|
+
eventSpec.properties[key] = u.choose(eventSpec.properties[key]);
|
|
553
|
+
} catch (e) {
|
|
554
|
+
console.error(`error with ${key} in ${eventSpec.event} event`, e);
|
|
555
|
+
debugger;
|
|
556
|
+
}
|
|
569
557
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
558
|
+
delete eventSpec.isFirstEvent;
|
|
559
|
+
delete eventSpec.weight;
|
|
560
|
+
eventSpec.properties = { ...eventSpec.properties, ...chosenFunnelProps };
|
|
561
|
+
return eventSpec;
|
|
562
|
+
})
|
|
563
|
+
.reduce((acc, step) => {
|
|
564
|
+
if (!requireRepeats) {
|
|
565
|
+
if (acc.find(e => e.event === step.event)) {
|
|
566
|
+
if (config.chance.bool({ likelihood: 50 })) {
|
|
567
|
+
conversionRate = Math.floor(conversionRate * 1.25); //increase conversion rate
|
|
568
|
+
acc.push(step);
|
|
569
|
+
}
|
|
570
|
+
//A SKIPPED STEP!
|
|
571
|
+
else {
|
|
572
|
+
conversionRate = Math.floor(conversionRate * .75); //reduce conversion rate
|
|
573
|
+
return acc; //early return to skip the step
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
acc.push(step);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
acc.push(step);
|
|
582
|
+
}
|
|
583
|
+
return acc;
|
|
584
|
+
}, []);
|
|
576
585
|
|
|
577
|
-
|
|
586
|
+
let doesUserConvert = config.chance.bool({ likelihood: conversionRate });
|
|
578
587
|
let numStepsUserWillTake = sequence.length;
|
|
579
588
|
if (!doesUserConvert) numStepsUserWillTake = u.integer(1, sequence.length - 1);
|
|
580
589
|
const funnelTotalRelativeTimeInHours = timeToConvert / numStepsUserWillTake;
|
|
581
590
|
const msInHour = 60000 * 60;
|
|
591
|
+
const funnelStepsUserWillTake = funnelPossibleEvents.slice(0, numStepsUserWillTake);
|
|
592
|
+
|
|
593
|
+
let funnelActualOrder = [];
|
|
594
|
+
|
|
595
|
+
switch (order) {
|
|
596
|
+
case "sequential":
|
|
597
|
+
funnelActualOrder = funnelStepsUserWillTake;
|
|
598
|
+
break;
|
|
599
|
+
case "random":
|
|
600
|
+
funnelActualOrder = u.shuffleArray(funnelStepsUserWillTake);
|
|
601
|
+
break;
|
|
602
|
+
case "first-fixed":
|
|
603
|
+
funnelActualOrder = u.shuffleExceptFirst(funnelStepsUserWillTake);
|
|
604
|
+
break;
|
|
605
|
+
case "last-fixed":
|
|
606
|
+
funnelActualOrder = u.shuffleExceptLast(funnelStepsUserWillTake);
|
|
607
|
+
break;
|
|
608
|
+
case "first-and-last-fixed":
|
|
609
|
+
funnelActualOrder = u.fixFirstAndLast(funnelStepsUserWillTake);
|
|
610
|
+
break;
|
|
611
|
+
case "middle-fixed":
|
|
612
|
+
funnelActualOrder = u.shuffleOutside(funnelStepsUserWillTake);
|
|
613
|
+
break;
|
|
614
|
+
case "interrupted":
|
|
615
|
+
const potentialSubstitutes = config?.events
|
|
616
|
+
?.filter(e => !e.isFirstEvent)
|
|
617
|
+
?.filter(e => !sequence.includes(e.event)) || [];
|
|
618
|
+
funnelActualOrder = u.interruptArray(funnelStepsUserWillTake, potentialSubstitutes);
|
|
619
|
+
break;
|
|
620
|
+
default:
|
|
621
|
+
funnelActualOrder = funnelStepsUserWillTake;
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
|
|
582
626
|
|
|
583
627
|
let lastTimeJump = 0;
|
|
584
|
-
const
|
|
628
|
+
const funnelActualEventsWithOffset = funnelActualOrder
|
|
585
629
|
.map((event, index) => {
|
|
586
630
|
if (index === 0) {
|
|
587
631
|
event.relativeTimeMs = 0;
|
|
@@ -606,36 +650,9 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
|
606
650
|
});
|
|
607
651
|
|
|
608
652
|
|
|
609
|
-
let funnelActualOrder = [];
|
|
610
|
-
|
|
611
|
-
//todo
|
|
612
|
-
switch (order) {
|
|
613
|
-
case "sequential":
|
|
614
|
-
funnelActualOrder = funnelActualEvents;
|
|
615
|
-
break;
|
|
616
|
-
case "random":
|
|
617
|
-
funnelActualOrder = u.shuffleArray(funnelActualEvents);
|
|
618
|
-
break;
|
|
619
|
-
case "first-fixed":
|
|
620
|
-
funnelActualOrder = u.shuffleExceptFirst(funnelActualEvents);
|
|
621
|
-
break;
|
|
622
|
-
case "last-fixed":
|
|
623
|
-
funnelActualOrder = u.shuffleExceptLast(funnelActualEvents);
|
|
624
|
-
break;
|
|
625
|
-
case "first-and-last-fixed":
|
|
626
|
-
funnelActualOrder = u.fixFirstAndLast(funnelActualEvents);
|
|
627
|
-
break;
|
|
628
|
-
case "middle-fixed":
|
|
629
|
-
funnelActualOrder = u.shuffleOutside(funnelActualEvents);
|
|
630
|
-
break;
|
|
631
|
-
default:
|
|
632
|
-
funnelActualOrder = funnelActualEvents;
|
|
633
|
-
break;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
653
|
const earliestTime = firstEventTime || dayjs(created).unix();
|
|
637
654
|
let funnelStartTime;
|
|
638
|
-
let finalEvents =
|
|
655
|
+
let finalEvents = funnelActualEventsWithOffset
|
|
639
656
|
.map((event, index) => {
|
|
640
657
|
const newEvent = makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, event, {}, groupKeys);
|
|
641
658
|
if (index === 0) {
|
|
@@ -643,9 +660,15 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
|
643
660
|
delete newEvent.relativeTimeMs;
|
|
644
661
|
return newEvent;
|
|
645
662
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
663
|
+
try {
|
|
664
|
+
newEvent.time = dayjs(funnelStartTime).add(event.relativeTimeMs, "milliseconds").toISOString();
|
|
665
|
+
delete newEvent.relativeTimeMs;
|
|
666
|
+
return newEvent;
|
|
667
|
+
}
|
|
668
|
+
catch (e) {
|
|
669
|
+
|
|
670
|
+
debugger;
|
|
671
|
+
}
|
|
649
672
|
});
|
|
650
673
|
|
|
651
674
|
|
|
@@ -654,6 +677,49 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
|
|
|
654
677
|
}
|
|
655
678
|
|
|
656
679
|
|
|
680
|
+
function inferFunnels(events) {
|
|
681
|
+
const createdFunnels = [];
|
|
682
|
+
const firstEvents = events.filter((e) => e.isFirstEvent).map((e) => e.event);
|
|
683
|
+
const usageEvents = events.filter((e) => !e.isFirstEvent).map((e) => e.event);
|
|
684
|
+
const numFunnelsToCreate = Math.ceil(usageEvents.length);
|
|
685
|
+
/** @type {Funnel} */
|
|
686
|
+
const funnelTemplate = {
|
|
687
|
+
sequence: [],
|
|
688
|
+
conversionRate: 50,
|
|
689
|
+
order: 'sequential',
|
|
690
|
+
requireRepeats: false,
|
|
691
|
+
props: {},
|
|
692
|
+
timeToConvert: 1,
|
|
693
|
+
isFirstFunnel: false,
|
|
694
|
+
weight: 1
|
|
695
|
+
};
|
|
696
|
+
if (firstEvents.length) {
|
|
697
|
+
for (const event of firstEvents) {
|
|
698
|
+
createdFunnels.push({ ...clone(funnelTemplate), sequence: [event], isFirstFunnel: true, conversionRate: 100 });
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
//at least one funnel with all usage events
|
|
703
|
+
createdFunnels.push({ ...clone(funnelTemplate), sequence: usageEvents });
|
|
704
|
+
|
|
705
|
+
//for the rest, make random funnels
|
|
706
|
+
followUpFunnels: for (let i = 1; i < numFunnelsToCreate; i++) {
|
|
707
|
+
/** @type {Funnel} */
|
|
708
|
+
const funnel = { ...clone(funnelTemplate) };
|
|
709
|
+
funnel.conversionRate = u.integer(25, 75);
|
|
710
|
+
funnel.timeToConvert = u.integer(1, 10);
|
|
711
|
+
funnel.weight = u.integer(1, 10);
|
|
712
|
+
const sequence = u.shuffleArray(usageEvents).slice(0, u.integer(2, usageEvents.length));
|
|
713
|
+
funnel.sequence = sequence;
|
|
714
|
+
funnel.order = 'random';
|
|
715
|
+
createdFunnels.push(funnel);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return createdFunnels;
|
|
719
|
+
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
|
|
657
723
|
function makeProfile(props, defaults) {
|
|
658
724
|
//build the spec
|
|
659
725
|
const profile = {
|
|
@@ -704,29 +770,8 @@ function makeSCD(prop, scdKey, distinct_id, mutations, created) {
|
|
|
704
770
|
return scdEntries;
|
|
705
771
|
}
|
|
706
772
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
*/
|
|
710
|
-
function validateEvents(events) {
|
|
711
|
-
if (!Array.isArray(events)) throw new Error("events must be an array");
|
|
712
|
-
const cleanEventConfig = [];
|
|
713
|
-
for (const event of events) {
|
|
714
|
-
if (typeof event === "string") {
|
|
715
|
-
/** @type {EventConfig} */
|
|
716
|
-
const eventTemplate = {
|
|
717
|
-
event,
|
|
718
|
-
isFirstEvent: false,
|
|
719
|
-
properties: {},
|
|
720
|
-
weight: u.integer(1, 5)
|
|
721
|
-
};
|
|
722
|
-
cleanEventConfig.push(eventTemplate);
|
|
723
|
-
}
|
|
724
|
-
if (typeof event === "object") {
|
|
725
|
-
cleanEventConfig.push(event);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
return cleanEventConfig;
|
|
729
|
-
}
|
|
773
|
+
|
|
774
|
+
|
|
730
775
|
|
|
731
776
|
|
|
732
777
|
|
package/package.json
CHANGED
package/schemas/complex.js
CHANGED
|
@@ -28,7 +28,7 @@ const config = {
|
|
|
28
28
|
"event": "checkout",
|
|
29
29
|
"weight": 2,
|
|
30
30
|
"properties": {
|
|
31
|
-
amount: weightedRange(5, 500, .25
|
|
31
|
+
amount: weightedRange(5, 500, .25),
|
|
32
32
|
currency: ["USD", "USD", "USD", "CAD", "EUR", "EUR", "BTC", "BTC", "ETH", "JPY"],
|
|
33
33
|
cart: makeProducts(12),
|
|
34
34
|
}
|
|
@@ -37,9 +37,9 @@ const config = {
|
|
|
37
37
|
"event": "add to cart",
|
|
38
38
|
"weight": 4,
|
|
39
39
|
"properties": {
|
|
40
|
-
amount: weightedRange(5, 500, .25
|
|
40
|
+
amount: weightedRange(5, 500, .25),
|
|
41
41
|
qty: integer(1, 5),
|
|
42
|
-
product_id: weightedRange(1, 1000, 1.4
|
|
42
|
+
product_id: weightedRange(1, 1000, 1.4)
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
{
|
|
@@ -56,10 +56,10 @@ const config = {
|
|
|
56
56
|
"properties": {
|
|
57
57
|
category: ["funny", "educational", "inspirational", "music", "news", "sports", "cooking", "DIY", "travel", "gaming"],
|
|
58
58
|
hashTags: makeHashTags,
|
|
59
|
-
watchTimeSec: weightedRange(10, 600
|
|
59
|
+
watchTimeSec: weightedRange(10, 600, .25,),
|
|
60
60
|
quality: ["2160p", "1440p", "1080p", "720p", "480p", "360p", "240p"],
|
|
61
61
|
format: ["mp4", "avi", "mov", "mpg"],
|
|
62
|
-
video_id: weightedRange(1, 50000, 1.4
|
|
62
|
+
video_id: weightedRange(1, 50000, 1.4),
|
|
63
63
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
@@ -67,8 +67,8 @@ const config = {
|
|
|
67
67
|
"event": "comment",
|
|
68
68
|
"weight": 2,
|
|
69
69
|
"properties": {
|
|
70
|
-
length: weightedRange(1, 500, .25
|
|
71
|
-
video_id: weightedRange(1, 50000, 1.4
|
|
70
|
+
length: weightedRange(1, 500, .25),
|
|
71
|
+
video_id: weightedRange(1, 50000, 1.4),
|
|
72
72
|
has_replies: [true, false, false, false, false],
|
|
73
73
|
has_photo: [true, false, false, false, false],
|
|
74
74
|
|
|
@@ -78,7 +78,7 @@ const config = {
|
|
|
78
78
|
"event": "save video",
|
|
79
79
|
"weight": 4,
|
|
80
80
|
"properties": {
|
|
81
|
-
video_id: weightedRange(1, 50000, 1.4
|
|
81
|
+
video_id: weightedRange(1, 50000, 1.4),
|
|
82
82
|
ui_control: ["toolbar", "menu", "keyboard"]
|
|
83
83
|
|
|
84
84
|
|
|
@@ -88,7 +88,7 @@ const config = {
|
|
|
88
88
|
"event": "view item",
|
|
89
89
|
"weight": 8,
|
|
90
90
|
"properties": {
|
|
91
|
-
product_id: weightedRange(1,
|
|
91
|
+
product_id: weightedRange(1, 24, 3),
|
|
92
92
|
colors: ["light", "dark", "custom", "dark"]
|
|
93
93
|
}
|
|
94
94
|
},
|
|
@@ -96,7 +96,7 @@ const config = {
|
|
|
96
96
|
"event": "save item",
|
|
97
97
|
"weight": 5,
|
|
98
98
|
"properties": {
|
|
99
|
-
product_id: weightedRange(1, 1000, 12
|
|
99
|
+
product_id: weightedRange(1, 1000, 12),
|
|
100
100
|
colors: ["light", "dark", "custom", "dark"]
|
|
101
101
|
}
|
|
102
102
|
},
|
|
@@ -104,7 +104,7 @@ const config = {
|
|
|
104
104
|
"event": "support ticket",
|
|
105
105
|
"weight": 2,
|
|
106
106
|
"properties": {
|
|
107
|
-
product_id: weightedRange(1, 1000, .6
|
|
107
|
+
product_id: weightedRange(1, 1000, .6),
|
|
108
108
|
description: chance.sentence.bind(chance),
|
|
109
109
|
severity: ["low", "medium", "high"],
|
|
110
110
|
ticket_id: chance.guid.bind(chance)
|
|
@@ -144,8 +144,8 @@ const config = {
|
|
|
144
144
|
/** each generates it's own table */
|
|
145
145
|
scdProps: {
|
|
146
146
|
plan: ["free", "free", "free", "free", "basic", "basic", "basic", "premium", "premium", "enterprise"],
|
|
147
|
-
MRR: weightedRange(0, 10000,
|
|
148
|
-
NPS: weightedRange(0, 10,
|
|
147
|
+
MRR: weightedRange(0, 10000, .15),
|
|
148
|
+
NPS: weightedRange(0, 10, 2, 150),
|
|
149
149
|
subscribed: [true, true, true, true, true, true, false, false, false, false, "it's complicated"],
|
|
150
150
|
renewalDate: date(100, false),
|
|
151
151
|
},
|
|
@@ -155,7 +155,7 @@ const config = {
|
|
|
155
155
|
profit: { events: ["checkout"], values: [4, 2, 42, 420] },
|
|
156
156
|
watchTimeSec: {
|
|
157
157
|
events: ["watch video"],
|
|
158
|
-
values: weightedRange(50, 1200, 6
|
|
158
|
+
values: weightedRange(50, 1200, 6, 247)
|
|
159
159
|
}
|
|
160
160
|
},
|
|
161
161
|
|
package/schemas/funnels.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)
|
|
@@ -107,69 +107,68 @@ const config = {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
],
|
|
110
|
-
funnels: [
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
110
|
+
funnels: [
|
|
111
|
+
// {
|
|
112
|
+
// sequence: ["page view", "view item", "page view", "sign up"],
|
|
113
|
+
// weight: 1,
|
|
114
|
+
// isFirstFunnel: true,
|
|
115
|
+
// order: "sequential",
|
|
116
|
+
// conversionRate: 50,
|
|
117
|
+
// timeToConvert: 2,
|
|
118
|
+
// props: {
|
|
119
|
+
// variants: ["A", "B", "C", "Control"],
|
|
120
|
+
// flows: ["new", "existing", "loyal", "churned"],
|
|
121
|
+
// flags: ["on", "off"],
|
|
122
|
+
// experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
|
|
123
|
+
// multiVariate: [true, false]
|
|
124
|
+
|
|
125
|
+
// },
|
|
123
126
|
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
// isInterruptedFunnel: false, // an interrupted funnel will have random events interspersed with the sequence
|
|
127
|
-
// fixedTimeFunnel: 30, // if set this funnel will occur for all users at the same time ['cart charged', 'charge complete']
|
|
128
|
-
// churn: {
|
|
129
|
-
// isChurnFunnel: true, //if the user completes this funnel, they churn
|
|
130
|
-
// probabilityToReturn: 0.1, //if the user churns, this is the probability they will return
|
|
131
127
|
// },
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
sequence: ["
|
|
172
|
-
|
|
128
|
+
// {
|
|
129
|
+
// sequence: ["app install", "app open", "tutorial", "sign up"],
|
|
130
|
+
// weight: 1,
|
|
131
|
+
// isFirstFunnel: true,
|
|
132
|
+
// order: "sequential",
|
|
133
|
+
// conversionRate: 50,
|
|
134
|
+
// timeToConvert: 2,
|
|
135
|
+
// props: {
|
|
136
|
+
// variants: ["A", "B", "C", "Control"],
|
|
137
|
+
// flows: ["new", "existing", "loyal", "churned"],
|
|
138
|
+
// flags: ["on", "off"],
|
|
139
|
+
// experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
|
|
140
|
+
// multiVariate: [true, false]
|
|
141
|
+
|
|
142
|
+
// }
|
|
143
|
+
// },
|
|
144
|
+
// {
|
|
145
|
+
// sequence: ["view item", "add to cart", "checkout", "rage", "cage", "mage"],
|
|
146
|
+
// order: "interrupted"
|
|
147
|
+
// },
|
|
148
|
+
// {
|
|
149
|
+
// sequence: ["page view", "view item", "add to cart", "add to cart", "checkout"],
|
|
150
|
+
// weight: 3,
|
|
151
|
+
// isFirstFunnel: false,
|
|
152
|
+
// order: "sequential",
|
|
153
|
+
// conversionRate: 70,
|
|
154
|
+
// timeToConvert: 7 * 24,
|
|
155
|
+
// props: {
|
|
156
|
+
// variants: ["A", "B", "C", "Control"],
|
|
157
|
+
// flows: ["new", "existing", "loyal", "churned"],
|
|
158
|
+
// flags: ["on", "off"],
|
|
159
|
+
// experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
|
|
160
|
+
// multiVariate: [true, false]
|
|
161
|
+
|
|
162
|
+
// }
|
|
163
|
+
// },
|
|
164
|
+
// {
|
|
165
|
+
// timeToConvert: 2,
|
|
166
|
+
// conversionRate: 66,
|
|
167
|
+
// sequence: ["foo", "bar", "baz", "qux"],
|
|
168
|
+
// }, {
|
|
169
|
+
// weight: 4,
|
|
170
|
+
// sequence: ["video", "video", "attack", "defend", "click"],
|
|
171
|
+
// }
|
|
173
172
|
],
|
|
174
173
|
superProps: {
|
|
175
174
|
platform: ["web", "mobile", "web", "mobile", "web", "web", "kiosk", "smartTV"],
|
|
@@ -189,14 +188,14 @@ const config = {
|
|
|
189
188
|
|
|
190
189
|
scdProps: {
|
|
191
190
|
nps: [1, 1, 1, 4, 4, 4, 5, 5, 6, 7, 8, 9],
|
|
192
|
-
mrr: () => { weightedRange(10, 1000
|
|
191
|
+
mrr: () => { weightedRange(10, 1000, .25); },
|
|
193
192
|
},
|
|
194
193
|
mirrorProps: {
|
|
195
194
|
isBot: { events: "*", values: [false, false, false, false, true] },
|
|
196
|
-
profit: { events: ["checkout"], values: [4, 2, 42
|
|
195
|
+
profit: { events: ["checkout"], values: [4, 2, 42] },
|
|
197
196
|
watchTimeSec: {
|
|
198
197
|
events: ["watch video"],
|
|
199
|
-
values: weightedRange(50, 1200, 6
|
|
198
|
+
values: weightedRange(50, 1200, 6)
|
|
200
199
|
}
|
|
201
200
|
|
|
202
201
|
},
|