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.
- package/dungeons/student-teacher.js +38 -87
- package/entry.js +7 -1
- package/index.js +90 -8
- package/lib/cli/cli.js +15 -1
- package/lib/core/config-validator.js +230 -219
- package/lib/core/context.js +13 -1
- package/lib/core/storage.js +88 -23
- package/lib/generators/events.js +17 -16
- package/lib/generators/funnels.js +8 -6
- package/lib/orchestrators/mixpanel-sender.js +5 -2
- package/lib/orchestrators/user-loop.js +212 -181
- package/lib/templates/abbreviated.d.ts +4 -3
- package/lib/templates/instructions.txt +1 -0
- package/lib/templates/{dungeon-template.js → scratch-dungeon-template.js} +9 -3
- package/lib/templates/verbose-schema.js +31 -4
- package/lib/utils/utils.js +178 -14
- package/package.json +5 -4
- package/types.d.ts +9 -4
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
245
|
+
function boolCoerce(value) {
|
|
232
246
|
if (typeof value === 'boolean') return value;
|
|
233
247
|
if (typeof value === 'string') {
|
|
234
248
|
return value.toLowerCase() === 'true';
|