@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.
- package/CHANGELOG.md +42 -0
- package/README.md +51 -13
- package/dungeons/technical/ad-spend.js +2 -2
- package/dungeons/technical/anonymous-users.js +2 -2
- package/dungeons/technical/array-of-object-lookup.js +2 -4
- package/dungeons/technical/experiments.js +2 -2
- package/dungeons/technical/foobar.js +2 -2
- package/dungeons/technical/group-analytics.js +2 -2
- package/dungeons/technical/mirror-strategies.js +2 -2
- package/dungeons/technical/nested-objects.js +2 -2
- package/dungeons/technical/retention-cadence.js +2 -3
- package/dungeons/technical/sanity.js +2 -2
- package/dungeons/technical/scale-test.js +2 -2
- package/dungeons/technical/scd.js +2 -2
- package/dungeons/technical/simple.js +5 -6
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +3 -3
- package/dungeons/vertical/ai-platform.js +858 -0
- package/dungeons/vertical/community.js +84 -40
- package/dungeons/vertical/crypto.js +830 -0
- package/dungeons/vertical/dating.js +744 -0
- package/dungeons/vertical/devtools.js +175 -69
- package/dungeons/vertical/ecommerce.js +242 -94
- package/dungeons/vertical/education.js +330 -313
- package/dungeons/vertical/fintech.js +442 -313
- package/dungeons/vertical/fitness.js +143 -61
- package/dungeons/vertical/food-delivery.js +327 -353
- package/dungeons/vertical/gaming.js +912 -382
- package/dungeons/vertical/healthcare.js +142 -63
- package/dungeons/vertical/insurance-application.js +170 -76
- package/dungeons/vertical/logistics.js +115 -20
- package/dungeons/vertical/marketplace.js +152 -58
- package/dungeons/vertical/media.js +248 -384
- package/dungeons/vertical/real-estate.js +781 -0
- package/dungeons/vertical/sass.js +255 -266
- package/dungeons/vertical/social.js +264 -206
- package/dungeons/vertical/travel.js +117 -41
- package/index.js +17 -17
- package/lib/core/config-validator.js +159 -31
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +11 -14
- package/lib/generators/funnels.js +12 -4
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/orchestrators/user-loop.js +81 -47
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/lib/utils/utils.js +71 -39
- package/package.json +8 -2
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +251 -51
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -438
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/rpg-schema.json +0 -2491
- package/dungeons/vertical/rpg.js +0 -976
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- 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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
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
|
|
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
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
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
|
|
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
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
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
|
|
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
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
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
|
|
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
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
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
|
|
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
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
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
|
|
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
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
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 (
|
|
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
|
-
*
|
|
128
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
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
|
-
*
|
|
256
|
+
* EXPECTED METRICS SUMMARY
|
|
148
257
|
* =====================================================================================
|
|
149
258
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* -
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* -
|
|
161
|
-
*
|
|
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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
|
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 -
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
559
|
-
|
|
560
|
-
if (event.event === "
|
|
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:
|
|
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
|
-
//
|
|
583
|
-
|
|
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:
|
|
596
|
-
const viralLikes = chance.integer({ min:
|
|
597
|
-
const viralShares = chance.integer({ min:
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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.
|
|
728
|
-
|
|
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
|
-
//
|
|
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 =
|
|
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
|
-
|
|
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;
|