make-mp-data 2.0.21 → 2.0.23

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.
@@ -1,4 +1,3 @@
1
- // Generated from prompt: Create a digital classroom data set where we have two types of users students and teachers and students do student actions like reading writing arithmetic homework tests and teachers do teaching actions like teach grade take attendance etc. importantly each event done by each persona should have a 'student:' or teacher: prefix to indicate that this is an event on by students or this is not done by teachers... make many unique events + funnels.
2
1
 
3
2
  import dayjs from "dayjs";
4
3
  import utc from "dayjs/plugin/utc.js";
@@ -24,7 +23,7 @@ const config = {
24
23
  hasAnonIds: false,
25
24
  hasSessionIds: false,
26
25
  format: "json",
27
- alsoInferFunnels: true,
26
+ alsoInferFunnels: false,
28
27
  hasLocation: true,
29
28
  hasAndroidDevices: true,
30
29
  hasIOSDevices: true,
@@ -53,6 +52,9 @@ const config = {
53
52
  "isFirstFunnel": true,
54
53
  "conversionRate": 95,
55
54
  "timeToConvert": 0.1,
55
+ "conditions": {
56
+ "role": "student"
57
+ },
56
58
  "order": "sequential",
57
59
  "props": {
58
60
  "onboarding_source": [
@@ -69,6 +71,9 @@ const config = {
69
71
  "teacher: create class",
70
72
  "teacher: post announcement"
71
73
  ],
74
+ "conditions": {
75
+ "role": "teacher"
76
+ },
72
77
  "isFirstFunnel": true,
73
78
  "conversionRate": 98,
74
79
  "timeToConvert": 0.2,
@@ -82,6 +87,9 @@ const config = {
82
87
  "student: submit assignment",
83
88
  "student: view grade"
84
89
  ],
90
+ "conditions": {
91
+ "role": "student"
92
+ },
85
93
  "isFirstFunnel": false,
86
94
  "conversionRate": 70,
87
95
  "timeToConvert": 48,
@@ -92,6 +100,9 @@ const config = {
92
100
  },
93
101
  {
94
102
  "name": "Student Takes Test",
103
+ "conditions": {
104
+ "role": "student"
105
+ },
95
106
  "sequence": [
96
107
  "student: view assignment",
97
108
  "student: start test",
@@ -109,6 +120,9 @@ const config = {
109
120
  },
110
121
  {
111
122
  "name": "Teacher Grades Assignment",
123
+ "conditions": {
124
+ "role": "teacher"
125
+ },
112
126
  "sequence": [
113
127
  "teacher: create assignment",
114
128
  "teacher: assign homework",
@@ -126,10 +140,21 @@ const config = {
126
140
  "teacher: start lesson",
127
141
  "teacher: end lesson"
128
142
  ],
143
+ "conditions": {
144
+ "role": "teacher"
145
+ },
129
146
  "isFirstFunnel": false,
130
147
  "conversionRate": 99,
131
148
  "timeToConvert": 1,
132
149
  "order": "sequential"
150
+ },
151
+ {
152
+ "name": "all peoples doing stuf (no conditions)",
153
+ "sequence": [
154
+ "foo",
155
+ "bar",
156
+ "baz"
157
+ ]
133
158
  }
134
159
  ],
135
160
  "events": [
@@ -291,6 +316,15 @@ const config = {
291
316
  "Event"
292
317
  ]
293
318
  }
319
+ },
320
+ {
321
+ "event": "foo"
322
+ },
323
+ {
324
+ "event": "bar"
325
+ },
326
+ {
327
+ "event": "baz"
294
328
  }
295
329
  ],
296
330
  "superProps": {
@@ -326,8 +360,6 @@ const config = {
326
360
  "student",
327
361
  "teacher"
328
362
  ],
329
- "first_name": "chance.first.bind(chance)",
330
- "last_name": "chance.last.bind(chance)"
331
363
  },
332
364
  "scdProps": {
333
365
  "gpa": {
@@ -358,88 +390,7 @@ const config = {
358
390
  "max": 12
359
391
  }
360
392
  },
361
- "groupKeys": [
362
- [
363
- "class_id",
364
- 100
365
- ]
366
- ],
367
- "groupProps": {
368
- "class_id": {
369
- "teacher_name": "() => `Mrs. ${chance.last()}`",
370
- "school_name": "() => `${chance.city()} ${chance.pickone(['Elementary', 'Middle School', 'High School'])}`",
371
- "class_size": "weighNumRange(15, 35, 0.5)",
372
- "subject_id": "range(1, 20)"
373
- }
374
- },
375
- "lookupTables": [
376
- {
377
- "key": "assignment_id",
378
- "entries": 500,
379
- "attributes": {
380
- "type": [
381
- "homework",
382
- "test",
383
- "quiz",
384
- "project",
385
- "reading"
386
- ],
387
- "difficulty": [
388
- "easy",
389
- "medium",
390
- "hard"
391
- ],
392
- "max_points": [
393
- 10,
394
- 20,
395
- 25,
396
- 50,
397
- 100
398
- ],
399
- "subject_id": "range(1, 20)"
400
- }
401
- },
402
- {
403
- "key": "subject_id",
404
- "entries": 20,
405
- "attributes": {
406
- "subject_name": [
407
- "Mathematics",
408
- "English Language Arts",
409
- "Science",
410
- "Social Studies",
411
- "History",
412
- "Art",
413
- "Music",
414
- "Physical Education",
415
- "Computer Science",
416
- "Spanish",
417
- "French",
418
- "German",
419
- "Biology",
420
- "Chemistry",
421
- "Physics",
422
- "Algebra",
423
- "Geometry",
424
- "Calculus",
425
- "Literature",
426
- "World History"
427
- ],
428
- "department": [
429
- "STEM",
430
- "Humanities",
431
- "Arts",
432
- "Foreign Language"
433
- ],
434
- "is_core_subject": [
435
- true,
436
- true,
437
- false
438
- ]
439
- }
440
- }
441
- ]
442
- ,
393
+
443
394
 
444
395
  hook: function (record, type, meta) {
445
396
  const NOW = dayjs();
@@ -460,7 +411,7 @@ const config = {
460
411
 
461
412
  }
462
413
 
463
- if (type === "scd") {
414
+ if (type === "scd-pre") {
464
415
 
465
416
  }
466
417
 
package/entry.js CHANGED
@@ -17,11 +17,17 @@ import getCliParams from './lib/cli/cli.js';
17
17
  if (cliConfig.complex) {
18
18
  const complexConfig = await import('./dungeons/complex.js');
19
19
  finalConfig = { ...complexConfig.default, ...cliConfig };
20
- } else if (cliConfig.simple || (!cliConfig.complex && !cliConfig.simple)) {
20
+ }
21
+ else if (cliConfig.sanity) {
22
+ const sanityConfig = await import('./dungeons/sanity.js');
23
+ finalConfig = { ...sanityConfig.default, ...cliConfig };
24
+ }
25
+ else if (cliConfig.simple || (!cliConfig.complex && !cliConfig.simple)) {
21
26
  // Default to simple mode when no flags or when --simple is explicitly set
22
27
  const simpleConfig = await import('./dungeons/simple.js');
23
28
  finalConfig = { ...simpleConfig.default, ...cliConfig };
24
29
  }
30
+
25
31
 
26
32
 
27
33
  const result = await main(finalConfig);
package/index.js CHANGED
@@ -89,7 +89,7 @@ function displayConfigurationSummary(config) {
89
89
  if (funnel.sequence) {
90
90
  const arrow = ' → ';
91
91
  const sequence = funnel.sequence.join(arrow);
92
- const rate = funnel.conversionRate ? ` (${(funnel.conversionRate * 100).toFixed(0)}% conversion)` : '';
92
+ const rate = funnel.conversionRate ? ` (${(funnel.conversionRate).toFixed(0)}% conversion)` : '';
93
93
  console.log(` ${i + 1}. ${sequence}${rate}`);
94
94
  }
95
95
  });
@@ -194,7 +194,12 @@ async function main(config) {
194
194
  await generateCharts(context);
195
195
  }
196
196
 
197
- // Step 11: Flush storage containers to disk (if writeToDisk enabled)
197
+ // Step 11a: flush lookup tables to disk (always as CSVs)
198
+ if (validatedConfig.writeToDisk) {
199
+ await flushLookupTablesToDisk(storage, validatedConfig);
200
+ }
201
+
202
+ // Step 11b: Flush other storage containers to disk (if writeToDisk enabled)
198
203
  if (validatedConfig.writeToDisk) {
199
204
  await flushStorageToDisk(storage, validatedConfig);
200
205
  }
@@ -214,7 +219,7 @@ async function main(config) {
214
219
  return {
215
220
  ...extractedData,
216
221
  importResults,
217
- files: extractFileInfo(storage),
222
+ files: await extractFileInfo(storage, validatedConfig),
218
223
  time: { start, end, delta, human },
219
224
  operations: context.getOperations(),
220
225
  eventCount: context.getEventCount(),
@@ -423,7 +428,7 @@ async function generateCharts(context) {
423
428
  if (config.makeChart && storage.eventData?.length > 0) {
424
429
  const chartPath = typeof config.makeChart === 'string'
425
430
  ? config.makeChart
426
- : `./${config.simulationName}-timeline`;
431
+ : `./${config.name}-timeline`;
427
432
 
428
433
  await generateLineChart(storage.eventData, undefined, chartPath);
429
434
 
@@ -436,7 +441,33 @@ async function generateCharts(context) {
436
441
  }
437
442
 
438
443
  /**
439
- * Flush all storage containers to disk
444
+ * Flush lookup tables to disk (always runs, regardless of writeToDisk setting)
445
+ * @param {import('./types').Storage} storage - Storage containers
446
+ * @param {import('./types').Dungeon} config - Configuration object
447
+ */
448
+ async function flushLookupTablesToDisk(storage, config) {
449
+ if (!storage.lookupTableData || !Array.isArray(storage.lookupTableData) || storage.lookupTableData.length === 0) {
450
+ return; // No lookup tables to flush
451
+ }
452
+
453
+ if (config.verbose) {
454
+ console.log('💾 Writing lookup tables to disk...');
455
+ }
456
+
457
+ const flushPromises = [];
458
+ storage.lookupTableData.forEach(container => {
459
+ if (container?.flush) flushPromises.push(container.flush());
460
+ });
461
+
462
+ await Promise.all(flushPromises);
463
+
464
+ if (config.verbose) {
465
+ console.log('🗂️ Lookup tables flushed to disk successfully');
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Flush all storage containers to disk (excluding lookup tables)
440
471
  * @param {import('./types').Storage} storage - Storage containers
441
472
  * @param {import('./types').Dungeon} config - Configuration object
442
473
  */
@@ -454,8 +485,8 @@ async function flushStorageToDisk(storage, config) {
454
485
  if (storage.mirrorEventData?.flush) flushPromises.push(storage.mirrorEventData.flush());
455
486
  if (storage.groupEventData?.flush) flushPromises.push(storage.groupEventData.flush());
456
487
 
457
- // Flush arrays of HookedArrays
458
- [storage.scdTableData, storage.groupProfilesData, storage.lookupTableData].forEach(arrayOfContainers => {
488
+ // Flush arrays of HookedArrays (excluding lookup tables which are handled separately)
489
+ [storage.scdTableData, storage.groupProfilesData].forEach(arrayOfContainers => {
459
490
  if (Array.isArray(arrayOfContainers)) {
460
491
  arrayOfContainers.forEach(container => {
461
492
  if (container?.flush) flushPromises.push(container.flush());
@@ -473,11 +504,13 @@ async function flushStorageToDisk(storage, config) {
473
504
  /**
474
505
  * Extract file information from storage containers
475
506
  * @param {import('./types').Storage} storage - Storage object
507
+ * @param {import('./types').Dungeon} config - Configuration object
476
508
  * @returns {string[]} Array of file paths
477
509
  */
478
- function extractFileInfo(storage) {
510
+ async function extractFileInfo(storage, config) {
479
511
  const files = [];
480
512
 
513
+ // Try to get paths from containers first
481
514
  Object.values(storage).forEach(container => {
482
515
  if (Array.isArray(container)) {
483
516
  container.forEach(subContainer => {
@@ -490,6 +523,55 @@ function extractFileInfo(storage) {
490
523
  }
491
524
  });
492
525
 
526
+ // If no files found from containers and writeToDisk is enabled, scan the data directory
527
+ if (files.length === 0 && config.writeToDisk) {
528
+ try {
529
+ const fs = await import('fs');
530
+ const path = await import('path');
531
+
532
+ let dataDir = path.resolve("./data");
533
+ if (!fs.existsSync(dataDir)) {
534
+ dataDir = path.resolve("./");
535
+ }
536
+
537
+ if (fs.existsSync(dataDir)) {
538
+ const allFiles = fs.readdirSync(dataDir);
539
+ const simulationName = config.name;
540
+
541
+ // Filter files that match our patterns and were likely created by this run
542
+ const relevantFiles = allFiles.filter(file => {
543
+ // Skip system files
544
+ if (file.startsWith('.')) return false;
545
+
546
+ // If we have a simulation name, only include files with that prefix
547
+ if (simulationName && !file.startsWith(simulationName)) {
548
+ return false;
549
+ }
550
+
551
+ // Check for common patterns
552
+ const hasEventPattern = file.includes('-EVENTS.');
553
+ const hasUserPattern = file.includes('-USERS.');
554
+ const hasScdPattern = file.includes('-SCD.');
555
+ const hasGroupPattern = file.includes('-GROUPS.');
556
+ const hasLookupPattern = file.includes('-LOOKUP.');
557
+ const hasAdspendPattern = file.includes('-ADSPEND.');
558
+ const hasMirrorPattern = file.includes('-MIRROR.');
559
+
560
+ return hasEventPattern || hasUserPattern || hasScdPattern ||
561
+ hasGroupPattern || hasLookupPattern || hasAdspendPattern || hasMirrorPattern;
562
+ });
563
+
564
+ // Convert to full paths
565
+ relevantFiles.forEach(file => {
566
+ files.push(path.join(dataDir, file));
567
+ });
568
+ }
569
+ } catch (error) {
570
+ // If scanning fails, just return empty array
571
+ console.warn('Warning: Could not scan data directory for files:', error.message);
572
+ }
573
+ }
574
+
493
575
  return files;
494
576
  }
495
577
 
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,
@@ -215,6 +223,12 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
215
223
  type: 'boolean',
216
224
  coerce: boolCoerce
217
225
  })
226
+ .option("name", {
227
+ alias: 'n',
228
+ demandOption: false,
229
+ describe: 'custom name for generated files (prefix)',
230
+ type: 'string'
231
+ })
218
232
 
219
233
  .help()
220
234
  .wrap(null)
@@ -228,7 +242,7 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
228
242
  }
229
243
 
230
244
 
231
- function boolCoerce(value, foo) {
245
+ function boolCoerce(value) {
232
246
  if (typeof value === 'boolean') return value;
233
247
  if (typeof value === 'string') {
234
248
  return value.toLowerCase() === 'true';