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