@ak--47/dungeon-master 1.3.0 → 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/dungeons/technical/array-of-object-lookup.js +0 -2
- package/dungeons/technical/simple.js +3 -4
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/text-generation.js +1 -1
- package/dungeons/vertical/ai-platform.js +192 -133
- package/dungeons/vertical/community.js +64 -34
- package/dungeons/vertical/crypto.js +333 -224
- package/dungeons/vertical/dating.js +228 -282
- package/dungeons/vertical/devtools.js +164 -62
- package/dungeons/vertical/ecommerce.js +240 -91
- package/dungeons/vertical/education.js +328 -310
- package/dungeons/vertical/fintech.js +437 -311
- package/dungeons/vertical/fitness.js +141 -58
- package/dungeons/vertical/food-delivery.js +298 -318
- package/dungeons/vertical/gaming.js +378 -231
- package/dungeons/vertical/healthcare.js +140 -60
- package/dungeons/vertical/insurance-application.js +168 -73
- package/dungeons/vertical/logistics.js +95 -6
- package/dungeons/vertical/marketplace.js +137 -51
- package/dungeons/vertical/media.js +241 -386
- package/dungeons/vertical/real-estate.js +322 -315
- package/dungeons/vertical/sass.js +253 -263
- package/dungeons/vertical/social.js +262 -206
- package/dungeons/vertical/travel.js +59 -16
- package/index.js +17 -17
- package/lib/core/config-validator.js +93 -21
- 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 +47 -47
- package/lib/utils/utils.js +71 -39
- package/package.json +1 -1
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +38 -15
- 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/vertical/ai-platform-schema.json +0 -617
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/crypto-schema.json +0 -546
- package/dungeons/vertical/dating-schema.json +0 -401
- 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 -2703
- 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/real-estate-schema.json +0 -527
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
|
@@ -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
|
+
* -------------------------------------------------------------------------------------
|
|
134
228
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
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
|
|
243
|
+
*
|
|
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,7 +278,10 @@ const postIds = v.range(1, 1001).map(n => `post_${v.uid(8)}`);
|
|
|
171
278
|
const config = {
|
|
172
279
|
token,
|
|
173
280
|
seed: SEED,
|
|
174
|
-
|
|
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,
|
|
175
285
|
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
176
286
|
numUsers: num_users,
|
|
177
287
|
hasAnonIds: false,
|
|
@@ -272,7 +382,6 @@ const config = {
|
|
|
272
382
|
properties: {
|
|
273
383
|
"signup_method": ["email", "google", "apple", "sso"],
|
|
274
384
|
"referred_by": ["organic", "friend", "ad", "influencer"],
|
|
275
|
-
"toxic_user": [false],
|
|
276
385
|
}
|
|
277
386
|
},
|
|
278
387
|
{
|
|
@@ -283,11 +392,6 @@ const config = {
|
|
|
283
392
|
"character_count": u.weighNumRange(1, 280),
|
|
284
393
|
"has_media": [false, false, false, true, true],
|
|
285
394
|
"hashtag_count": u.weighNumRange(0, 10, 0.5),
|
|
286
|
-
"weekend_surge": [false],
|
|
287
|
-
"weekend_duplicate": [false],
|
|
288
|
-
"monetized_creator": [false],
|
|
289
|
-
"follow_back_effect": [false],
|
|
290
|
-
"toxic_user": [false],
|
|
291
395
|
}
|
|
292
396
|
},
|
|
293
397
|
{
|
|
@@ -297,11 +401,6 @@ const config = {
|
|
|
297
401
|
"post_type": ["text", "image", "video", "poll", "link"],
|
|
298
402
|
"view_duration_sec": u.weighNumRange(1, 120, 0.3, 5),
|
|
299
403
|
"source": ["feed", "explore", "search", "profile", "notification"],
|
|
300
|
-
"engagement_bait": [false],
|
|
301
|
-
"trending_reengagement": [false],
|
|
302
|
-
"viral_cascade": [false],
|
|
303
|
-
"monetized_creator": [false],
|
|
304
|
-
"toxic_user": [false],
|
|
305
404
|
}
|
|
306
405
|
},
|
|
307
406
|
{
|
|
@@ -309,8 +408,6 @@ const config = {
|
|
|
309
408
|
weight: 18,
|
|
310
409
|
properties: {
|
|
311
410
|
"post_type": ["text", "image", "video", "poll", "link"],
|
|
312
|
-
"viral_cascade": [false],
|
|
313
|
-
"toxic_user": [false],
|
|
314
411
|
}
|
|
315
412
|
},
|
|
316
413
|
{
|
|
@@ -318,8 +415,6 @@ const config = {
|
|
|
318
415
|
weight: 6,
|
|
319
416
|
properties: {
|
|
320
417
|
"share_destination": ["repost", "dm", "external", "copy_link"],
|
|
321
|
-
"viral_cascade": [false],
|
|
322
|
-
"toxic_user": [false],
|
|
323
418
|
}
|
|
324
419
|
},
|
|
325
420
|
{
|
|
@@ -328,8 +423,6 @@ const config = {
|
|
|
328
423
|
properties: {
|
|
329
424
|
"comment_length": u.weighNumRange(1, 500, 0.3, 20),
|
|
330
425
|
"has_mention": [true, false, false],
|
|
331
|
-
"follow_back_effect": [false],
|
|
332
|
-
"toxic_user": [false],
|
|
333
426
|
}
|
|
334
427
|
},
|
|
335
428
|
{
|
|
@@ -337,7 +430,6 @@ const config = {
|
|
|
337
430
|
weight: 8,
|
|
338
431
|
properties: {
|
|
339
432
|
"discovery_source": ["suggested", "search", "post", "profile", "mutual"],
|
|
340
|
-
"toxic_user": [false],
|
|
341
433
|
}
|
|
342
434
|
},
|
|
343
435
|
{
|
|
@@ -345,7 +437,6 @@ const config = {
|
|
|
345
437
|
weight: 2,
|
|
346
438
|
properties: {
|
|
347
439
|
"reason": ["content_quality", "too_frequent", "lost_interest", "offensive"],
|
|
348
|
-
"toxic_user": [false],
|
|
349
440
|
}
|
|
350
441
|
},
|
|
351
442
|
{
|
|
@@ -355,7 +446,6 @@ const config = {
|
|
|
355
446
|
"story_type": ["photo", "video", "text"],
|
|
356
447
|
"view_duration_sec": u.weighNumRange(1, 30, 0.5, 5),
|
|
357
448
|
"completed": [false, false, true, true, true],
|
|
358
|
-
"toxic_user": [false],
|
|
359
449
|
}
|
|
360
450
|
},
|
|
361
451
|
{
|
|
@@ -365,10 +455,6 @@ const config = {
|
|
|
365
455
|
"story_type": ["photo", "video", "text"],
|
|
366
456
|
"has_filter": [true, false],
|
|
367
457
|
"has_sticker": [false, false, true],
|
|
368
|
-
"weekend_surge": [false],
|
|
369
|
-
"weekend_duplicate": [false],
|
|
370
|
-
"monetized_creator": [false],
|
|
371
|
-
"toxic_user": [false],
|
|
372
458
|
}
|
|
373
459
|
},
|
|
374
460
|
{
|
|
@@ -377,7 +463,6 @@ const config = {
|
|
|
377
463
|
properties: {
|
|
378
464
|
"search_type": ["users", "hashtags", "posts"],
|
|
379
465
|
"results_count": u.weighNumRange(0, 50, 0.5, 10),
|
|
380
|
-
"toxic_user": [false],
|
|
381
466
|
}
|
|
382
467
|
},
|
|
383
468
|
{
|
|
@@ -386,7 +471,6 @@ const config = {
|
|
|
386
471
|
properties: {
|
|
387
472
|
"notification_type": ["like", "follow", "comment", "mention", "trending"],
|
|
388
473
|
"clicked": [false, false, false, true, true],
|
|
389
|
-
"toxic_user": [false],
|
|
390
474
|
}
|
|
391
475
|
},
|
|
392
476
|
{
|
|
@@ -395,7 +479,6 @@ const config = {
|
|
|
395
479
|
properties: {
|
|
396
480
|
"message_type": ["text", "image", "voice", "link"],
|
|
397
481
|
"conversation_length": u.weighNumRange(1, 100),
|
|
398
|
-
"toxic_user": [false],
|
|
399
482
|
}
|
|
400
483
|
},
|
|
401
484
|
{
|
|
@@ -405,7 +488,6 @@ const config = {
|
|
|
405
488
|
"ad_format": ["feed_native", "story", "banner", "video"],
|
|
406
489
|
"ad_category": ["retail", "tech", "food", "finance", "entertainment"],
|
|
407
490
|
"view_duration_sec": u.weighNumRange(1, 30, 0.3),
|
|
408
|
-
"toxic_user": [false],
|
|
409
491
|
}
|
|
410
492
|
},
|
|
411
493
|
{
|
|
@@ -414,7 +496,6 @@ const config = {
|
|
|
414
496
|
properties: {
|
|
415
497
|
"ad_format": ["feed_native", "story", "banner", "video"],
|
|
416
498
|
"ad_category": ["retail", "tech", "food", "finance", "entertainment"],
|
|
417
|
-
"toxic_user": [false],
|
|
418
499
|
}
|
|
419
500
|
},
|
|
420
501
|
{
|
|
@@ -423,7 +504,6 @@ const config = {
|
|
|
423
504
|
properties: {
|
|
424
505
|
"report_type": ["spam", "harassment", "misinformation", "hate_speech", "other"],
|
|
425
506
|
"content_type": ["post", "comment", "user", "dm"],
|
|
426
|
-
"toxic_user": [false],
|
|
427
507
|
}
|
|
428
508
|
},
|
|
429
509
|
{
|
|
@@ -431,7 +511,6 @@ const config = {
|
|
|
431
511
|
weight: 3,
|
|
432
512
|
properties: {
|
|
433
513
|
"field_updated": ["bio", "avatar", "display_name", "privacy_settings", "interests"],
|
|
434
|
-
"toxic_user": [false],
|
|
435
514
|
}
|
|
436
515
|
},
|
|
437
516
|
{
|
|
@@ -440,7 +519,6 @@ const config = {
|
|
|
440
519
|
properties: {
|
|
441
520
|
"tier": ["basic", "premium", "vip"],
|
|
442
521
|
"price_usd": [4.99, 9.99, 19.99],
|
|
443
|
-
"toxic_user": [false],
|
|
444
522
|
}
|
|
445
523
|
},
|
|
446
524
|
],
|
|
@@ -476,56 +554,69 @@ const config = {
|
|
|
476
554
|
lookupTables: [],
|
|
477
555
|
|
|
478
556
|
hook: function (record, type, meta) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
+
|
|
483
578
|
|
|
484
579
|
// ─── EVENT-LEVEL HOOKS ───────────────────────────────────────────
|
|
485
580
|
|
|
486
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');
|
|
487
585
|
const EVENT_TIME = dayjs(record.time);
|
|
488
586
|
|
|
489
|
-
// 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.
|
|
490
589
|
if (record.event === "post viewed") {
|
|
491
590
|
if (EVENT_TIME.isAfter(ALGORITHM_CHANGE_DAY)) {
|
|
492
|
-
// After day 45: 70% explore, 15% feed
|
|
493
591
|
if (chance.bool({ likelihood: 70 })) {
|
|
494
592
|
record.source = "explore";
|
|
495
593
|
}
|
|
496
594
|
} else {
|
|
497
|
-
// Before day 45: 70% feed, 15% explore (reinforce default)
|
|
498
595
|
if (chance.bool({ likelihood: 70 })) {
|
|
499
596
|
record.source = "feed";
|
|
500
597
|
}
|
|
501
598
|
}
|
|
502
599
|
}
|
|
503
600
|
|
|
504
|
-
// 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.
|
|
505
603
|
if (record.event === "post viewed") {
|
|
506
|
-
// 20% of post views are engagement-bait content
|
|
507
604
|
if (chance.bool({ likelihood: 20 })) {
|
|
508
605
|
record.view_duration_sec = chance.integer({ min: 1, max: 5 });
|
|
509
|
-
record.engagement_bait = true;
|
|
510
|
-
} else {
|
|
511
|
-
record.engagement_bait = false;
|
|
512
606
|
}
|
|
513
607
|
|
|
514
|
-
// 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.
|
|
515
610
|
if (EVENT_TIME.isAfter(REENGAGEMENT_START) && chance.bool({ likelihood: 30 })) {
|
|
516
611
|
record.source = "notification";
|
|
517
|
-
record.trending_reengagement = true;
|
|
518
|
-
} else {
|
|
519
|
-
record.trending_reengagement = false;
|
|
520
612
|
}
|
|
521
613
|
}
|
|
522
|
-
|
|
523
|
-
// Hook #8: WEEKEND CONTENT SURGE - tagging moved to everything hook (sessionization reassigns times after event hook)
|
|
524
614
|
}
|
|
525
615
|
|
|
526
616
|
// ─── EVERYTHING-LEVEL HOOKS ──────────────────────────────────────
|
|
527
617
|
|
|
528
618
|
if (type === "everything") {
|
|
619
|
+
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
529
620
|
const userEvents = record;
|
|
530
621
|
if (!userEvents || userEvents.length === 0) return record;
|
|
531
622
|
|
|
@@ -536,56 +627,35 @@ const config = {
|
|
|
536
627
|
e.account_type = profile.account_type;
|
|
537
628
|
});
|
|
538
629
|
|
|
539
|
-
//
|
|
630
|
+
// First pass: identify behavioral patterns (no flags written)
|
|
540
631
|
let postCreatedCount = 0;
|
|
541
632
|
let followReceivedCount = 0;
|
|
542
633
|
let reportSubmittedCount = 0;
|
|
543
634
|
let hasCreatorSubscription = false;
|
|
544
635
|
let isViralCreator = false;
|
|
545
636
|
|
|
546
|
-
// First pass: identify user patterns
|
|
547
637
|
userEvents.forEach((event) => {
|
|
548
|
-
if (event.event === "post created")
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (event.event === "
|
|
552
|
-
followReceivedCount++;
|
|
553
|
-
}
|
|
554
|
-
if (event.event === "report submitted") {
|
|
555
|
-
reportSubmittedCount++;
|
|
556
|
-
}
|
|
557
|
-
if (event.event === "creator subscription started") {
|
|
558
|
-
hasCreatorSubscription = true;
|
|
559
|
-
}
|
|
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;
|
|
560
642
|
});
|
|
561
643
|
|
|
562
|
-
// Hook #1: VIRAL CONTENT CASCADE
|
|
563
|
-
// Users with 10+ posts and 5% random chance are viral creators
|
|
564
644
|
if (postCreatedCount >= 10 && chance.bool({ likelihood: 5 })) {
|
|
565
645
|
isViralCreator = true;
|
|
566
646
|
}
|
|
567
647
|
|
|
568
|
-
// Second pass:
|
|
648
|
+
// Second pass: inject cloned events (no behavioral cohort flags)
|
|
569
649
|
for (let idx = userEvents.length - 1; idx >= 0; idx--) {
|
|
570
650
|
const event = userEvents[idx];
|
|
571
651
|
const eventTime = dayjs(event.time);
|
|
572
652
|
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
if (event.monetized_creator === undefined) event.monetized_creator = false;
|
|
576
|
-
if (event.follow_back_effect === undefined) event.follow_back_effect = false;
|
|
577
|
-
}
|
|
578
|
-
if (event.event === "post viewed") {
|
|
579
|
-
if (event.viral_cascade === undefined) event.viral_cascade = false;
|
|
580
|
-
}
|
|
581
|
-
if (event.toxic_user === undefined) event.toxic_user = false;
|
|
582
|
-
|
|
583
|
-
// Hook #1: VIRAL CONTENT CASCADE
|
|
584
|
-
// 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.
|
|
585
655
|
if (isViralCreator && event.event === "post created") {
|
|
586
|
-
const viralViews = chance.integer({ min:
|
|
587
|
-
const viralLikes = chance.integer({ min:
|
|
588
|
-
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 });
|
|
589
659
|
const injected = [];
|
|
590
660
|
|
|
591
661
|
const viewTemplate = userEvents.find(e => e.event === "post viewed");
|
|
@@ -601,9 +671,6 @@ const config = {
|
|
|
601
671
|
post_type: event.post_type || "text",
|
|
602
672
|
source: chance.pickone(["feed", "explore", "search"]),
|
|
603
673
|
view_duration_sec: chance.integer({ min: 5, max: 90 }),
|
|
604
|
-
viral_cascade: true,
|
|
605
|
-
engagement_bait: false,
|
|
606
|
-
trending_reengagement: false,
|
|
607
674
|
});
|
|
608
675
|
}
|
|
609
676
|
for (let i = 0; i < viralLikes; i++) {
|
|
@@ -613,7 +680,6 @@ const config = {
|
|
|
613
680
|
time: eventTime.add(chance.integer({ min: 2, max: 240 }), 'minutes').toISOString(),
|
|
614
681
|
user_id: event.user_id,
|
|
615
682
|
post_type: event.post_type || "text",
|
|
616
|
-
viral_cascade: true,
|
|
617
683
|
});
|
|
618
684
|
}
|
|
619
685
|
for (let i = 0; i < viralShares; i++) {
|
|
@@ -623,16 +689,14 @@ const config = {
|
|
|
623
689
|
time: eventTime.add(chance.integer({ min: 5, max: 300 }), 'minutes').toISOString(),
|
|
624
690
|
user_id: event.user_id,
|
|
625
691
|
share_destination: chance.pickone(["repost", "dm", "external", "copy_link"]),
|
|
626
|
-
viral_cascade: true,
|
|
627
692
|
});
|
|
628
693
|
}
|
|
629
694
|
|
|
630
|
-
// Splice all injected events after the post created event
|
|
631
695
|
userEvents.splice(idx + 1, 0, ...injected);
|
|
632
696
|
}
|
|
633
697
|
|
|
634
|
-
// Hook #2: FOLLOW-BACK SNOWBALL
|
|
635
|
-
//
|
|
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.
|
|
636
700
|
if (followReceivedCount >= 5 && event.event === "post created") {
|
|
637
701
|
if (chance.bool({ likelihood: 50 })) {
|
|
638
702
|
const commentTemplate = userEvents.find(e => e.event === "comment posted");
|
|
@@ -644,7 +708,6 @@ const config = {
|
|
|
644
708
|
character_count: chance.integer({ min: 10, max: 280 }),
|
|
645
709
|
has_media: chance.bool({ likelihood: 60 }),
|
|
646
710
|
hashtag_count: chance.integer({ min: 0, max: 5 }),
|
|
647
|
-
follow_back_effect: true,
|
|
648
711
|
};
|
|
649
712
|
const extraComment = {
|
|
650
713
|
...(commentTemplate || event),
|
|
@@ -653,16 +716,14 @@ const config = {
|
|
|
653
716
|
user_id: event.user_id,
|
|
654
717
|
comment_length: chance.integer({ min: 5, max: 200 }),
|
|
655
718
|
has_mention: chance.bool({ likelihood: 40 }),
|
|
656
|
-
follow_back_effect: true,
|
|
657
719
|
};
|
|
658
720
|
userEvents.splice(idx + 1, 0, duplicatePost, extraComment);
|
|
659
721
|
}
|
|
660
722
|
}
|
|
661
723
|
|
|
662
|
-
// Hook #6: CREATOR MONETIZATION
|
|
663
|
-
//
|
|
724
|
+
// Hook #6: CREATOR MONETIZATION — 3x post/story rate for subscribers.
|
|
725
|
+
// Discovery: cohort users with creator-subscription-started event, compare posts/user.
|
|
664
726
|
if (hasCreatorSubscription && event.event === "post created") {
|
|
665
|
-
// Triple frequency: add 2 extra posts for each existing one
|
|
666
727
|
for (let i = 0; i < 2; i++) {
|
|
667
728
|
const extraPost = {
|
|
668
729
|
...event,
|
|
@@ -672,13 +733,11 @@ const config = {
|
|
|
672
733
|
character_count: chance.integer({ min: 20, max: 280 }),
|
|
673
734
|
has_media: chance.bool({ likelihood: 70 }),
|
|
674
735
|
hashtag_count: chance.integer({ min: 1, max: 8 }),
|
|
675
|
-
monetized_creator: true,
|
|
676
736
|
};
|
|
677
737
|
userEvents.splice(idx + 1, 0, extraPost);
|
|
678
738
|
}
|
|
679
739
|
}
|
|
680
740
|
if (hasCreatorSubscription && event.event === "story created") {
|
|
681
|
-
// Also triple story creation
|
|
682
741
|
for (let i = 0; i < 2; i++) {
|
|
683
742
|
const extraStory = {
|
|
684
743
|
...event,
|
|
@@ -687,12 +746,10 @@ const config = {
|
|
|
687
746
|
story_type: chance.pickone(["photo", "video", "text"]),
|
|
688
747
|
has_filter: chance.bool({ likelihood: 60 }),
|
|
689
748
|
has_sticker: chance.bool({ likelihood: 40 }),
|
|
690
|
-
monetized_creator: true,
|
|
691
749
|
};
|
|
692
750
|
userEvents.splice(idx + 1, 0, extraStory);
|
|
693
751
|
}
|
|
694
752
|
}
|
|
695
|
-
// Monetized creators also check their analytics more (extra post views)
|
|
696
753
|
if (hasCreatorSubscription && event.event === "post viewed") {
|
|
697
754
|
if (chance.bool({ likelihood: 25 })) {
|
|
698
755
|
const analyticsView = {
|
|
@@ -702,58 +759,57 @@ const config = {
|
|
|
702
759
|
post_type: event.post_type || "text",
|
|
703
760
|
source: "profile",
|
|
704
761
|
view_duration_sec: chance.integer({ min: 10, max: 60 }),
|
|
705
|
-
monetized_creator: true,
|
|
706
|
-
engagement_bait: false,
|
|
707
|
-
trending_reengagement: false,
|
|
708
|
-
viral_cascade: false,
|
|
709
762
|
};
|
|
710
763
|
userEvents.splice(idx + 1, 0, analyticsView);
|
|
711
764
|
}
|
|
712
765
|
}
|
|
713
766
|
}
|
|
714
767
|
|
|
715
|
-
// Hook #8: WEEKEND CONTENT SURGE
|
|
716
|
-
|
|
717
|
-
if (event.event === "post created" || event.event === "story created") {
|
|
718
|
-
const dow = new Date(event.time).getUTCDay(); // 0 = Sunday, 6 = Saturday
|
|
719
|
-
if (dow === 0 || dow === 6) {
|
|
720
|
-
event.weekend_surge = true;
|
|
721
|
-
} else {
|
|
722
|
-
event.weekend_surge = false;
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
|
-
// 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.
|
|
727
770
|
for (let idx = userEvents.length - 1; idx >= 0; idx--) {
|
|
728
771
|
const event = userEvents[idx];
|
|
729
|
-
if (event.
|
|
730
|
-
|
|
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 })) {
|
|
731
775
|
const etime = dayjs(event.time);
|
|
732
776
|
const dup = {
|
|
733
777
|
...event,
|
|
734
778
|
time: etime.add(chance.integer({ min: 1, max: 3 }), 'hours').toISOString(),
|
|
735
|
-
weekend_duplicate: true,
|
|
736
779
|
};
|
|
737
780
|
userEvents.splice(idx + 1, 0, dup);
|
|
738
781
|
}
|
|
739
782
|
}
|
|
740
783
|
}
|
|
741
784
|
|
|
742
|
-
// Hook #7: TOXICITY CHURN
|
|
743
|
-
//
|
|
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.
|
|
744
787
|
if (reportSubmittedCount >= 3) {
|
|
745
|
-
const churnCutoff =
|
|
788
|
+
const churnCutoff = datasetStart.add(30, 'days');
|
|
746
789
|
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
747
790
|
const evt = userEvents[i];
|
|
748
|
-
if (dayjs(evt.time).isAfter(churnCutoff)) {
|
|
749
|
-
|
|
750
|
-
userEvents.splice(i, 1);
|
|
751
|
-
} else {
|
|
752
|
-
evt.toxic_user = true;
|
|
753
|
-
}
|
|
791
|
+
if (dayjs(evt.time).isAfter(churnCutoff) && chance.bool({ likelihood: 60 })) {
|
|
792
|
+
userEvents.splice(i, 1);
|
|
754
793
|
}
|
|
755
794
|
}
|
|
756
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
|
+
}
|
|
757
813
|
}
|
|
758
814
|
|
|
759
815
|
return record;
|