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/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 = validateEvents(events);
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
- const createdFunnels = [];
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
- const dayShift = actualNow.diff(global.NOW, "day");
271
+
304
272
  eventData.forEach((event) => {
305
- const newTime = dayjs(event.time).add(timeShift, "second");
306
- event.time = newTime.toISOString();
307
- if (epochStart && newTime.unix() < epochStart) event = {};
308
- if (epochEnd && newTime.unix() > epochEnd) event = {};
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
- userProfilesData.forEach((profile) => {
312
- const newTime = dayjs(profile.created).add(dayShift, "day");
313
- profile.created = newTime.toISOString();
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
- * creates a funnel of events for a user
532
- * this is called multiple times for a user
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
- const { sequence, conversionRate = 50, order = 'sequential', timeToConvert = 1, props } = funnel;
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.map((event) => {
560
- const foundEvent = config.events.find((e) => e.event === event);
561
- /** @type {EventConfig} */
562
- const eventSpec = foundEvent || { event, properties: {} };
563
- for (const key in eventSpec.properties) {
564
- try {
565
- eventSpec.properties[key] = u.choose(eventSpec.properties[key]);
566
- } catch (e) {
567
- console.error(`error with ${key} in ${eventSpec.event} event`, e);
568
- debugger;
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
- delete eventSpec.isFirstEvent;
572
- delete eventSpec.weight;
573
- eventSpec.properties = { ...eventSpec.properties, ...chosenFunnelProps };
574
- return eventSpec;
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
- const doesUserConvert = config.chance.bool({ likelihood: conversionRate });
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 funnelActualEvents = funnelPossibleEvents.slice(0, numStepsUserWillTake)
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 = funnelActualOrder
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
- newEvent.time = dayjs(funnelStartTime).add(event.relativeTimeMs, "milliseconds").toISOString();
647
- delete newEvent.relativeTimeMs;
648
- return newEvent;
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
- * @param {EventConfig[] | string[]} events
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-mp-data",
3
- "version": "1.4.0",
3
+ "version": "1.4.01",
4
4
  "description": "builds all mixpanel primitives for a given project",
5
5
  "main": "index.js",
6
6
  "types": "types.d.ts",
@@ -28,7 +28,7 @@ const config = {
28
28
  "event": "checkout",
29
29
  "weight": 2,
30
30
  "properties": {
31
- amount: weightedRange(5, 500, .25, 1000),
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, 1000),
40
+ amount: weightedRange(5, 500, .25),
41
41
  qty: integer(1, 5),
42
- product_id: weightedRange(1, 1000, 1.4, 42)
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,.25, 1000),
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, 420000),
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, 1000),
71
- video_id: weightedRange(1, 50000, 1.4, 420000),
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, 420000),
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, 24, 3, 1000),
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, 8 ),
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, 420),
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, .15, 1000),
148
- NPS: weightedRange(0, 10, 2, 150),
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 ,247)
158
+ values: weightedRange(50, 1200, 6, 247)
159
159
  }
160
160
  },
161
161
 
@@ -37,7 +37,7 @@ const config = {
37
37
  event: "checkout",
38
38
  weight: 2,
39
39
  properties: {
40
- amount: weightedRange(5, 500, .25, 1000),
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, 1000),
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, 1000),
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
- sequence: ["page view", "view item", "page view", "sign up"],
112
- weight: 1,
113
- isFirstFunnel: true,
114
- order: "sequential",
115
- conversionRate: 50,
116
- timeToConvert: 2,
117
- props: {
118
- variants: ["A", "B", "C", "Control"],
119
- flows: ["new", "existing", "loyal", "churned"],
120
- flags: ["on", "off"],
121
- experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
122
- multiVariate: [true, false]
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
- sequence: ["app install", "app open", "tutorial", "sign up"],
135
- weight: 1,
136
- isFirstFunnel: true,
137
- order: "sequential",
138
- conversionRate: 50,
139
- timeToConvert: 2,
140
- props: {
141
- variants: ["A", "B", "C", "Control"],
142
- flows: ["new", "existing", "loyal", "churned"],
143
- flags: ["on", "off"],
144
- experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
145
- multiVariate: [true, false]
146
-
147
- }
148
- },
149
- {
150
- sequence: ["page view", "view item", "add to cart", "add to cart", "checkout"],
151
- weight: 3,
152
- isFirstFunnel: false,
153
- order: "sequential",
154
- conversionRate: 70,
155
- timeToConvert: 7 * 24,
156
- props: {
157
- variants: ["A", "B", "C", "Control"],
158
- flows: ["new", "existing", "loyal", "churned"],
159
- flags: ["on", "off"],
160
- experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
161
- multiVariate: [true, false]
162
-
163
- }
164
- },
165
- {
166
- timeToConvert: 2,
167
- conversionRate: 66,
168
- sequence: ["foo", "bar", "baz", "qux"],
169
- }, {
170
- weight: 4,
171
- sequence: ["video", "video", "attack", "defend", "click"],
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,.25, 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, 420] },
195
+ profit: { events: ["checkout"], values: [4, 2, 42] },
197
196
  watchTimeSec: {
198
197
  events: ["watch video"],
199
- values: weightedRange(50, 1200, 6, 247)
198
+ values: weightedRange(50, 1200, 6)
200
199
  }
201
200
 
202
201
  },