make-mp-data 1.4.2 → 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.
package/README.md CHANGED
@@ -9,7 +9,7 @@ the CLI looks like this:
9
9
 
10
10
  ![Generate Mixpanel data](https://aktunes.neocities.org/makeDataDemo.gif)
11
11
 
12
- under the hood, `make-mp-data` is modeling data adherent to match [Mixpanel's data model](https://docs.mixpanel.com/docs/data-structure/concepts), giving you the tools you need for robust, realistical field ready test data.
12
+ under the hood, `make-mp-data` is modeling data adherent to match [Mixpanel's data model](https://docs.mixpanel.com/docs/data-structure/concepts), giving you the tools you need for robust, realistic field ready test data.
13
13
 
14
14
  ## 🚀 Quick Start
15
15
 
@@ -64,8 +64,9 @@ npx make-mp-data [dataModel.js] [options]
64
64
  Example:
65
65
 
66
66
  ```bash
67
- npx make-mp-data ecommSpec.js --token 1234 --numDays 30 --numUsers 1000 --numEvents 1000000
67
+ npx make-mp-data myDataSpec.js --token 1234 --numDays 30 --numUsers 1000 --numEvents 1000000
68
68
  ```
69
+ where `myDataSpec.js` exports a [JS object of this shape](https://github.com/ak--47/make-mp-data/blob/main/types.d.ts#L8) ... (see [`./schemas`](https://github.com/ak--47/make-mp-data/tree/main/schemas) for examples)
69
70
 
70
71
  ### Data Models
71
72
 
@@ -1,5 +1,5 @@
1
1
  const yargs = require('yargs');
2
- const { version } = require('./package.json');
2
+ const { version } = require('../package.json');
3
3
 
4
4
  const hero = String.raw`
5
5
 
@@ -1,7 +1,8 @@
1
1
  /* cSpell:disable */
2
2
  //? https://docs.mixpanel.com/docs/data-structure/property-reference#default-properties
3
3
 
4
-
4
+ const domainSuffix = ["com", "com", "com", "com", "net", "org", "net", "org", "io", "co", "co.uk", "us", "biz", "info", "gov", "edu"];
5
+ const domainPrefix = ["gmail", "gmail", "gmail", "gmail", "gmail", "gmail", "yahoo", "yahoo", "icloud", "icloud", "icloud", "icloud", "hotmail", "hotmail", "gmail", "gmail", "gmail", "gmail", "gmail", "gmail", "yahoo", "yahoo", "icloud", "icloud", "icloud", "hotmail", "hotmail", "outlook", "aol", "outlook", "aol", "protonmail", "zoho", "gmx", "yandex", "mail", "inbox", "fastmail", "tutanota", "mailfence", "disroot", "riseup", "posteo", "runbox", "kolabnow", "mailbox", "scryptmail", "ctemplar", "countermail", "hushmail", "startmail", "privatemail"];
5
6
  const campaigns = [
6
7
  {
7
8
  utm_campaign: ["$organic"],
@@ -12,77 +13,60 @@ const campaigns = [
12
13
  },
13
14
  {
14
15
  utm_source: ["facebook"],
15
- utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
16
- utm_medium: ["influencer", "promoted", "sidebar", "search"],
17
- utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
18
- utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
16
+ utm_campaign: ["fb_free_trial", "fb_discount_US", "fb_summer_sale", "fb_black_friday", "fb_lookalike_audience"],
17
+ utm_medium: ["social_influencer", "paid_promoted", "ad_sidebar", "sponsored_search"],
18
+ utm_content: ["fb_control_group", "fb_variant_A", "fb_variant_B", "fb_variant_C", "fb_variant_D"],
19
+ utm_term: ["fb_jan_feb", "fb_mar_apr", "fb_may_jun", "fb_jul_aug", "fb_sep_oct", "fb_nov_dec"]
19
20
  },
20
21
  {
21
- utm_source: ["x"],
22
- utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
23
- utm_medium: ["influencer", "promoted", "sidebar", "search"],
24
- utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
25
- utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
22
+ utm_source: ["snapchat"],
23
+ utm_campaign: ["sc_free_trial", "sc_discount_US", "sc_spring_sale", "sc_cyber_monday", "sc_lookalike_audience"],
24
+ utm_medium: ["promoted_tweet", "sponsored_post", "sidebar_ad", "search_ad"],
25
+ utm_content: ["sc_control_group", "sc_variant_A", "sc_variant_B", "sc_variant_C", "sc_variant_D"],
26
+ utm_term: ["sc_jan_feb", "sc_mar_apr", "sc_may_jun", "sc_jul_aug", "sc_sep_oct", "sc_nov_dec"]
26
27
  },
28
+
27
29
  {
28
30
  utm_source: ["linkedin"],
29
- utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
30
- utm_medium: ["influencer", "promoted", "sidebar", "search"],
31
- utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
32
- utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
31
+ utm_campaign: ["li_free_trial", "li_discount_US", "li_fall_sale", "li_holiday_special", "li_lookalike_audience"],
32
+ utm_medium: ["influencer_post", "promoted_content", "sidebar_ad", "search_ad"],
33
+ utm_content: ["li_control_group", "li_variant_A", "li_variant_B", "li_variant_C", "li_variant_D"],
34
+ utm_term: ["li_jan_feb", "li_mar_apr", "li_may_jun", "li_jul_aug", "li_sep_oct", "li_nov_dec"]
33
35
  },
34
-
35
36
  {
36
37
  utm_source: ["instagram"],
37
- utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
38
- utm_medium: ["influencer", "promoted", "sidebar", "search"],
39
- utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
40
- utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
38
+ utm_campaign: ["ig_free_trial", "ig_discount_US", "ig_winter_sale", "ig_flash_sale", "ig_lookalike_audience"],
39
+ utm_medium: ["story_ad", "influencer_post", "promoted_post", "search_ad"],
40
+ utm_content: ["ig_control_group", "ig_variant_A", "ig_variant_B", "ig_variant_C", "ig_variant_D"],
41
+ utm_term: ["ig_jan_feb", "ig_mar_apr", "ig_may_jun", "ig_jul_aug", "ig_sep_oct", "ig_nov_dec"]
41
42
  },
42
-
43
43
  {
44
- utm_source: ["google ads"],
45
- utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
46
- utm_medium: ["search", "promoted", "sidebar"],
47
- utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
48
- utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
44
+ utm_source: ["google"],
45
+ utm_campaign: ["ga_free_trial", "ga_discount_US", "ga_spring_promo", "ga_summer_promo", "ga_lookalike_audience"],
46
+ utm_medium: ["search_ad", "display_ad", "sidebar_ad"],
47
+ utm_content: ["ga_control_group", "ga_variant_A", "ga_variant_B", "ga_variant_C", "ga_variant_D"],
48
+ utm_term: ["ga_jan_feb", "ga_mar_apr", "ga_may_jun", "ga_jul_aug", "ga_sep_oct", "ga_nov_dec"]
49
49
  },
50
50
  {
51
51
  utm_source: ["youtube"],
52
- utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
53
- utm_medium: ["search", "promoted", "sidebar"],
54
- utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
55
- utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
56
- },
57
- {
58
- utm_source: ["$referral"],
59
- utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
60
- utm_medium: ["email", "referral link"],
61
- utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
62
- utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
63
- },
64
- {
65
- utm_source: ["google ads"],
66
- utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
67
- utm_medium: ["inbox", "sidebar", "keywords"],
68
- utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
69
- utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
70
- },
71
-
72
-
52
+ utm_campaign: ["yt_free_trial", "yt_discount_US", "yt_autumn_promo", "yt_end_of_year", "yt_lookalike_audience"],
53
+ utm_medium: ["video_ad", "display_ad", "sidebar_ad"],
54
+ utm_content: ["yt_control_group", "yt_variant_A", "yt_variant_B", "yt_variant_C", "yt_variant_D"],
55
+ utm_term: ["yt_jan_feb", "yt_mar_apr", "yt_may_jun", "yt_jul_aug", "yt_sep_oct", "yt_nov_dec"]
56
+ },
73
57
  {
74
58
  utm_source: ["tiktok"],
75
- utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
76
- utm_medium: ["search", "promoted", "sidebar"],
77
- utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
78
- utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
59
+ utm_campaign: ["tt_free_trial", "tt_discount_US", "tt_flash_sale", "tt_special_offer", "tt_lookalike_audience"],
60
+ utm_medium: ["video_ad", "promoted_content", "sidebar_ad"],
61
+ utm_content: ["tt_control_group", "tt_variant_A", "tt_variant_B", "tt_variant_C", "tt_variant_D"],
62
+ utm_term: ["tt_jan_feb", "tt_mar_apr", "tt_may_jun", "tt_jul_aug", "tt_sep_oct", "tt_nov_dec"]
79
63
  },
80
64
  {
81
65
  utm_source: ["reddit"],
82
- utm_campaign: ["free trial", "discount country_code", "all for one", "one for all", "look-alike"],
83
- utm_medium: ["search", "promoted", "sidebar"],
84
- utm_content: ["control group", "variant A", "variant B", "variant C", "variant D"],
85
- utm_term: ["Jan-Feb", "Mar-Apr", "May-June", "July-Aug", "Sept-Oct", "Nov-Dec"]
66
+ utm_campaign: ["rd_free_trial", "rd_discount_US", "rd_winter_promo", "rd_new_year_offer", "rd_lookalike_audience"],
67
+ utm_medium: ["promoted_post", "display_ad", "sidebar_ad"],
68
+ utm_content: ["rd_control_group", "rd_variant_A", "rd_variant_B", "rd_variant_C", "rd_variant_D"],
69
+ utm_term: ["rd_jan_feb", "rd_mar_apr", "rd_may_jun", "rd_jul_aug", "rd_sep_oct", "rd_nov_dec"]
86
70
  }
87
71
  ];
88
72
 
@@ -338,13 +322,13 @@ const desktopDevices = [
338
322
  screen_height: '2520',
339
323
  screen_width: '4480',
340
324
  os: 'macOS',
341
- Platform : 'Desktop'
325
+ Platform: 'Desktop'
342
326
  }, {
343
327
  model: 'iMac 27-inch (2020)',
344
328
  screen_height: '2880',
345
329
  screen_width: '5120',
346
330
  os: 'macOS',
347
- Platform : 'Desktop'
331
+ Platform: 'Desktop'
348
332
  }, {
349
333
  model: 'Mac Mini (M2, 2023)',
350
334
  screen_height: '2160',
@@ -356,31 +340,31 @@ const desktopDevices = [
356
340
  screen_height: '1664',
357
341
  screen_width: '2560',
358
342
  os: 'macOS',
359
- Platform : 'Desktop'
343
+ Platform: 'Desktop'
360
344
  }, {
361
345
  model: 'MacBook Pro 14-inch (M2, 2023)',
362
346
  screen_height: '1964',
363
347
  screen_width: '3024',
364
348
  os: 'macOS',
365
- Platform : 'Desktop'
349
+ Platform: 'Desktop'
366
350
  }, {
367
351
  model: 'MacBook Pro 16-inch (M2, 2023)',
368
352
  screen_height: '2234',
369
353
  screen_width: '3456',
370
354
  os: 'macOS',
371
- Platform : 'Desktop'
355
+ Platform: 'Desktop'
372
356
  }, {
373
357
  model: 'Mac Pro (2019)',
374
358
  screen_height: '2160',
375
359
  screen_width: '3840',
376
360
  os: 'macOS',
377
- Platform : 'Desktop'
361
+ Platform: 'Desktop'
378
362
  }, {
379
363
  model: 'iMac Pro (2017)',
380
364
  screen_height: '2880',
381
365
  screen_width: '5120',
382
366
  os: 'macOS',
383
- Platform : 'Desktop'
367
+ Platform: 'Desktop'
384
368
  }, {
385
369
  model: 'Dell XPS 15',
386
370
  screen_height: '2160',
@@ -970,5 +954,7 @@ module.exports = {
970
954
  iosDevices,
971
955
  desktopDevices
972
956
  },
973
- locations
957
+ locations,
958
+ domainSuffix,
959
+ domainPrefix
974
960
  };
@@ -6,18 +6,18 @@ by AK
6
6
  ak@mixpanel.com
7
7
  */
8
8
 
9
- //todo: ads-data
10
- //todo: cart analysis
11
9
  //todo: churn ... is churnFunnel, possible to return, etc
12
- //todo: fixedTimeFunnel? if set this funnel will occur for all users at the same time ['cart charged', 'charge complete']
13
-
14
- /** @typedef {import('./types.d.ts').Config} Config */
15
- /** @typedef {import('./types.d.ts').EventConfig} EventConfig */
16
- /** @typedef {import('./types.d.ts').Funnel} Funnel */
17
- /** @typedef {import('./types.d.ts').Person} Person */
18
- /** @typedef {import('./types.d.ts').SCDTableRow} SCDTableRow */
19
- /** @typedef {import('./types.d.ts').UserProfile} UserProfile */
20
- /** @typedef {import('./types.d.ts').EventSpec} EventSpec */
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
21
 
22
22
  const dayjs = require("dayjs");
23
23
  const utc = require("dayjs/plugin/utc");
@@ -29,10 +29,11 @@ const os = require("os");
29
29
  const path = require("path");
30
30
  const { comma, bytesHuman, makeName, md5, clone, tracker, uid } = require("ak-tools");
31
31
  const { generateLineChart } = require('./chart.js');
32
- const { version } = require('./package.json');
32
+ const { version } = require('../package.json');
33
33
  const mp = require("mixpanel-import");
34
34
  const metrics = tracker("make-mp-data", "db99eb8f67ae50949a13c27cacf57d41", os.userInfo().username);
35
35
 
36
+
36
37
  const u = require("./utils.js");
37
38
  const getCliParams = require("./cli.js");
38
39
  const { campaigns, devices, locations } = require('./defaults.js');
@@ -41,7 +42,7 @@ let VERBOSE = false;
41
42
  let isCLI = false;
42
43
  /** @type {Config} */
43
44
  let CONFIG;
44
-
45
+ let CAMPAIGNS;
45
46
  let DEFAULTS;
46
47
  require('dotenv').config();
47
48
 
@@ -156,6 +157,7 @@ async function main(config) {
156
157
  global.MP_SIMULATION_CONFIG = config;
157
158
  CONFIG = config;
158
159
  VERBOSE = verbose;
160
+ CAMPAIGNS = campaigns;
159
161
  DEFAULTS = {
160
162
  locations: u.pickAWinner(locations, 0),
161
163
  iOSDevices: u.pickAWinner(devices.iosDevices, 0),
@@ -177,6 +179,7 @@ async function main(config) {
177
179
  //setup all the data structures we will push into
178
180
  const eventData = u.enrichArray([], { hook, type: "event", config });
179
181
  const userProfilesData = u.enrichArray([], { hook, type: "user", config });
182
+ const adSpendData = u.enrichArray([], { hook, type: "ad-spend", config });
180
183
  const scdTableKeys = Object.keys(scdProps);
181
184
  const scdTableData = [];
182
185
  for (const [index, key] of scdTableKeys.entries()) {
@@ -196,14 +199,14 @@ async function main(config) {
196
199
  //user loop
197
200
  log(`---------------SIMULATION----------------`, "\n\n");
198
201
  loopUsers: for (let i = 1; i < numUsers + 1; i++) {
199
- u.progress("users", i);
202
+ u.progress([["users", i], ["events", eventData.length]]);
200
203
  const userId = chance.guid();
201
204
  const user = u.person(userId, numDays, isAnonymous);
202
205
  const { distinct_id, created, anonymousIds, sessionIds } = user;
203
206
  let numEventsPreformed = 0;
204
207
 
205
208
  if (hasLocation) {
206
- const location = u.choose(DEFAULTS.locations().map(l => { delete l.country; return l; }));
209
+ const location = u.choose(clone(DEFAULTS.locations()).map(l => { delete l.country; return l; }));
207
210
  for (const key in location) {
208
211
  user[key] = location[key];
209
212
  }
@@ -269,6 +272,17 @@ async function main(config) {
269
272
  // end individual user loop
270
273
  }
271
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
+
272
286
  //flatten SCD tables
273
287
  scdTableData.forEach((table, index) => scdTableData[index] = table.flat());
274
288
 
@@ -280,7 +294,7 @@ async function main(config) {
280
294
  const groupCardinality = groupPair[1];
281
295
  const groupProfiles = [];
282
296
  for (let i = 1; i < groupCardinality + 1; i++) {
283
- u.progress("groups", i);
297
+ u.progress([["groups", i]]);
284
298
  const group = {
285
299
  [groupKey]: i,
286
300
  ...makeProfile(groupProps[groupKey])
@@ -297,7 +311,7 @@ async function main(config) {
297
311
  const { key, entries, attributes } = lookupTable;
298
312
  const data = [];
299
313
  for (let i = 1; i < entries + 1; i++) {
300
- u.progress("lookups", i);
314
+ u.progress([["lookups", i]]);
301
315
  const item = {
302
316
  [key]: i,
303
317
  ...makeProfile(attributes),
@@ -352,11 +366,12 @@ async function main(config) {
352
366
  }
353
367
  }
354
368
 
355
- const { eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, mirrorFiles, folder } =
369
+ const { eventFiles, userFiles, scdFiles, groupFiles, lookupFiles, mirrorFiles, folder, adSpendFiles } =
356
370
  u.buildFileNames(config);
357
371
  const pairs = [
358
372
  [eventFiles, [eventData]],
359
373
  [userFiles, [userProfilesData]],
374
+ [adSpendFiles, [adSpendData]],
360
375
  [scdFiles, scdTableData],
361
376
  [groupFiles, groupProfilesData],
362
377
  [lookupFiles, lookupTableData],
@@ -431,7 +446,7 @@ async function main(config) {
431
446
 
432
447
  if (eventData) {
433
448
  log(`importing events to mixpanel...\n`);
434
- const imported = await mp(creds, eventData, {
449
+ const imported = await mp(creds, clone(eventData), {
435
450
  recordType: "event",
436
451
  ...commonOpts,
437
452
  });
@@ -440,19 +455,28 @@ async function main(config) {
440
455
  }
441
456
  if (userProfilesData) {
442
457
  log(`importing user profiles to mixpanel...\n`);
443
- const imported = await mp(creds, userProfilesData, {
458
+ const imported = await mp(creds, clone(userProfilesData), {
444
459
  recordType: "user",
445
460
  ...commonOpts,
446
461
  });
447
462
  log(`\tsent ${comma(imported.success)} user profiles\n`);
448
463
  importResults.users = imported;
449
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
+ }
450
474
  if (groupProfilesData) {
451
475
  for (const groupProfiles of groupProfilesData) {
452
476
  const groupKey = groupProfiles.key;
453
477
  const data = groupProfiles.data;
454
478
  log(`importing ${groupKey} profiles to mixpanel...\n`);
455
- const imported = await mp({ token, groupKey }, data, {
479
+ const imported = await mp({ token, groupKey }, clone(data), {
456
480
  recordType: "group",
457
481
  ...commonOpts,
458
482
 
@@ -475,6 +499,7 @@ async function main(config) {
475
499
  groupProfilesData,
476
500
  lookupTableData,
477
501
  mirrorEventData,
502
+ adSpendData
478
503
  };
479
504
  }
480
505
 
@@ -506,7 +531,7 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
506
531
 
507
532
  let defaultProps = {};
508
533
  let devicePool = [];
509
- if (hasLocation) defaultProps.location = DEFAULTS.locations().map(l => { delete l.country_code; return l; });
534
+ if (hasLocation) defaultProps.location = clone(DEFAULTS.locations()).map(l => { delete l.country_code; return l; });
510
535
  if (hasBrowser) defaultProps.browser = DEFAULTS.browsers();
511
536
  if (hasAndroidDevices) devicePool.push(DEFAULTS.androidDevices());
512
537
  if (hasIOSDevices) devicePool.push(DEFAULTS.iOSDevices());
@@ -563,11 +588,20 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, chosenEv
563
588
 
564
589
  else if (typeof choice === "object") {
565
590
  for (const subKey in choice) {
566
- if (Array.isArray(choice[subKey])) {
591
+ if (typeof choice[subKey] === "string") {
592
+ if (!eventTemplate[subKey]) eventTemplate[subKey] = choice[subKey];
593
+ }
594
+ else if (Array.isArray(choice[subKey])) {
567
595
  const subChoice = u.choose(choice[subKey]);
568
596
  if (!eventTemplate[subKey]) eventTemplate[subKey] = subChoice;
569
597
  }
570
- if (!eventTemplate[subKey]) eventTemplate[subKey] = choice[subKey];
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
+
571
605
  }
572
606
  }
573
607
 
@@ -787,7 +821,7 @@ function makeProfile(props, defaults) {
787
821
  }
788
822
 
789
823
  /**
790
- * @param {import('./types.d.ts').ValueValid} prop
824
+ * @param {import('../types').ValueValid} prop
791
825
  * @param {string} scdKey
792
826
  * @param {string} distinct_id
793
827
  * @param {number} mutations
@@ -816,68 +850,58 @@ function makeSCD(prop, scdKey, distinct_id, mutations, created) {
816
850
  }
817
851
 
818
852
  //todo
819
- // function makeAdSpend(spec, usersCreated, specialDays = []) {
820
- // const networks = [
821
- // ...new Set(
822
- // attribution
823
- // .filter(camp => !["$organic", "$referral"].includes(camp.utm_source.join()))
824
- // .map(network => network.utm_source)
825
- // .flat()
826
- // )
827
- // ];
828
-
829
- // const campaigns = attribution.slice().pop().utm_campaign;
830
- // const campaignParams = attribution.slice().pop();
831
- // const { startDate, endDate } = spec;
832
- // const days = getDeltaDays(startDate, endDate);
833
- // const numDays = days.length;
834
- // const data = [];
835
- // for (const network of networks) {
836
- // for (const day of days) {
837
- // //cost per acquisition ~ $10-50
838
- // let CAC = chance.integer({ min: 10, max: 50 });
839
- // // daily spend is total users / total days * CAC / num networks
840
- // let cost = Math.floor(((usersCreated / numDays) * CAC) / networks.length);
841
- // // CTR ~ 0.5-10%
842
- // let clicks = Math.floor(cost / chance.floating({ min: 0.5, max: 10 }));
843
- // //boost CTR on "special days" ~ 15-50%
844
- // if (day in specialDays) clicks *= Math.floor(clicks * chance.floating({ min: 1.15, max: 1.5 }));
845
- // //impressions ~100-500 * cost
846
- // let impressions = Math.floor(cost * chance.integer({ min: 100, max: 500 }));
847
- // // views ~ 25-80% of impressions
848
- // let views = Math.floor(impressions * chance.floating({ min: 0.25, max: 0.8 }));
849
-
850
- // let campaign_name = chance.pickone(campaigns);
851
- // let campaign_id = md5(campaign_name);
852
- // data.push({
853
- // event: "ad data",
854
- // properties: {
855
- // cost,
856
- // clicks,
857
- // impressions,
858
- // campaign_name,
859
- // campaign_id,
860
- // network,
861
- // views,
862
-
863
- // //addendum
864
- // utm_campaign: campaign_name,
865
- // utm_source: network,
866
- // utm_medium: campaignParams.utm_medium,
867
- // utm_content: campaignParams.utm_content,
868
- // utm_term: campaignParams.utm_term,
869
-
870
- // source: network,
871
- // $insert_id: campaign_id,
872
- // time: dayjs.utc(day).hour(12).unix(),
873
- // $source: "DM3",
874
- // $mp_lib: "DM3"
875
- // }
876
- // });
877
- // }
878
- // }
879
- // return data;
880
- // }
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
+ }
900
+
901
+
902
+ }
903
+ return adSpendEvents;
904
+ }
881
905
 
882
906
 
883
907
 
@@ -905,12 +929,12 @@ if (require.main === module) {
905
929
  console.log(`... using default COMPLEX configuration [everything] ...\n`);
906
930
  console.log(`... for more simple data, don't use the --complex flag ...\n`);
907
931
  console.log(`... or specify your own js config file (see docs or --help) ...\n`);
908
- config = require(path.resolve(__dirname, "./schemas/complex.js"));
932
+ config = require(path.resolve(__dirname, "../schemas/complex.js"));
909
933
  }
910
934
  else {
911
935
  console.log(`... using default SIMPLE configuration [events + users] ...\n`);
912
936
  console.log(`... for more complex data, use the --complex flag ...\n`);
913
- config = require(path.resolve(__dirname, "./schemas/simple.js"));
937
+ config = require(path.resolve(__dirname, "../schemas/simple.js"));
914
938
  }
915
939
  }
916
940