make-mp-data 2.0.19 → 2.0.22

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
@@ -47,6 +47,65 @@ global.FIXED_NOW = FIXED_NOW;
47
47
  let FIXED_BEGIN = dayjs.unix(FIXED_NOW).subtract(90, 'd').unix();
48
48
  global.FIXED_BEGIN = FIXED_BEGIN;
49
49
 
50
+ /**
51
+ * Display configuration summary for CLI mode
52
+ * @param {Config} config - Validated configuration object
53
+ */
54
+ function displayConfigurationSummary(config) {
55
+ console.log('\n📋 Configuration Summary');
56
+ console.log('─'.repeat(40));
57
+
58
+ // Core parameters
59
+ console.log(`🎯 Target: ${config.numUsers?.toLocaleString()} users, ${config.numEvents?.toLocaleString()} events`);
60
+ console.log(`📅 Timeline: ${config.numDays} days (${config.seed ? config.seed : 'random seed'})`);
61
+ console.log(`💾 Output: ${config.format} format${config.region ? ` (${config.region})` : ''}`);
62
+ console.log(`⚡ Performance: ${config.concurrency || 'auto'} threads`);
63
+
64
+ // Feature flags
65
+ const features = [];
66
+ if (config.hasAnonIds) features.push('anonymous IDs');
67
+ if (config.hasSessionIds) features.push('session IDs');
68
+ if (config.alsoInferFunnels) features.push('funnel inference');
69
+ if (config.makeChart) features.push('chart generation');
70
+ if (config.writeToDisk) features.push('disk output');
71
+
72
+ if (features.length > 0) {
73
+ console.log(`🔧 Features: ${features.join(', ')}`);
74
+ }
75
+
76
+ // Schema preview
77
+ if (config.events && config.events.length > 0) {
78
+ console.log('\n🎭 Event Schema');
79
+ console.log('─'.repeat(40));
80
+ const eventNames = config.events.slice(0, 6).map(e => e.event || e).join(', ');
81
+ const more = config.events.length > 6 ? ` (+${config.events.length - 6} more)` : '';
82
+ console.log(`📊 Events: ${eventNames}${more}`);
83
+ }
84
+
85
+ // Funnels preview
86
+ if (config.funnels && config.funnels.length > 0) {
87
+ console.log(`🔄 Funnels: ${config.funnels.length} funnel${config.funnels.length > 1 ? 's' : ''} configured`);
88
+ config.funnels.slice(0, 4).forEach((funnel, i) => {
89
+ if (funnel.sequence) {
90
+ const arrow = ' → ';
91
+ const sequence = funnel.sequence.join(arrow);
92
+ const rate = funnel.conversionRate ? ` (${(funnel.conversionRate).toFixed(0)}% conversion)` : '';
93
+ console.log(` ${i + 1}. ${sequence}${rate}`);
94
+ }
95
+ });
96
+ if (config.funnels.length > 4) {
97
+ console.log(` ...and ${config.funnels.length - 4} more funnels`);
98
+ }
99
+ }
100
+
101
+ // Group analytics
102
+ if (config.groupKeys && config.groupKeys.length > 0) {
103
+ const groups = config.groupKeys.map(([key, count]) => `${count} ${key}s`).join(', ');
104
+ console.log(`👥 Groups: ${groups}`);
105
+ }
106
+
107
+ console.log(''); // Extra spacing before generation starts
108
+ }
50
109
 
51
110
  /**
52
111
  * Main data generation function
@@ -81,6 +140,11 @@ async function main(config) {
81
140
  try {
82
141
  // Step 1: Validate and enrich configuration
83
142
  validatedConfig = validateDungeonConfig(config);
143
+
144
+ // Step 1.5: Display configuration summary (CLI mode only)
145
+ if (isCLI && validatedConfig.verbose) {
146
+ displayConfigurationSummary(validatedConfig);
147
+ }
84
148
 
85
149
  // Step 2: Create context with validated config
86
150
  const context = createContext(validatedConfig, null, isCLI);
@@ -90,6 +154,8 @@ async function main(config) {
90
154
  const storage = await storageManager.initializeContainers();
91
155
  updateContextWithStorage(context, storage);
92
156
 
157
+ // ! DATA GENERATION STARTS HERE
158
+
93
159
  // Step 4: Generate ad spend data (if enabled)
94
160
  if (validatedConfig.hasAdSpend) {
95
161
  await generateAdSpendData(context);
@@ -119,12 +185,19 @@ async function main(config) {
119
185
  await makeMirror(context);
120
186
  }
121
187
 
188
+ if (context.config.verbose) console.log(`\n✅ Data generation completed successfully!\n`);
189
+
190
+ // ! DATA GENERATION ENDS HERE
191
+
122
192
  // Step 10: Generate charts (if enabled)
123
193
  if (validatedConfig.makeChart) {
124
194
  await generateCharts(context);
125
195
  }
126
196
 
127
- // Step 11: Flush storage containers to disk (if writeToDisk enabled)
197
+ // Step 11a: Always flush lookup tables to disk (regardless of writeToDisk setting)
198
+ await flushLookupTablesToDisk(storage, validatedConfig);
199
+
200
+ // Step 11b: Flush other storage containers to disk (if writeToDisk enabled)
128
201
  if (validatedConfig.writeToDisk) {
129
202
  await flushStorageToDisk(storage, validatedConfig);
130
203
  }
@@ -252,7 +325,8 @@ async function generateLookupTables(context) {
252
325
 
253
326
  for (let j = 0; j < entries; j++) {
254
327
  const lookupEntry = await makeProfile(context, attributes, {
255
- [key]: `${key}_${j + 1}`
328
+ id: j + 1 //primary key is always a number so it joins simply with events
329
+ // [key]: `${key}_${j + 1}` // we don't want to use the lookup name as a prefix here
256
330
  });
257
331
 
258
332
  await lookupContainer.hookPush(lookupEntry);
@@ -352,7 +426,7 @@ async function generateCharts(context) {
352
426
  if (config.makeChart && storage.eventData?.length > 0) {
353
427
  const chartPath = typeof config.makeChart === 'string'
354
428
  ? config.makeChart
355
- : `./charts/${config.simulationName}-timeline.png`;
429
+ : `./${config.simulationName}-timeline`;
356
430
 
357
431
  await generateLineChart(storage.eventData, undefined, chartPath);
358
432
 
@@ -365,7 +439,33 @@ async function generateCharts(context) {
365
439
  }
366
440
 
367
441
  /**
368
- * Flush all storage containers to disk
442
+ * Flush lookup tables to disk (always runs, regardless of writeToDisk setting)
443
+ * @param {import('./types').Storage} storage - Storage containers
444
+ * @param {import('./types').Dungeon} config - Configuration object
445
+ */
446
+ async function flushLookupTablesToDisk(storage, config) {
447
+ if (!storage.lookupTableData || !Array.isArray(storage.lookupTableData) || storage.lookupTableData.length === 0) {
448
+ return; // No lookup tables to flush
449
+ }
450
+
451
+ if (config.verbose) {
452
+ console.log('💾 Writing lookup tables to disk...');
453
+ }
454
+
455
+ const flushPromises = [];
456
+ storage.lookupTableData.forEach(container => {
457
+ if (container?.flush) flushPromises.push(container.flush());
458
+ });
459
+
460
+ await Promise.all(flushPromises);
461
+
462
+ if (config.verbose) {
463
+ console.log('🗂️ Lookup tables flushed to disk successfully');
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Flush all storage containers to disk (excluding lookup tables)
369
469
  * @param {import('./types').Storage} storage - Storage containers
370
470
  * @param {import('./types').Dungeon} config - Configuration object
371
471
  */
@@ -383,8 +483,8 @@ async function flushStorageToDisk(storage, config) {
383
483
  if (storage.mirrorEventData?.flush) flushPromises.push(storage.mirrorEventData.flush());
384
484
  if (storage.groupEventData?.flush) flushPromises.push(storage.groupEventData.flush());
385
485
 
386
- // Flush arrays of HookedArrays
387
- [storage.scdTableData, storage.groupProfilesData, storage.lookupTableData].forEach(arrayOfContainers => {
486
+ // Flush arrays of HookedArrays (excluding lookup tables which are handled separately)
487
+ [storage.scdTableData, storage.groupProfilesData].forEach(arrayOfContainers => {
388
488
  if (Array.isArray(arrayOfContainers)) {
389
489
  arrayOfContainers.forEach(container => {
390
490
  if (container?.flush) flushPromises.push(container.flush());
@@ -395,7 +495,7 @@ async function flushStorageToDisk(storage, config) {
395
495
  await Promise.all(flushPromises);
396
496
 
397
497
  if (config.verbose) {
398
- console.log(' Data flushed to disk successfully');
498
+ console.log('🙏 Data flushed to disk successfully');
399
499
  }
400
500
  }
401
501
 
package/lib/cli/cli.js CHANGED
@@ -121,6 +121,14 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
121
121
  type: 'boolean',
122
122
  coerce: boolCoerce
123
123
  })
124
+ .options("sanity", {
125
+ demandOption: false,
126
+ default: false,
127
+ describe: 'run sanity checks on the generated data',
128
+ alias: 'san',
129
+ type: 'boolean',
130
+ coerce: boolCoerce
131
+ })
124
132
  .option("writeToDisk", {
125
133
  demandOption: false,
126
134
  default: true,