@ak--47/dungeon-master 1.5.0 → 1.5.1

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 (62) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/HOOKS.md +13 -0
  3. package/dungeons/technical/ad-spend.js +41 -49
  4. package/dungeons/technical/anonymous-users.js +38 -36
  5. package/dungeons/technical/array-of-object-lookup.js +136 -153
  6. package/dungeons/technical/datagen-v15-verify.js +24 -11
  7. package/dungeons/technical/experiments.js +42 -40
  8. package/dungeons/technical/foobar.js +114 -118
  9. package/dungeons/technical/group-analytics.js +42 -40
  10. package/dungeons/technical/hook-helpers-verify.js +69 -50
  11. package/dungeons/technical/identity-model-verify.js +22 -12
  12. package/dungeons/technical/mirror-strategies.js +37 -39
  13. package/dungeons/technical/nested-objects.js +119 -118
  14. package/dungeons/technical/pattern-aggregate-by-bin.js +21 -8
  15. package/dungeons/technical/pattern-attributed-by-source.js +23 -9
  16. package/dungeons/technical/pattern-frequency-by-frequency.js +21 -8
  17. package/dungeons/technical/pattern-funnel-frequency.js +30 -15
  18. package/dungeons/technical/pattern-ttc-by-segment.js +21 -8
  19. package/dungeons/technical/retention-cadence.js +115 -112
  20. package/dungeons/technical/sanity.js +86 -80
  21. package/dungeons/technical/scale-test.js +34 -38
  22. package/dungeons/technical/scd.js +111 -128
  23. package/dungeons/technical/simple.js +134 -141
  24. package/dungeons/technical/simplest.js +54 -62
  25. package/dungeons/technical/text-generation.js +110 -146
  26. package/dungeons/vertical/ai-platform.js +296 -333
  27. package/dungeons/vertical/community.js +284 -255
  28. package/dungeons/vertical/crypto.js +395 -391
  29. package/dungeons/vertical/dating.js +411 -378
  30. package/dungeons/vertical/devtools.js +336 -298
  31. package/dungeons/vertical/ecommerce.js +316 -394
  32. package/dungeons/vertical/education.js +369 -325
  33. package/dungeons/vertical/fintech.js +358 -325
  34. package/dungeons/vertical/fitness.js +335 -291
  35. package/dungeons/vertical/food-delivery.js +343 -307
  36. package/dungeons/vertical/gaming.js +480 -444
  37. package/dungeons/vertical/healthcare.js +306 -262
  38. package/dungeons/vertical/insurance-application.js +427 -409
  39. package/dungeons/vertical/logistics.js +271 -252
  40. package/dungeons/vertical/marketplace.js +333 -323
  41. package/dungeons/vertical/media.js +382 -335
  42. package/dungeons/vertical/real-estate.js +395 -346
  43. package/dungeons/vertical/sass.js +319 -333
  44. package/dungeons/vertical/social.js +368 -316
  45. package/dungeons/vertical/travel.js +297 -295
  46. package/index.js +46 -4
  47. package/lib/core/config-validator.js +126 -28
  48. package/lib/generators/funnels.js +4 -1
  49. package/lib/orchestrators/mixpanel-sender.js +7 -0
  50. package/lib/orchestrators/user-loop.js +132 -31
  51. package/lib/templates/defaults.js +59 -59
  52. package/lib/templates/macro-presets.js +14 -2
  53. package/lib/utils/dataset-context.js +103 -0
  54. package/lib/utils/retention-curve.js +140 -0
  55. package/lib/utils/utils.js +149 -38
  56. package/lib/verify/counting.js +40 -0
  57. package/lib/verify/emulate-breakdown.js +20 -1
  58. package/lib/verify/index.js +1 -0
  59. package/lib/verify/schema-validator.js +3 -1
  60. package/package.json +11 -2
  61. package/scripts/run-dungeon.mjs +12 -1
  62. package/types.d.ts +117 -1
@@ -1,5 +1,14 @@
1
- /**
2
- * datagen-v1.5-verify.js Canonical fixture exercising all v1.5 generation primitives.
1
+ // ── IMPORTS ──
2
+ import * as u from "../../lib/utils/utils.js";
3
+ /** @typedef {import("../../types").Dungeon} Config */
4
+
5
+ // ── OVERVIEW ──
6
+ /*
7
+ * NAME: datagen-v1.5-verify
8
+ * PURPOSE: Canonical v1.5 fixture — active-days, conversion window, touchpoint cap, auto-sort, determinism
9
+ * SCALE: 500 users, ~60K events, 30 days
10
+ * EVENTS (4): page view, click, sign up, purchase
11
+ * FUNNELS (1): page view → sign up → purchase (50%, 14d window)
3
12
  *
4
13
  * Used by:
5
14
  * - tests/active-days.test.js → asserts distinct-day distribution shape
@@ -8,21 +17,21 @@
8
17
  * - tests/datagen-determinism.test.js → byte-equal verification across runs
9
18
  * - tests/auto-sort.test.js → relies on sorted output
10
19
  *
11
- * Realistic small-scale config: 500 users × 30 days × 4 events/day. Includes
12
- * `hasCampaigns: true` + an explicit `isAttributionEvent` flagged event so the
13
- * touchpoint cap has eligible candidates beyond the default ~25% legacy fallback.
20
+ * Realistic small-scale config with `hasCampaigns: true` + an explicit
21
+ * `isAttributionEvent` flagged event so the touchpoint cap has eligible
22
+ * candidates beyond the default ~25% legacy fallback.
14
23
  *
15
24
  * **DO NOT enable `engagementDecay`** — it interacts with `avgActiveDaysPerUser`
16
25
  * by dropping events on late picked days, eroding the effective active-day count
17
26
  * below the configured target. See HOOKS.md §2.5.
18
27
  */
19
28
 
20
- import * as u from "../../lib/utils/utils.js";
21
-
29
+ // ── SCALE ──
22
30
  const SEED = "datagen-v1.5-verify";
23
31
  u.initChance(SEED);
24
32
 
25
- /** @type {import('../../types').Dungeon} */
33
+ // ── CONFIG ──
34
+ /** @type {Config} */
26
35
  const config = {
27
36
  seed: SEED,
28
37
  // Pin the dataset window for full determinism (independent of run date).
@@ -34,9 +43,13 @@ const config = {
34
43
  avgActiveDaysPerUser: 6,
35
44
  // v1.5 attribution cap — explicit (default is also 10).
36
45
  maxTouchpointsPerUser: 10,
37
- hasCampaigns: true,
38
- hasSessionIds: true,
39
- avgDevicePerUser: 1,
46
+ switches: {
47
+ hasCampaigns: true,
48
+ hasSessionIds: true,
49
+ },
50
+ identity: {
51
+ avgDevicePerUser: 1,
52
+ },
40
53
  autoSortAfterEverything: true,
41
54
  events: [
42
55
  { event: "page view", weight: 5, isAttributionEvent: true },
@@ -1,50 +1,52 @@
1
- // ── TWEAK THESE ──
2
- const SEED = "experiments";
3
- const num_days = 60;
4
- const num_users = 2_000;
5
- const avg_events_per_user_per_day = 0.83;
6
- let token = "";
7
-
8
- // ── env overrides ──
9
- if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
1
+ // ── IMPORTS ──
2
+ import Chance from "chance";
3
+ import { weighNumRange, weighChoices } from "../../lib/utils/utils.js";
4
+ /** @typedef {import("../../types").Dungeon} Config */
10
5
 
11
- /**
12
- * Experiments — tests funnel experiments with A/B/C variants.
13
- *
14
- * Exercises: experiment: true for variant generation, bindPropsIndex
15
- * for property binding, and multiple funnel ordering modes
16
- * (sequential, random, first-and-last-fixed).
17
- *
18
- * - 2000 users, 100K events, 60 days
19
- * - 7 events, 4 funnels with different experiment/order configs
20
- * - No hooks
6
+ // ── OVERVIEW ──
7
+ /*
8
+ * NAME: experiments
9
+ * PURPOSE: Exercises funnel experiments (A/B/C variants), bindPropsIndex,
10
+ * and multiple funnel ordering modes (sequential, first-and-last-fixed, random).
11
+ * SCALE: 2,000 users, ~100K events, 60 days
12
+ * EVENTS (7): page view, signup, feature viewed, action taken, checkout, button click, help viewed
13
+ * FUNNELS (4): Onboarding Flow (sequential, experiment), Purchase Funnel (first-and-last-fixed, experiment),
14
+ * Feature Adoption (random, experiment, bindPropsIndex), Quick Signup (sequential, control)
21
15
  */
22
16
 
23
- import Chance from 'chance';
24
- let chance = new Chance();
25
- import { weighNumRange, weighChoices } from "../../lib/utils/utils.js";
17
+ // ── SCALE ──
18
+ const SEED = "experiments";
19
+ const NUM_DAYS = 60;
20
+ const NUM_USERS = 2_000;
21
+ const EVENTS_PER_DAY = 0.83;
22
+ const token = process.env.MP_TOKEN || "";
26
23
 
27
- /** @typedef {import("../../types").Dungeon} Config */
28
- /** @type {import('../../types').Dungeon} */
24
+ const chance = new Chance();
25
+
26
+ // ── CONFIG ──
27
+ /** @type {Config} */
29
28
  const config = {
30
- token,
31
29
  seed: SEED,
32
- numDays: num_days,
33
- avgEventsPerUserPerDay: avg_events_per_user_per_day,
34
- numUsers: num_users,
30
+ numDays: NUM_DAYS,
31
+ avgEventsPerUserPerDay: EVENTS_PER_DAY,
32
+ numUsers: NUM_USERS,
35
33
  format: "json",
36
- region: "US",
37
- hasAnonIds: false,
38
- hasSessionIds: true,
39
- hasAdSpend: false,
40
- hasLocation: false,
41
- hasAndroidDevices: true,
42
- hasIOSDevices: true,
43
- hasDesktopDevices: true,
44
- hasBrowser: true,
45
- hasCampaigns: false,
46
- isAnonymous: false,
47
- alsoInferFunnels: false,
34
+ credentials: {
35
+ token,
36
+ region: "US",
37
+ },
38
+ switches: {
39
+ hasSessionIds: true,
40
+ hasAdSpend: false,
41
+ hasLocation: false,
42
+ hasAndroidDevices: true,
43
+ hasIOSDevices: true,
44
+ hasDesktopDevices: true,
45
+ hasBrowser: true,
46
+ hasCampaigns: false,
47
+ isAnonymous: false,
48
+ alsoInferFunnels: false,
49
+ },
48
50
  concurrency: 1,
49
51
  writeToDisk: false,
50
52
 
@@ -1,63 +1,39 @@
1
- // ── TWEAK THESE ──
2
- const SEED = "dm4-foobar";
3
- const num_days = 30;
4
- const num_users = 4_000_000;
5
- const avg_events_per_user_per_day = 16.67;
6
- let token = "";
1
+ // ── IMPORTS ──
2
+ /** @typedef {import("../../types").Dungeon} Config */
7
3
 
8
- // ── env overrides ──
9
- if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
4
+ // ── OVERVIEW ──
5
+ /*
6
+ * NAME: foobar
7
+ * PURPOSE: minimal stress/sanity dungeon — abstract event names, weight-driven distribution, no funnels, no event properties
8
+ * SCALE: 4,000,000 users, ~2B events, 30 days (configurable for stress testing)
9
+ * EVENTS (10): foo, bar, baz, qux, garply, durtle, linny, fonk, crumn, yak
10
+ * FUNNELS (0): none
11
+ */
10
12
 
11
- /**
12
- * ═══════════════════════════════════════════════════════════════
13
- * DATASET OVERVIEW
14
- * ═══════════════════════════════════════════════════════════════
15
- *
16
- * Foobar — minimal test/sanity dungeon with abstract event names.
17
- * - Configurable scale (default 2 billion events for stress testing)
18
- * - 10 events: foo, bar, baz, qux, garply, durtle, linny, fonk, crumn, yak
19
- * - Super props: string, number, boolean, date
20
- * - No funnels, no properties on events — pure weight-driven distribution
21
- *
22
- * ═══════════════════════════════════════════════════════════════
23
- * ANALYTICS HOOKS (2 patterns)
24
- * ═══════════════════════════════════════════════════════════════
25
- *
13
+ // ── HOOK STORIES ──
14
+ /*
26
15
  * 1. TEMPERATURE TAGGING (event hook)
27
16
  * foo/bar/baz = "hot", crumn/yak = "cold", everything else = "warm".
28
- *
29
- * Mixpanel Report:
30
- * - Insights: any event, total events, breakdown by temperature
31
- * Expected: "hot" dominates (highest-weight events)
17
+ * Mixpanel report: Insights, any event, total events, breakdown by temperature.
18
+ * Expected: "hot" dominates (highest-weight events).
32
19
  *
33
20
  * 2. HASH-BASED POWER USERS (everything hook)
34
- * ~10% of users (by distinct_id hash) get 3 extra duplicate events.
35
- *
36
- * Mixpanel Report:
37
- * - Insights: any event, total per user, breakdown by is_duplicate
38
- * Expected: ~10% of users have extra events tagged is_duplicate=true
21
+ * ~10% of users (by distinct_id charCode mod 10) get 3 extra duplicate events
22
+ * tagged is_duplicate=true.
23
+ * Mixpanel report: Insights, any event, total per user, breakdown by is_duplicate.
24
+ * Expected: ~10% of users have extra events tagged is_duplicate=true.
39
25
  */
40
26
 
27
+ // ── SCALE ──
28
+ const SEED = "dm4-foobar";
29
+ const NUM_DAYS = 30;
30
+ const NUM_USERS = 4_000_000;
31
+ const EVENTS_PER_DAY = 16.67;
32
+ const token = process.env.MP_TOKEN || "";
41
33
 
42
-
43
-
44
- function integer(min = 1, max = 100) {
45
- // If min equals max, just return the value
46
- if (min === max) {
47
- return min;
48
- }
49
-
50
- // Swap min and max if min is greater than max
51
- if (min > max) {
52
- [min, max] = [max, min];
53
- }
54
-
55
- // Generate a random integer between min and max (inclusive)
56
- return Math.floor(Math.random() * (max - min + 1)) + min;
57
- }
58
-
59
- /** @typedef {import("../../types").Dungeon} Config */
60
-
34
+ // ── DATA ARRAYS ──
35
+ const colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
36
+ const spiritAnimals = ["duck", "dog", "otter", "penguin", "cat", "elephant", "lion", "cheetah", "giraffe", "zebra", "rhino", "hippo", "whale", "dolphin", "shark", "octopus", "squid", "jellyfish", "starfish", "seahorse", "crab", "lobster", "shrimp", "clam", "snail", "slug", "butterfly", "moth", "bee", "wasp", "ant", "beetle", "ladybug", "caterpillar", "centipede", "millipede", "scorpion", "spider", "tarantula", "tick", "mite", "mosquito", "fly", "dragonfly", "damselfly", "grasshopper", "cricket", "locust", "mantis", "cockroach", "termite", "praying mantis", "walking stick", "stick bug", "leaf insect", "lacewing", "aphid", "cicada", "thrips", "psyllid", "scale insect", "whitefly", "mealybug", "planthopper", "leafhopper", "treehopper", "flea", "louse", "bedbug", "flea beetle", "weevil", "longhorn beetle", "leaf beetle", "tiger beetle", "ground beetle", "lady beetle", "firefly", "click beetle", "rove beetle", "scarab beetle", "dung beetle", "stag beetle", "rhinoceros beetle", "hercules beetle", "goliath beetle", "jewel beetle", "tortoise beetle"];
61
37
  const dates = [
62
38
  "2024-09-18T21:48:28.173Z",
63
39
  "2024-12-09T03:17:46.661Z",
@@ -161,26 +137,88 @@ const dates = [
161
137
  "2024-06-03T06:52:21.652Z"
162
138
  ];
163
139
 
164
- /** @type {import('../../types').Dungeon} */
140
+ // ── HELPER FUNCTIONS ──
141
+ function integer(min = 1, max = 100) {
142
+ // If min equals max, just return the value
143
+ if (min === max) {
144
+ return min;
145
+ }
146
+
147
+ // Swap min and max if min is greater than max
148
+ if (min > max) {
149
+ [min, max] = [max, min];
150
+ }
151
+
152
+ // Generate a random integer between min and max (inclusive)
153
+ return Math.floor(Math.random() * (max - min + 1)) + min;
154
+ }
155
+
156
+ function handleEventHook(record) {
157
+ // H1: temperature tagging — high-weight events "hot", low-weight "cold", rest "warm"
158
+ const hotEvents = ["foo", "bar", "baz"];
159
+ const coldEvents = ["crumn", "yak"];
160
+ if (hotEvents.includes(record.event)) {
161
+ record.temperature = "hot";
162
+ } else if (coldEvents.includes(record.event)) {
163
+ record.temperature = "cold";
164
+ } else {
165
+ record.temperature = "warm";
166
+ }
167
+ return record;
168
+ }
169
+
170
+ function handleEverythingHook(record, meta) {
171
+ // stamp profile-derived superProps for consistency
172
+ const profile = meta.profile;
173
+ record.forEach(e => {
174
+ e.string = profile.string;
175
+ e.number = profile.number;
176
+ e.boolean = profile.boolean;
177
+ e.date = profile.date;
178
+ e.temperature = profile.temperature;
179
+ });
180
+
181
+ // H2: hash-based power users — ~10% of users (distinct_id charCode mod 10) get 3 extra duplicate events
182
+ if (record.length > 0) {
183
+ const userId = record[0].user_id || record[0].distinct_id || "";
184
+ if (userId && userId.charCodeAt(0) % 10 === 0) {
185
+ const extras = record.slice(0, 3).map(e => ({
186
+ ...e,
187
+ event: e.event,
188
+ user_id: e.user_id,
189
+ time: e.time,
190
+ is_duplicate: true,
191
+ }));
192
+ return record.concat(extras);
193
+ }
194
+ }
195
+ return record;
196
+ }
197
+
198
+ // ── CONFIG ──
199
+ /** @type {Config} */
165
200
  const config = {
166
- token,
167
201
  seed: SEED,
168
- numDays: num_days,
169
- avgEventsPerUserPerDay: avg_events_per_user_per_day,
170
- numUsers: num_users,
202
+ numDays: NUM_DAYS,
203
+ avgEventsPerUserPerDay: EVENTS_PER_DAY,
204
+ numUsers: NUM_USERS,
171
205
  format: 'json', //csv or json
172
- region: "US",
173
- hasAnonIds: false, //if true, anonymousIds are created for each user
174
- hasSessionIds: false, //if true, hasSessionIds are created for each user
175
- hasAdSpend: false,
176
- hasAvatar: false,
177
- hasBrowser: false,
178
- hasCampaigns: false,
179
- hasIOSDevices: false,
180
- hasLocation: false,
181
- isAnonymous: true,
182
- hasAndroidDevices: false,
183
- hasDesktopDevices: false,
206
+ credentials: {
207
+ token,
208
+ region: "US",
209
+ },
210
+ switches: {
211
+ hasSessionIds: false, //if true, hasSessionIds are created for each user
212
+ hasAdSpend: false,
213
+ hasAvatar: false,
214
+ hasBrowser: false,
215
+ hasCampaigns: false,
216
+ hasIOSDevices: false,
217
+ hasLocation: false,
218
+ isAnonymous: true,
219
+ hasAndroidDevices: false,
220
+ hasDesktopDevices: false,
221
+ },
184
222
  writeToDisk: false,
185
223
  concurrency: 1,
186
224
 
@@ -237,7 +275,7 @@ const config = {
237
275
  }
238
276
  ],
239
277
  superProps: {
240
- string: ["red", "orange", "yellow", "green", "blue", "indigo", "violet"],
278
+ string: colors,
241
279
  number: integer,
242
280
  boolean: [true, false],
243
281
  date: dates,
@@ -245,9 +283,9 @@ const config = {
245
283
  },
246
284
  userProps: {
247
285
  luckyNumber: integer,
248
- spiritAnimal: ["duck", "dog", "otter", "penguin", "cat", "elephant", "lion", "cheetah", "giraffe", "zebra", "rhino", "hippo", "whale", "dolphin", "shark", "octopus", "squid", "jellyfish", "starfish", "seahorse", "crab", "lobster", "shrimp", "clam", "snail", "slug", "butterfly", "moth", "bee", "wasp", "ant", "beetle", "ladybug", "caterpillar", "centipede", "millipede", "scorpion", "spider", "tarantula", "tick", "mite", "mosquito", "fly", "dragonfly", "damselfly", "grasshopper", "cricket", "locust", "mantis", "cockroach", "termite", "praying mantis", "walking stick", "stick bug", "leaf insect", "lacewing", "aphid", "cicada", "thrips", "psyllid", "scale insect", "whitefly", "mealybug", "planthopper", "leafhopper", "treehopper", "flea", "louse", "bedbug", "flea beetle", "weevil", "longhorn beetle", "leaf beetle", "tiger beetle", "ground beetle", "lady beetle", "firefly", "click beetle", "rove beetle", "scarab beetle", "dung beetle", "stag beetle", "rhinoceros beetle", "hercules beetle", "goliath beetle", "jewel beetle", "tortoise beetle"],
286
+ spiritAnimal: spiritAnimals,
249
287
  created: dates,
250
- string: ["red", "orange", "yellow", "green", "blue", "indigo", "violet"],
288
+ string: colors,
251
289
  number: integer,
252
290
  boolean: [true, false],
253
291
  date: dates,
@@ -257,56 +295,14 @@ const config = {
257
295
  scdProps: {},
258
296
  mirrorProps: {},
259
297
  lookupTables: [],
260
- groupKeys: [
261
- ],
262
- groupProps: {
263
- },
298
+ groupKeys: [],
299
+ groupProps: {},
264
300
 
265
301
  hook: function (record, type, meta) {
266
- // event hook: high-weight events get a "hot" flag, low-weight get "cold"
267
- if (type === "event") {
268
- const hotEvents = ["foo", "bar", "baz"];
269
- const coldEvents = ["crumn", "yak"];
270
- if (hotEvents.includes(record.event)) {
271
- record.temperature = "hot";
272
- } else if (coldEvents.includes(record.event)) {
273
- record.temperature = "cold";
274
- } else {
275
- record.temperature = "warm";
276
- }
277
- }
278
-
279
- // everything hook: hash-based cohort — 10% of users (by distinct_id) get doubled events
280
- if (type === "everything") {
281
- const profile = meta.profile;
282
- record.forEach(e => {
283
- e.string = profile.string;
284
- e.number = profile.number;
285
- e.boolean = profile.boolean;
286
- e.date = profile.date;
287
- e.temperature = profile.temperature;
288
- });
289
- if (record.length > 0) {
290
- const userId = record[0].user_id || record[0].distinct_id || "";
291
- if (userId && userId.charCodeAt(0) % 10 === 0) {
292
- // power user: duplicate each event with a slight time offset
293
- const extras = record.slice(0, 3).map(e => ({
294
- ...e,
295
- event: e.event,
296
- user_id: e.user_id,
297
- time: e.time,
298
- is_duplicate: true,
299
- }));
300
- return record.concat(extras);
301
- }
302
- }
303
- return record;
304
- }
305
-
302
+ if (type === "event") return handleEventHook(record);
303
+ if (type === "everything") return handleEverythingHook(record, meta);
306
304
  return record;
307
305
  }
308
306
  };
309
307
 
310
-
311
-
312
- export default config;
308
+ export default config;
@@ -1,50 +1,52 @@
1
- // ── TWEAK THESE ──
2
- const SEED = "group-analytics";
3
- const num_days = 60;
4
- const num_users = 500;
5
- const avg_events_per_user_per_day = 1;
6
- let token = "";
7
-
8
- // ── env overrides ──
9
- if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
1
+ // ── IMPORTS ──
2
+ import Chance from 'chance';
3
+ import { weighNumRange, weighChoices } from "../../lib/utils/utils.js";
4
+ /** @typedef {import("../../types").Dungeon} Config */
10
5
 
11
- /**
12
- * Group Analytics — tests multiple group keys with varied entity counts.
13
- *
14
- * Exercises: 3 group keys (company, team, project) at different scales,
15
- * each with distinct event associations and group-level properties.
16
- *
17
- * - 500 users, 30K events, 60 days
18
- * - 3 group keys: company_id (50), team_id (200), project_id (500)
19
- * - groupProps with varied property types per key
20
- * - No hooks
6
+ // ── OVERVIEW ──
7
+ /*
8
+ * NAME: group-analytics
9
+ * PURPOSE: exercises 3 groupKeys (company/team/project) at varied scales with per-key groupProps
10
+ * SCALE: 500 users, ~30K events, 60 days
11
+ * EVENTS (8): feature used, subscription change, support ticket, task completed,
12
+ * meeting scheduled, code committed, deployment run, login
13
+ * FUNNELS (0): none
14
+ * GROUPS: company_id (50), team_id (200), project_id (500)
21
15
  */
22
16
 
23
- import Chance from 'chance';
24
- let chance = new Chance();
25
- import { weighNumRange, weighChoices } from "../../lib/utils/utils.js";
17
+ // ── SCALE ──
18
+ const SEED = "group-analytics";
19
+ const NUM_DAYS = 60;
20
+ const NUM_USERS = 500;
21
+ const EVENTS_PER_DAY = 1;
22
+ const token = process.env.MP_TOKEN || "";
26
23
 
27
- /** @typedef {import("../../types").Dungeon} Config */
28
- /** @type {import('../../types').Dungeon} */
24
+ const chance = new Chance();
25
+
26
+ // ── CONFIG ──
27
+ /** @type {Config} */
29
28
  const config = {
30
- token,
31
29
  seed: SEED,
32
- numDays: num_days,
33
- avgEventsPerUserPerDay: avg_events_per_user_per_day,
34
- numUsers: num_users,
30
+ numDays: NUM_DAYS,
31
+ avgEventsPerUserPerDay: EVENTS_PER_DAY,
32
+ numUsers: NUM_USERS,
35
33
  format: "json",
36
- region: "US",
37
- hasAnonIds: false,
38
- hasSessionIds: false,
39
- hasAdSpend: false,
40
- hasLocation: false,
41
- hasAndroidDevices: false,
42
- hasIOSDevices: false,
43
- hasDesktopDevices: true,
44
- hasBrowser: true,
45
- hasCampaigns: false,
46
- isAnonymous: false,
47
- alsoInferFunnels: false,
34
+ credentials: {
35
+ token,
36
+ region: "US",
37
+ },
38
+ switches: {
39
+ hasSessionIds: false,
40
+ hasAdSpend: false,
41
+ hasLocation: false,
42
+ hasAndroidDevices: false,
43
+ hasIOSDevices: false,
44
+ hasDesktopDevices: true,
45
+ hasBrowser: true,
46
+ hasCampaigns: false,
47
+ isAnonymous: false,
48
+ alsoInferFunnels: false,
49
+ },
48
50
  concurrency: 1,
49
51
  writeToDisk: false,
50
52