@ak--47/dungeon-master 1.0.0

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.
Files changed (66) hide show
  1. package/README.md +518 -0
  2. package/dungeons/array-of-object-lookup-schema.json +327 -0
  3. package/dungeons/array-of-object-lookup.js +220 -0
  4. package/dungeons/ecommerce-schema.json +462 -0
  5. package/dungeons/ecommerce.js +447 -0
  6. package/dungeons/education-schema.json +2409 -0
  7. package/dungeons/education.js +768 -0
  8. package/dungeons/fintech-schema.json +14034 -0
  9. package/dungeons/fintech.js +696 -0
  10. package/dungeons/foobar-schema.json +403 -0
  11. package/dungeons/foobar.js +296 -0
  12. package/dungeons/food-delivery-schema.json +192 -0
  13. package/dungeons/food-delivery.js +602 -0
  14. package/dungeons/food-schema.json +1152 -0
  15. package/dungeons/food.js +754 -0
  16. package/dungeons/gaming-schema.json +1270 -0
  17. package/dungeons/gaming.js +508 -0
  18. package/dungeons/insurance-application-schema.json +204 -0
  19. package/dungeons/insurance-application.js +605 -0
  20. package/dungeons/media-schema.json +906 -0
  21. package/dungeons/media.js +790 -0
  22. package/dungeons/retention-cadence-schema.json +78 -0
  23. package/dungeons/retention-cadence.js +244 -0
  24. package/dungeons/rpg-schema.json +4526 -0
  25. package/dungeons/rpg.js +919 -0
  26. package/dungeons/sanity-schema.json +255 -0
  27. package/dungeons/sanity.js +152 -0
  28. package/dungeons/sass-schema.json +1291 -0
  29. package/dungeons/sass.js +795 -0
  30. package/dungeons/scd-schema.json +919 -0
  31. package/dungeons/scd.js +277 -0
  32. package/dungeons/simple-schema.json +608 -0
  33. package/dungeons/simple.js +285 -0
  34. package/dungeons/simplest-schema.json +1418 -0
  35. package/dungeons/simplest.js +392 -0
  36. package/dungeons/social-schema.json +1118 -0
  37. package/dungeons/social.js +686 -0
  38. package/dungeons/text-generation-schema.json +3096 -0
  39. package/dungeons/text-generation.js +812 -0
  40. package/index.js +567 -0
  41. package/lib/core/config-validator.js +395 -0
  42. package/lib/core/context.js +204 -0
  43. package/lib/core/dungeon-loader.js +337 -0
  44. package/lib/core/storage.js +379 -0
  45. package/lib/generators/adspend.js +132 -0
  46. package/lib/generators/events.js +271 -0
  47. package/lib/generators/funnels.js +407 -0
  48. package/lib/generators/mirror.js +167 -0
  49. package/lib/generators/product-lookup.js +262 -0
  50. package/lib/generators/product-names.js +195 -0
  51. package/lib/generators/profiles.js +93 -0
  52. package/lib/generators/scd.js +124 -0
  53. package/lib/generators/text.js +1192 -0
  54. package/lib/orchestrators/mixpanel-sender.js +266 -0
  55. package/lib/orchestrators/user-loop.js +335 -0
  56. package/lib/templates/abbreviated.d.ts +169 -0
  57. package/lib/templates/defaults.js +1405 -0
  58. package/lib/templates/phrases.js +2526 -0
  59. package/lib/templates/schema.d.ts +173 -0
  60. package/lib/templates/soup-presets.js +188 -0
  61. package/lib/utils/function-registry.js +302 -0
  62. package/lib/utils/json-evaluator.js +172 -0
  63. package/lib/utils/logger.js +34 -0
  64. package/lib/utils/utils.js +1490 -0
  65. package/package.json +89 -0
  66. package/types.d.ts +865 -0
@@ -0,0 +1,78 @@
1
+ {
2
+ "schema": {
3
+ "token": "",
4
+ "seed": "lets go",
5
+ "numDays": 360,
6
+ "numEvents": 5000,
7
+ "numUsers": 100,
8
+ "hasAnonIds": false,
9
+ "hasSessionIds": false,
10
+ "format": "json",
11
+ "alsoInferFunnels": false,
12
+ "hasLocation": false,
13
+ "hasAndroidDevices": false,
14
+ "hasIOSDevices": false,
15
+ "hasDesktopDevices": false,
16
+ "hasBrowser": false,
17
+ "hasCampaigns": false,
18
+ "isAnonymous": false,
19
+ "hasAdSpend": false,
20
+ "hasAvatar": false,
21
+ "batchSize": 5500000,
22
+ "concurrency": 1,
23
+ "writeToDisk": false,
24
+ "percentUsersBornInDataset": 100,
25
+ "funnels": [],
26
+ "events": [
27
+ {
28
+ "event": "first behavior",
29
+ "weight": 1,
30
+ "isFirstEvent": true,
31
+ "properties": {}
32
+ },
33
+ {
34
+ "event": "hourly behavior",
35
+ "weight": 40,
36
+ "properties": {}
37
+ },
38
+ {
39
+ "event": "daily behavior",
40
+ "weight": 30,
41
+ "properties": {}
42
+ },
43
+ {
44
+ "event": "weekly behavior",
45
+ "weight": 15,
46
+ "properties": {}
47
+ },
48
+ {
49
+ "event": "monthly behavior",
50
+ "weight": 10,
51
+ "properties": {}
52
+ },
53
+ {
54
+ "event": "placeholder",
55
+ "weight": 50,
56
+ "properties": {}
57
+ }
58
+ ],
59
+ "superProps": {},
60
+ "userProps": {
61
+ "favorite color": [
62
+ "red",
63
+ "blue",
64
+ "green",
65
+ "yellow",
66
+ "purple"
67
+ ]
68
+ },
69
+ "scdProps": {},
70
+ "mirrorProps": {},
71
+ "groupKeys": [],
72
+ "groupProps": {},
73
+ "lookupTables": []
74
+ },
75
+ "hooks": "function (record, type, meta) {\n\t\tif (type === \"everything\") {\n\t\t\tif (!record.length) return record;\n\n\t\t\tconst userId = record[0].user_id || record[0].device_id;\n\t\t\tif (!userId) return record;\n\n\t\t\tconst favoriteColor = meta.profile?.[\"favorite color\"] || \"green\";\n\t\t\tconst colorChurnRates = CHURN_RATES[favoriteColor] || CHURN_RATES.green;\n\n\t\t\t// Find the time range from the raw events\n\t\t\tconst times = record.map(e => new Date(e.time).getTime()).filter(t => !isNaN(t));\n\t\t\tif (!times.length) return record;\n\t\t\tconst startTime = dayjs(Math.min(...times));\n\t\t\tconst endTime = dayjs(Math.max(...times));\n\t\t\tconst totalSpanHours = endTime.diff(startTime, 'hour');\n\n\t\t\tif (totalSpanHours < 1) return record;\n\n\t\t\t// Keep the \"first behavior\" event from the original stream\n\t\t\tconst firstBehavior = record.find(e => e.event === \"first behavior\");\n\t\t\tconst newEvents = [];\n\n\t\t\tif (firstBehavior) {\n\t\t\t\tnewEvents.push(firstBehavior);\n\t\t\t} else {\n\t\t\t\t// Create a first behavior event at the start\n\t\t\t\tnewEvents.push({\n\t\t\t\t\tevent: \"first behavior\",\n\t\t\t\t\ttime: startTime.toISOString(),\n\t\t\t\t\tuser_id: userId,\n\t\t\t\t\tinsert_id: v.uid(12),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// For each behavior type, determine churn and generate cadenced events\n\t\t\tfor (const [behaviorKey, cadence] of Object.entries(BEHAVIORS)) {\n\t\t\t\tconst churnRate = colorChurnRates[behaviorKey];\n\t\t\t\tlet churnTime = null;\n\n\t\t\t\tif (chance.bool({ likelihood: churnRate })) {\n\t\t\t\t\t// Pick churn time between 20% and 80% into the active period\n\t\t\t\t\tconst churnOffsetHours = chance.integer({\n\t\t\t\t\t\tmin: Math.floor(totalSpanHours * 0.2),\n\t\t\t\t\t\tmax: Math.floor(totalSpanHours * 0.8),\n\t\t\t\t\t});\n\t\t\t\t\tchurnTime = startTime.add(churnOffsetHours, 'hour');\n\t\t\t\t}\n\n\t\t\t\tconst behaviorEvents = generateCadencedEvents(\n\t\t\t\t\tbehaviorKey,\n\t\t\t\t\tcadence,\n\t\t\t\t\tstartTime,\n\t\t\t\t\tendTime,\n\t\t\t\t\tchurnTime,\n\t\t\t\t\tuserId\n\t\t\t\t);\n\t\t\t\tnewEvents.push(...behaviorEvents);\n\t\t\t}\n\n\t\t\t// Sort all events by time\n\t\t\tnewEvents.sort((a, b) => new Date(a.time) - new Date(b.time));\n\t\t\treturn newEvents;\n\t\t}\n\n\t\treturn record;\n\t}",
76
+ "timestamp": "2026-04-10T01:39:07.299Z",
77
+ "version": "4.0"
78
+ }
@@ -0,0 +1,244 @@
1
+ const SEED = "kurby-retention";
2
+ import dayjs from 'dayjs';
3
+ import utc from 'dayjs/plugin/utc.js';
4
+ dayjs.extend(utc);
5
+ import * as u from "../lib/utils/utils.js";
6
+ import * as v from 'ak-tools';
7
+ const chance = u.initChance(SEED);
8
+
9
+ const num_users = 100;
10
+ const days = 360;
11
+
12
+ /** @typedef {import("../types.js").Dungeon} Config */
13
+
14
+ /**
15
+ * ═══════════════════════════════════════════════════════════════
16
+ * DATASET OVERVIEW
17
+ * ═══════════════════════════════════════════════════════════════
18
+ *
19
+ * Retention Cadence — tests behavior frequency and color-based churn.
20
+ * - 100 users over 360 days, ~5K base events (replaced by cadenced events)
21
+ * - 4 behavior cadences: hourly, daily, weekly, monthly
22
+ * - Churn rates vary by user's "favorite color" (red, blue, green, yellow, purple)
23
+ * - The everything hook REPLACES all generated events with precisely
24
+ * cadenced behavior events, making this a pure retention analysis dataset
25
+ *
26
+ * ═══════════════════════════════════════════════════════════════
27
+ * ANALYTICS HOOKS (1 pattern — everything hook)
28
+ * ═══════════════════════════════════════════════════════════════
29
+ *
30
+ * 1. COLOR-BASED CADENCED CHURN (everything hook)
31
+ * All raw events are replaced with 4 cadenced behavior streams
32
+ * (hourly/daily/weekly/monthly). Each user's "favorite color"
33
+ * determines their churn likelihood per cadence:
34
+ * - red: high daily churn (40%)
35
+ * - blue: high weekly churn (40%)
36
+ * - green: low churn across all cadences (5%)
37
+ * - yellow: high monthly churn (40%)
38
+ * - purple: high hourly churn (40%)
39
+ * Churned behaviors get a "churn - X behavior" event at the churn point.
40
+ *
41
+ * Mixpanel Report:
42
+ * - Retention: Event A: "first behavior", Event B: "daily behavior",
43
+ * breakdown by "favorite color"
44
+ * Expected: red users show ~40% daily churn, green users retain well
45
+ * - Insights: "churn - *" events, breakdown by "favorite color"
46
+ * Expected: each color dominates its respective cadence's churn events
47
+ */
48
+
49
+ // Churn rates by favorite color per behavior type (percentage likelihood)
50
+ const CHURN_RATES = {
51
+ red: { hourly: 15, daily: 40, weekly: 15, monthly: 10 },
52
+ blue: { hourly: 15, daily: 15, weekly: 40, monthly: 10 },
53
+ green: { hourly: 5, daily: 5, weekly: 5, monthly: 5 },
54
+ yellow: { hourly: 15, daily: 15, weekly: 15, monthly: 40 },
55
+ purple: { hourly: 40, daily: 15, weekly: 15, monthly: 10 },
56
+ };
57
+
58
+ // Behavior cadence definitions: [intervalAmount, intervalUnit, jitterMax, jitterUnit]
59
+ const BEHAVIORS = {
60
+ hourly: { interval: 1, unit: 'hour', jitterMax: 10, jitterUnit: 'minute' },
61
+ daily: { interval: 1, unit: 'day', jitterMax: 2, jitterUnit: 'hour' },
62
+ weekly: { interval: 7, unit: 'day', jitterMax: 12, jitterUnit: 'hour' },
63
+ monthly: { interval: 30, unit: 'day', jitterMax: 2, jitterUnit: 'day' },
64
+ };
65
+
66
+ function generateCadencedEvents(behaviorName, cadence, startTime, endTime, churnTime, userId) {
67
+ const events = [];
68
+ let cursor = dayjs(startTime);
69
+ const end = dayjs(endTime);
70
+
71
+ while (cursor.isBefore(end)) {
72
+ if (churnTime && cursor.isAfter(churnTime)) break;
73
+
74
+ const jitter = chance.integer({ min: -cadence.jitterMax, max: cadence.jitterMax });
75
+ const eventTime = cursor.add(jitter, cadence.jitterUnit);
76
+
77
+ if (eventTime.isBefore(end) && eventTime.isAfter(startTime)) {
78
+ events.push({
79
+ event: `${behaviorName} behavior`,
80
+ time: eventTime.toISOString(),
81
+ user_id: userId,
82
+ insert_id: v.uid(12),
83
+ });
84
+ }
85
+
86
+ cursor = cursor.add(cadence.interval, cadence.unit);
87
+ }
88
+
89
+ // Insert churn event if user churns on this behavior
90
+ if (churnTime && dayjs(churnTime).isBefore(end)) {
91
+ events.push({
92
+ event: `churn - ${behaviorName} behavior`,
93
+ time: dayjs(churnTime).toISOString(),
94
+ user_id: userId,
95
+ insert_id: v.uid(12),
96
+ });
97
+ }
98
+
99
+ return events;
100
+ }
101
+
102
+ /** @type {Config} */
103
+ const config = {
104
+ token: "",
105
+ seed: "lets go",
106
+ numDays: days,
107
+ numEvents: num_users * 50,
108
+ numUsers: num_users,
109
+ hasAnonIds: false,
110
+ hasSessionIds: false,
111
+ format: "json",
112
+ alsoInferFunnels: false,
113
+ hasLocation: false,
114
+ hasAndroidDevices: false,
115
+ hasIOSDevices: false,
116
+ hasDesktopDevices: false,
117
+ hasBrowser: false,
118
+ hasCampaigns: false,
119
+ isAnonymous: false,
120
+ hasAdSpend: false,
121
+ hasAvatar: false,
122
+ batchSize: 5_500_000,
123
+ concurrency: 1,
124
+ writeToDisk: false,
125
+ percentUsersBornInDataset: 100,
126
+
127
+ funnels: [],
128
+
129
+ events: [
130
+ {
131
+ event: "first behavior",
132
+ weight: 1,
133
+ isFirstEvent: true,
134
+ properties: {},
135
+ },
136
+ {
137
+ event: "hourly behavior",
138
+ weight: 40,
139
+ properties: {},
140
+ },
141
+ {
142
+ event: "daily behavior",
143
+ weight: 30,
144
+ properties: {},
145
+ },
146
+ {
147
+ event: "weekly behavior",
148
+ weight: 15,
149
+ properties: {},
150
+ },
151
+ {
152
+ event: "monthly behavior",
153
+ weight: 10,
154
+ properties: {},
155
+ },
156
+ {
157
+ event: "placeholder",
158
+ weight: 50,
159
+ properties: {},
160
+ },
161
+ ],
162
+
163
+ superProps: {},
164
+
165
+ userProps: {
166
+ "favorite color": ["red", "blue", "green", "yellow", "purple"],
167
+ },
168
+
169
+ scdProps: {},
170
+ mirrorProps: {},
171
+ groupKeys: [],
172
+ groupProps: {},
173
+ lookupTables: [],
174
+
175
+ hook: function (record, type, meta) {
176
+ if (type === "everything") {
177
+ if (!record.length) return record;
178
+
179
+ const userId = record[0].user_id || record[0].device_id;
180
+ if (!userId) return record;
181
+
182
+ const favoriteColor = meta.profile?.["favorite color"] || "green";
183
+ const colorChurnRates = CHURN_RATES[favoriteColor] || CHURN_RATES.green;
184
+
185
+ // Find the time range from the raw events
186
+ const times = record.map(e => new Date(e.time).getTime()).filter(t => !isNaN(t));
187
+ if (!times.length) return record;
188
+ const startTime = dayjs(Math.min(...times));
189
+ const endTime = dayjs(Math.max(...times));
190
+ const totalSpanHours = endTime.diff(startTime, 'hour');
191
+
192
+ if (totalSpanHours < 1) return record;
193
+
194
+ // Keep the "first behavior" event from the original stream
195
+ const firstBehavior = record.find(e => e.event === "first behavior");
196
+ const newEvents = [];
197
+
198
+ if (firstBehavior) {
199
+ newEvents.push(firstBehavior);
200
+ } else {
201
+ // Create a first behavior event at the start
202
+ newEvents.push({
203
+ event: "first behavior",
204
+ time: startTime.toISOString(),
205
+ user_id: userId,
206
+ insert_id: v.uid(12),
207
+ });
208
+ }
209
+
210
+ // For each behavior type, determine churn and generate cadenced events
211
+ for (const [behaviorKey, cadence] of Object.entries(BEHAVIORS)) {
212
+ const churnRate = colorChurnRates[behaviorKey];
213
+ let churnTime = null;
214
+
215
+ if (chance.bool({ likelihood: churnRate })) {
216
+ // Pick churn time between 20% and 80% into the active period
217
+ const churnOffsetHours = chance.integer({
218
+ min: Math.floor(totalSpanHours * 0.2),
219
+ max: Math.floor(totalSpanHours * 0.8),
220
+ });
221
+ churnTime = startTime.add(churnOffsetHours, 'hour');
222
+ }
223
+
224
+ const behaviorEvents = generateCadencedEvents(
225
+ behaviorKey,
226
+ cadence,
227
+ startTime,
228
+ endTime,
229
+ churnTime,
230
+ userId
231
+ );
232
+ newEvents.push(...behaviorEvents);
233
+ }
234
+
235
+ // Sort all events by time
236
+ newEvents.sort((a, b) => new Date(a.time) - new Date(b.time));
237
+ return newEvents;
238
+ }
239
+
240
+ return record;
241
+ },
242
+ };
243
+
244
+ export default config;