@ak--47/dungeon-master 1.0.0 → 1.2.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 (104) hide show
  1. package/README.md +7 -7
  2. package/dungeons/technical/ad-spend-schema.json +128 -0
  3. package/dungeons/technical/ad-spend.js +116 -0
  4. package/dungeons/technical/anonymous-users-schema.json +92 -0
  5. package/dungeons/technical/anonymous-users.js +102 -0
  6. package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
  7. package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +12 -5
  8. package/dungeons/technical/experiments-schema.json +203 -0
  9. package/dungeons/technical/experiments.js +168 -0
  10. package/dungeons/technical/foobar-schema.json +362 -0
  11. package/dungeons/{foobar.js → technical/foobar.js} +3 -3
  12. package/dungeons/technical/group-analytics-schema.json +241 -0
  13. package/dungeons/technical/group-analytics.js +151 -0
  14. package/dungeons/technical/mirror-strategies-schema.json +84 -0
  15. package/dungeons/technical/mirror-strategies.js +126 -0
  16. package/dungeons/technical/nested-objects-schema.json +145 -0
  17. package/dungeons/technical/nested-objects.js +207 -0
  18. package/dungeons/technical/retention-cadence-schema.json +37 -0
  19. package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +2 -3
  20. package/dungeons/technical/sanity-schema.json +185 -0
  21. package/dungeons/{sanity.js → technical/sanity.js} +4 -2
  22. package/dungeons/technical/scale-test-schema.json +70 -0
  23. package/dungeons/technical/scale-test.js +90 -0
  24. package/dungeons/technical/scd-schema.json +467 -0
  25. package/dungeons/{scd.js → technical/scd.js} +20 -10
  26. package/dungeons/technical/simple-schema.json +362 -0
  27. package/dungeons/{simple.js → technical/simple.js} +7 -7
  28. package/dungeons/technical/simplest-schema.json +724 -0
  29. package/dungeons/{simplest.js → technical/simplest.js} +7 -7
  30. package/dungeons/technical/text-generation-schema.json +1062 -0
  31. package/dungeons/{text-generation.js → technical/text-generation.js} +24 -11
  32. package/dungeons/vertical/community-schema.json +579 -0
  33. package/dungeons/vertical/community.js +794 -0
  34. package/dungeons/vertical/devtools-schema.json +601 -0
  35. package/dungeons/vertical/devtools.js +843 -0
  36. package/dungeons/vertical/ecommerce-schema.json +604 -0
  37. package/dungeons/{ecommerce.js → vertical/ecommerce.js} +43 -5
  38. package/dungeons/vertical/education-schema.json +5686 -0
  39. package/dungeons/{education.js → vertical/education.js} +70 -39
  40. package/dungeons/vertical/fintech-schema.json +630 -0
  41. package/dungeons/{fintech.js → vertical/fintech.js} +107 -56
  42. package/dungeons/vertical/fitness-schema.json +530 -0
  43. package/dungeons/vertical/fitness.js +808 -0
  44. package/dungeons/vertical/food-delivery-schema.json +36728 -0
  45. package/dungeons/{food.js → vertical/food-delivery.js} +67 -26
  46. package/dungeons/vertical/gaming-schema.json +438 -0
  47. package/dungeons/{gaming.js → vertical/gaming.js} +29 -16
  48. package/dungeons/vertical/healthcare-schema.json +549 -0
  49. package/dungeons/vertical/healthcare.js +775 -0
  50. package/dungeons/vertical/insurance-application-schema.json +485 -0
  51. package/dungeons/{insurance-application.js → vertical/insurance-application.js} +41 -31
  52. package/dungeons/vertical/logistics-schema.json +574 -0
  53. package/dungeons/vertical/logistics.js +799 -0
  54. package/dungeons/vertical/marketplace-schema.json +533 -0
  55. package/dungeons/vertical/marketplace.js +850 -0
  56. package/dungeons/vertical/media-schema.json +4749 -0
  57. package/dungeons/{media.js → vertical/media.js} +103 -19
  58. package/dungeons/vertical/rpg-schema.json +2491 -0
  59. package/dungeons/{rpg.js → vertical/rpg.js} +87 -60
  60. package/dungeons/vertical/sass-schema.json +3128 -0
  61. package/dungeons/{sass.js → vertical/sass.js} +65 -26
  62. package/dungeons/vertical/social-schema.json +620 -0
  63. package/dungeons/{social.js → vertical/social.js} +76 -17
  64. package/dungeons/vertical/travel-schema.json +580 -0
  65. package/dungeons/vertical/travel.js +742 -0
  66. package/index.js +20 -16
  67. package/lib/core/config-validator.js +286 -1
  68. package/lib/core/context.js +16 -5
  69. package/lib/core/storage.js +2 -0
  70. package/lib/generators/adspend.js +3 -3
  71. package/lib/generators/events.js +141 -14
  72. package/lib/generators/funnels.js +67 -7
  73. package/lib/generators/text.js +2 -2
  74. package/lib/orchestrators/user-loop.js +444 -22
  75. package/lib/templates/soup-presets.js +2 -2
  76. package/lib/utils/utils.js +4 -5
  77. package/package.json +4 -3
  78. package/scripts/dungeon-to-json.mjs +163 -0
  79. package/scripts/extract-dungeon-schema.mjs +343 -0
  80. package/scripts/json-to-dungeon.mjs +200 -0
  81. package/scripts/run-dungeon.mjs +76 -0
  82. package/scripts/run-many.mjs +118 -0
  83. package/scripts/verify-runner.mjs +42 -0
  84. package/types.d.ts +316 -1
  85. package/dungeons/array-of-object-lookup-schema.json +0 -327
  86. package/dungeons/ecommerce-schema.json +0 -462
  87. package/dungeons/education-schema.json +0 -2409
  88. package/dungeons/fintech-schema.json +0 -14034
  89. package/dungeons/foobar-schema.json +0 -403
  90. package/dungeons/food-delivery-schema.json +0 -192
  91. package/dungeons/food-delivery.js +0 -602
  92. package/dungeons/food-schema.json +0 -1152
  93. package/dungeons/gaming-schema.json +0 -1270
  94. package/dungeons/insurance-application-schema.json +0 -204
  95. package/dungeons/media-schema.json +0 -906
  96. package/dungeons/retention-cadence-schema.json +0 -78
  97. package/dungeons/rpg-schema.json +0 -4526
  98. package/dungeons/sanity-schema.json +0 -255
  99. package/dungeons/sass-schema.json +0 -1291
  100. package/dungeons/scd-schema.json +0 -919
  101. package/dungeons/simple-schema.json +0 -608
  102. package/dungeons/simplest-schema.json +0 -1418
  103. package/dungeons/social-schema.json +0 -1118
  104. package/dungeons/text-generation-schema.json +0 -3096
@@ -0,0 +1,794 @@
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 = "dm4-community";
8
+ dayjs.extend(utc);
9
+ const chance = u.initChance(SEED);
10
+ const num_users = 5_000;
11
+ const days = 100;
12
+ const NOW = dayjs();
13
+ const DATASET_START = NOW.subtract(days, "days");
14
+
15
+ /** @typedef {import("../../types").Dungeon} Config */
16
+
17
+ // Generate consistent wiki/article IDs at module level
18
+ const wikiIds = v.range(1, 500).map(() => `WIKI_${v.uid(6)}`);
19
+ const communityIds = v.range(1, 30).map(() => `COMM_${v.uid(4)}`);
20
+
21
+ /**
22
+ * ===================================================================
23
+ * DATASET OVERVIEW
24
+ * ===================================================================
25
+ *
26
+ * FanVerse -- a fan wiki and community discussion platform where
27
+ * users create articles, discuss topics, moderate content, and
28
+ * build collaborative knowledge bases across fandoms.
29
+ *
30
+ * - 5,000 users over 100 days, ~600K events
31
+ * - Multi-role system: power creators (5%), moderators (8%),
32
+ * active contributors (25%), readers (45%), lurkers (17%)
33
+ * - Core loop: sign up -> search -> read articles -> contribute -> discuss
34
+ * - Revenue: free / supporter ($4.99, ad-free) / pro ($12.99, analytics+badges)
35
+ *
36
+ * Advanced Features:
37
+ * - Personas: 5 archetypes (power_creator, moderator, active_contributor, reader, lurker)
38
+ * - Engagement Decay: linear decline with floor
39
+ * - Subscription: 3-tier with supporter and pro plans
40
+ * - Attribution: google_search, reddit_referral, youtube_link, organic
41
+ * - Features: live_discussions (day 25), interactive_polls (day 55)
42
+ *
43
+ * Key entities:
44
+ * - wiki_id: unique wiki article identifier
45
+ * - community_id: community/fandom grouping
46
+ * - content_hub: topical category (gaming, anime, movies, tv, comics, music)
47
+ * - discussion_mode: classic vs live (driven by Feature rollout)
48
+ */
49
+
50
+ /**
51
+ * ===================================================================
52
+ * ANALYTICS HOOKS (8 hooks)
53
+ * ===================================================================
54
+ *
55
+ * -------------------------------------------------------------------
56
+ * 1. WEEKEND CONTENT SURGE (event hook)
57
+ * -------------------------------------------------------------------
58
+ *
59
+ * PATTERN: Articles published on weekends (Sat/Sun) have 1.5x
60
+ * word_count. Creators have more time on weekends to write longer,
61
+ * more detailed wiki articles.
62
+ *
63
+ * HOW TO FIND IT IN MIXPANEL:
64
+ *
65
+ * Report 1: Weekend vs Weekday Word Count
66
+ * - Report type: Insights
67
+ * - Event: "article published"
68
+ * - Measure: Average of "word_count"
69
+ * - Breakdown: "is_weekend"
70
+ * - Expected: is_weekend=true should show ~1.5x avg word_count vs false
71
+ * (true ~ 2250, false ~ 1500)
72
+ *
73
+ * REAL-WORLD ANALOGUE: Community wikis see longer, more thoughtful
74
+ * contributions on weekends when creators have uninterrupted time.
75
+ *
76
+ * -------------------------------------------------------------------
77
+ * 2. TRENDING TOPIC WINDOW (event hook)
78
+ * -------------------------------------------------------------------
79
+ *
80
+ * PATTERN: During days 35-50, articles in the "gaming" hub get 2x
81
+ * view_count. Simulates a major game release driving traffic to
82
+ * gaming wiki pages.
83
+ *
84
+ * HOW TO FIND IT IN MIXPANEL:
85
+ *
86
+ * Report 1: Gaming Hub View Spike
87
+ * - Report type: Insights
88
+ * - Event: "article viewed"
89
+ * - Measure: Average of "view_count"
90
+ * - Filter: content_hub = "gaming"
91
+ * - Line chart by week
92
+ * - Expected: Clear spike during days 35-50 (~2x normal)
93
+ *
94
+ * Report 2: Hub Comparison During Trend Window
95
+ * - Report type: Insights
96
+ * - Event: "article viewed"
97
+ * - Measure: Average of "view_count"
98
+ * - Breakdown: "content_hub"
99
+ * - Filter: time within trend window
100
+ * - Expected: gaming ~2x vs other hubs
101
+ *
102
+ * REAL-WORLD ANALOGUE: Major franchise releases (game launches,
103
+ * movie premieres) drive massive traffic spikes to related wikis.
104
+ *
105
+ * -------------------------------------------------------------------
106
+ * 3. POWER CREATOR ENGAGEMENT LIFT (everything hook)
107
+ * -------------------------------------------------------------------
108
+ *
109
+ * PATTERN: Users who published >20 articles get 3x avg upvote_count
110
+ * on their content events. Prolific creators earn community trust
111
+ * and visibility, amplifying their engagement metrics.
112
+ *
113
+ * HOW TO FIND IT IN MIXPANEL:
114
+ *
115
+ * Report 1: Upvote Count by Creator Volume
116
+ * - Report type: Insights
117
+ * - Event: "upvote given"
118
+ * - Measure: Average of "upvote_count"
119
+ * - Breakdown: user property "role"
120
+ * - Expected: creators (high volume) show ~3x upvote_count
121
+ * (creators ~ 15, others ~ 5)
122
+ *
123
+ * REAL-WORLD ANALOGUE: Power contributors on platforms like Fandom
124
+ * and Wikipedia earn disproportionate engagement due to reputation
125
+ * and content quality.
126
+ *
127
+ * -------------------------------------------------------------------
128
+ * 4. DISCUSSION DEPTH BY CONTRIBUTOR TYPE (everything hook)
129
+ * -------------------------------------------------------------------
130
+ *
131
+ * PATTERN: Active contributors (segment "active_contributor") get
132
+ * cloned comment_posted events to simulate deeper discussion threads.
133
+ * Each existing comment has a 50% chance of spawning a follow-up.
134
+ *
135
+ * HOW TO FIND IT IN MIXPANEL:
136
+ *
137
+ * Report 1: Comments Per User by Segment
138
+ * - Report type: Insights
139
+ * - Event: "comment posted"
140
+ * - Measure: Total per user
141
+ * - Breakdown: user property "segment"
142
+ * - Expected: active_contributor ~1.5x comments vs others
143
+ *
144
+ * REAL-WORLD ANALOGUE: Engaged contributors create deeper discussion
145
+ * threads, replying to comments and fostering community dialogue.
146
+ *
147
+ * -------------------------------------------------------------------
148
+ * 5. EDIT WAR DETECTION (everything hook)
149
+ * -------------------------------------------------------------------
150
+ *
151
+ * PATTERN: Users with >5 rapid article_edited events within a short
152
+ * window get reduced edit_quality score (set to 1-2 range vs normal
153
+ * 1-5). Simulates contentious edits degrading quality.
154
+ *
155
+ * HOW TO FIND IT IN MIXPANEL:
156
+ *
157
+ * Report 1: Edit Quality by Volume
158
+ * - Report type: Insights
159
+ * - Event: "article edited"
160
+ * - Measure: Average of "edit_quality"
161
+ * - Breakdown: user property "segment"
162
+ * - Expected: Users with many edits show lower avg quality
163
+ * (high-edit users ~ 1.5 vs normal ~ 3.0)
164
+ *
165
+ * REAL-WORLD ANALOGUE: Wiki edit wars (e.g., Wikipedia) degrade
166
+ * content quality as users repeatedly override each other's changes.
167
+ *
168
+ * -------------------------------------------------------------------
169
+ * 6. LURKER CHURN (everything hook)
170
+ * -------------------------------------------------------------------
171
+ *
172
+ * PATTERN: Users with <5 total events lose 60% of events after
173
+ * day 10 of their activity. Simulates lurkers quickly losing
174
+ * interest and churning out.
175
+ *
176
+ * HOW TO FIND IT IN MIXPANEL:
177
+ *
178
+ * Report 1: Event Volume by Segment Over Time
179
+ * - Report type: Insights
180
+ * - Event: Any event
181
+ * - Measure: Total per user
182
+ * - Breakdown: user property "segment"
183
+ * - Line chart by week
184
+ * - Expected: lurker segment drops off sharply after first 10 days
185
+ *
186
+ * REAL-WORLD ANALOGUE: Most community platforms see >60% of new
187
+ * signups become inactive within the first 2-3 weeks.
188
+ *
189
+ * -------------------------------------------------------------------
190
+ * 7. CREATOR PROFILES (user hook)
191
+ * -------------------------------------------------------------------
192
+ *
193
+ * PATTERN: Users with role "creator" get articles_created set to
194
+ * 50-200 range and reputation_score to 80-100. Moderators get
195
+ * mid-range reputation. Readers/lurkers stay at defaults.
196
+ *
197
+ * HOW TO FIND IT IN MIXPANEL:
198
+ *
199
+ * Report 1: Reputation Distribution by Role
200
+ * - Report type: Insights
201
+ * - Event: any event
202
+ * - Measure: Average of user property "reputation_score"
203
+ * - Breakdown: user property "role"
204
+ * - Expected: creator ~ 90, moderator ~ 55, reader ~ 25
205
+ *
206
+ * Report 2: Articles Created by Role
207
+ * - Report type: Insights
208
+ * - Measure: Average of user property "articles_created"
209
+ * - Breakdown: user property "role"
210
+ * - Expected: creator ~ 125, others ~ 0
211
+ *
212
+ * REAL-WORLD ANALOGUE: Top wiki contributors have hundreds of
213
+ * articles and high community reputation scores.
214
+ *
215
+ * -------------------------------------------------------------------
216
+ * 8. PRO SUBSCRIBER CONTENT CREATION LIFT (funnel-pre hook)
217
+ * -------------------------------------------------------------------
218
+ *
219
+ * PATTERN: Users with subscription_tier "pro" get 1.5x conversion
220
+ * rate on the Content Creation funnel. Pro users have analytics and
221
+ * tools that help them publish more effectively.
222
+ *
223
+ * HOW TO FIND IT IN MIXPANEL:
224
+ *
225
+ * Report 1: Content Creation Conversion by Tier
226
+ * - Report type: Funnels
227
+ * - Steps: "article viewed" -> "article published" -> "comment posted"
228
+ * - Breakdown: "subscription_tier" (superProp)
229
+ * - Expected: pro ~ 52% vs free ~ 35% conversion
230
+ *
231
+ * REAL-WORLD ANALOGUE: Premium wiki tools (analytics dashboards,
232
+ * badge systems) incentivize more content creation from subscribers.
233
+ *
234
+ * ===================================================================
235
+ * EXPECTED METRICS SUMMARY
236
+ * ===================================================================
237
+ *
238
+ * Hook | Metric | Baseline | Effect | Ratio
239
+ * ------------------------------|---------------------|----------|---------|------
240
+ * Weekend Content Surge | word_count | 1500 | 2250 | 1.5x
241
+ * Trending Topic Window | view_count (gaming) | 50 | 100 | 2x
242
+ * Power Creator Engagement | upvote_count | 5 | 15 | 3x
243
+ * Discussion Depth | comments/user | 3 | 4.5 | 1.5x
244
+ * Edit War Detection | edit_quality | 3.0 | 1.5 | 0.5x
245
+ * Lurker Churn | events after day 10 | 100% | 40% | 0.4x
246
+ * Creator Profiles | reputation_score | 25 | 90 | 3.6x
247
+ * Pro Content Creation Lift | funnel conversion | 35% | 52% | 1.5x
248
+ */
249
+
250
+ /** @type {Config} */
251
+ const config = {
252
+ token: "",
253
+ seed: SEED,
254
+ numDays: days,
255
+ numEvents: num_users * 120,
256
+ numUsers: num_users,
257
+ hasAnonIds: false,
258
+ hasSessionIds: true,
259
+ format: "json",
260
+ gzip: true,
261
+ alsoInferFunnels: false,
262
+ hasLocation: true,
263
+ hasAndroidDevices: true,
264
+ hasIOSDevices: true,
265
+ hasDesktopDevices: true,
266
+ hasBrowser: false,
267
+ hasCampaigns: false,
268
+ isAnonymous: false,
269
+ hasAdSpend: false,
270
+ percentUsersBornInDataset: 35,
271
+ hasAvatar: true,
272
+ concurrency: 1,
273
+ writeToDisk: false,
274
+ scdProps: {
275
+ contributor_level: {
276
+ values: ["newcomer", "regular", "trusted", "admin"],
277
+ frequency: "month",
278
+ timing: "fuzzy",
279
+ max: 8
280
+ }
281
+ },
282
+ mirrorProps: {},
283
+ lookupTables: [],
284
+
285
+ // -- Events (18) --------------------------------------------------
286
+ events: [
287
+ {
288
+ event: "account created",
289
+ weight: 1,
290
+ isFirstEvent: true,
291
+ properties: {
292
+ referral_source: ["organic", "google_search", "reddit_referral", "youtube_link", "friend_invite"],
293
+ },
294
+ },
295
+ {
296
+ event: "article viewed",
297
+ weight: 9,
298
+ properties: {
299
+ wiki_id: chance.pickone.bind(chance, wikiIds),
300
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
301
+ view_count: u.weighNumRange(1, 100, 0.4, 50),
302
+ time_on_page_sec: u.weighNumRange(5, 600, 0.4, 45),
303
+ is_weekend: [false],
304
+ },
305
+ },
306
+ {
307
+ event: "article published",
308
+ weight: 3,
309
+ properties: {
310
+ wiki_id: chance.pickone.bind(chance, wikiIds),
311
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
312
+ word_count: u.weighNumRange(200, 5000, 0.4, 1500),
313
+ has_images: [true, true, true, false],
314
+ category: ["lore", "character", "episode_guide", "review", "tutorial", "news"],
315
+ is_weekend: [false],
316
+ poll_enabled: [false],
317
+ },
318
+ },
319
+ {
320
+ event: "article edited",
321
+ weight: 4,
322
+ properties: {
323
+ wiki_id: chance.pickone.bind(chance, wikiIds),
324
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
325
+ edit_type: ["content", "formatting", "grammar", "citation", "revert"],
326
+ edit_quality: u.weighNumRange(1, 5, 0.8, 3),
327
+ chars_changed: u.weighNumRange(5, 2000, 0.3, 150),
328
+ },
329
+ },
330
+ {
331
+ event: "discussion posted",
332
+ weight: 5,
333
+ properties: {
334
+ community_id: chance.pickone.bind(chance, communityIds),
335
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
336
+ topic_type: ["theory", "question", "news", "review", "recommendation", "debate"],
337
+ reply_count: u.weighNumRange(0, 50, 0.3, 5),
338
+ discussion_mode: ["classic"],
339
+ },
340
+ },
341
+ {
342
+ event: "comment posted",
343
+ weight: 6,
344
+ properties: {
345
+ community_id: chance.pickone.bind(chance, communityIds),
346
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
347
+ comment_length: u.weighNumRange(10, 500, 0.4, 80),
348
+ is_reply: [true, true, false],
349
+ discussion_mode: ["classic"],
350
+ },
351
+ },
352
+ {
353
+ event: "upvote given",
354
+ weight: 7,
355
+ properties: {
356
+ content_type: ["article", "article", "discussion", "comment"],
357
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
358
+ upvote_count: u.weighNumRange(1, 10, 0.5, 5),
359
+ },
360
+ },
361
+ {
362
+ event: "search performed",
363
+ weight: 6,
364
+ properties: {
365
+ search_term: ["walkthrough", "character list", "ending explained", "tier list", "release date", "easter eggs", "best builds", "lore timeline", "voice actors", "soundtrack"],
366
+ results_count: u.weighNumRange(0, 50, 0.5, 12),
367
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
368
+ },
369
+ },
370
+ {
371
+ event: "wiki page created",
372
+ weight: 2,
373
+ properties: {
374
+ wiki_id: chance.pickone.bind(chance, wikiIds),
375
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
376
+ page_type: ["character", "location", "item", "episode", "concept", "organization"],
377
+ word_count: u.weighNumRange(100, 3000, 0.4, 500),
378
+ },
379
+ },
380
+ {
381
+ event: "media uploaded",
382
+ weight: 2,
383
+ properties: {
384
+ media_type: ["image", "image", "image", "gif", "video_clip", "screenshot"],
385
+ file_size_kb: u.weighNumRange(50, 5000, 0.3, 500),
386
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
387
+ },
388
+ },
389
+ {
390
+ event: "moderation action",
391
+ weight: 2,
392
+ properties: {
393
+ action_type: ["warn", "edit_revert", "content_removal", "user_mute", "spam_flag", "lock_thread"],
394
+ severity: ["low", "low", "medium", "medium", "high"],
395
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
396
+ resolution_time_hours: u.weighNumRange(0.1, 48, 0.3, 2),
397
+ },
398
+ },
399
+ {
400
+ event: "user followed",
401
+ weight: 3,
402
+ properties: {
403
+ follow_source: ["profile", "article", "discussion", "recommendation"],
404
+ },
405
+ },
406
+ {
407
+ event: "notification received",
408
+ weight: 6,
409
+ properties: {
410
+ notification_type: ["reply", "reply", "mention", "upvote", "follow", "article_update", "moderation"],
411
+ channel: ["push", "push", "email", "in_app"],
412
+ opened: [true, true, true, false],
413
+ },
414
+ },
415
+ {
416
+ event: "support ticket created",
417
+ weight: 1,
418
+ properties: {
419
+ category: ["bug_report", "content_dispute", "account_issue", "feature_request", "abuse_report", "other"],
420
+ priority: ["low", "low", "medium", "medium", "high"],
421
+ resolution_hours: u.weighNumRange(1, 96, 0.4, 24),
422
+ },
423
+ },
424
+ {
425
+ event: "profile updated",
426
+ weight: 2,
427
+ properties: {
428
+ field_updated: ["avatar", "bio", "display_name", "preferred_hub", "notification_settings", "badges"],
429
+ },
430
+ },
431
+ {
432
+ event: "app session",
433
+ weight: 8,
434
+ properties: {
435
+ session_duration_sec: u.weighNumRange(10, 3600, 0.4, 180),
436
+ pages_viewed: u.weighNumRange(1, 30, 0.5, 5),
437
+ },
438
+ },
439
+ {
440
+ event: "report submitted",
441
+ weight: 1,
442
+ properties: {
443
+ report_type: ["spam", "harassment", "misinformation", "vandalism", "copyright", "other"],
444
+ content_type: ["article", "comment", "discussion", "media"],
445
+ },
446
+ },
447
+ {
448
+ event: "account deactivated",
449
+ weight: 1,
450
+ isChurnEvent: true,
451
+ returnLikelihood: 0.15,
452
+ isStrictEvent: true,
453
+ properties: {
454
+ reason: ["lost_interest", "toxicity", "no_time", "switched_platform", "privacy_concern"],
455
+ },
456
+ },
457
+ ],
458
+
459
+ // -- Funnels (5) --------------------------------------------------
460
+ funnels: [
461
+ {
462
+ name: "Onboarding Flow",
463
+ sequence: ["account created", "search performed", "article viewed", "discussion posted"],
464
+ conversionRate: 40,
465
+ order: "sequential",
466
+ isFirstFunnel: true,
467
+ timeToConvert: 72,
468
+ weight: 3,
469
+ },
470
+ {
471
+ name: "Content Creation",
472
+ sequence: ["article viewed", "article published", "comment posted"],
473
+ conversionRate: 35,
474
+ order: "sequential",
475
+ timeToConvert: 48,
476
+ weight: 5,
477
+ },
478
+ {
479
+ name: "Engagement Loop",
480
+ sequence: ["article viewed", "upvote given", "comment posted", "discussion posted"],
481
+ conversionRate: 30,
482
+ order: "sequential",
483
+ timeToConvert: 96,
484
+ weight: 4,
485
+ },
486
+ {
487
+ name: "Creator to Supporter",
488
+ sequence: ["article published", "profile updated", "notification received"],
489
+ conversionRate: 45,
490
+ order: "sequential",
491
+ timeToConvert: 168,
492
+ weight: 2,
493
+ },
494
+ {
495
+ name: "Moderation Pipeline",
496
+ sequence: ["report submitted", "moderation action"],
497
+ conversionRate: 60,
498
+ order: "sequential",
499
+ timeToConvert: 48,
500
+ weight: 2,
501
+ },
502
+ ],
503
+
504
+ // -- SuperProps ----------------------------------------------------
505
+ superProps: {
506
+ subscription_tier: ["free", "free", "free", "free", "supporter", "pro"],
507
+ platform: ["ios", "android", "web"],
508
+ content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
509
+ },
510
+
511
+ // -- UserProps -----------------------------------------------------
512
+ userProps: {
513
+ role: ["reader", "reader", "reader", "reader", "reader", "reader", "contributor", "contributor", "moderator", "creator"],
514
+ contributor_level: ["newcomer"],
515
+ articles_created: [0],
516
+ reputation_score: u.weighNumRange(0, 100, 0.3, 25),
517
+ preferred_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
518
+ },
519
+
520
+ // -- Phase 2: Personas --------------------------------------------
521
+ personas: [
522
+ {
523
+ name: "power_creator",
524
+ weight: 5,
525
+ eventMultiplier: 8.0,
526
+ conversionModifier: 2.0,
527
+ churnRate: 0.01,
528
+ properties: {
529
+ role: "creator",
530
+ segment: "power_creator",
531
+ },
532
+ },
533
+ {
534
+ name: "moderator",
535
+ weight: 8,
536
+ eventMultiplier: 3.0,
537
+ conversionModifier: 1.5,
538
+ churnRate: 0.02,
539
+ properties: {
540
+ role: "moderator",
541
+ segment: "moderator",
542
+ },
543
+ },
544
+ {
545
+ name: "active_contributor",
546
+ weight: 25,
547
+ eventMultiplier: 1.5,
548
+ conversionModifier: 1.0,
549
+ churnRate: 0.05,
550
+ properties: {
551
+ role: "contributor",
552
+ segment: "active_contributor",
553
+ },
554
+ },
555
+ {
556
+ name: "reader",
557
+ weight: 45,
558
+ eventMultiplier: 0.3,
559
+ conversionModifier: 0.5,
560
+ churnRate: 0.12,
561
+ properties: {
562
+ role: "reader",
563
+ segment: "reader",
564
+ },
565
+ },
566
+ {
567
+ name: "lurker",
568
+ weight: 17,
569
+ eventMultiplier: 0.1,
570
+ conversionModifier: 0.2,
571
+ churnRate: 0.5,
572
+ properties: {
573
+ role: "reader",
574
+ segment: "lurker",
575
+ },
576
+ activeWindow: { maxDays: 21 },
577
+ },
578
+ ],
579
+
580
+ // -- Phase 2: Subscription ----------------------------------------
581
+ subscription: {
582
+ plans: [
583
+ { name: "free", price: 0, default: true },
584
+ { name: "supporter", price: 4.99 },
585
+ { name: "pro", price: 12.99 },
586
+ ],
587
+ lifecycle: {
588
+ trialToPayRate: 0.25,
589
+ upgradeRate: 0.06,
590
+ downgradeRate: 0.04,
591
+ churnRate: 0.06,
592
+ winBackRate: 0.08,
593
+ winBackDelay: 21,
594
+ paymentFailureRate: 0.02,
595
+ },
596
+ },
597
+
598
+ // -- Phase 2: Attribution -----------------------------------------
599
+ attribution: {
600
+ model: "last_touch",
601
+ window: 7,
602
+ campaigns: [
603
+ {
604
+ name: "google_search",
605
+ source: "google",
606
+ medium: "organic",
607
+ activeDays: [0, 100],
608
+ dailyBudget: [100, 400],
609
+ acquisitionRate: 0.03,
610
+ userPersonaBias: { reader: 0.5, lurker: 0.3 },
611
+ },
612
+ {
613
+ name: "reddit_referral",
614
+ source: "reddit",
615
+ medium: "referral",
616
+ activeDays: [0, 100],
617
+ dailyBudget: [50, 200],
618
+ acquisitionRate: 0.02,
619
+ userPersonaBias: { active_contributor: 0.5, moderator: 0.3 },
620
+ },
621
+ {
622
+ name: "youtube_link",
623
+ source: "youtube",
624
+ medium: "referral",
625
+ activeDays: [0, 100],
626
+ dailyBudget: [75, 250],
627
+ acquisitionRate: 0.02,
628
+ userPersonaBias: { lurker: 0.5, reader: 0.3 },
629
+ },
630
+ ],
631
+ organicRate: 0.35,
632
+ },
633
+
634
+ // -- Phase 2: Engagement Decay ------------------------------------
635
+ engagementDecay: {
636
+ model: "linear",
637
+ halfLife: 60,
638
+ floor: 0.15,
639
+ },
640
+
641
+ // -- Phase 2: Features --------------------------------------------
642
+ features: [
643
+ {
644
+ name: "live_discussions",
645
+ launchDay: 25,
646
+ adoptionCurve: "fast",
647
+ property: "discussion_mode",
648
+ values: ["classic", "live"],
649
+ defaultBefore: "classic",
650
+ affectsEvents: ["discussion posted", "comment posted"],
651
+ },
652
+ {
653
+ name: "interactive_polls",
654
+ launchDay: 55,
655
+ adoptionCurve: { k: 0.1, midpoint: 20 },
656
+ property: "poll_enabled",
657
+ values: [false, true],
658
+ defaultBefore: false,
659
+ affectsEvents: ["article published"],
660
+ },
661
+ ],
662
+
663
+ // -- Hook Function ------------------------------------------------
664
+ hook: function (record, type, meta) {
665
+ // -- HOOK 7: CREATOR PROFILES (user) --------------------------
666
+ // Creators get high articles_created and reputation. Moderators
667
+ // get mid-range reputation. Readers/lurkers stay at defaults.
668
+ if (type === "user") {
669
+ if (record.role === "creator") {
670
+ record.articles_created = chance.integer({ min: 50, max: 200 });
671
+ record.reputation_score = chance.integer({ min: 80, max: 100 });
672
+ record.contributor_level = "admin";
673
+ } else if (record.role === "moderator") {
674
+ record.articles_created = chance.integer({ min: 10, max: 50 });
675
+ record.reputation_score = chance.integer({ min: 40, max: 70 });
676
+ record.contributor_level = "trusted";
677
+ } else if (record.role === "contributor") {
678
+ record.articles_created = chance.integer({ min: 1, max: 15 });
679
+ record.reputation_score = chance.integer({ min: 15, max: 50 });
680
+ record.contributor_level = "regular";
681
+ } else {
682
+ record.articles_created = 0;
683
+ record.reputation_score = chance.integer({ min: 0, max: 20 });
684
+ record.contributor_level = "newcomer";
685
+ }
686
+ }
687
+
688
+ // -- HOOK 8: PRO SUBSCRIBER CONTENT CREATION LIFT (funnel-pre)
689
+ // Pro subscribers convert 1.5x better through content creation.
690
+ if (type === "funnel-pre") {
691
+ if (meta && meta.profile) {
692
+ const tier = meta.profile.subscription_tier;
693
+ if (tier === "pro") {
694
+ record.conversionRate = Math.min(record.conversionRate * 1.5, 95);
695
+ } else if (tier === "supporter") {
696
+ record.conversionRate = Math.min(record.conversionRate * 1.2, 90);
697
+ }
698
+ }
699
+ }
700
+
701
+ // -- HOOK 1: WEEKEND CONTENT SURGE (event) --------------------
702
+ // Articles published on weekends get 1.5x word_count.
703
+ if (type === "event") {
704
+ if (record.event === "article published" || record.event === "article viewed") {
705
+ const dow = dayjs(record.time).day();
706
+ if (dow === 0 || dow === 6) {
707
+ record.is_weekend = true;
708
+ if (record.word_count) {
709
+ record.word_count = Math.floor(record.word_count * 1.5);
710
+ }
711
+ }
712
+ }
713
+
714
+ // -- HOOK 2: TRENDING TOPIC WINDOW (event) ----------------
715
+ // Days 35-50: gaming hub articles get 2x view_count.
716
+ const TREND_START = DATASET_START.add(35, "days");
717
+ const TREND_END = DATASET_START.add(50, "days");
718
+ const eventTime = dayjs(record.time);
719
+ if (record.event === "article viewed" && eventTime.isAfter(TREND_START) && eventTime.isBefore(TREND_END)) {
720
+ if (record.content_hub === "gaming") {
721
+ record.view_count = Math.floor((record.view_count || 50) * 2);
722
+ }
723
+ }
724
+ }
725
+
726
+ // -- EVERYTHING HOOKS -----------------------------------------
727
+ if (type === "everything") {
728
+ const events = record;
729
+ if (!events.length) return record;
730
+
731
+ // -- HOOK 3: POWER CREATOR ENGAGEMENT LIFT ----------------
732
+ // Users with >20 article_published events get 3x upvote_count.
733
+ let publishCount = 0;
734
+ events.forEach(e => {
735
+ if (e.event === "article published") publishCount++;
736
+ });
737
+
738
+ if (publishCount > 20) {
739
+ events.forEach(e => {
740
+ if (e.event === "upvote given" && e.upvote_count) {
741
+ e.upvote_count = Math.floor(e.upvote_count * 3);
742
+ }
743
+ });
744
+ }
745
+
746
+ // -- HOOK 4: DISCUSSION DEPTH BY CONTRIBUTOR TYPE ---------
747
+ // Active contributors get cloned comment_posted events.
748
+ if (meta && meta.profile && meta.profile.segment === "active_contributor") {
749
+ const templateComment = events.find(e => e.event === "comment posted");
750
+ if (templateComment) {
751
+ const existingComments = events.filter(e => e.event === "comment posted");
752
+ existingComments.forEach(c => {
753
+ if (chance.bool({ likelihood: 50 })) {
754
+ events.push({
755
+ ...templateComment,
756
+ time: dayjs(c.time).add(chance.integer({ min: 1, max: 120 }), "minutes").toISOString(),
757
+ user_id: c.user_id,
758
+ is_reply: true,
759
+ comment_length: chance.integer({ min: 20, max: 300 }),
760
+ });
761
+ }
762
+ });
763
+ }
764
+ }
765
+
766
+ // -- HOOK 5: EDIT WAR DETECTION ---------------------------
767
+ // Users with >5 article_edited events get reduced edit_quality.
768
+ const editEvents = events.filter(e => e.event === "article edited");
769
+ if (editEvents.length > 5) {
770
+ editEvents.forEach(e => {
771
+ e.edit_quality = chance.floating({ min: 1.0, max: 2.0, fixed: 1 });
772
+ });
773
+ }
774
+
775
+ // -- HOOK 6: LURKER CHURN ---------------------------------
776
+ // Users with <5 total events lose 60% after day 10.
777
+ if (events.length < 5 && events.length > 0) {
778
+ const firstEventTime = dayjs(events[0].time);
779
+ const cutoff = firstEventTime.add(10, "days");
780
+ for (let i = events.length - 1; i >= 0; i--) {
781
+ if (dayjs(events[i].time).isAfter(cutoff) && chance.bool({ likelihood: 60 })) {
782
+ events.splice(i, 1);
783
+ }
784
+ }
785
+ }
786
+
787
+ return record;
788
+ }
789
+
790
+ return record;
791
+ },
792
+ };
793
+
794
+ export default config;