make-mp-data 1.4.0 → 1.4.2

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/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const Chance = require('chance');
3
3
  const readline = require('readline');
4
- const { comma, uid } = require('ak-tools');
4
+ const { comma, uid, clone } = require('ak-tools');
5
5
  const { spawn } = require('child_process');
6
6
  const dayjs = require('dayjs');
7
7
  const utc = require('dayjs/plugin/utc');
@@ -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;
@@ -223,6 +225,7 @@ function integer(min = 1, max = 100) {
223
225
 
224
226
  function pickAWinner(items, mostChosenIndex) {
225
227
  const chance = getChance();
228
+ if (!mostChosenIndex) mostChosenIndex = integer(0, items.length);
226
229
  if (mostChosenIndex > items.length) mostChosenIndex = items.length;
227
230
  return function () {
228
231
  const weighted = [];
@@ -250,6 +253,48 @@ function pickAWinner(items, mostChosenIndex) {
250
253
  }
251
254
 
252
255
 
256
+ function inferFunnels(events) {
257
+ const createdFunnels = [];
258
+ const firstEvents = events.filter((e) => e.isFirstEvent).map((e) => e.event);
259
+ const usageEvents = events.filter((e) => !e.isFirstEvent).map((e) => e.event);
260
+ const numFunnelsToCreate = Math.ceil(usageEvents.length);
261
+ /** @type {Funnel} */
262
+ const funnelTemplate = {
263
+ sequence: [],
264
+ conversionRate: 50,
265
+ order: 'sequential',
266
+ requireRepeats: false,
267
+ props: {},
268
+ timeToConvert: 1,
269
+ isFirstFunnel: false,
270
+ weight: 1
271
+ };
272
+ if (firstEvents.length) {
273
+ for (const event of firstEvents) {
274
+ createdFunnels.push({ ...clone(funnelTemplate), sequence: [event], isFirstFunnel: true, conversionRate: 100 });
275
+ }
276
+ }
277
+
278
+ //at least one funnel with all usage events
279
+ createdFunnels.push({ ...clone(funnelTemplate), sequence: usageEvents });
280
+
281
+ //for the rest, make random funnels
282
+ followUpFunnels: for (let i = 1; i < numFunnelsToCreate; i++) {
283
+ /** @type {Funnel} */
284
+ const funnel = { ...clone(funnelTemplate) };
285
+ funnel.conversionRate = integer(25, 75);
286
+ funnel.timeToConvert = integer(1, 10);
287
+ funnel.weight = integer(1, 10);
288
+ const sequence = shuffleArray(usageEvents).slice(0, integer(2, usageEvents.length));
289
+ funnel.sequence = sequence;
290
+ funnel.order = 'random';
291
+ createdFunnels.push(funnel);
292
+ }
293
+
294
+ return createdFunnels;
295
+
296
+ }
297
+
253
298
  /*
254
299
  ----
255
300
  GENERATORS
@@ -268,6 +313,17 @@ function boxMullerRandom() {
268
313
  return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
269
314
  };
270
315
 
316
+ function optimizedBoxMuller() {
317
+ const chance = getChance();
318
+ const u = Math.max(Math.min(chance.normal({ mean: .5, dev: .25 }), 1), 0);
319
+ const v = Math.max(Math.min(chance.normal({ mean: .5, dev: .25 }), 1), 0);
320
+ const result = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
321
+ //ensure we didn't get infinity
322
+ if (result === Infinity || result === -Infinity) return chance.floating({ min: 0, max: 1 });
323
+ return result;
324
+
325
+ }
326
+
271
327
  /**
272
328
  * applies a skew to a value;
273
329
  * Skew=0.5: When the skew is 0.5, the distribution becomes more compressed, with values clustering closer to the mean.
@@ -377,12 +433,14 @@ function weighFunnels(acc, funnel) {
377
433
  * @param {number} skew=1
378
434
  * @param {number} size=100
379
435
  */
380
- function weightedRange(min, max, skew = 1, size = 100) {
436
+ function weightedRange(min, max, skew = 1, size = 50) {
437
+ if (size > 2000) size = 2000;
381
438
  const mean = (max + min) / 2;
382
439
  const sd = (max - min) / 4;
383
440
  const array = [];
384
441
  while (array.length < size) {
385
- const normalValue = boxMullerRandom();
442
+ // const normalValue = boxMullerRandom();
443
+ const normalValue = optimizedBoxMuller();
386
444
  const skewedValue = applySkew(normalValue, skew);
387
445
  const mappedValue = mapToRange(skewedValue, mean, sd);
388
446
  if (mappedValue >= min && mappedValue <= max) {
@@ -458,6 +516,76 @@ function shuffleOutside(array) {
458
516
  return [outsideShuffled[0], ...middleFixed, outsideShuffled[1]];
459
517
  }
460
518
 
519
+ /**
520
+ * @param {EventConfig[]} funnel
521
+ * @param {EventConfig[]} possibles
522
+ */
523
+ function interruptArray(funnel, possibles, percent = 50) {
524
+ if (!Array.isArray(funnel)) return funnel;
525
+ if (!Array.isArray(possibles)) return funnel;
526
+ if (!funnel.length) return funnel;
527
+ if (!possibles.length) return funnel;
528
+ const ignorePositions = [0, funnel.length - 1];
529
+ const chance = getChance();
530
+ loopSteps: for (const [index, event] of funnel.entries()) {
531
+ if (ignorePositions.includes(index)) continue loopSteps;
532
+ if (chance.bool({ likelihood: percent })) {
533
+ funnel[index] = chance.pickone(possibles);
534
+ }
535
+ }
536
+
537
+ return funnel;
538
+ }
539
+
540
+ /*
541
+ ----
542
+ VALIDATORS
543
+ ----
544
+ */
545
+
546
+
547
+ /**
548
+ * @param {EventConfig[] | string[]} events
549
+ */
550
+ function validateEventConfig(events) {
551
+ if (!Array.isArray(events)) throw new Error("events must be an array");
552
+ const cleanEventConfig = [];
553
+ for (const event of events) {
554
+ if (typeof event === "string") {
555
+ /** @type {EventConfig} */
556
+ const eventTemplate = {
557
+ event,
558
+ isFirstEvent: false,
559
+ properties: {},
560
+ weight: integer(1, 5)
561
+ };
562
+ cleanEventConfig.push(eventTemplate);
563
+ }
564
+ if (typeof event === "object") {
565
+ cleanEventConfig.push(event);
566
+ }
567
+ }
568
+ return cleanEventConfig;
569
+ }
570
+
571
+ function validateTime(chosenTime, earliestTime, latestTime) {
572
+ if (!earliestTime) earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
573
+ if (!latestTime) latestTime = global.NOW;
574
+
575
+ if (typeof chosenTime === 'number') {
576
+ if (chosenTime > 0) {
577
+ if (chosenTime > earliestTime) {
578
+ if (chosenTime < latestTime) {
579
+ return true;
580
+ }
581
+
582
+ }
583
+ }
584
+ }
585
+ return false;
586
+ }
587
+
588
+
461
589
  /*
462
590
  ----
463
591
  META
@@ -474,33 +602,43 @@ function enrichArray(arr = [], opts = {}) {
474
602
  const { hook = a => a, type = "", ...rest } = opts;
475
603
 
476
604
  function transformThenPush(item) {
477
- if (item === null) return 0;
478
- if (item === undefined) return 0;
605
+ if (item === null) return false;
606
+ if (item === undefined) return false;
479
607
  if (typeof item === 'object') {
480
- if (Object.keys(item).length === 0) return 0;
608
+ if (Object.keys(item).length === 0) return false;
481
609
  }
610
+
611
+ //hook is passed an array
482
612
  if (Array.isArray(item)) {
483
613
  for (const i of item) {
484
614
  try {
485
615
  const enriched = hook(i, type, rest);
486
- arr.push(enriched);
616
+ if (Array.isArray(enriched)) enriched.forEach(e => arr.push(e));
617
+ else arr.push(enriched);
618
+
487
619
  }
488
620
  catch (e) {
489
621
  console.error(`\n\nyour hook had an error\n\n`, e);
490
622
  arr.push(i);
623
+ return false;
491
624
  }
492
625
 
493
626
  }
494
- return -1;
627
+ return true;
495
628
  }
629
+
630
+ //hook is passed a single item
496
631
  else {
497
632
  try {
498
633
  const enriched = hook(item, type, rest);
499
- return arr.push(enriched);
634
+ if (Array.isArray(enriched)) enriched.forEach(e => arr.push(e));
635
+ else arr.push(enriched);
636
+ return true;
500
637
  }
501
638
  catch (e) {
502
639
  console.error(`\n\nyour hook had an error\n\n`, e);
503
- return arr.push(item);
640
+ arr.push(item);
641
+ return false;
504
642
  }
505
643
  }
506
644
 
@@ -674,50 +812,39 @@ function TimeSoup(earliestTime, latestTime, peaks = 5, deviation = 2, mean = 0)
674
812
  }
675
813
 
676
814
 
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
815
 
694
816
 
695
817
  /**
818
+ * @param {string} userId
696
819
  * @param {number} bornDaysAgo=30
820
+ * @param {boolean} isAnonymous
697
821
  * @return {Person}
698
822
  */
699
- function person(bornDaysAgo = 30) {
823
+ function person(userId, bornDaysAgo = 30, isAnonymous = false) {
700
824
  const chance = getChance();
701
825
  //names and photos
826
+ const l = chance.letter;
702
827
  let gender = chance.pickone(['male', 'female']);
703
828
  if (!gender) gender = "female";
704
829
  // @ts-ignore
705
- const first = chance.first({ gender });
706
- const last = chance.last();
707
- const name = `${first} ${last}`;
708
- const email = `${first[0]}.${last}@${chance.domain()}.com`;
709
- const avatarPrefix = `https://randomuser.me/api/portraits`;
710
- const randomAvatarNumber = chance.integer({
830
+ let first = chance.first({ gender });
831
+ let last = chance.last();
832
+ let name = `${first} ${last}`;
833
+ let email = `${first[0]}.${last}@${chance.domain()}.com`;
834
+ let avatarPrefix = `https://randomuser.me/api/portraits`;
835
+ let randomAvatarNumber = chance.integer({
711
836
  min: 1,
712
837
  max: 99
713
838
  });
714
- const avPath = gender === 'male' ? `/men/${randomAvatarNumber}.jpg` : `/women/${randomAvatarNumber}.jpg`;
715
- const avatar = avatarPrefix + avPath;
716
- const created = dayjs.unix(global.NOW).subtract(bornDaysAgo, 'day').format('YYYY-MM-DD');
839
+ let avPath = gender === 'male' ? `/men/${randomAvatarNumber}.jpg` : `/women/${randomAvatarNumber}.jpg`;
840
+ let avatar = avatarPrefix + avPath;
841
+ let created = dayjs.unix(global.NOW).subtract(bornDaysAgo, 'day').format('YYYY-MM-DD');
717
842
  // const created = date(bornDaysAgo, true)();
718
843
 
844
+
719
845
  /** @type {Person} */
720
846
  const user = {
847
+ distinct_id: userId,
721
848
  name,
722
849
  email,
723
850
  avatar,
@@ -726,6 +853,13 @@ function person(bornDaysAgo = 30) {
726
853
  sessionIds: []
727
854
  };
728
855
 
856
+ if (isAnonymous) {
857
+ user.name = "Anonymous User";
858
+ user.email = `${l()}${l()}****${l()}${l()}@${l()}**${l()}*.com`;
859
+ delete user.avatar;
860
+
861
+ }
862
+
729
863
  //anon Ids
730
864
  if (global.MP_SIMULATION_CONFIG?.anonIds) {
731
865
  const clusterSize = integer(2, 10);
@@ -819,7 +953,7 @@ module.exports = {
819
953
 
820
954
  initChance,
821
955
  getChance,
822
-
956
+ validateTime,
823
957
  boxMullerRandom,
824
958
  applySkew,
825
959
  mapToRange,
@@ -832,18 +966,19 @@ module.exports = {
832
966
  pickAWinner,
833
967
  weighArray,
834
968
  weighFunnels,
835
-
969
+ validateEventConfig,
836
970
  shuffleArray,
837
971
  shuffleExceptFirst,
838
972
  shuffleExceptLast,
839
973
  fixFirstAndLast,
840
974
  shuffleMiddle,
841
975
  shuffleOutside,
842
-
976
+ interruptArray,
843
977
  generateUser,
844
978
  enrichArray,
845
-
979
+ optimizedBoxMuller,
846
980
  buildFileNames,
847
981
  streamJSON,
848
- streamCSV
982
+ streamCSV,
983
+ inferFunnels
849
984
  };