@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,686 @@
1
+ import dayjs from "dayjs";
2
+ import utc from "dayjs/plugin/utc.js";
3
+ import "dotenv/config";
4
+ import * as u from "../lib/utils/utils.js";
5
+ import * as v from "ak-tools";
6
+
7
+ const SEED = "harness-social";
8
+ dayjs.extend(utc);
9
+ const chance = u.initChance(SEED);
10
+ const num_users = 5_000;
11
+ const days = 100;
12
+
13
+ /** @typedef {import("../types.js").Dungeon} Config */
14
+
15
+ /*
16
+ * =====================================================================================
17
+ * DATASET OVERVIEW
18
+ * =====================================================================================
19
+ *
20
+ * Chirp — A Twitter+Instagram-style social media platform with algorithmic feed,
21
+ * creator monetization, communities, and direct messaging.
22
+ *
23
+ * CORE LOOP:
24
+ * Users sign up, build a profile, follow people, consume content in their feed,
25
+ * create their own posts/stories, and engage via likes, shares, and comments.
26
+ * Power users become "creators" with subscriber tiers. Monetization through
27
+ * native ads woven into feed and story placements.
28
+ *
29
+ * - 5,000 users over 100 days
30
+ * - 600,000 base events across 18 event types
31
+ * - 8 funnels (onboarding, engagement, discovery, creator journey, ads)
32
+ * - Group analytics (100 communities)
33
+ * - Account types: personal, creator, business
34
+ * =====================================================================================
35
+ */
36
+
37
+ /*
38
+ * =====================================================================================
39
+ * ANALYTICS HOOKS
40
+ * =====================================================================================
41
+ *
42
+ * 8 deliberately architected patterns hidden in the data:
43
+ *
44
+ * -------------------------------------------------------------------------------------
45
+ * 1. VIRAL CONTENT CASCADE (everything hook)
46
+ * -------------------------------------------------------------------------------------
47
+ * 5% of users with 10+ posts become "viral creators." Each of their posts generates
48
+ * 10-20 extra post viewed, post liked, and post shared events (viral_cascade: true).
49
+ *
50
+ * Mixpanel Steps:
51
+ * - Insights > "post viewed" > Total events > Breakdown: "viral_cascade"
52
+ * - Insights > "post liked" > Total events per user > Breakdown: "viral_cascade"
53
+ *
54
+ * -------------------------------------------------------------------------------------
55
+ * 2. FOLLOW-BACK SNOWBALL (everything hook)
56
+ * -------------------------------------------------------------------------------------
57
+ * Users with 5+ "user followed" events become prolific creators. 50% of their posts
58
+ * get duplicated (follow_back_effect: true), plus extra comments are injected.
59
+ *
60
+ * Mixpanel Steps:
61
+ * - Insights > "post created" > Total per user > Breakdown: "follow_back_effect"
62
+ * - Insights > "post created" > Total per user > Cohort: users with 5+ follows
63
+ *
64
+ * -------------------------------------------------------------------------------------
65
+ * 3. ALGORITHM CHANGE (event hook)
66
+ * -------------------------------------------------------------------------------------
67
+ * Day 45: content discovery flips from feed to explore. Before day 45, 70% of
68
+ * post viewed source = "feed." After, 70% shift to source = "explore."
69
+ *
70
+ * Mixpanel Steps:
71
+ * - Insights (line) > "post viewed" > Total > Breakdown: "source" > Daily trend
72
+ * - Compare date ranges before/after day 45: feed vs explore ratio inverts
73
+ *
74
+ * -------------------------------------------------------------------------------------
75
+ * 4. ENGAGEMENT BAIT (event hook)
76
+ * -------------------------------------------------------------------------------------
77
+ * 20% of post viewed events are engagement_bait: true with view durations of 1-5 sec.
78
+ * High impressions but terrible engagement quality.
79
+ *
80
+ * Mixpanel Steps:
81
+ * - Insights > "post viewed" > Avg "view_duration_sec" > Breakdown: "engagement_bait"
82
+ * - Expected: bait ~2-3 sec avg vs normal ~15-30 sec avg
83
+ *
84
+ * -------------------------------------------------------------------------------------
85
+ * 5. NOTIFICATION RE-ENGAGEMENT (event hook)
86
+ * -------------------------------------------------------------------------------------
87
+ * After day 30, 30% of post viewed events get source overridden to "notification"
88
+ * with trending_reengagement: true.
89
+ *
90
+ * Mixpanel Steps:
91
+ * - Insights (line) > "post viewed" > Total > Filter: source = "notification" > Daily
92
+ * - Near-zero before day 30, then ~30% of views from notifications
93
+ *
94
+ * -------------------------------------------------------------------------------------
95
+ * 6. CREATOR MONETIZATION (everything hook)
96
+ * -------------------------------------------------------------------------------------
97
+ * Users with any "creator subscription started" event post 3x more. Two extra posts
98
+ * injected per original (monetized_creator: true). Also extra profile-source views.
99
+ *
100
+ * Mixpanel Steps:
101
+ * - Insights > "post created" > Total per user > Breakdown: "monetized_creator"
102
+ * - Insights > "post viewed" > Filter: source = "profile" > Breakdown: "monetized_creator"
103
+ *
104
+ * -------------------------------------------------------------------------------------
105
+ * 7. TOXICITY CHURN (everything hook)
106
+ * -------------------------------------------------------------------------------------
107
+ * Users with 3+ reports lose 60% of events after day 30. Remaining events tagged
108
+ * toxic_user: true. Simulates churn from toxic content exposure.
109
+ *
110
+ * Mixpanel Steps:
111
+ * - Retention > Segment: users with 3+ "report submitted" vs fewer
112
+ * - Insights (line) > Any event > Total per user > Breakdown: "toxic_user" > Weekly
113
+ *
114
+ * -------------------------------------------------------------------------------------
115
+ * 8. WEEKEND CONTENT SURGE (event + everything hook)
116
+ * -------------------------------------------------------------------------------------
117
+ * Saturday/Sunday post/story events tagged weekend_surge: true. 30% get a duplicate
118
+ * event 1-3 hours later (weekend_duplicate: true).
119
+ *
120
+ * Mixpanel Steps:
121
+ * - Insights (bar) > "post created" > Total > Breakdown: Day of Week
122
+ * - Expected: Sat/Sun bars ~30% taller than weekday bars
123
+ *
124
+ * =====================================================================================
125
+ * EXPECTED METRICS SUMMARY
126
+ * =====================================================================================
127
+ *
128
+ * Hook | Metric | Baseline | Hook Effect | Ratio
129
+ * --------------------------|-------------------------|--------------|----------------|-------
130
+ * Viral Content Cascade | Engagement per post | 1-2x | 10-20x | ~15x
131
+ * Follow-Back Snowball | Posts per user | ~4 | ~8 | 2x
132
+ * Algorithm Change | Feed vs. Explore source | 70/15 | 15/70 | Flip
133
+ * Engagement Bait | View duration (sec) | 15-30 | 1-5 | ~0.2x
134
+ * Notification Re-engage | Notification source % | ~10% | ~30% | 3x
135
+ * Creator Monetization | Content creation freq | 1x | 3x | 3x
136
+ * Toxicity Churn | Post-day-30 retention | ~80% | ~40% | 0.5x
137
+ * Weekend Content Surge | Weekend vs. weekday vol | 1x | 1.3x | 1.3x
138
+ *
139
+ * =====================================================================================
140
+ * ADVANCED ANALYSIS IDEAS
141
+ * =====================================================================================
142
+ *
143
+ * - Viral Creators + Algorithm Change: Do viral creators benefit more from the
144
+ * explore-based algorithm? Compare viral cascade engagement before/after day 45.
145
+ * - Follow-Back Snowball + Creator Monetization: Users with both effects compound
146
+ * to ~6x content output (2x from follows * 3x from monetization).
147
+ * - Engagement Bait + Toxicity Churn: Correlation between engagement_bait exposure
148
+ * and toxic_user tagging / report submission rates.
149
+ * - Weekend Surge + Viral Cascade: Compounding creates extreme engagement spikes
150
+ * on weekend days.
151
+ * - Notification Re-engagement + Toxicity Churn: Do trending notifications help
152
+ * retain toxic_user-tagged users, or do they still churn?
153
+ * - Cohort by signup method, content niche, account type, community membership,
154
+ * or join week (users joining during algorithm change see a different product).
155
+ * - Funnel analysis: onboarding by signup method, engagement by source, creator
156
+ * journey before/after algorithm change.
157
+ * =====================================================================================
158
+ */
159
+
160
+ // Generate consistent post IDs for lookup tables
161
+ const postIds = v.range(1, 1001).map(n => `post_${v.uid(8)}`);
162
+
163
+ /** @type {Config} */
164
+ const config = {
165
+ token: "",
166
+ seed: SEED,
167
+ numDays: days,
168
+ numEvents: num_users * 120,
169
+ numUsers: num_users,
170
+ hasAnonIds: false,
171
+ hasSessionIds: true,
172
+ format: "json",
173
+ gzip: true,
174
+ alsoInferFunnels: false,
175
+ hasLocation: true,
176
+ hasAndroidDevices: true,
177
+ hasIOSDevices: true,
178
+ hasDesktopDevices: true,
179
+ hasBrowser: false,
180
+ hasCampaigns: false,
181
+ isAnonymous: false,
182
+ hasAdSpend: false,
183
+ percentUsersBornInDataset: 50,
184
+ hasAvatar: true,
185
+ batchSize: 2_500_000,
186
+ concurrency: 1,
187
+ writeToDisk: false,
188
+ scdProps: {},
189
+
190
+ funnels: [
191
+ {
192
+ sequence: ["account created", "profile updated", "post created"],
193
+ isFirstFunnel: true,
194
+ conversionRate: 70,
195
+ timeToConvert: 0.5,
196
+ },
197
+ {
198
+ // Feed consumption: view → like → comment (most common loop)
199
+ sequence: ["post viewed", "post liked", "comment posted"],
200
+ conversionRate: 45,
201
+ timeToConvert: 0.5,
202
+ weight: 6,
203
+ },
204
+ {
205
+ // Content creation cycle: create → views → engagement
206
+ sequence: ["post created", "post viewed", "post liked", "post shared"],
207
+ conversionRate: 30,
208
+ timeToConvert: 3,
209
+ weight: 3,
210
+ },
211
+ {
212
+ // Stories engagement
213
+ sequence: ["story created", "story viewed", "dm sent"],
214
+ conversionRate: 40,
215
+ timeToConvert: 1,
216
+ weight: 3,
217
+ },
218
+ {
219
+ // Discovery and follow loop
220
+ sequence: ["search performed", "post viewed", "user followed"],
221
+ conversionRate: 35,
222
+ timeToConvert: 1,
223
+ weight: 2,
224
+ },
225
+ {
226
+ // Notifications driving re-engagement
227
+ sequence: ["notification received", "post viewed", "post liked"],
228
+ conversionRate: 50,
229
+ timeToConvert: 0.5,
230
+ weight: 2,
231
+ },
232
+ {
233
+ // Profile management and creator monetization
234
+ sequence: ["profile updated", "creator subscription started", "post created"],
235
+ conversionRate: 15,
236
+ timeToConvert: 24,
237
+ weight: 1,
238
+ },
239
+ {
240
+ // Ad interaction and moderation
241
+ sequence: ["ad viewed", "ad clicked", "report submitted"],
242
+ conversionRate: 20,
243
+ timeToConvert: 2,
244
+ weight: 1,
245
+ },
246
+ ],
247
+
248
+ events: [
249
+ {
250
+ event: "account created",
251
+ weight: 1,
252
+ isFirstEvent: true,
253
+ properties: {
254
+ "signup_method": ["email", "google", "apple", "sso"],
255
+ "referred_by": ["organic", "friend", "ad", "influencer"],
256
+ }
257
+ },
258
+ {
259
+ event: "post created",
260
+ weight: 12,
261
+ properties: {
262
+ "post_type": ["text", "image", "video", "poll", "link"],
263
+ "character_count": u.weighNumRange(1, 280),
264
+ "has_media": u.pickAWinner([true, false], 0.4),
265
+ "hashtag_count": u.weighNumRange(0, 10, 0.5),
266
+ }
267
+ },
268
+ {
269
+ event: "post viewed",
270
+ weight: 30,
271
+ properties: {
272
+ "post_type": ["text", "image", "video", "poll", "link"],
273
+ "view_duration_sec": u.weighNumRange(1, 120, 0.3, 5),
274
+ "source": ["feed", "explore", "search", "profile", "notification"],
275
+ }
276
+ },
277
+ {
278
+ event: "post liked",
279
+ weight: 18,
280
+ properties: {
281
+ "post_type": ["text", "image", "video", "poll", "link"],
282
+ }
283
+ },
284
+ {
285
+ event: "post shared",
286
+ weight: 6,
287
+ properties: {
288
+ "share_destination": ["repost", "dm", "external", "copy_link"],
289
+ }
290
+ },
291
+ {
292
+ event: "comment posted",
293
+ weight: 10,
294
+ properties: {
295
+ "comment_length": u.weighNumRange(1, 500, 0.3, 20),
296
+ "has_mention": u.pickAWinner([true, false, false]),
297
+ }
298
+ },
299
+ {
300
+ event: "user followed",
301
+ weight: 8,
302
+ properties: {
303
+ "discovery_source": ["suggested", "search", "post", "profile", "mutual"],
304
+ }
305
+ },
306
+ {
307
+ event: "user unfollowed",
308
+ weight: 2,
309
+ properties: {
310
+ "reason": ["content_quality", "too_frequent", "lost_interest", "offensive"],
311
+ }
312
+ },
313
+ {
314
+ event: "story viewed",
315
+ weight: 15,
316
+ properties: {
317
+ "story_type": ["photo", "video", "text"],
318
+ "view_duration_sec": u.weighNumRange(1, 30, 0.5, 5),
319
+ "completed": u.pickAWinner([true, false], 0.6),
320
+ }
321
+ },
322
+ {
323
+ event: "story created",
324
+ weight: 5,
325
+ properties: {
326
+ "story_type": ["photo", "video", "text"],
327
+ "has_filter": u.pickAWinner([true, false], 0.5),
328
+ "has_sticker": u.pickAWinner([true, false], 0.3),
329
+ }
330
+ },
331
+ {
332
+ event: "search performed",
333
+ weight: 7,
334
+ properties: {
335
+ "search_type": ["users", "hashtags", "posts"],
336
+ "results_count": u.weighNumRange(0, 50, 0.5, 10),
337
+ }
338
+ },
339
+ {
340
+ event: "notification received",
341
+ weight: 12,
342
+ properties: {
343
+ "notification_type": ["like", "follow", "comment", "mention", "trending"],
344
+ "clicked": u.pickAWinner([true, false], 0.4),
345
+ }
346
+ },
347
+ {
348
+ event: "dm sent",
349
+ weight: 8,
350
+ properties: {
351
+ "message_type": ["text", "image", "voice", "link"],
352
+ "conversation_length": u.weighNumRange(1, 100),
353
+ }
354
+ },
355
+ {
356
+ event: "ad viewed",
357
+ weight: 10,
358
+ properties: {
359
+ "ad_format": ["feed_native", "story", "banner", "video"],
360
+ "ad_category": ["retail", "tech", "food", "finance", "entertainment"],
361
+ "view_duration_sec": u.weighNumRange(1, 30, 0.3),
362
+ }
363
+ },
364
+ {
365
+ event: "ad clicked",
366
+ weight: 2,
367
+ properties: {
368
+ "ad_format": ["feed_native", "story", "banner", "video"],
369
+ "ad_category": ["retail", "tech", "food", "finance", "entertainment"],
370
+ }
371
+ },
372
+ {
373
+ event: "report submitted",
374
+ weight: 1,
375
+ properties: {
376
+ "report_type": ["spam", "harassment", "misinformation", "hate_speech", "other"],
377
+ "content_type": ["post", "comment", "user", "dm"],
378
+ }
379
+ },
380
+ {
381
+ event: "profile updated",
382
+ weight: 3,
383
+ properties: {
384
+ "field_updated": ["bio", "avatar", "display_name", "privacy_settings", "interests"],
385
+ }
386
+ },
387
+ {
388
+ event: "creator subscription started",
389
+ weight: 2,
390
+ properties: {
391
+ "tier": ["basic", "premium", "vip"],
392
+ "price_usd": u.pickAWinner([4.99, 9.99, 19.99]),
393
+ }
394
+ },
395
+ ],
396
+
397
+ superProps: {
398
+ app_version: ["4.0", "4.1", "4.2", "4.3", "5.0"],
399
+ account_type: ["personal", "creator", "business"],
400
+ },
401
+
402
+ userProps: {
403
+ "follower_count": u.weighNumRange(0, 10000, 0.2, 50),
404
+ "following_count": u.weighNumRange(0, 5000, 0.3, 100),
405
+ "bio_length": u.weighNumRange(0, 160),
406
+ "verified": u.pickAWinner([true, false], 0.05),
407
+ "content_niche": ["lifestyle", "tech", "food", "fitness", "travel", "comedy", "news", "art"],
408
+ },
409
+
410
+ groupKeys: [
411
+ ["community_id", 100, ["post created", "comment posted", "post liked", "post shared"]],
412
+ ],
413
+
414
+ groupProps: {
415
+ community_id: {
416
+ "name": () => `${chance.word()} ${chance.pickone(["Hub", "Circle", "Squad", "Zone", "Space"])}`,
417
+ "member_count": u.weighNumRange(50, 5000, 0.3, 200),
418
+ "category": ["technology", "entertainment", "sports", "politics", "art", "science"],
419
+ "is_moderated": u.pickAWinner([true, false], 0.7),
420
+ }
421
+ },
422
+
423
+ lookupTables: [],
424
+
425
+ hook: function (record, type, meta) {
426
+ const NOW = dayjs();
427
+ const DATASET_START = NOW.subtract(days, 'days');
428
+ const ALGORITHM_CHANGE_DAY = DATASET_START.add(45, 'days');
429
+ const REENGAGEMENT_START = DATASET_START.add(30, 'days');
430
+
431
+ // ─── EVENT-LEVEL HOOKS ───────────────────────────────────────────
432
+
433
+ if (type === "event") {
434
+ const EVENT_TIME = dayjs(record.time);
435
+
436
+ // Hook #3: ALGORITHM CHANGE - Day 45 flips feed to explore
437
+ if (record.event === "post viewed") {
438
+ if (EVENT_TIME.isAfter(ALGORITHM_CHANGE_DAY)) {
439
+ // After day 45: 70% explore, 15% feed
440
+ if (chance.bool({ likelihood: 70 })) {
441
+ record.source = "explore";
442
+ }
443
+ } else {
444
+ // Before day 45: 70% feed, 15% explore (reinforce default)
445
+ if (chance.bool({ likelihood: 70 })) {
446
+ record.source = "feed";
447
+ }
448
+ }
449
+ }
450
+
451
+ // Hook #4: ENGAGEMENT BAIT - High hashtag posts get short view durations
452
+ if (record.event === "post viewed") {
453
+ // 20% of post views are engagement-bait content
454
+ if (chance.bool({ likelihood: 20 })) {
455
+ record.view_duration_sec = chance.integer({ min: 1, max: 5 });
456
+ record.engagement_bait = true;
457
+ } else {
458
+ record.engagement_bait = false;
459
+ }
460
+
461
+ // Hook #5: NOTIFICATION RE-ENGAGEMENT - Trending drives views after day 30
462
+ if (EVENT_TIME.isAfter(REENGAGEMENT_START) && chance.bool({ likelihood: 30 })) {
463
+ record.source = "notification";
464
+ record.trending_reengagement = true;
465
+ } else {
466
+ record.trending_reengagement = false;
467
+ }
468
+ }
469
+
470
+ // Hook #8: WEEKEND CONTENT SURGE - tag weekend content (duplication handled in everything hook)
471
+ if (record.event === "post created" || record.event === "story created") {
472
+ const dayOfWeek = EVENT_TIME.day(); // 0 = Sunday, 6 = Saturday
473
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
474
+ record.weekend_surge = true;
475
+ } else {
476
+ record.weekend_surge = false;
477
+ }
478
+ }
479
+ }
480
+
481
+ // ─── EVERYTHING-LEVEL HOOKS ──────────────────────────────────────
482
+
483
+ if (type === "everything") {
484
+ const userEvents = record;
485
+ if (!userEvents || userEvents.length === 0) return record;
486
+
487
+ // Tracking variables for user patterns
488
+ let postCreatedCount = 0;
489
+ let followReceivedCount = 0;
490
+ let reportSubmittedCount = 0;
491
+ let hasCreatorSubscription = false;
492
+ let isViralCreator = false;
493
+
494
+ // First pass: identify user patterns
495
+ userEvents.forEach((event) => {
496
+ if (event.event === "post created") {
497
+ postCreatedCount++;
498
+ }
499
+ if (event.event === "user followed") {
500
+ followReceivedCount++;
501
+ }
502
+ if (event.event === "report submitted") {
503
+ reportSubmittedCount++;
504
+ }
505
+ if (event.event === "creator subscription started") {
506
+ hasCreatorSubscription = true;
507
+ }
508
+ });
509
+
510
+ // Hook #1: VIRAL CONTENT CASCADE
511
+ // Users with 10+ posts and 5% random chance are viral creators
512
+ if (postCreatedCount >= 10 && chance.bool({ likelihood: 5 })) {
513
+ isViralCreator = true;
514
+ }
515
+
516
+ // Second pass: set schema defaults then modify/inject based on patterns
517
+ for (let idx = userEvents.length - 1; idx >= 0; idx--) {
518
+ const event = userEvents[idx];
519
+ const eventTime = dayjs(event.time);
520
+
521
+ // Set schema defaults for conditional properties
522
+ if (event.event === "post created" || event.event === "story created") {
523
+ if (event.monetized_creator === undefined) event.monetized_creator = false;
524
+ if (event.follow_back_effect === undefined) event.follow_back_effect = false;
525
+ }
526
+ if (event.event === "post viewed") {
527
+ if (event.viral_cascade === undefined) event.viral_cascade = false;
528
+ }
529
+ if (event.toxic_user === undefined) event.toxic_user = false;
530
+
531
+ // Hook #1: VIRAL CONTENT CASCADE
532
+ // Viral creators get 10-20x engagement on their posts
533
+ if (isViralCreator && event.event === "post created") {
534
+ const viralViews = chance.integer({ min: 10, max: 20 });
535
+ const viralLikes = chance.integer({ min: 10, max: 20 });
536
+ const viralShares = chance.integer({ min: 10, max: 20 });
537
+ const injected = [];
538
+
539
+ for (let i = 0; i < viralViews; i++) {
540
+ injected.push({
541
+ event: "post viewed",
542
+ time: eventTime.add(chance.integer({ min: 1, max: 180 }), 'minutes').toISOString(),
543
+ user_id: event.user_id,
544
+ post_type: event.post_type || "text",
545
+ source: chance.pickone(["feed", "explore", "search"]),
546
+ view_duration_sec: chance.integer({ min: 5, max: 90 }),
547
+ viral_cascade: true,
548
+ });
549
+ }
550
+ for (let i = 0; i < viralLikes; i++) {
551
+ injected.push({
552
+ event: "post liked",
553
+ time: eventTime.add(chance.integer({ min: 2, max: 240 }), 'minutes').toISOString(),
554
+ user_id: event.user_id,
555
+ post_type: event.post_type || "text",
556
+ viral_cascade: true,
557
+ });
558
+ }
559
+ for (let i = 0; i < viralShares; i++) {
560
+ injected.push({
561
+ event: "post shared",
562
+ time: eventTime.add(chance.integer({ min: 5, max: 300 }), 'minutes').toISOString(),
563
+ user_id: event.user_id,
564
+ share_destination: chance.pickone(["repost", "dm", "external", "copy_link"]),
565
+ viral_cascade: true,
566
+ });
567
+ }
568
+
569
+ // Splice all injected events after the post created event
570
+ userEvents.splice(idx + 1, 0, ...injected);
571
+ }
572
+
573
+ // Hook #2: FOLLOW-BACK SNOWBALL
574
+ // Users with 5+ follows become prolific creators
575
+ if (followReceivedCount >= 5 && event.event === "post created") {
576
+ if (chance.bool({ likelihood: 50 })) {
577
+ const duplicatePost = {
578
+ event: "post created",
579
+ time: eventTime.add(chance.integer({ min: 30, max: 240 }), 'minutes').toISOString(),
580
+ user_id: event.user_id,
581
+ post_type: chance.pickone(["text", "image", "video"]),
582
+ character_count: chance.integer({ min: 10, max: 280 }),
583
+ has_media: chance.bool({ likelihood: 60 }),
584
+ hashtag_count: chance.integer({ min: 0, max: 5 }),
585
+ follow_back_effect: true,
586
+ };
587
+ const extraComment = {
588
+ event: "comment posted",
589
+ time: eventTime.add(chance.integer({ min: 10, max: 120 }), 'minutes').toISOString(),
590
+ user_id: event.user_id,
591
+ comment_length: chance.integer({ min: 5, max: 200 }),
592
+ has_mention: chance.bool({ likelihood: 40 }),
593
+ follow_back_effect: true,
594
+ };
595
+ userEvents.splice(idx + 1, 0, duplicatePost, extraComment);
596
+ }
597
+ }
598
+
599
+ // Hook #6: CREATOR MONETIZATION
600
+ // Monetized creators post 3x more frequently
601
+ if (hasCreatorSubscription && event.event === "post created") {
602
+ // Triple frequency: add 2 extra posts for each existing one
603
+ for (let i = 0; i < 2; i++) {
604
+ const extraPost = {
605
+ event: "post created",
606
+ time: eventTime.add(chance.integer({ min: 1, max: 12 }), 'hours').toISOString(),
607
+ user_id: event.user_id,
608
+ post_type: chance.pickone(["text", "image", "video", "link"]),
609
+ character_count: chance.integer({ min: 20, max: 280 }),
610
+ has_media: chance.bool({ likelihood: 70 }),
611
+ hashtag_count: chance.integer({ min: 1, max: 8 }),
612
+ monetized_creator: true,
613
+ };
614
+ userEvents.splice(idx + 1, 0, extraPost);
615
+ }
616
+ }
617
+ if (hasCreatorSubscription && event.event === "story created") {
618
+ // Also triple story creation
619
+ for (let i = 0; i < 2; i++) {
620
+ const extraStory = {
621
+ event: "story created",
622
+ time: eventTime.add(chance.integer({ min: 1, max: 8 }), 'hours').toISOString(),
623
+ user_id: event.user_id,
624
+ story_type: chance.pickone(["photo", "video", "text"]),
625
+ has_filter: chance.bool({ likelihood: 60 }),
626
+ has_sticker: chance.bool({ likelihood: 40 }),
627
+ monetized_creator: true,
628
+ };
629
+ userEvents.splice(idx + 1, 0, extraStory);
630
+ }
631
+ }
632
+ // Monetized creators also check their analytics more (extra post views)
633
+ if (hasCreatorSubscription && event.event === "post viewed") {
634
+ if (chance.bool({ likelihood: 25 })) {
635
+ const analyticsView = {
636
+ event: "post viewed",
637
+ time: eventTime.add(chance.integer({ min: 1, max: 30 }), 'minutes').toISOString(),
638
+ user_id: event.user_id,
639
+ post_type: event.post_type || "text",
640
+ source: "profile",
641
+ view_duration_sec: chance.integer({ min: 10, max: 60 }),
642
+ monetized_creator: true,
643
+ };
644
+ userEvents.splice(idx + 1, 0, analyticsView);
645
+ }
646
+ }
647
+ }
648
+
649
+ // Hook #8: WEEKEND CONTENT SURGE - inject duplicate events for weekend content
650
+ for (let idx = userEvents.length - 1; idx >= 0; idx--) {
651
+ const event = userEvents[idx];
652
+ if (event.weekend_surge && !event.weekend_duplicate) {
653
+ if (chance.bool({ likelihood: 30 })) {
654
+ const etime = dayjs(event.time);
655
+ const dup = {
656
+ ...event,
657
+ time: etime.add(chance.integer({ min: 1, max: 3 }), 'hours').toISOString(),
658
+ weekend_duplicate: true,
659
+ };
660
+ userEvents.splice(idx + 1, 0, dup);
661
+ }
662
+ }
663
+ }
664
+
665
+ // Hook #7: TOXICITY CHURN
666
+ // Users with 3+ reports lose 60% of activity after day 30
667
+ if (reportSubmittedCount >= 3) {
668
+ const churnCutoff = DATASET_START.add(30, 'days');
669
+ for (let i = userEvents.length - 1; i >= 0; i--) {
670
+ const evt = userEvents[i];
671
+ if (dayjs(evt.time).isAfter(churnCutoff)) {
672
+ if (chance.bool({ likelihood: 60 })) {
673
+ userEvents.splice(i, 1);
674
+ } else {
675
+ evt.toxic_user = true;
676
+ }
677
+ }
678
+ }
679
+ }
680
+ }
681
+
682
+ return record;
683
+ }
684
+ };
685
+
686
+ export default config;