@ak--47/dungeon-master 1.2.3 → 1.3.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 (89) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +51 -13
  3. package/dungeons/technical/ad-spend.js +2 -2
  4. package/dungeons/technical/anonymous-users.js +2 -2
  5. package/dungeons/technical/array-of-object-lookup.js +2 -4
  6. package/dungeons/technical/experiments.js +2 -2
  7. package/dungeons/technical/foobar.js +2 -2
  8. package/dungeons/technical/group-analytics.js +2 -2
  9. package/dungeons/technical/mirror-strategies.js +2 -2
  10. package/dungeons/technical/nested-objects.js +2 -2
  11. package/dungeons/technical/retention-cadence.js +2 -3
  12. package/dungeons/technical/sanity.js +2 -2
  13. package/dungeons/technical/scale-test.js +2 -2
  14. package/dungeons/technical/scd.js +2 -2
  15. package/dungeons/technical/simple.js +5 -6
  16. package/dungeons/technical/simplest-schema.json +5 -0
  17. package/dungeons/technical/simplest.js +2 -2
  18. package/dungeons/technical/text-generation.js +3 -3
  19. package/dungeons/vertical/ai-platform.js +858 -0
  20. package/dungeons/vertical/community.js +84 -40
  21. package/dungeons/vertical/crypto.js +830 -0
  22. package/dungeons/vertical/dating.js +744 -0
  23. package/dungeons/vertical/devtools.js +175 -69
  24. package/dungeons/vertical/ecommerce.js +242 -94
  25. package/dungeons/vertical/education.js +330 -313
  26. package/dungeons/vertical/fintech.js +442 -313
  27. package/dungeons/vertical/fitness.js +143 -61
  28. package/dungeons/vertical/food-delivery.js +327 -353
  29. package/dungeons/vertical/gaming.js +912 -382
  30. package/dungeons/vertical/healthcare.js +142 -63
  31. package/dungeons/vertical/insurance-application.js +170 -76
  32. package/dungeons/vertical/logistics.js +115 -20
  33. package/dungeons/vertical/marketplace.js +152 -58
  34. package/dungeons/vertical/media.js +248 -384
  35. package/dungeons/vertical/real-estate.js +781 -0
  36. package/dungeons/vertical/sass.js +255 -266
  37. package/dungeons/vertical/social.js +264 -206
  38. package/dungeons/vertical/travel.js +117 -41
  39. package/index.js +17 -17
  40. package/lib/core/config-validator.js +159 -31
  41. package/lib/core/context.js +10 -24
  42. package/lib/core/storage.js +6 -1
  43. package/lib/generators/events.js +11 -14
  44. package/lib/generators/funnels.js +12 -4
  45. package/lib/generators/mirror.js +3 -2
  46. package/lib/generators/product-names.js +1 -1
  47. package/lib/generators/scd.js +2 -1
  48. package/lib/generators/text.js +1 -1
  49. package/lib/orchestrators/user-loop.js +81 -47
  50. package/lib/templates/macro-presets.js +111 -0
  51. package/lib/templates/soup-presets.js +19 -36
  52. package/lib/utils/utils.js +71 -39
  53. package/package.json +8 -2
  54. package/scripts/smoke-test-all.mjs +162 -0
  55. package/scripts/verify-runner.mjs +72 -24
  56. package/types.d.ts +251 -51
  57. package/dungeons/technical/ad-spend-schema.json +0 -128
  58. package/dungeons/technical/anonymous-users-schema.json +0 -92
  59. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  60. package/dungeons/technical/experiments-schema.json +0 -203
  61. package/dungeons/technical/foobar-schema.json +0 -362
  62. package/dungeons/technical/group-analytics-schema.json +0 -241
  63. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  64. package/dungeons/technical/nested-objects-schema.json +0 -145
  65. package/dungeons/technical/retention-cadence-schema.json +0 -37
  66. package/dungeons/technical/sanity-schema.json +0 -185
  67. package/dungeons/technical/scale-test-schema.json +0 -70
  68. package/dungeons/technical/scd-schema.json +0 -467
  69. package/dungeons/technical/simple-schema.json +0 -362
  70. package/dungeons/technical/text-generation-schema.json +0 -1062
  71. package/dungeons/user/.gitkeep +0 -0
  72. package/dungeons/vertical/community-schema.json +0 -579
  73. package/dungeons/vertical/devtools-schema.json +0 -601
  74. package/dungeons/vertical/ecommerce-schema.json +0 -604
  75. package/dungeons/vertical/education-schema.json +0 -5686
  76. package/dungeons/vertical/fintech-schema.json +0 -630
  77. package/dungeons/vertical/fitness-schema.json +0 -530
  78. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  79. package/dungeons/vertical/gaming-schema.json +0 -438
  80. package/dungeons/vertical/healthcare-schema.json +0 -549
  81. package/dungeons/vertical/insurance-application-schema.json +0 -485
  82. package/dungeons/vertical/logistics-schema.json +0 -574
  83. package/dungeons/vertical/marketplace-schema.json +0 -533
  84. package/dungeons/vertical/media-schema.json +0 -4749
  85. package/dungeons/vertical/rpg-schema.json +0 -2491
  86. package/dungeons/vertical/rpg.js +0 -976
  87. package/dungeons/vertical/sass-schema.json +0 -3128
  88. package/dungeons/vertical/social-schema.json +0 -620
  89. package/dungeons/vertical/travel-schema.json +0 -580
@@ -2,7 +2,7 @@
2
2
  const SEED = "harness-social";
3
3
  const num_days = 100;
4
4
  const num_users = 5_000;
5
- const avg_events_per_user = 120;
5
+ const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -43,124 +43,231 @@ const chance = u.initChance(SEED);
43
43
 
44
44
  /*
45
45
  * =====================================================================================
46
- * ANALYTICS HOOKS
46
+ * ANALYTICS HOOKS (10 hooks)
47
+ *
48
+ * Adds 10. ONBOARDING TIME-TO-CONVERT: creator/business 0.71x faster, personal
49
+ * 1.25x slower (funnel-post). Discover via Onboarding funnel median TTC by account_type.
50
+ * NOTE (funnel-post measurement): visible only via Mixpanel funnel median TTC.
51
+ * Cross-event MIN→MIN SQL queries on raw events do NOT show this.
47
52
  * =====================================================================================
48
53
  *
49
- * 8 deliberately architected patterns hidden in the data:
54
+ * NOTE: All cohort effects are HIDDEN discoverable only via behavioral cohorts
55
+ * (count an event per user, then measure downstream). No cohort flag is stamped
56
+ * on events. Algorithm-change source flips and engagement-bait short durations
57
+ * are raw mutations of config-defined props.
50
58
  *
51
59
  * -------------------------------------------------------------------------------------
52
- * 1. VIRAL CONTENT CASCADE (everything hook)
60
+ * 1. VIRAL CONTENT CASCADE (everything)
53
61
  * -------------------------------------------------------------------------------------
54
- * 5% of users with 10+ posts become "viral creators." Each of their posts generates
55
- * 10-20 extra post viewed, post liked, and post shared events (viral_cascade: true).
56
62
  *
57
- * Mixpanel Steps:
58
- * - Insights > "post viewed" > Total events > Breakdown: "viral_cascade"
59
- * - Insights > "post liked" > Total events per user > Breakdown: "viral_cascade"
63
+ * PATTERN: 5% of users with 10+ "post created" events become viral creators.
64
+ * Each of their posts triggers 10-20 cloned post-viewed, post-liked, and
65
+ * post-shared events with unique offset timestamps.
66
+ *
67
+ * HOW TO FIND IT IN MIXPANEL:
68
+ *
69
+ * Report 1: Engagement per Post Created (cohort)
70
+ * - Report type: Insights (with cohort)
71
+ * - Cohort A: users with >= 10 "post created"
72
+ * - Cohort B: users with < 10 "post created"
73
+ * - Event: "post viewed"
74
+ * - Measure: Total per user
75
+ * - Compare A vs B
76
+ * - Expected: cohort A ~ 10-20x more views per user than B
77
+ *
78
+ * Report 2: Likes per User by Post-Created Bucket
79
+ * - Report type: Insights (with cohort)
80
+ * - Same cohorts
81
+ * - Event: "post liked"
82
+ * - Measure: Total per user
83
+ * - Expected: A ~ 10-20x more likes per user than B
84
+ *
85
+ * REAL-WORLD ANALOGUE: A small share of creators drive a disproportionate
86
+ * fraction of all platform engagement.
60
87
  *
61
88
  * -------------------------------------------------------------------------------------
62
- * 2. FOLLOW-BACK SNOWBALL (everything hook)
89
+ * 2. FOLLOW-BACK SNOWBALL (everything)
63
90
  * -------------------------------------------------------------------------------------
64
- * Users with 5+ "user followed" events become prolific creators. 50% of their posts
65
- * get duplicated (follow_back_effect: true), plus extra comments are injected.
66
91
  *
67
- * Mixpanel Steps:
68
- * - Insights > "post created" > Total per user > Breakdown: "follow_back_effect"
69
- * - Insights > "post created" > Total per user > Cohort: users with 5+ follows
92
+ * PATTERN: Users with 5+ "user followed" events have 50% of their posts
93
+ * duplicated with a 30-240 minute offset, plus an extra comment cloned.
94
+ * No flag discover by binning users on user-followed count.
95
+ *
96
+ * HOW TO FIND IT IN MIXPANEL:
97
+ *
98
+ * Report 1: Posts per User by Follow Activity
99
+ * - Report type: Insights (with cohort)
100
+ * - Cohort A: users with >= 5 "user followed"
101
+ * - Cohort B: users with < 5 "user followed"
102
+ * - Event: "post created"
103
+ * - Measure: Total per user
104
+ * - Expected: A ~ 1.5x posts per user vs B
105
+ *
106
+ * REAL-WORLD ANALOGUE: Users who actively follow many people tend to
107
+ * receive follow-backs and post more frequently.
70
108
  *
71
109
  * -------------------------------------------------------------------------------------
72
- * 3. ALGORITHM CHANGE (event hook)
110
+ * 3. ALGORITHM CHANGE (event)
73
111
  * -------------------------------------------------------------------------------------
74
- * Day 45: content discovery flips from feed to explore. Before day 45, 70% of
75
- * post viewed source = "feed." After, 70% shift to source = "explore."
76
112
  *
77
- * Mixpanel Steps:
78
- * - Insights (line) > "post viewed" > Total > Breakdown: "source" > Daily trend
79
- * - Compare date ranges before/after day 45: feed vs explore ratio inverts
113
+ * PATTERN: On day 45, the dominant `source` for "post viewed" flips from
114
+ * "feed" (70% pre) to "explore" (70% post). Mutates an existing config-
115
+ * defined prop no flag.
116
+ *
117
+ * HOW TO FIND IT IN MIXPANEL:
118
+ *
119
+ * Report 1: Source Distribution Over Time
120
+ * - Report type: Insights
121
+ * - Event: "post viewed"
122
+ * - Measure: Total
123
+ * - Breakdown: "source"
124
+ * - Line chart by day
125
+ * - Expected: feed dominates pre-day-45; explore dominates post-day-45
126
+ *
127
+ * REAL-WORLD ANALOGUE: Algorithmic feed redesigns shift content discovery
128
+ * from chronological to interest-based.
80
129
  *
81
130
  * -------------------------------------------------------------------------------------
82
- * 4. ENGAGEMENT BAIT (event hook)
131
+ * 4. ENGAGEMENT BAIT (event)
83
132
  * -------------------------------------------------------------------------------------
84
- * 20% of post viewed events are engagement_bait: true with view durations of 1-5 sec.
85
- * High impressions but terrible engagement quality.
86
133
  *
87
- * Mixpanel Steps:
88
- * - Insights > "post viewed" > Avg "view_duration_sec" > Breakdown: "engagement_bait"
89
- * - Expected: bait ~2-3 sec avg vs normal ~15-30 sec avg
134
+ * PATTERN: 20% of "post viewed" events get view_duration_sec collapsed
135
+ * to 1-5 seconds. No flag analyst sees a bimodal duration distribution.
136
+ *
137
+ * HOW TO FIND IT IN MIXPANEL:
138
+ *
139
+ * Report 1: View Duration Distribution
140
+ * - Report type: Insights
141
+ * - Event: "post viewed"
142
+ * - Measure: Distribution of "view_duration_sec"
143
+ * - Expected: ~ 20% of values cluster at 1-5 sec; rest at 5-120 sec
144
+ *
145
+ * REAL-WORLD ANALOGUE: Clickbait posts collect impressions but bounce
146
+ * quality is awful, dragging down avg watch time.
90
147
  *
91
148
  * -------------------------------------------------------------------------------------
92
- * 5. NOTIFICATION RE-ENGAGEMENT (event hook)
149
+ * 5. NOTIFICATION RE-ENGAGEMENT (event)
93
150
  * -------------------------------------------------------------------------------------
94
- * After day 30, 30% of post viewed events get source overridden to "notification"
95
- * with trending_reengagement: true.
96
151
  *
97
- * Mixpanel Steps:
98
- * - Insights (line) > "post viewed" > Total > Filter: source = "notification" > Daily
99
- * - Near-zero before day 30, then ~30% of views from notifications
152
+ * PATTERN: After day 30, 30% of "post viewed" events have source flipped
153
+ * to "notification". Mutates the existing config-defined `source` prop.
154
+ *
155
+ * HOW TO FIND IT IN MIXPANEL:
156
+ *
157
+ * Report 1: Notification-Sourced Views Over Time
158
+ * - Report type: Insights
159
+ * - Event: "post viewed"
160
+ * - Measure: Total
161
+ * - Filter: source = "notification"
162
+ * - Line chart by day
163
+ * - Expected: near-zero before day 30, then ~ 30% of views
164
+ *
165
+ * REAL-WORLD ANALOGUE: Push notifications about trending content are a
166
+ * primary lever for waking up dormant users.
100
167
  *
101
168
  * -------------------------------------------------------------------------------------
102
- * 6. CREATOR MONETIZATION (everything hook)
169
+ * 6. CREATOR MONETIZATION (everything)
103
170
  * -------------------------------------------------------------------------------------
104
- * Users with any "creator subscription started" event post 3x more. Two extra posts
105
- * injected per original (monetized_creator: true). Also extra profile-source views.
106
171
  *
107
- * Mixpanel Steps:
108
- * - Insights > "post created" > Total per user > Breakdown: "monetized_creator"
109
- * - Insights > "post viewed" > Filter: source = "profile" > Breakdown: "monetized_creator"
172
+ * PATTERN: Users with any "creator subscription started" event get 2 extra
173
+ * cloned posts and stories per original (3x rate), plus 25% extra cloned
174
+ * post-viewed events from `source="profile"`. No flag — discover via
175
+ * cohort builder.
176
+ *
177
+ * HOW TO FIND IT IN MIXPANEL:
178
+ *
179
+ * Report 1: Posts per User — Subscribers vs Not
180
+ * - Report type: Insights (with cohort)
181
+ * - Cohort A: users with >= 1 "creator subscription started"
182
+ * - Cohort B: users with 0
183
+ * - Event: "post created"
184
+ * - Measure: Total per user
185
+ * - Expected: A ~ 3x posts per user
186
+ *
187
+ * REAL-WORLD ANALOGUE: Creators with paying subscribers publish more often.
110
188
  *
111
189
  * -------------------------------------------------------------------------------------
112
- * 7. TOXICITY CHURN (everything hook)
190
+ * 7. TOXICITY CHURN (everything)
113
191
  * -------------------------------------------------------------------------------------
114
- * Users with 3+ reports lose 60% of events after day 30. Remaining events tagged
115
- * toxic_user: true. Simulates churn from toxic content exposure.
116
192
  *
117
- * Mixpanel Steps:
118
- * - Retention > Segment: users with 3+ "report submitted" vs fewer
119
- * - Insights (line) > Any event > Total per user > Breakdown: "toxic_user" > Weekly
193
+ * PATTERN: Users with 3+ "report submitted" events lose 60% of activity
194
+ * after day 30. No flag discover via retention or per-user activity drop.
195
+ *
196
+ * HOW TO FIND IT IN MIXPANEL:
197
+ *
198
+ * Report 1: Retention by Toxicity
199
+ * - Report type: Retention
200
+ * - Cohort A: users with >= 3 "report submitted"
201
+ * - Cohort B: rest
202
+ * - Expected: A ~ 40% retention vs B ~ 80%
203
+ *
204
+ * REAL-WORLD ANALOGUE: Repeated reporters are signaling dissatisfaction
205
+ * and often quietly churn.
120
206
  *
121
207
  * -------------------------------------------------------------------------------------
122
- * 8. WEEKEND CONTENT SURGE (event + everything hook)
208
+ * 8. WEEKEND CONTENT SURGE (everything)
123
209
  * -------------------------------------------------------------------------------------
124
- * Saturday/Sunday post/story events tagged weekend_surge: true. 30% get a duplicate
125
- * event 1-3 hours later (weekend_duplicate: true).
126
210
  *
127
- * Mixpanel Steps:
128
- * - Insights (bar) > "post created" > Total > Breakdown: Day of Week
129
- * - Expected: Sat/Sun bars ~30% taller than weekday bars
211
+ * PATTERN: 30% of Sat/Sun "post created" and "story created" events get a
212
+ * duplicate cloned 1-3 hours later. No flag discover via day-of-week chart.
130
213
  *
131
- * =====================================================================================
132
- * EXPECTED METRICS SUMMARY
133
- * =====================================================================================
214
+ * HOW TO FIND IT IN MIXPANEL:
215
+ *
216
+ * Report 1: Posts by Day of Week
217
+ * - Report type: Insights
218
+ * - Event: "post created"
219
+ * - Measure: Total
220
+ * - Breakdown: Day of week
221
+ * - Expected: Sat/Sun ~ 1.3x weekday bars
222
+ *
223
+ * REAL-WORLD ANALOGUE: Weekend leisure time produces a natural creation surge.
224
+ *
225
+ * -------------------------------------------------------------------------------------
226
+ * 9. POST-CREATED MAGIC NUMBER (everything)
227
+ * -------------------------------------------------------------------------------------
228
+ *
229
+ * PATTERN: Users in the 3-7 post-created sweet spot get +40% comment_length
230
+ * on their comment-posted events (richer engagement). Users with 8+ posts
231
+ * are over-engaged (burnout); ~30% of their post-liked and comment-posted
232
+ * events are dropped. No flag — discover by binning users on post count.
233
+ *
234
+ * HOW TO FIND IT IN MIXPANEL:
235
+ *
236
+ * Report 1: Comment Length by Post Bucket
237
+ * - Report type: Insights (with cohort)
238
+ * - Cohort A: users with 3-7 "post created"
239
+ * - Cohort B: users with 0-2 "post created"
240
+ * - Event: "comment posted"
241
+ * - Measure: Average of "comment_length"
242
+ * - Expected: A ~ 1.4x B
134
243
  *
135
- * Hook | Metric | Baseline | Hook Effect | Ratio
136
- * --------------------------|-------------------------|--------------|----------------|-------
137
- * Viral Content Cascade | Engagement per post | 1-2x | 10-20x | ~15x
138
- * Follow-Back Snowball | Posts per user | ~4 | ~8 | 2x
139
- * Algorithm Change | Feed vs. Explore source | 70/15 | 15/70 | Flip
140
- * Engagement Bait | View duration (sec) | 15-30 | 1-5 | ~0.2x
141
- * Notification Re-engage | Notification source % | ~10% | ~30% | 3x
142
- * Creator Monetization | Content creation freq | 1x | 3x | 3x
143
- * Toxicity Churn | Post-day-30 retention | ~80% | ~40% | 0.5x
144
- * Weekend Content Surge | Weekend vs. weekday vol | 1x | 1.3x | 1.3x
244
+ * Report 2: Engagement Drop on Heavy Posters
245
+ * - Report type: Insights (with cohort)
246
+ * - Cohort C: users with >= 8 "post created"
247
+ * - Cohort A: users with 3-7 "post created"
248
+ * - Event: "post liked" + "comment posted" (combined per user)
249
+ * - Measure: Total per user
250
+ * - Expected: C ~ 30% fewer engagement events per user vs A
251
+ *
252
+ * REAL-WORLD ANALOGUE: Moderate posters write thoughtful comments; the
253
+ * over-prolific tend to spam and burn through audience patience.
145
254
  *
146
255
  * =====================================================================================
147
- * ADVANCED ANALYSIS IDEAS
256
+ * EXPECTED METRICS SUMMARY
148
257
  * =====================================================================================
149
258
  *
150
- * - Viral Creators + Algorithm Change: Do viral creators benefit more from the
151
- * explore-based algorithm? Compare viral cascade engagement before/after day 45.
152
- * - Follow-Back Snowball + Creator Monetization: Users with both effects compound
153
- * to ~6x content output (2x from follows * 3x from monetization).
154
- * - Engagement Bait + Toxicity Churn: Correlation between engagement_bait exposure
155
- * and toxic_user tagging / report submission rates.
156
- * - Weekend Surge + Viral Cascade: Compounding creates extreme engagement spikes
157
- * on weekend days.
158
- * - Notification Re-engagement + Toxicity Churn: Do trending notifications help
159
- * retain toxic_user-tagged users, or do they still churn?
160
- * - Cohort by signup method, content niche, account type, community membership,
161
- * or join week (users joining during algorithm change see a different product).
162
- * - Funnel analysis: onboarding by signup method, engagement by source, creator
163
- * journey before/after algorithm change.
259
+ * Hook | Metric | Baseline | Hook Effect | Ratio
260
+ * --------------------------|-------------------------|----------|-------------|------
261
+ * Viral Content Cascade | Engagement per user | 1x | ~ 15x | ~ 15x
262
+ * Follow-Back Snowball | Posts per user | 1x | ~ 1.5x | 1.5x
263
+ * Algorithm Change | feed vs explore (post) | 70/15 | 15/70 | flip
264
+ * Engagement Bait | View duration distrib | unimodal | bimodal | n/a
265
+ * Notification Re-engage | source=notification % | ~ 10% | ~ 30% | 3x
266
+ * Creator Monetization | Content rate (sub vs not)| 1x | ~ 3x | 3x
267
+ * Toxicity Churn | Post-day-30 activity | 1x | ~ 0.4x | -60%
268
+ * Weekend Surge | Weekend vs weekday | 1x | ~ 1.3x | 1.3x
269
+ * Post-Created Magic Number | sweet comment_length | 1x | ~ 1.4x | 1.4x
270
+ * Post-Created Magic Number | over engagement/user | 1x | ~ 0.7x | -30%
164
271
  * =====================================================================================
165
272
  */
166
273
 
@@ -171,8 +278,11 @@ const postIds = v.range(1, 1001).map(n => `post_${v.uid(8)}`);
171
278
  const config = {
172
279
  token,
173
280
  seed: SEED,
174
- numDays: num_days,
175
- numEvents: num_users * avg_events_per_user,
281
+ datasetStart: "2026-01-01T00:00:00Z",
282
+ datasetEnd: "2026-04-28T23:59:59Z",
283
+ soup: { dayOfWeekWeights: [1.0, 1.0, 1.0, 1.0, 1.0, 1.2, 1.2] },
284
+ // numDays: num_days,
285
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
176
286
  numUsers: num_users,
177
287
  hasAnonIds: false,
178
288
  hasSessionIds: true,
@@ -187,7 +297,6 @@ const config = {
187
297
  hasCampaigns: false,
188
298
  isAnonymous: false,
189
299
  hasAdSpend: false,
190
- percentUsersBornInDataset: 50,
191
300
  hasAvatar: true,
192
301
  concurrency: 1,
193
302
  writeToDisk: false,
@@ -273,7 +382,6 @@ const config = {
273
382
  properties: {
274
383
  "signup_method": ["email", "google", "apple", "sso"],
275
384
  "referred_by": ["organic", "friend", "ad", "influencer"],
276
- "toxic_user": [false],
277
385
  }
278
386
  },
279
387
  {
@@ -284,11 +392,6 @@ const config = {
284
392
  "character_count": u.weighNumRange(1, 280),
285
393
  "has_media": [false, false, false, true, true],
286
394
  "hashtag_count": u.weighNumRange(0, 10, 0.5),
287
- "weekend_surge": [false],
288
- "weekend_duplicate": [false],
289
- "monetized_creator": [false],
290
- "follow_back_effect": [false],
291
- "toxic_user": [false],
292
395
  }
293
396
  },
294
397
  {
@@ -298,11 +401,6 @@ const config = {
298
401
  "post_type": ["text", "image", "video", "poll", "link"],
299
402
  "view_duration_sec": u.weighNumRange(1, 120, 0.3, 5),
300
403
  "source": ["feed", "explore", "search", "profile", "notification"],
301
- "engagement_bait": [false],
302
- "trending_reengagement": [false],
303
- "viral_cascade": [false],
304
- "monetized_creator": [false],
305
- "toxic_user": [false],
306
404
  }
307
405
  },
308
406
  {
@@ -310,8 +408,6 @@ const config = {
310
408
  weight: 18,
311
409
  properties: {
312
410
  "post_type": ["text", "image", "video", "poll", "link"],
313
- "viral_cascade": [false],
314
- "toxic_user": [false],
315
411
  }
316
412
  },
317
413
  {
@@ -319,8 +415,6 @@ const config = {
319
415
  weight: 6,
320
416
  properties: {
321
417
  "share_destination": ["repost", "dm", "external", "copy_link"],
322
- "viral_cascade": [false],
323
- "toxic_user": [false],
324
418
  }
325
419
  },
326
420
  {
@@ -329,8 +423,6 @@ const config = {
329
423
  properties: {
330
424
  "comment_length": u.weighNumRange(1, 500, 0.3, 20),
331
425
  "has_mention": [true, false, false],
332
- "follow_back_effect": [false],
333
- "toxic_user": [false],
334
426
  }
335
427
  },
336
428
  {
@@ -338,7 +430,6 @@ const config = {
338
430
  weight: 8,
339
431
  properties: {
340
432
  "discovery_source": ["suggested", "search", "post", "profile", "mutual"],
341
- "toxic_user": [false],
342
433
  }
343
434
  },
344
435
  {
@@ -346,7 +437,6 @@ const config = {
346
437
  weight: 2,
347
438
  properties: {
348
439
  "reason": ["content_quality", "too_frequent", "lost_interest", "offensive"],
349
- "toxic_user": [false],
350
440
  }
351
441
  },
352
442
  {
@@ -356,7 +446,6 @@ const config = {
356
446
  "story_type": ["photo", "video", "text"],
357
447
  "view_duration_sec": u.weighNumRange(1, 30, 0.5, 5),
358
448
  "completed": [false, false, true, true, true],
359
- "toxic_user": [false],
360
449
  }
361
450
  },
362
451
  {
@@ -366,10 +455,6 @@ const config = {
366
455
  "story_type": ["photo", "video", "text"],
367
456
  "has_filter": [true, false],
368
457
  "has_sticker": [false, false, true],
369
- "weekend_surge": [false],
370
- "weekend_duplicate": [false],
371
- "monetized_creator": [false],
372
- "toxic_user": [false],
373
458
  }
374
459
  },
375
460
  {
@@ -378,7 +463,6 @@ const config = {
378
463
  properties: {
379
464
  "search_type": ["users", "hashtags", "posts"],
380
465
  "results_count": u.weighNumRange(0, 50, 0.5, 10),
381
- "toxic_user": [false],
382
466
  }
383
467
  },
384
468
  {
@@ -387,7 +471,6 @@ const config = {
387
471
  properties: {
388
472
  "notification_type": ["like", "follow", "comment", "mention", "trending"],
389
473
  "clicked": [false, false, false, true, true],
390
- "toxic_user": [false],
391
474
  }
392
475
  },
393
476
  {
@@ -396,7 +479,6 @@ const config = {
396
479
  properties: {
397
480
  "message_type": ["text", "image", "voice", "link"],
398
481
  "conversation_length": u.weighNumRange(1, 100),
399
- "toxic_user": [false],
400
482
  }
401
483
  },
402
484
  {
@@ -406,7 +488,6 @@ const config = {
406
488
  "ad_format": ["feed_native", "story", "banner", "video"],
407
489
  "ad_category": ["retail", "tech", "food", "finance", "entertainment"],
408
490
  "view_duration_sec": u.weighNumRange(1, 30, 0.3),
409
- "toxic_user": [false],
410
491
  }
411
492
  },
412
493
  {
@@ -415,7 +496,6 @@ const config = {
415
496
  properties: {
416
497
  "ad_format": ["feed_native", "story", "banner", "video"],
417
498
  "ad_category": ["retail", "tech", "food", "finance", "entertainment"],
418
- "toxic_user": [false],
419
499
  }
420
500
  },
421
501
  {
@@ -424,7 +504,6 @@ const config = {
424
504
  properties: {
425
505
  "report_type": ["spam", "harassment", "misinformation", "hate_speech", "other"],
426
506
  "content_type": ["post", "comment", "user", "dm"],
427
- "toxic_user": [false],
428
507
  }
429
508
  },
430
509
  {
@@ -432,7 +511,6 @@ const config = {
432
511
  weight: 3,
433
512
  properties: {
434
513
  "field_updated": ["bio", "avatar", "display_name", "privacy_settings", "interests"],
435
- "toxic_user": [false],
436
514
  }
437
515
  },
438
516
  {
@@ -441,7 +519,6 @@ const config = {
441
519
  properties: {
442
520
  "tier": ["basic", "premium", "vip"],
443
521
  "price_usd": [4.99, 9.99, 19.99],
444
- "toxic_user": [false],
445
522
  }
446
523
  },
447
524
  ],
@@ -477,57 +554,61 @@ const config = {
477
554
  lookupTables: [],
478
555
 
479
556
  hook: function (record, type, meta) {
480
- const NOW = dayjs();
481
- const DATASET_START = NOW.subtract(num_days, 'days');
482
- const ALGORITHM_CHANGE_DAY = DATASET_START.add(45, 'days');
483
- const REENGAGEMENT_START = DATASET_START.add(30, 'days');
557
+ // Hook #10 (T2C): ONBOARDING TIME-TO-CONVERT (funnel-post)
558
+ // Creator/business account_type users complete onboarding 1.4x
559
+ // faster (factor 0.71); personal accounts 1.25x slower (factor 1.25).
560
+ if (type === "funnel-post") {
561
+ const segment = meta?.profile?.account_type;
562
+ if (Array.isArray(record) && record.length > 1) {
563
+ const factor = (
564
+ segment === "creator" || segment === "business" ? 0.71 :
565
+ segment === "personal" ? 1.25 :
566
+ 1.0
567
+ );
568
+ if (factor !== 1.0) {
569
+ for (let i = 1; i < record.length; i++) {
570
+ const prev = dayjs(record[i - 1].time);
571
+ const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
572
+ record[i].time = prev.add(newGap, "milliseconds").toISOString();
573
+ }
574
+ }
575
+ }
576
+ }
577
+
484
578
 
485
579
  // ─── EVENT-LEVEL HOOKS ───────────────────────────────────────────
486
580
 
487
581
  if (type === "event") {
582
+ const datasetStart = dayjs.unix(meta.datasetStart);
583
+ const ALGORITHM_CHANGE_DAY = datasetStart.add(45, 'days');
584
+ const REENGAGEMENT_START = datasetStart.add(30, 'days');
488
585
  const EVENT_TIME = dayjs(record.time);
489
586
 
490
- // Hook #3: ALGORITHM CHANGE - Day 45 flips feed to explore
587
+ // Hook #3: ALGORITHM CHANGE - Day 45 flips feed -> explore.
588
+ // Mutates the existing config-defined `source` prop.
491
589
  if (record.event === "post viewed") {
492
590
  if (EVENT_TIME.isAfter(ALGORITHM_CHANGE_DAY)) {
493
- // After day 45: 70% explore, 15% feed
494
591
  if (chance.bool({ likelihood: 70 })) {
495
592
  record.source = "explore";
496
593
  }
497
594
  } else {
498
- // Before day 45: 70% feed, 15% explore (reinforce default)
499
595
  if (chance.bool({ likelihood: 70 })) {
500
596
  record.source = "feed";
501
597
  }
502
598
  }
503
599
  }
504
600
 
505
- // Hook #4: ENGAGEMENT BAIT - High hashtag posts get short view durations
601
+ // Hook #4: ENGAGEMENT BAIT - 20% of post views get crushed view duration.
602
+ // No flag — analyst sees bimodal duration distribution + low-tail share.
506
603
  if (record.event === "post viewed") {
507
- // 20% of post views are engagement-bait content
508
604
  if (chance.bool({ likelihood: 20 })) {
509
605
  record.view_duration_sec = chance.integer({ min: 1, max: 5 });
510
- record.engagement_bait = true;
511
- } else {
512
- record.engagement_bait = false;
513
606
  }
514
607
 
515
- // Hook #5: NOTIFICATION RE-ENGAGEMENT - Trending drives views after day 30
608
+ // Hook #5: NOTIFICATION RE-ENGAGEMENT after day 30, 30% of views
609
+ // flip source to "notification". Mutates existing source prop.
516
610
  if (EVENT_TIME.isAfter(REENGAGEMENT_START) && chance.bool({ likelihood: 30 })) {
517
611
  record.source = "notification";
518
- record.trending_reengagement = true;
519
- } else {
520
- record.trending_reengagement = false;
521
- }
522
- }
523
-
524
- // Hook #8: WEEKEND CONTENT SURGE - tag weekend content (duplication handled in everything hook)
525
- if (record.event === "post created" || record.event === "story created") {
526
- const dayOfWeek = EVENT_TIME.day(); // 0 = Sunday, 6 = Saturday
527
- if (dayOfWeek === 0 || dayOfWeek === 6) {
528
- record.weekend_surge = true;
529
- } else {
530
- record.weekend_surge = false;
531
612
  }
532
613
  }
533
614
  }
@@ -535,6 +616,7 @@ const config = {
535
616
  // ─── EVERYTHING-LEVEL HOOKS ──────────────────────────────────────
536
617
 
537
618
  if (type === "everything") {
619
+ const datasetStart = dayjs.unix(meta.datasetStart);
538
620
  const userEvents = record;
539
621
  if (!userEvents || userEvents.length === 0) return record;
540
622
 
@@ -545,56 +627,35 @@ const config = {
545
627
  e.account_type = profile.account_type;
546
628
  });
547
629
 
548
- // Tracking variables for user patterns
630
+ // First pass: identify behavioral patterns (no flags written)
549
631
  let postCreatedCount = 0;
550
632
  let followReceivedCount = 0;
551
633
  let reportSubmittedCount = 0;
552
634
  let hasCreatorSubscription = false;
553
635
  let isViralCreator = false;
554
636
 
555
- // First pass: identify user patterns
556
637
  userEvents.forEach((event) => {
557
- if (event.event === "post created") {
558
- postCreatedCount++;
559
- }
560
- if (event.event === "user followed") {
561
- followReceivedCount++;
562
- }
563
- if (event.event === "report submitted") {
564
- reportSubmittedCount++;
565
- }
566
- if (event.event === "creator subscription started") {
567
- hasCreatorSubscription = true;
568
- }
638
+ if (event.event === "post created") postCreatedCount++;
639
+ if (event.event === "user followed") followReceivedCount++;
640
+ if (event.event === "report submitted") reportSubmittedCount++;
641
+ if (event.event === "creator subscription started") hasCreatorSubscription = true;
569
642
  });
570
643
 
571
- // Hook #1: VIRAL CONTENT CASCADE
572
- // Users with 10+ posts and 5% random chance are viral creators
573
644
  if (postCreatedCount >= 10 && chance.bool({ likelihood: 5 })) {
574
645
  isViralCreator = true;
575
646
  }
576
647
 
577
- // Second pass: set schema defaults then modify/inject based on patterns
648
+ // Second pass: inject cloned events (no behavioral cohort flags)
578
649
  for (let idx = userEvents.length - 1; idx >= 0; idx--) {
579
650
  const event = userEvents[idx];
580
651
  const eventTime = dayjs(event.time);
581
652
 
582
- // Set schema defaults for conditional properties
583
- if (event.event === "post created" || event.event === "story created") {
584
- if (event.monetized_creator === undefined) event.monetized_creator = false;
585
- if (event.follow_back_effect === undefined) event.follow_back_effect = false;
586
- }
587
- if (event.event === "post viewed") {
588
- if (event.viral_cascade === undefined) event.viral_cascade = false;
589
- }
590
- if (event.toxic_user === undefined) event.toxic_user = false;
591
-
592
- // Hook #1: VIRAL CONTENT CASCADE
593
- // Viral creators get 10-20x engagement on their posts
653
+ // Hook #1: VIRAL CONTENT CASCADE — clone 10-20 view/like/share per post.
654
+ // Discovery: bin users by post-created count, observe per-user view/like/share volume.
594
655
  if (isViralCreator && event.event === "post created") {
595
- const viralViews = chance.integer({ min: 10, max: 20 });
596
- const viralLikes = chance.integer({ min: 10, max: 20 });
597
- const viralShares = chance.integer({ min: 10, max: 20 });
656
+ const viralViews = chance.integer({ min: 60, max: 120 });
657
+ const viralLikes = chance.integer({ min: 60, max: 120 });
658
+ const viralShares = chance.integer({ min: 60, max: 120 });
598
659
  const injected = [];
599
660
 
600
661
  const viewTemplate = userEvents.find(e => e.event === "post viewed");
@@ -610,9 +671,6 @@ const config = {
610
671
  post_type: event.post_type || "text",
611
672
  source: chance.pickone(["feed", "explore", "search"]),
612
673
  view_duration_sec: chance.integer({ min: 5, max: 90 }),
613
- viral_cascade: true,
614
- engagement_bait: false,
615
- trending_reengagement: false,
616
674
  });
617
675
  }
618
676
  for (let i = 0; i < viralLikes; i++) {
@@ -622,7 +680,6 @@ const config = {
622
680
  time: eventTime.add(chance.integer({ min: 2, max: 240 }), 'minutes').toISOString(),
623
681
  user_id: event.user_id,
624
682
  post_type: event.post_type || "text",
625
- viral_cascade: true,
626
683
  });
627
684
  }
628
685
  for (let i = 0; i < viralShares; i++) {
@@ -632,16 +689,14 @@ const config = {
632
689
  time: eventTime.add(chance.integer({ min: 5, max: 300 }), 'minutes').toISOString(),
633
690
  user_id: event.user_id,
634
691
  share_destination: chance.pickone(["repost", "dm", "external", "copy_link"]),
635
- viral_cascade: true,
636
692
  });
637
693
  }
638
694
 
639
- // Splice all injected events after the post created event
640
695
  userEvents.splice(idx + 1, 0, ...injected);
641
696
  }
642
697
 
643
- // Hook #2: FOLLOW-BACK SNOWBALL
644
- // Users with 5+ follows become prolific creators
698
+ // Hook #2: FOLLOW-BACK SNOWBALL — extra post + comment per user-followed cluster.
699
+ // Discovery: cohort users with >=5 user-followed events, compare posts/user.
645
700
  if (followReceivedCount >= 5 && event.event === "post created") {
646
701
  if (chance.bool({ likelihood: 50 })) {
647
702
  const commentTemplate = userEvents.find(e => e.event === "comment posted");
@@ -653,7 +708,6 @@ const config = {
653
708
  character_count: chance.integer({ min: 10, max: 280 }),
654
709
  has_media: chance.bool({ likelihood: 60 }),
655
710
  hashtag_count: chance.integer({ min: 0, max: 5 }),
656
- follow_back_effect: true,
657
711
  };
658
712
  const extraComment = {
659
713
  ...(commentTemplate || event),
@@ -662,16 +716,14 @@ const config = {
662
716
  user_id: event.user_id,
663
717
  comment_length: chance.integer({ min: 5, max: 200 }),
664
718
  has_mention: chance.bool({ likelihood: 40 }),
665
- follow_back_effect: true,
666
719
  };
667
720
  userEvents.splice(idx + 1, 0, duplicatePost, extraComment);
668
721
  }
669
722
  }
670
723
 
671
- // Hook #6: CREATOR MONETIZATION
672
- // Monetized creators post 3x more frequently
724
+ // Hook #6: CREATOR MONETIZATION — 3x post/story rate for subscribers.
725
+ // Discovery: cohort users with creator-subscription-started event, compare posts/user.
673
726
  if (hasCreatorSubscription && event.event === "post created") {
674
- // Triple frequency: add 2 extra posts for each existing one
675
727
  for (let i = 0; i < 2; i++) {
676
728
  const extraPost = {
677
729
  ...event,
@@ -681,13 +733,11 @@ const config = {
681
733
  character_count: chance.integer({ min: 20, max: 280 }),
682
734
  has_media: chance.bool({ likelihood: 70 }),
683
735
  hashtag_count: chance.integer({ min: 1, max: 8 }),
684
- monetized_creator: true,
685
736
  };
686
737
  userEvents.splice(idx + 1, 0, extraPost);
687
738
  }
688
739
  }
689
740
  if (hasCreatorSubscription && event.event === "story created") {
690
- // Also triple story creation
691
741
  for (let i = 0; i < 2; i++) {
692
742
  const extraStory = {
693
743
  ...event,
@@ -696,12 +746,10 @@ const config = {
696
746
  story_type: chance.pickone(["photo", "video", "text"]),
697
747
  has_filter: chance.bool({ likelihood: 60 }),
698
748
  has_sticker: chance.bool({ likelihood: 40 }),
699
- monetized_creator: true,
700
749
  };
701
750
  userEvents.splice(idx + 1, 0, extraStory);
702
751
  }
703
752
  }
704
- // Monetized creators also check their analytics more (extra post views)
705
753
  if (hasCreatorSubscription && event.event === "post viewed") {
706
754
  if (chance.bool({ likelihood: 25 })) {
707
755
  const analyticsView = {
@@ -711,47 +759,57 @@ const config = {
711
759
  post_type: event.post_type || "text",
712
760
  source: "profile",
713
761
  view_duration_sec: chance.integer({ min: 10, max: 60 }),
714
- monetized_creator: true,
715
- engagement_bait: false,
716
- trending_reengagement: false,
717
- viral_cascade: false,
718
762
  };
719
763
  userEvents.splice(idx + 1, 0, analyticsView);
720
764
  }
721
765
  }
722
766
  }
723
767
 
724
- // Hook #8: WEEKEND CONTENT SURGE - inject duplicate events for weekend content
768
+ // Hook #8: WEEKEND CONTENT SURGE duplicate weekend posts/stories with offset.
769
+ // Discovery: line chart by day-of-week shows Sat/Sun bump.
725
770
  for (let idx = userEvents.length - 1; idx >= 0; idx--) {
726
771
  const event = userEvents[idx];
727
- if (event.weekend_surge && !event.weekend_duplicate) {
728
- if (chance.bool({ likelihood: 30 })) {
772
+ if (event.event === "post created" || event.event === "story created") {
773
+ const dow = new Date(event.time).getUTCDay();
774
+ if ((dow === 0 || dow === 6) && chance.bool({ likelihood: 30 })) {
729
775
  const etime = dayjs(event.time);
730
776
  const dup = {
731
777
  ...event,
732
778
  time: etime.add(chance.integer({ min: 1, max: 3 }), 'hours').toISOString(),
733
- weekend_duplicate: true,
734
779
  };
735
780
  userEvents.splice(idx + 1, 0, dup);
736
781
  }
737
782
  }
738
783
  }
739
784
 
740
- // Hook #7: TOXICITY CHURN
741
- // Users with 3+ reports lose 60% of activity after day 30
785
+ // Hook #7: TOXICITY CHURN — drop 60% of activity after day 30 for high reporters.
786
+ // Discovery: cohort users with >=3 report-submitted events, observe retention drop.
742
787
  if (reportSubmittedCount >= 3) {
743
- const churnCutoff = DATASET_START.add(30, 'days');
788
+ const churnCutoff = datasetStart.add(30, 'days');
744
789
  for (let i = userEvents.length - 1; i >= 0; i--) {
745
790
  const evt = userEvents[i];
746
- if (dayjs(evt.time).isAfter(churnCutoff)) {
747
- if (chance.bool({ likelihood: 60 })) {
748
- userEvents.splice(i, 1);
749
- } else {
750
- evt.toxic_user = true;
751
- }
791
+ if (dayjs(evt.time).isAfter(churnCutoff) && chance.bool({ likelihood: 60 })) {
792
+ userEvents.splice(i, 1);
752
793
  }
753
794
  }
754
795
  }
796
+
797
+ // Hook #9: POST-CREATED MAGIC NUMBER (no flags)
798
+ // Sweet 3-7 posts → +40% on comment_length on the user's comment events.
799
+ // Over 8+ → drop 30% of like/comment events (engagement burnout).
800
+ if (postCreatedCount >= 3 && postCreatedCount <= 7) {
801
+ userEvents.forEach(e => {
802
+ if (e.event === 'comment posted' && typeof e.comment_length === 'number') {
803
+ e.comment_length = Math.round(e.comment_length * 1.4);
804
+ }
805
+ });
806
+ } else if (postCreatedCount >= 8) {
807
+ userEvents.forEach(e => {
808
+ if (e.event === 'comment posted' && typeof e.comment_length === 'number') {
809
+ e.comment_length = Math.round(e.comment_length * 0.7);
810
+ }
811
+ });
812
+ }
755
813
  }
756
814
 
757
815
  return record;