make-mp-data 1.4.1 → 1.4.3

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.
@@ -7,31 +7,43 @@ ak@mixpanel.com
7
7
  */
8
8
 
9
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
-
10
+ //todo: fixedTimeFunnel? if set this funnel will occur for all users at the same time ['cards charged', 'charge complete']
11
+ //todo: send SCD data to mixpanel
12
+ //todo: send and map lookup tables to mixpanel
13
+
14
+ /** @typedef {import('../types').Config} Config */
15
+ /** @typedef {import('../types').EventConfig} EventConfig */
16
+ /** @typedef {import('../types').Funnel} Funnel */
17
+ /** @typedef {import('../types').Person} Person */
18
+ /** @typedef {import('../types').SCDTableRow} SCDTableRow */
19
+ /** @typedef {import('../types').UserProfile} UserProfile */
20
+ /** @typedef {import('../types').EventSpec} EventSpec */
21
+
15
22
  const dayjs = require("dayjs");
16
23
  const utc = require("dayjs/plugin/utc");
17
24
  dayjs.extend(utc);
18
25
  const NOW = dayjs('2024-02-02').unix(); //this is a FIXED POINT and we will shift it later
19
26
  global.NOW = NOW;
20
- const mp = require("mixpanel-import");
27
+
28
+ const os = require("os");
21
29
  const path = require("path");
22
30
  const { comma, bytesHuman, makeName, md5, clone, tracker, uid } = require("ak-tools");
23
31
  const { generateLineChart } = require('./chart.js');
24
- const { version } = require('./package.json');
25
- const os = require("os");
32
+ const { version } = require('../package.json');
33
+ const mp = require("mixpanel-import");
26
34
  const metrics = tracker("make-mp-data", "db99eb8f67ae50949a13c27cacf57d41", os.userInfo().username);
27
35
 
28
36
 
29
37
  const u = require("./utils.js");
30
- const cliParams = require("./cli.js");
38
+ const getCliParams = require("./cli.js");
39
+ const { campaigns, devices, locations } = require('./defaults.js');
31
40
 
32
41
  let VERBOSE = false;
33
42
  let isCLI = false;
43
+ /** @type {Config} */
34
44
  let CONFIG;
45
+ let CAMPAIGNS;
46
+ let DEFAULTS;
35
47
  require('dotenv').config();
36
48
 
37
49
 
@@ -40,25 +52,20 @@ function track(name, props, ...rest) {
40
52
  metrics(name, props, ...rest);
41
53
  }
42
54
 
43
- /** @typedef {import('./types.d.ts').Config} Config */
44
- /** @typedef {import('./types.d.ts').EventConfig} EventConfig */
45
- /** @typedef {import('./types.d.ts').Funnel} Funnel */
46
- /** @typedef {import('./types.d.ts').Person} Person */
47
- /** @typedef {import('./types.d.ts').SCDTableRow} SCDTableRow */
48
- /** @typedef {import('./types.d.ts').UserProfile} UserProfile */
49
- /** @typedef {import('./types.d.ts').EventSpec} EventSpec */
55
+
50
56
 
51
57
  /**
52
58
  * generates fake mixpanel data
53
59
  * @param {Config} config
54
60
  */
55
61
  async function main(config) {
56
- //PARAMS
62
+
63
+ //seed the random number generator
64
+ // ^ this is critical; same seed = same data; seed can be passed in as an env var or in the config
57
65
  const seedWord = process.env.SEED || config.seed || "hello friend!";
58
66
  config.seed = seedWord;
59
67
  u.initChance(seedWord);
60
- const chance = u.getChance();
61
- config.chance = chance;
68
+ const chance = u.getChance(); // ! this is the only safe way to get the chance instance
62
69
  let {
63
70
  seed,
64
71
  numEvents = 100000,
@@ -67,10 +74,9 @@ async function main(config) {
67
74
  epochStart = 0,
68
75
  epochEnd = dayjs().unix(),
69
76
  events = [{ event: "foo" }, { event: "bar" }, { event: "baz" }],
70
- superProps = { platform: ["web", "iOS", "Android"] },
77
+ superProps = { luckyNumber: [2, 2, 4, 4, 42, 42, 42, 2, 2, 4, 4, 42, 42, 42, 420] },
71
78
  funnels = [],
72
79
  userProps = {
73
- favoriteColor: ["red", "green", "blue", "yellow"],
74
80
  spiritAnimal: chance.animal.bind(chance),
75
81
  },
76
82
  scdProps = {},
@@ -88,10 +94,20 @@ async function main(config) {
88
94
  makeChart = false,
89
95
  soup = {},
90
96
  hook = (record) => record,
97
+ hasAdSpend = false,
98
+ hasCampaigns = false,
99
+ hasLocation = false,
100
+ isAnonymous = false,
101
+ hasBrowser = false,
102
+ hasAndroidDevices = false,
103
+ hasDesktopDevices = false,
104
+ hasIOSDevices = false
91
105
  } = config;
106
+
92
107
  if (!config.superProps) config.superProps = superProps;
93
108
  if (!config.userProps || Object.keys(config?.userProps)) config.userProps = userProps;
94
- VERBOSE = verbose;
109
+
110
+
95
111
  config.simulationName = makeName();
96
112
  const { simulationName } = config;
97
113
  if (epochStart && !numDays) numDays = dayjs.unix(epochEnd).diff(dayjs.unix(epochStart), "day");
@@ -123,13 +139,34 @@ async function main(config) {
123
139
  config.makeChart = makeChart;
124
140
  config.soup = soup;
125
141
  config.hook = hook;
142
+ config.hasAdSpend = hasAdSpend;
143
+ config.hasCampaigns = hasCampaigns;
144
+ config.hasLocation = hasLocation;
145
+ config.isAnonymous = isAnonymous;
146
+ config.hasBrowser = hasBrowser;
147
+ config.hasAndroidDevices = hasAndroidDevices;
148
+ config.hasDesktopDevices = hasDesktopDevices;
149
+ config.hasIOSDevices = hasIOSDevices;
126
150
 
127
151
  //event validation
128
152
  const validatedEvents = u.validateEventConfig(events);
129
153
  events = validatedEvents;
130
154
  config.events = validatedEvents;
155
+
156
+ //globals
131
157
  global.MP_SIMULATION_CONFIG = config;
132
158
  CONFIG = config;
159
+ VERBOSE = verbose;
160
+ CAMPAIGNS = campaigns;
161
+ DEFAULTS = {
162
+ locations: u.pickAWinner(locations, 0),
163
+ iOSDevices: u.pickAWinner(devices.iosDevices, 0),
164
+ androidDevices: u.pickAWinner(devices.androidDevices, 0),
165
+ desktopDevices: u.pickAWinner(devices.desktopDevices, 0),
166
+ browsers: u.pickAWinner(devices.browsers, 0),
167
+ campaigns: u.pickAWinner(campaigns, 0),
168
+ };
169
+
133
170
  const runId = uid(42);
134
171
  let trackingParams = { runId, seed, numEvents, numUsers, numDays, anonIds, sessionIds, format, targetToken: token, region, writeToDisk, isCLI, version };
135
172
  track('start simulation', trackingParams);
@@ -142,6 +179,7 @@ async function main(config) {
142
179
  //setup all the data structures we will push into
143
180
  const eventData = u.enrichArray([], { hook, type: "event", config });
144
181
  const userProfilesData = u.enrichArray([], { hook, type: "user", config });
182
+ const adSpendData = u.enrichArray([], { hook, type: "ad-spend", config });
145
183
  const scdTableKeys = Object.keys(scdProps);
146
184
  const scdTableData = [];
147
185
  for (const [index, key] of scdTableKeys.entries()) {
@@ -153,7 +191,7 @@ async function main(config) {
153
191
 
154
192
  // if no funnels, make some out of events...
155
193
  if (!funnels || !funnels.length) {
156
- funnels = inferFunnels(events);
194
+ funnels = u.inferFunnels(events);
157
195
  config.funnels = funnels;
158
196
  CONFIG = config;
159
197
  }
@@ -161,13 +199,21 @@ async function main(config) {
161
199
  //user loop
162
200
  log(`---------------SIMULATION----------------`, "\n\n");
163
201
  loopUsers: for (let i = 1; i < numUsers + 1; i++) {
164
- u.progress("users", i);
202
+ u.progress([["users", i], ["events", eventData.length]]);
165
203
  const userId = chance.guid();
166
- // const user = u.generateUser(userId, numDays, amp, freq, skew);
167
- const user = u.generateUser(userId, numDays);
204
+ const user = u.person(userId, numDays, isAnonymous);
168
205
  const { distinct_id, created, anonymousIds, sessionIds } = user;
169
206
  let numEventsPreformed = 0;
170
207
 
208
+ if (hasLocation) {
209
+ const location = u.choose(clone(DEFAULTS.locations()).map(l => { delete l.country; return l; }));
210
+ for (const key in location) {
211
+ user[key] = location[key];
212
+ }
213
+ }
214
+
215
+
216
+
171
217
  // profile creation
172
218
  const profile = makeProfile(userProps, user);
173
219
  userProfilesData.hookPush(profile);
@@ -226,6 +272,17 @@ async function main(config) {
226
272
  // end individual user loop
227
273
  }
228
274
 
275
+ if (hasAdSpend) {
276
+ const days = u.datesBetween(epochStart, epochEnd);
277
+ for (const day of days) {
278
+ const dailySpendData = makeAdSpend(day);
279
+ for (const spendEvent of dailySpendData) {
280
+ adSpendData.hookPush(spendEvent);
281
+ }
282
+ }
283
+
284
+ }
285
+
229
286
  //flatten SCD tables
230
287
  scdTableData.forEach((table, index) => scdTableData[index] = table.flat());
231
288
 
@@ -237,7 +294,7 @@ async function main(config) {
237
294
  const groupCardinality = groupPair[1];
238
295
  const groupProfiles = [];
239
296
  for (let i = 1; i < groupCardinality + 1; i++) {
240
- u.progress("groups", i);
297
+ u.progress([["groups", i]]);
241
298
  const group = {
242
299
  [groupKey]: i,
243
300
  ...makeProfile(groupProps[groupKey])
@@ -254,7 +311,7 @@ async function main(config) {
254
311
  const { key, entries, attributes } = lookupTable;
255
312
  const data = [];
256
313
  for (let i = 1; i < entries + 1; i++) {
257
- u.progress("lookups", i);
314
+ u.progress([["lookups", i]]);
258
315
  const item = {
259
316
  [key]: i,
260
317
  ...makeProfile(attributes),
@@ -309,11 +366,12 @@ async function main(config) {
309
366
  }
310
367
  }
311
368
 
312
- const { eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, mirrorFiles, folder } =
369
+ const { eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, mirrorFiles, folder, adSpendFiles } =
313
370
  u.buildFileNames(config);
314
371
  const pairs = [
315
372
  [eventFiles, [eventData]],
316
373
  [userFiles, [userProfilesData]],
374
+ [adSpendFiles, [adSpendData]],
317
375
  [scdFiles, scdTableData],
318
376
  [groupFiles, groupProfilesData],
319
377
  [lookupFiles, lookupTableData],
@@ -379,7 +437,7 @@ async function main(config) {
379
437
  fixData: true,
380
438
  verbose: false,
381
439
  forceStream: true,
382
- strict: true,
440
+ strict: false, //! sometimes we get events in the future... it happens
383
441
  dryRun: false,
384
442
  abridged: false,
385
443
  fixJson: true,
@@ -388,7 +446,7 @@ async function main(config) {
388
446
 
389
447
  if (eventData) {
390
448
  log(`importing events to mixpanel...\n`);
391
- const imported = await mp(creds, eventData, {
449
+ const imported = await mp(creds, clone(eventData), {
392
450
  recordType: "event",
393
451
  ...commonOpts,
394
452
  });
@@ -397,19 +455,28 @@ async function main(config) {
397
455
  }
398
456
  if (userProfilesData) {
399
457
  log(`importing user profiles to mixpanel...\n`);
400
- const imported = await mp(creds, userProfilesData, {
458
+ const imported = await mp(creds, clone(userProfilesData), {
401
459
  recordType: "user",
402
460
  ...commonOpts,
403
461
  });
404
462
  log(`\tsent ${comma(imported.success)} user profiles\n`);
405
463
  importResults.users = imported;
406
464
  }
465
+ if (adSpendData) {
466
+ log(`importing ad spend data to mixpanel...\n`);
467
+ const imported = await mp(creds, clone(adSpendData), {
468
+ recordType: "event",
469
+ ...commonOpts,
470
+ });
471
+ log(`\tsent ${comma(imported.success)} ad spend events\n`);
472
+ importResults.adSpend = imported;
473
+ }
407
474
  if (groupProfilesData) {
408
475
  for (const groupProfiles of groupProfilesData) {
409
476
  const groupKey = groupProfiles.key;
410
477
  const data = groupProfiles.data;
411
478
  log(`importing ${groupKey} profiles to mixpanel...\n`);
412
- const imported = await mp({ token, groupKey }, data, {
479
+ const imported = await mp({ token, groupKey }, clone(data), {
413
480
  recordType: "group",
414
481
  ...commonOpts,
415
482
 
@@ -432,6 +499,7 @@ async function main(config) {
432
499
  groupProfilesData,
433
500
  lookupTableData,
434
501
  mirrorEventData,
502
+ adSpendData
435
503
  };
436
504
  }
437
505
 
@@ -452,27 +520,42 @@ async function main(config) {
452
520
  * @param {Boolean} isFirstEvent=false
453
521
  */
454
522
  function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEvent, superProps, groupKeys, isFirstEvent = false) {
455
- const { mean = 0, dev = 2, peaks = 5 } = CONFIG.soup;
523
+ const chance = u.getChance();
524
+ const { mean = 0, deviation = 2, peaks = 5 } = CONFIG.soup;
525
+ const { hasAndroidDevices, hasBrowser, hasCampaigns, hasDesktopDevices, hasIOSDevices, hasLocation } = CONFIG;
456
526
  //event model
457
527
  const eventTemplate = {
458
528
  event: chosenEvent.event,
459
529
  source: "dm4",
460
530
  };
461
531
 
532
+ let defaultProps = {};
533
+ let devicePool = [];
534
+ if (hasLocation) defaultProps.location = clone(DEFAULTS.locations()).map(l => { delete l.country_code; return l; });
535
+ if (hasBrowser) defaultProps.browser = DEFAULTS.browsers();
536
+ if (hasAndroidDevices) devicePool.push(DEFAULTS.androidDevices());
537
+ if (hasIOSDevices) devicePool.push(DEFAULTS.iOSDevices());
538
+ if (hasDesktopDevices) devicePool.push(DEFAULTS.desktopDevices());
539
+ // we don't always have campaigns, because of attribution
540
+ if (hasCampaigns && chance.bool({ likelihood: 25 })) defaultProps.campaigns = DEFAULTS.campaigns();
541
+ const devices = devicePool.flat();
542
+ if (devices.length) defaultProps.device = devices;
543
+
544
+
462
545
  //event time
463
546
  if (earliestTime > NOW) {
464
547
  earliestTime = dayjs.unix(NOW).subtract(2, 'd').unix();
465
548
  };
466
549
 
467
550
  if (isFirstEvent) eventTemplate.time = dayjs.unix(earliestTime).toISOString();
468
- if (!isFirstEvent) eventTemplate.time = u.TimeSoup(earliestTime, NOW, peaks, dev, mean);
551
+ if (!isFirstEvent) eventTemplate.time = u.TimeSoup(earliestTime, NOW, peaks, deviation, mean);
469
552
 
470
553
  // anonymous and session ids
471
- if (CONFIG?.anonIds) eventTemplate.device_id = CONFIG.chance.pickone(anonymousIds);
472
- if (CONFIG?.sessionIds) eventTemplate.session_id = CONFIG.chance.pickone(sessionIds);
554
+ if (CONFIG?.anonIds) eventTemplate.device_id = chance.pickone(anonymousIds);
555
+ if (CONFIG?.sessionIds) eventTemplate.session_id = chance.pickone(sessionIds);
473
556
 
474
557
  //sometimes have a user_id
475
- if (!isFirstEvent && CONFIG.chance.bool({ likelihood: 42 })) eventTemplate.user_id = distinct_id;
558
+ if (!isFirstEvent && chance.bool({ likelihood: 42 })) eventTemplate.user_id = distinct_id;
476
559
 
477
560
  // ensure that there is a user_id or device_id
478
561
  if (!eventTemplate.user_id && !eventTemplate.device_id) eventTemplate.user_id = distinct_id;
@@ -489,6 +572,43 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
489
572
  }
490
573
  }
491
574
 
575
+ //iterate through default properties
576
+ for (const key in defaultProps) {
577
+ if (Array.isArray(defaultProps[key])) {
578
+ const choice = u.choose(defaultProps[key]);
579
+ if (typeof choice === "string") {
580
+ if (!eventTemplate[key]) eventTemplate[key] = choice;
581
+ }
582
+
583
+ else if (Array.isArray(choice)) {
584
+ for (const subChoice of choice) {
585
+ if (!eventTemplate[key]) eventTemplate[key] = subChoice;
586
+ }
587
+ }
588
+
589
+ else if (typeof choice === "object") {
590
+ for (const subKey in choice) {
591
+ if (typeof choice[subKey] === "string") {
592
+ if (!eventTemplate[subKey]) eventTemplate[subKey] = choice[subKey];
593
+ }
594
+ else if (Array.isArray(choice[subKey])) {
595
+ const subChoice = u.choose(choice[subKey]);
596
+ if (!eventTemplate[subKey]) eventTemplate[subKey] = subChoice;
597
+ }
598
+
599
+ else if (typeof choice[subKey] === "object") {
600
+ for (const subSubKey in choice[subKey]) {
601
+ if (!eventTemplate[subSubKey]) eventTemplate[subSubKey] = choice[subKey][subSubKey];
602
+ }
603
+ }
604
+
605
+ }
606
+ }
607
+
608
+
609
+ }
610
+ }
611
+
492
612
  //iterate through groups
493
613
  for (const groupPair of groupKeys) {
494
614
  const groupKey = groupPair[0];
@@ -518,6 +638,7 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
518
638
  * @return {[EventSpec[], Boolean]}
519
639
  */
520
640
  function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
641
+ const chance = u.getChance();
521
642
  const { hook } = config;
522
643
  hook(funnel, "funnel-pre", { user, profile, scd, funnel, config });
523
644
  let {
@@ -532,6 +653,7 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
532
653
  const { superProps, groupKeys } = config;
533
654
  const { name, email } = profile;
534
655
 
656
+ //choose the properties for this funnel
535
657
  const chosenFunnelProps = { ...props, ...superProps };
536
658
  for (const key in props) {
537
659
  try {
@@ -563,7 +685,7 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
563
685
  .reduce((acc, step) => {
564
686
  if (!requireRepeats) {
565
687
  if (acc.find(e => e.event === step.event)) {
566
- if (config.chance.bool({ likelihood: 50 })) {
688
+ if (chance.bool({ likelihood: 50 })) {
567
689
  conversionRate = Math.floor(conversionRate * 1.25); //increase conversion rate
568
690
  acc.push(step);
569
691
  }
@@ -583,7 +705,7 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
583
705
  return acc;
584
706
  }, []);
585
707
 
586
- let doesUserConvert = config.chance.bool({ likelihood: conversionRate });
708
+ let doesUserConvert = chance.bool({ likelihood: conversionRate });
587
709
  let numStepsUserWillTake = sequence.length;
588
710
  if (!doesUserConvert) numStepsUserWillTake = u.integer(1, sequence.length - 1);
589
711
  const funnelTotalRelativeTimeInHours = timeToConvert / numStepsUserWillTake;
@@ -677,49 +799,6 @@ function makeFunnel(funnel, user, profile, scd, firstEventTime, config) {
677
799
  }
678
800
 
679
801
 
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
-
723
802
  function makeProfile(props, defaults) {
724
803
  //build the spec
725
804
  const profile = {
@@ -742,7 +821,7 @@ function makeProfile(props, defaults) {
742
821
  }
743
822
 
744
823
  /**
745
- * @param {import('./types.d.ts').ValueValid} prop
824
+ * @param {import('../types').ValueValid} prop
746
825
  * @param {string} scdKey
747
826
  * @param {string} distinct_id
748
827
  * @param {number} mutations
@@ -770,8 +849,59 @@ function makeSCD(prop, scdKey, distinct_id, mutations, created) {
770
849
  return scdEntries;
771
850
  }
772
851
 
852
+ //todo
853
+ function makeAdSpend(day) {
854
+ const chance = u.getChance();
855
+ const adSpendEvents = [];
856
+ for (const network of CAMPAIGNS) {
857
+ const campaigns = network.utm_campaign;
858
+ loopCampaigns: for (const campaign of campaigns) {
859
+ if (campaign === "$organic") continue loopCampaigns;
860
+
861
+ const CAC = u.integer(42, 420); //todo: get the # of users created in this day from eventData
862
+ // Randomly generating cost
863
+ const cost = chance.floating({ min: 10, max: 250, fixed: 2 });
864
+
865
+ // Ensuring realistic CPC and CTR
866
+ const avgCPC = chance.floating({ min: 0.33, max: 2.00, fixed: 4 });
867
+ const avgCTR = chance.floating({ min: 0.05, max: 0.25, fixed: 4 });
868
+
869
+ // Deriving impressions from cost and avg CPC
870
+ const clicks = Math.floor(cost / avgCPC);
871
+ const impressions = Math.floor(clicks / avgCTR);
872
+ const views = Math.floor(impressions * avgCTR);
873
+
874
+ //tags
875
+ const utm_medium = u.choose(u.pickAWinner(network.utm_medium)());
876
+ const utm_content = u.choose(u.pickAWinner(network.utm_content)());
877
+ const utm_term = u.choose(u.pickAWinner(network.utm_term)());
878
+ //each of these is a campaign
879
+ const adSpendEvent = {
880
+ event: "Ad Data",
881
+ time: day,
882
+ source: 'dm4',
883
+ utm_campaign: campaign,
884
+ campaign_id: md5(network.utm_source[0] + '-' + campaign),
885
+ network: network.utm_source[0].toUpperCase(),
886
+ distinct_id: network.utm_source[0].toUpperCase(),
887
+ utm_source: network.utm_source[0],
888
+ utm_medium,
889
+ utm_content,
890
+ utm_term,
891
+
892
+ clicks,
893
+ views,
894
+ impressions,
895
+ cost,
896
+ date: dayjs(day).format("YYYY-MM-DD"),
897
+ };
898
+ adSpendEvents.push(adSpendEvent);
899
+ }
773
900
 
774
901
 
902
+ }
903
+ return adSpendEvents;
904
+ }
775
905
 
776
906
 
777
907
 
@@ -782,7 +912,7 @@ function makeSCD(prop, scdKey, distinct_id, mutations, created) {
782
912
  // this is for CLI
783
913
  if (require.main === module) {
784
914
  isCLI = true;
785
- const args = cliParams();
915
+ const args = getCliParams();
786
916
  // @ts-ignore
787
917
  let { token, seed, format, numDays, numUsers, numEvents, region, writeToDisk, complex = false, sessionIds, anonIds } = args;
788
918
  // @ts-ignore
@@ -799,12 +929,12 @@ if (require.main === module) {
799
929
  console.log(`... using default COMPLEX configuration [everything] ...\n`);
800
930
  console.log(`... for more simple data, don't use the --complex flag ...\n`);
801
931
  console.log(`... or specify your own js config file (see docs or --help) ...\n`);
802
- config = require(path.resolve(__dirname, "./schemas/complex.js"));
932
+ config = require(path.resolve(__dirname, "../schemas/complex.js"));
803
933
  }
804
934
  else {
805
935
  console.log(`... using default SIMPLE configuration [events + users] ...\n`);
806
936
  console.log(`... for more complex data, use the --complex flag ...\n`);
807
- config = require(path.resolve(__dirname, "./schemas/simple.js"));
937
+ config = require(path.resolve(__dirname, "../schemas/simple.js"));
808
938
  }
809
939
  }
810
940