make-mp-data 3.0.3 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +46 -0
  2. package/dungeons/array-of-object-lookup-schema.json +327 -0
  3. package/dungeons/array-of-object-lookup.js +29 -9
  4. package/dungeons/capstone/capstone-ic3.js +291 -0
  5. package/dungeons/capstone/capstone-ic4.js +598 -0
  6. package/dungeons/capstone/capstone-ic5.js +668 -0
  7. package/dungeons/capstone/generate-product-lookup.js +309 -0
  8. package/dungeons/ecommerce-schema.json +462 -0
  9. package/dungeons/{copilot.js → ecommerce.js} +79 -17
  10. package/dungeons/education-schema.json +2409 -0
  11. package/dungeons/education.js +226 -462
  12. package/dungeons/fintech-schema.json +14034 -0
  13. package/dungeons/fintech.js +134 -413
  14. package/dungeons/foobar-schema.json +403 -0
  15. package/dungeons/foobar.js +27 -4
  16. package/dungeons/food-delivery-schema.json +192 -0
  17. package/dungeons/food-delivery.js +602 -0
  18. package/dungeons/food-schema.json +1152 -0
  19. package/dungeons/food.js +173 -406
  20. package/dungeons/gaming-schema.json +1270 -0
  21. package/dungeons/gaming.js +182 -42
  22. package/dungeons/insurance-application-schema.json +204 -0
  23. package/dungeons/insurance-application.js +605 -0
  24. package/dungeons/media-schema.json +906 -0
  25. package/dungeons/media.js +250 -420
  26. package/dungeons/retention-cadence-schema.json +78 -0
  27. package/dungeons/retention-cadence.js +35 -1
  28. package/dungeons/rpg-schema.json +4526 -0
  29. package/dungeons/rpg.js +171 -429
  30. package/dungeons/sanity-schema.json +255 -0
  31. package/dungeons/sanity.js +21 -10
  32. package/dungeons/sass-schema.json +1291 -0
  33. package/dungeons/sass.js +241 -368
  34. package/dungeons/scd-schema.json +919 -0
  35. package/dungeons/scd.js +41 -13
  36. package/dungeons/simple-schema.json +608 -0
  37. package/dungeons/simple.js +52 -15
  38. package/dungeons/simplest-schema.json +1418 -0
  39. package/dungeons/simplest.js +392 -0
  40. package/dungeons/social-schema.json +1118 -0
  41. package/dungeons/social.js +150 -391
  42. package/dungeons/text-generation-schema.json +3096 -0
  43. package/dungeons/text-generation.js +71 -0
  44. package/index.js +8 -6
  45. package/lib/core/config-validator.js +28 -8
  46. package/lib/core/storage.js +5 -5
  47. package/lib/generators/events.js +4 -4
  48. package/lib/orchestrators/mixpanel-sender.js +16 -13
  49. package/lib/orchestrators/user-loop.js +14 -6
  50. package/lib/templates/soup-presets.js +188 -0
  51. package/lib/utils/utils.js +52 -6
  52. package/package.json +1 -1
  53. package/types.d.ts +20 -3
  54. package/dungeons/adspend.js +0 -130
  55. package/dungeons/anon.js +0 -128
  56. package/dungeons/benchmark-heavy.js +0 -240
  57. package/dungeons/benchmark-light.js +0 -140
  58. package/dungeons/big.js +0 -226
  59. package/dungeons/business.js +0 -391
  60. package/dungeons/complex.js +0 -428
  61. package/dungeons/experiments.js +0 -137
  62. package/dungeons/funnels.js +0 -309
  63. package/dungeons/mil.js +0 -323
  64. package/dungeons/mirror.js +0 -161
  65. package/dungeons/soup-test.js +0 -52
  66. package/dungeons/streaming.js +0 -372
  67. package/dungeons/strict-event-test.js +0 -30
  68. package/dungeons/student-teacher.js +0 -438
  69. package/dungeons/too-big-events.js +0 -203
  70. package/dungeons/user-agent.js +0 -209
@@ -0,0 +1,1118 @@
1
+ {
2
+ "schema": {
3
+ "token": "",
4
+ "seed": "harness-social",
5
+ "numDays": 100,
6
+ "numEvents": 600000,
7
+ "numUsers": 5000,
8
+ "hasAnonIds": false,
9
+ "hasSessionIds": true,
10
+ "format": "json",
11
+ "gzip": true,
12
+ "alsoInferFunnels": false,
13
+ "hasLocation": true,
14
+ "hasAndroidDevices": true,
15
+ "hasIOSDevices": true,
16
+ "hasDesktopDevices": true,
17
+ "hasBrowser": false,
18
+ "hasCampaigns": false,
19
+ "isAnonymous": false,
20
+ "hasAdSpend": false,
21
+ "percentUsersBornInDataset": 50,
22
+ "hasAvatar": true,
23
+ "batchSize": 2500000,
24
+ "concurrency": 1,
25
+ "writeToDisk": false,
26
+ "scdProps": {},
27
+ "funnels": [
28
+ {
29
+ "sequence": [
30
+ "account created",
31
+ "profile updated",
32
+ "post created"
33
+ ],
34
+ "isFirstFunnel": true,
35
+ "conversionRate": 70,
36
+ "timeToConvert": 0.5
37
+ },
38
+ {
39
+ "sequence": [
40
+ "post viewed",
41
+ "post liked",
42
+ "comment posted"
43
+ ],
44
+ "conversionRate": 45,
45
+ "timeToConvert": 0.5,
46
+ "weight": 6
47
+ },
48
+ {
49
+ "sequence": [
50
+ "post created",
51
+ "post viewed",
52
+ "post liked",
53
+ "post shared"
54
+ ],
55
+ "conversionRate": 30,
56
+ "timeToConvert": 3,
57
+ "weight": 3
58
+ },
59
+ {
60
+ "sequence": [
61
+ "story created",
62
+ "story viewed",
63
+ "dm sent"
64
+ ],
65
+ "conversionRate": 40,
66
+ "timeToConvert": 1,
67
+ "weight": 3
68
+ },
69
+ {
70
+ "sequence": [
71
+ "search performed",
72
+ "post viewed",
73
+ "user followed"
74
+ ],
75
+ "conversionRate": 35,
76
+ "timeToConvert": 1,
77
+ "weight": 2
78
+ },
79
+ {
80
+ "sequence": [
81
+ "notification received",
82
+ "post viewed",
83
+ "post liked"
84
+ ],
85
+ "conversionRate": 50,
86
+ "timeToConvert": 0.5,
87
+ "weight": 2
88
+ },
89
+ {
90
+ "sequence": [
91
+ "profile updated",
92
+ "creator subscription started",
93
+ "post created"
94
+ ],
95
+ "conversionRate": 15,
96
+ "timeToConvert": 24,
97
+ "weight": 1
98
+ },
99
+ {
100
+ "sequence": [
101
+ "ad viewed",
102
+ "ad clicked",
103
+ "report submitted"
104
+ ],
105
+ "conversionRate": 20,
106
+ "timeToConvert": 2,
107
+ "weight": 1
108
+ }
109
+ ],
110
+ "events": [
111
+ {
112
+ "event": "account created",
113
+ "weight": 1,
114
+ "isFirstEvent": true,
115
+ "properties": {
116
+ "signup_method": [
117
+ "email",
118
+ "google",
119
+ "apple",
120
+ "sso"
121
+ ],
122
+ "referred_by": [
123
+ "organic",
124
+ "friend",
125
+ "ad",
126
+ "influencer"
127
+ ]
128
+ }
129
+ },
130
+ {
131
+ "event": "post created",
132
+ "weight": 12,
133
+ "properties": {
134
+ "post_type": [
135
+ "text",
136
+ "image",
137
+ "video",
138
+ "poll",
139
+ "link"
140
+ ],
141
+ "character_count": [
142
+ 18,
143
+ 111,
144
+ 139,
145
+ 273,
146
+ 47,
147
+ 75,
148
+ 119,
149
+ 132,
150
+ 257,
151
+ 183,
152
+ 24,
153
+ 142,
154
+ 167,
155
+ 87,
156
+ 196,
157
+ 76,
158
+ 97,
159
+ 68,
160
+ 109,
161
+ 233,
162
+ 158,
163
+ 183,
164
+ 22,
165
+ 105,
166
+ 155,
167
+ 117,
168
+ 135,
169
+ 118,
170
+ 46,
171
+ 158,
172
+ 196,
173
+ 68,
174
+ 62,
175
+ 217,
176
+ 124,
177
+ 72,
178
+ 246,
179
+ 64,
180
+ 113,
181
+ 60,
182
+ 162,
183
+ 138,
184
+ 118,
185
+ 76,
186
+ 141,
187
+ 106,
188
+ 113,
189
+ 119,
190
+ 106,
191
+ 153
192
+ ],
193
+ "has_media": {
194
+ "functionName": "arrow",
195
+ "body": "function () {\n\t\tconst weighted = [];\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tconst rand = chance.d10(); // Random number between 1 and 10\n\n\t\t\t// 35% chance to favor the most chosen index\n\t\t\tif (chance.bool({ likelihood: 35 })) {\n\t\t\t\t// 50% chance to slightly alter the index\n\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\tweighted.push(items[mostChosenIndex]);\n\t\t\t\t} else {\n\t\t\t\t\tconst addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;\n\t\t\t\t\tlet newIndex = mostChosenIndex + addOrSubtract;\n\n\t\t\t\t\t// Ensure newIndex is within bounds\n\t\t\t\t\tif (newIndex < 0) newIndex = 0;\n\t\t\t\t\tif (newIndex >= items.length) newIndex = items.length - 1;\n\t\t\t\t\tweighted.push(items[newIndex]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 25% chance to favor the second most chosen index\n\t\t\telse if (chance.bool({ likelihood: 25 })) {\n\t\t\t\tweighted.push(items[secondMostChosenIndex]);\n\t\t\t}\n\t\t\t// 15% chance to favor the third most chosen index\n\t\t\telse if (chance.bool({ likelihood: 15 })) {\n\t\t\t\tweighted.push(items[thirdMostChosenIndex]);\n\t\t\t}\n\t\t\t// Otherwise, pick a random item from the list\n\t\t\telse {\n\t\t\t\tweighted.push(chance.pickone(items));\n\t\t\t}\n\t\t}\n\t\treturn weighted;\n\t}"
196
+ },
197
+ "hashtag_count": [
198
+ 3,
199
+ 5,
200
+ 4,
201
+ 4,
202
+ 3,
203
+ 2,
204
+ 3,
205
+ 3,
206
+ 6,
207
+ 2,
208
+ 3,
209
+ 3,
210
+ 8,
211
+ 2,
212
+ 8,
213
+ 8,
214
+ 7,
215
+ 3,
216
+ 7,
217
+ 3,
218
+ 6,
219
+ 8,
220
+ 7,
221
+ 4,
222
+ 7,
223
+ 3,
224
+ 3,
225
+ 4,
226
+ 4,
227
+ 3,
228
+ 4,
229
+ 3,
230
+ 3,
231
+ 3,
232
+ 2,
233
+ 6,
234
+ 1,
235
+ 2,
236
+ 4,
237
+ 4,
238
+ 5,
239
+ 4,
240
+ 6,
241
+ 7,
242
+ 3,
243
+ 3,
244
+ 2,
245
+ 2,
246
+ 2,
247
+ 4
248
+ ]
249
+ }
250
+ },
251
+ {
252
+ "event": "post viewed",
253
+ "weight": 30,
254
+ "properties": {
255
+ "post_type": [
256
+ "text",
257
+ "image",
258
+ "video",
259
+ "poll",
260
+ "link"
261
+ ],
262
+ "view_duration_sec": [
263
+ 93,
264
+ 29,
265
+ 31,
266
+ 26,
267
+ 33
268
+ ],
269
+ "source": [
270
+ "feed",
271
+ "explore",
272
+ "search",
273
+ "profile",
274
+ "notification"
275
+ ]
276
+ }
277
+ },
278
+ {
279
+ "event": "post liked",
280
+ "weight": 18,
281
+ "properties": {
282
+ "post_type": [
283
+ "text",
284
+ "image",
285
+ "video",
286
+ "poll",
287
+ "link"
288
+ ]
289
+ }
290
+ },
291
+ {
292
+ "event": "post shared",
293
+ "weight": 6,
294
+ "properties": {
295
+ "share_destination": [
296
+ "repost",
297
+ "dm",
298
+ "external",
299
+ "copy_link"
300
+ ]
301
+ }
302
+ },
303
+ {
304
+ "event": "comment posted",
305
+ "weight": 10,
306
+ "properties": {
307
+ "comment_length": [
308
+ 146,
309
+ 162,
310
+ 369,
311
+ 146,
312
+ 166,
313
+ 387,
314
+ 163,
315
+ 125,
316
+ 141,
317
+ 136,
318
+ 110,
319
+ 139,
320
+ 384,
321
+ 191,
322
+ 207,
323
+ 343,
324
+ 134,
325
+ 121,
326
+ 134,
327
+ 359
328
+ ],
329
+ "has_mention": {
330
+ "functionName": "arrow",
331
+ "body": "function () {\n\t\tconst weighted = [];\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tconst rand = chance.d10(); // Random number between 1 and 10\n\n\t\t\t// 35% chance to favor the most chosen index\n\t\t\tif (chance.bool({ likelihood: 35 })) {\n\t\t\t\t// 50% chance to slightly alter the index\n\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\tweighted.push(items[mostChosenIndex]);\n\t\t\t\t} else {\n\t\t\t\t\tconst addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;\n\t\t\t\t\tlet newIndex = mostChosenIndex + addOrSubtract;\n\n\t\t\t\t\t// Ensure newIndex is within bounds\n\t\t\t\t\tif (newIndex < 0) newIndex = 0;\n\t\t\t\t\tif (newIndex >= items.length) newIndex = items.length - 1;\n\t\t\t\t\tweighted.push(items[newIndex]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 25% chance to favor the second most chosen index\n\t\t\telse if (chance.bool({ likelihood: 25 })) {\n\t\t\t\tweighted.push(items[secondMostChosenIndex]);\n\t\t\t}\n\t\t\t// 15% chance to favor the third most chosen index\n\t\t\telse if (chance.bool({ likelihood: 15 })) {\n\t\t\t\tweighted.push(items[thirdMostChosenIndex]);\n\t\t\t}\n\t\t\t// Otherwise, pick a random item from the list\n\t\t\telse {\n\t\t\t\tweighted.push(chance.pickone(items));\n\t\t\t}\n\t\t}\n\t\treturn weighted;\n\t}"
332
+ }
333
+ }
334
+ },
335
+ {
336
+ "event": "user followed",
337
+ "weight": 8,
338
+ "properties": {
339
+ "discovery_source": [
340
+ "suggested",
341
+ "search",
342
+ "post",
343
+ "profile",
344
+ "mutual"
345
+ ]
346
+ }
347
+ },
348
+ {
349
+ "event": "user unfollowed",
350
+ "weight": 2,
351
+ "properties": {
352
+ "reason": [
353
+ "content_quality",
354
+ "too_frequent",
355
+ "lost_interest",
356
+ "offensive"
357
+ ]
358
+ }
359
+ },
360
+ {
361
+ "event": "story viewed",
362
+ "weight": 15,
363
+ "properties": {
364
+ "story_type": [
365
+ "photo",
366
+ "video",
367
+ "text"
368
+ ],
369
+ "view_duration_sec": [
370
+ 8,
371
+ 14,
372
+ 10,
373
+ 10,
374
+ 9
375
+ ],
376
+ "completed": {
377
+ "functionName": "arrow",
378
+ "body": "function () {\n\t\tconst weighted = [];\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tconst rand = chance.d10(); // Random number between 1 and 10\n\n\t\t\t// 35% chance to favor the most chosen index\n\t\t\tif (chance.bool({ likelihood: 35 })) {\n\t\t\t\t// 50% chance to slightly alter the index\n\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\tweighted.push(items[mostChosenIndex]);\n\t\t\t\t} else {\n\t\t\t\t\tconst addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;\n\t\t\t\t\tlet newIndex = mostChosenIndex + addOrSubtract;\n\n\t\t\t\t\t// Ensure newIndex is within bounds\n\t\t\t\t\tif (newIndex < 0) newIndex = 0;\n\t\t\t\t\tif (newIndex >= items.length) newIndex = items.length - 1;\n\t\t\t\t\tweighted.push(items[newIndex]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 25% chance to favor the second most chosen index\n\t\t\telse if (chance.bool({ likelihood: 25 })) {\n\t\t\t\tweighted.push(items[secondMostChosenIndex]);\n\t\t\t}\n\t\t\t// 15% chance to favor the third most chosen index\n\t\t\telse if (chance.bool({ likelihood: 15 })) {\n\t\t\t\tweighted.push(items[thirdMostChosenIndex]);\n\t\t\t}\n\t\t\t// Otherwise, pick a random item from the list\n\t\t\telse {\n\t\t\t\tweighted.push(chance.pickone(items));\n\t\t\t}\n\t\t}\n\t\treturn weighted;\n\t}"
379
+ }
380
+ }
381
+ },
382
+ {
383
+ "event": "story created",
384
+ "weight": 5,
385
+ "properties": {
386
+ "story_type": [
387
+ "photo",
388
+ "video",
389
+ "text"
390
+ ],
391
+ "has_filter": {
392
+ "functionName": "arrow",
393
+ "body": "function () {\n\t\tconst weighted = [];\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tconst rand = chance.d10(); // Random number between 1 and 10\n\n\t\t\t// 35% chance to favor the most chosen index\n\t\t\tif (chance.bool({ likelihood: 35 })) {\n\t\t\t\t// 50% chance to slightly alter the index\n\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\tweighted.push(items[mostChosenIndex]);\n\t\t\t\t} else {\n\t\t\t\t\tconst addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;\n\t\t\t\t\tlet newIndex = mostChosenIndex + addOrSubtract;\n\n\t\t\t\t\t// Ensure newIndex is within bounds\n\t\t\t\t\tif (newIndex < 0) newIndex = 0;\n\t\t\t\t\tif (newIndex >= items.length) newIndex = items.length - 1;\n\t\t\t\t\tweighted.push(items[newIndex]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 25% chance to favor the second most chosen index\n\t\t\telse if (chance.bool({ likelihood: 25 })) {\n\t\t\t\tweighted.push(items[secondMostChosenIndex]);\n\t\t\t}\n\t\t\t// 15% chance to favor the third most chosen index\n\t\t\telse if (chance.bool({ likelihood: 15 })) {\n\t\t\t\tweighted.push(items[thirdMostChosenIndex]);\n\t\t\t}\n\t\t\t// Otherwise, pick a random item from the list\n\t\t\telse {\n\t\t\t\tweighted.push(chance.pickone(items));\n\t\t\t}\n\t\t}\n\t\treturn weighted;\n\t}"
394
+ },
395
+ "has_sticker": {
396
+ "functionName": "arrow",
397
+ "body": "function () {\n\t\tconst weighted = [];\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tconst rand = chance.d10(); // Random number between 1 and 10\n\n\t\t\t// 35% chance to favor the most chosen index\n\t\t\tif (chance.bool({ likelihood: 35 })) {\n\t\t\t\t// 50% chance to slightly alter the index\n\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\tweighted.push(items[mostChosenIndex]);\n\t\t\t\t} else {\n\t\t\t\t\tconst addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;\n\t\t\t\t\tlet newIndex = mostChosenIndex + addOrSubtract;\n\n\t\t\t\t\t// Ensure newIndex is within bounds\n\t\t\t\t\tif (newIndex < 0) newIndex = 0;\n\t\t\t\t\tif (newIndex >= items.length) newIndex = items.length - 1;\n\t\t\t\t\tweighted.push(items[newIndex]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 25% chance to favor the second most chosen index\n\t\t\telse if (chance.bool({ likelihood: 25 })) {\n\t\t\t\tweighted.push(items[secondMostChosenIndex]);\n\t\t\t}\n\t\t\t// 15% chance to favor the third most chosen index\n\t\t\telse if (chance.bool({ likelihood: 15 })) {\n\t\t\t\tweighted.push(items[thirdMostChosenIndex]);\n\t\t\t}\n\t\t\t// Otherwise, pick a random item from the list\n\t\t\telse {\n\t\t\t\tweighted.push(chance.pickone(items));\n\t\t\t}\n\t\t}\n\t\treturn weighted;\n\t}"
398
+ }
399
+ }
400
+ },
401
+ {
402
+ "event": "search performed",
403
+ "weight": 7,
404
+ "properties": {
405
+ "search_type": [
406
+ "users",
407
+ "hashtags",
408
+ "posts"
409
+ ],
410
+ "results_count": [
411
+ 20,
412
+ 10,
413
+ 12,
414
+ 37,
415
+ 14,
416
+ 19,
417
+ 13,
418
+ 19,
419
+ 35,
420
+ 15
421
+ ]
422
+ }
423
+ },
424
+ {
425
+ "event": "notification received",
426
+ "weight": 12,
427
+ "properties": {
428
+ "notification_type": [
429
+ "like",
430
+ "follow",
431
+ "comment",
432
+ "mention",
433
+ "trending"
434
+ ],
435
+ "clicked": {
436
+ "functionName": "arrow",
437
+ "body": "function () {\n\t\tconst weighted = [];\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tconst rand = chance.d10(); // Random number between 1 and 10\n\n\t\t\t// 35% chance to favor the most chosen index\n\t\t\tif (chance.bool({ likelihood: 35 })) {\n\t\t\t\t// 50% chance to slightly alter the index\n\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\tweighted.push(items[mostChosenIndex]);\n\t\t\t\t} else {\n\t\t\t\t\tconst addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;\n\t\t\t\t\tlet newIndex = mostChosenIndex + addOrSubtract;\n\n\t\t\t\t\t// Ensure newIndex is within bounds\n\t\t\t\t\tif (newIndex < 0) newIndex = 0;\n\t\t\t\t\tif (newIndex >= items.length) newIndex = items.length - 1;\n\t\t\t\t\tweighted.push(items[newIndex]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 25% chance to favor the second most chosen index\n\t\t\telse if (chance.bool({ likelihood: 25 })) {\n\t\t\t\tweighted.push(items[secondMostChosenIndex]);\n\t\t\t}\n\t\t\t// 15% chance to favor the third most chosen index\n\t\t\telse if (chance.bool({ likelihood: 15 })) {\n\t\t\t\tweighted.push(items[thirdMostChosenIndex]);\n\t\t\t}\n\t\t\t// Otherwise, pick a random item from the list\n\t\t\telse {\n\t\t\t\tweighted.push(chance.pickone(items));\n\t\t\t}\n\t\t}\n\t\treturn weighted;\n\t}"
438
+ }
439
+ }
440
+ },
441
+ {
442
+ "event": "dm sent",
443
+ "weight": 8,
444
+ "properties": {
445
+ "message_type": [
446
+ "text",
447
+ "image",
448
+ "voice",
449
+ "link"
450
+ ],
451
+ "conversation_length": [
452
+ 25,
453
+ 34,
454
+ 35,
455
+ 24,
456
+ 67,
457
+ 57,
458
+ 68,
459
+ 67,
460
+ 21,
461
+ 59,
462
+ 66,
463
+ 50,
464
+ 65,
465
+ 30,
466
+ 33,
467
+ 25,
468
+ 43,
469
+ 50,
470
+ 51,
471
+ 14,
472
+ 12,
473
+ 22,
474
+ 97,
475
+ 23,
476
+ 29,
477
+ 26,
478
+ 26,
479
+ 23,
480
+ 17,
481
+ 52,
482
+ 32,
483
+ 83,
484
+ 58,
485
+ 36,
486
+ 36,
487
+ 43,
488
+ 43,
489
+ 25,
490
+ 65,
491
+ 61,
492
+ 70,
493
+ 19,
494
+ 21,
495
+ 41,
496
+ 91,
497
+ 88,
498
+ 32,
499
+ 16,
500
+ 24,
501
+ 28
502
+ ]
503
+ }
504
+ },
505
+ {
506
+ "event": "ad viewed",
507
+ "weight": 10,
508
+ "properties": {
509
+ "ad_format": [
510
+ "feed_native",
511
+ "story",
512
+ "banner",
513
+ "video"
514
+ ],
515
+ "ad_category": [
516
+ "retail",
517
+ "tech",
518
+ "food",
519
+ "finance",
520
+ "entertainment"
521
+ ],
522
+ "view_duration_sec": [
523
+ 21,
524
+ 9,
525
+ 9,
526
+ 21,
527
+ 21,
528
+ 20,
529
+ 6,
530
+ 9,
531
+ 20,
532
+ 11,
533
+ 8,
534
+ 24,
535
+ 23,
536
+ 8,
537
+ 23,
538
+ 20,
539
+ 12,
540
+ 11,
541
+ 7,
542
+ 22,
543
+ 8,
544
+ 13,
545
+ 21,
546
+ 11,
547
+ 8,
548
+ 9,
549
+ 10,
550
+ 8,
551
+ 10,
552
+ 17,
553
+ 9,
554
+ 10,
555
+ 11,
556
+ 9,
557
+ 8,
558
+ 8,
559
+ 8,
560
+ 10,
561
+ 19,
562
+ 23,
563
+ 10,
564
+ 8,
565
+ 20,
566
+ 22,
567
+ 23,
568
+ 8,
569
+ 21,
570
+ 7,
571
+ 10,
572
+ 23
573
+ ]
574
+ }
575
+ },
576
+ {
577
+ "event": "ad clicked",
578
+ "weight": 2,
579
+ "properties": {
580
+ "ad_format": [
581
+ "feed_native",
582
+ "story",
583
+ "banner",
584
+ "video"
585
+ ],
586
+ "ad_category": [
587
+ "retail",
588
+ "tech",
589
+ "food",
590
+ "finance",
591
+ "entertainment"
592
+ ]
593
+ }
594
+ },
595
+ {
596
+ "event": "report submitted",
597
+ "weight": 1,
598
+ "properties": {
599
+ "report_type": [
600
+ "spam",
601
+ "harassment",
602
+ "misinformation",
603
+ "hate_speech",
604
+ "other"
605
+ ],
606
+ "content_type": [
607
+ "post",
608
+ "comment",
609
+ "user",
610
+ "dm"
611
+ ]
612
+ }
613
+ },
614
+ {
615
+ "event": "profile updated",
616
+ "weight": 3,
617
+ "properties": {
618
+ "field_updated": [
619
+ "bio",
620
+ "avatar",
621
+ "display_name",
622
+ "privacy_settings",
623
+ "interests"
624
+ ]
625
+ }
626
+ },
627
+ {
628
+ "event": "creator subscription started",
629
+ "weight": 2,
630
+ "properties": {
631
+ "tier": [
632
+ "basic",
633
+ "premium",
634
+ "vip"
635
+ ],
636
+ "price_usd": {
637
+ "functionName": "arrow",
638
+ "body": "function () {\n\t\tconst weighted = [];\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tconst rand = chance.d10(); // Random number between 1 and 10\n\n\t\t\t// 35% chance to favor the most chosen index\n\t\t\tif (chance.bool({ likelihood: 35 })) {\n\t\t\t\t// 50% chance to slightly alter the index\n\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\tweighted.push(items[mostChosenIndex]);\n\t\t\t\t} else {\n\t\t\t\t\tconst addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;\n\t\t\t\t\tlet newIndex = mostChosenIndex + addOrSubtract;\n\n\t\t\t\t\t// Ensure newIndex is within bounds\n\t\t\t\t\tif (newIndex < 0) newIndex = 0;\n\t\t\t\t\tif (newIndex >= items.length) newIndex = items.length - 1;\n\t\t\t\t\tweighted.push(items[newIndex]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 25% chance to favor the second most chosen index\n\t\t\telse if (chance.bool({ likelihood: 25 })) {\n\t\t\t\tweighted.push(items[secondMostChosenIndex]);\n\t\t\t}\n\t\t\t// 15% chance to favor the third most chosen index\n\t\t\telse if (chance.bool({ likelihood: 15 })) {\n\t\t\t\tweighted.push(items[thirdMostChosenIndex]);\n\t\t\t}\n\t\t\t// Otherwise, pick a random item from the list\n\t\t\telse {\n\t\t\t\tweighted.push(chance.pickone(items));\n\t\t\t}\n\t\t}\n\t\treturn weighted;\n\t}"
639
+ }
640
+ }
641
+ }
642
+ ],
643
+ "superProps": {
644
+ "app_version": [
645
+ "4.0",
646
+ "4.1",
647
+ "4.2",
648
+ "4.3",
649
+ "5.0"
650
+ ],
651
+ "account_type": [
652
+ "personal",
653
+ "creator",
654
+ "business"
655
+ ]
656
+ },
657
+ "userProps": {
658
+ "follower_count": [
659
+ 2597,
660
+ 2309,
661
+ 2449,
662
+ 2918,
663
+ 2259,
664
+ 2376,
665
+ 2504,
666
+ 2270,
667
+ 7335,
668
+ 7358,
669
+ 2457,
670
+ 2379,
671
+ 3823,
672
+ 2218,
673
+ 2396,
674
+ 2903,
675
+ 7072,
676
+ 3066,
677
+ 2760,
678
+ 7569,
679
+ 2481,
680
+ 7501,
681
+ 2534,
682
+ 7114,
683
+ 2673,
684
+ 3123,
685
+ 2685,
686
+ 2497,
687
+ 7415,
688
+ 2479,
689
+ 2550,
690
+ 7056,
691
+ 2487,
692
+ 6889,
693
+ 6817,
694
+ 2780,
695
+ 2880,
696
+ 5000,
697
+ 2552,
698
+ 2612,
699
+ 2574,
700
+ 2718,
701
+ 2702,
702
+ 2584,
703
+ 7800,
704
+ 2669,
705
+ 3374,
706
+ 2261,
707
+ 3317,
708
+ 2986
709
+ ],
710
+ "following_count": [
711
+ 1361,
712
+ 3819,
713
+ 1325,
714
+ 1347,
715
+ 1605,
716
+ 3340,
717
+ 3614,
718
+ 2500,
719
+ 1285,
720
+ 1430,
721
+ 1287,
722
+ 1618,
723
+ 1229,
724
+ 3633,
725
+ 3561,
726
+ 1694,
727
+ 3606,
728
+ 1376,
729
+ 3579,
730
+ 1656,
731
+ 1381,
732
+ 1469,
733
+ 1886,
734
+ 1153,
735
+ 1744,
736
+ 2500,
737
+ 1882,
738
+ 3783,
739
+ 3376,
740
+ 1374,
741
+ 2794,
742
+ 1294,
743
+ 1456,
744
+ 1630,
745
+ 1385,
746
+ 3599,
747
+ 3479,
748
+ 1130,
749
+ 1059,
750
+ 1352,
751
+ 3871,
752
+ 3594,
753
+ 1400,
754
+ 1484,
755
+ 3557,
756
+ 1536,
757
+ 1347,
758
+ 3525,
759
+ 1456,
760
+ 3560,
761
+ 1249,
762
+ 1365,
763
+ 2500,
764
+ 1581,
765
+ 1357,
766
+ 3816,
767
+ 3819,
768
+ 1250,
769
+ 1594,
770
+ 1348,
771
+ 1274,
772
+ 1316,
773
+ 997,
774
+ 3667,
775
+ 1304,
776
+ 1297,
777
+ 1356,
778
+ 1410,
779
+ 1110,
780
+ 1529,
781
+ 3729,
782
+ 1471,
783
+ 3470,
784
+ 1293,
785
+ 1167,
786
+ 1183,
787
+ 3877,
788
+ 2001,
789
+ 1284,
790
+ 3414,
791
+ 1388,
792
+ 978,
793
+ 3287,
794
+ 3672,
795
+ 3515,
796
+ 1341,
797
+ 1162,
798
+ 1391,
799
+ 1415,
800
+ 1187,
801
+ 1924,
802
+ 1301,
803
+ 1318,
804
+ 1279,
805
+ 2036,
806
+ 1104,
807
+ 1360,
808
+ 2500,
809
+ 942,
810
+ 3446
811
+ ],
812
+ "bio_length": [
813
+ 69,
814
+ 75,
815
+ 17,
816
+ 23,
817
+ 106,
818
+ 118,
819
+ 31,
820
+ 51,
821
+ 3,
822
+ 48,
823
+ 81,
824
+ 39,
825
+ 57,
826
+ 91,
827
+ 35,
828
+ 101,
829
+ 66,
830
+ 108,
831
+ 30,
832
+ 110,
833
+ 35,
834
+ 91,
835
+ 52,
836
+ 104,
837
+ 41,
838
+ 45,
839
+ 36,
840
+ 87,
841
+ 50,
842
+ 45,
843
+ 53,
844
+ 126,
845
+ 54,
846
+ 127,
847
+ 29,
848
+ 19,
849
+ 43,
850
+ 63,
851
+ 44,
852
+ 102,
853
+ 95,
854
+ 67,
855
+ 30,
856
+ 37,
857
+ 128,
858
+ 134,
859
+ 87,
860
+ 8,
861
+ 50,
862
+ 46
863
+ ],
864
+ "verified": {
865
+ "functionName": "arrow",
866
+ "body": "function () {\n\t\tconst weighted = [];\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tconst rand = chance.d10(); // Random number between 1 and 10\n\n\t\t\t// 35% chance to favor the most chosen index\n\t\t\tif (chance.bool({ likelihood: 35 })) {\n\t\t\t\t// 50% chance to slightly alter the index\n\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\tweighted.push(items[mostChosenIndex]);\n\t\t\t\t} else {\n\t\t\t\t\tconst addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;\n\t\t\t\t\tlet newIndex = mostChosenIndex + addOrSubtract;\n\n\t\t\t\t\t// Ensure newIndex is within bounds\n\t\t\t\t\tif (newIndex < 0) newIndex = 0;\n\t\t\t\t\tif (newIndex >= items.length) newIndex = items.length - 1;\n\t\t\t\t\tweighted.push(items[newIndex]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 25% chance to favor the second most chosen index\n\t\t\telse if (chance.bool({ likelihood: 25 })) {\n\t\t\t\tweighted.push(items[secondMostChosenIndex]);\n\t\t\t}\n\t\t\t// 15% chance to favor the third most chosen index\n\t\t\telse if (chance.bool({ likelihood: 15 })) {\n\t\t\t\tweighted.push(items[thirdMostChosenIndex]);\n\t\t\t}\n\t\t\t// Otherwise, pick a random item from the list\n\t\t\telse {\n\t\t\t\tweighted.push(chance.pickone(items));\n\t\t\t}\n\t\t}\n\t\treturn weighted;\n\t}"
867
+ },
868
+ "content_niche": [
869
+ "lifestyle",
870
+ "tech",
871
+ "food",
872
+ "fitness",
873
+ "travel",
874
+ "comedy",
875
+ "news",
876
+ "art"
877
+ ]
878
+ },
879
+ "groupKeys": [
880
+ [
881
+ "community_id",
882
+ 100,
883
+ [
884
+ "post created",
885
+ "comment posted",
886
+ "post liked",
887
+ "post shared"
888
+ ]
889
+ ]
890
+ ],
891
+ "groupProps": {
892
+ "community_id": {
893
+ "name": {
894
+ "functionName": "arrow",
895
+ "body": "() => `${chance.word()} ${chance.pickone([\"Hub\", \"Circle\", \"Squad\", \"Zone\", \"Space\"])}`"
896
+ },
897
+ "member_count": [
898
+ 3318,
899
+ 1526,
900
+ 1364,
901
+ 3127,
902
+ 1378,
903
+ 3629,
904
+ 1698,
905
+ 1286,
906
+ 1300,
907
+ 3265,
908
+ 1284,
909
+ 1930,
910
+ 1121,
911
+ 1386,
912
+ 3754,
913
+ 1827,
914
+ 1856,
915
+ 1506,
916
+ 1523,
917
+ 1061,
918
+ 1034,
919
+ 1325,
920
+ 3284,
921
+ 1558,
922
+ 1633,
923
+ 3369,
924
+ 3236,
925
+ 1339,
926
+ 3727,
927
+ 1295,
928
+ 3828,
929
+ 3597,
930
+ 1332,
931
+ 1481,
932
+ 1605,
933
+ 1088,
934
+ 3556,
935
+ 2987,
936
+ 3737,
937
+ 1692,
938
+ 3588,
939
+ 1578,
940
+ 1240,
941
+ 1762,
942
+ 1357,
943
+ 1364,
944
+ 2074,
945
+ 3775,
946
+ 4059,
947
+ 1332,
948
+ 3303,
949
+ 3286,
950
+ 1432,
951
+ 3022,
952
+ 1510,
953
+ 3310,
954
+ 1722,
955
+ 3721,
956
+ 1393,
957
+ 1139,
958
+ 1526,
959
+ 1547,
960
+ 3771,
961
+ 1691,
962
+ 3580,
963
+ 3656,
964
+ 1963,
965
+ 3886,
966
+ 3295,
967
+ 1239,
968
+ 1777,
969
+ 3067,
970
+ 1620,
971
+ 2120,
972
+ 1314,
973
+ 1321,
974
+ 3302,
975
+ 2037,
976
+ 1605,
977
+ 1496,
978
+ 1229,
979
+ 1393,
980
+ 1059,
981
+ 3944,
982
+ 1624,
983
+ 1389,
984
+ 3209,
985
+ 3316,
986
+ 2875,
987
+ 1170,
988
+ 3259,
989
+ 1260,
990
+ 3709,
991
+ 1236,
992
+ 3792,
993
+ 1281,
994
+ 1464,
995
+ 1584,
996
+ 3592,
997
+ 1217,
998
+ 3630,
999
+ 1348,
1000
+ 1665,
1001
+ 1275,
1002
+ 3532,
1003
+ 3610,
1004
+ 1657,
1005
+ 3723,
1006
+ 1538,
1007
+ 1286,
1008
+ 3703,
1009
+ 1653,
1010
+ 1329,
1011
+ 1474,
1012
+ 2951,
1013
+ 3482,
1014
+ 3875,
1015
+ 1661,
1016
+ 1040,
1017
+ 3729,
1018
+ 1379,
1019
+ 1262,
1020
+ 1777,
1021
+ 1582,
1022
+ 3006,
1023
+ 996,
1024
+ 1318,
1025
+ 1907,
1026
+ 1563,
1027
+ 3778,
1028
+ 3480,
1029
+ 1394,
1030
+ 3744,
1031
+ 3423,
1032
+ 3185,
1033
+ 3765,
1034
+ 1481,
1035
+ 3816,
1036
+ 1192,
1037
+ 3979,
1038
+ 1396,
1039
+ 1278,
1040
+ 1688,
1041
+ 1127,
1042
+ 1237,
1043
+ 1854,
1044
+ 1626,
1045
+ 1427,
1046
+ 1235,
1047
+ 3182,
1048
+ 3058,
1049
+ 1879,
1050
+ 1230,
1051
+ 2525,
1052
+ 3617,
1053
+ 1252,
1054
+ 3703,
1055
+ 3761,
1056
+ 3617,
1057
+ 1591,
1058
+ 1091,
1059
+ 1127,
1060
+ 3429,
1061
+ 3663,
1062
+ 1483,
1063
+ 3650,
1064
+ 1731,
1065
+ 3442,
1066
+ 3668,
1067
+ 2012,
1068
+ 1123,
1069
+ 3767,
1070
+ 1610,
1071
+ 3294,
1072
+ 3675,
1073
+ 3789,
1074
+ 3787,
1075
+ 1434,
1076
+ 1132,
1077
+ 1996,
1078
+ 3074,
1079
+ 1174,
1080
+ 3970,
1081
+ 1861,
1082
+ 1511,
1083
+ 2879,
1084
+ 1238,
1085
+ 3323,
1086
+ 3195,
1087
+ 1261,
1088
+ 1383,
1089
+ 3293,
1090
+ 1171,
1091
+ 1428,
1092
+ 1263,
1093
+ 1214,
1094
+ 3722,
1095
+ 3066,
1096
+ 3432,
1097
+ 1733
1098
+ ],
1099
+ "category": [
1100
+ "technology",
1101
+ "entertainment",
1102
+ "sports",
1103
+ "politics",
1104
+ "art",
1105
+ "science"
1106
+ ],
1107
+ "is_moderated": {
1108
+ "functionName": "arrow",
1109
+ "body": "function () {\n\t\tconst weighted = [];\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tconst rand = chance.d10(); // Random number between 1 and 10\n\n\t\t\t// 35% chance to favor the most chosen index\n\t\t\tif (chance.bool({ likelihood: 35 })) {\n\t\t\t\t// 50% chance to slightly alter the index\n\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\tweighted.push(items[mostChosenIndex]);\n\t\t\t\t} else {\n\t\t\t\t\tconst addOrSubtract = chance.bool({ likelihood: 50 }) ? -rand : rand;\n\t\t\t\t\tlet newIndex = mostChosenIndex + addOrSubtract;\n\n\t\t\t\t\t// Ensure newIndex is within bounds\n\t\t\t\t\tif (newIndex < 0) newIndex = 0;\n\t\t\t\t\tif (newIndex >= items.length) newIndex = items.length - 1;\n\t\t\t\t\tweighted.push(items[newIndex]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 25% chance to favor the second most chosen index\n\t\t\telse if (chance.bool({ likelihood: 25 })) {\n\t\t\t\tweighted.push(items[secondMostChosenIndex]);\n\t\t\t}\n\t\t\t// 15% chance to favor the third most chosen index\n\t\t\telse if (chance.bool({ likelihood: 15 })) {\n\t\t\t\tweighted.push(items[thirdMostChosenIndex]);\n\t\t\t}\n\t\t\t// Otherwise, pick a random item from the list\n\t\t\telse {\n\t\t\t\tweighted.push(chance.pickone(items));\n\t\t\t}\n\t\t}\n\t\treturn weighted;\n\t}"
1110
+ }
1111
+ }
1112
+ },
1113
+ "lookupTables": []
1114
+ },
1115
+ "hooks": "function (record, type, meta) {\n\t\tconst NOW = dayjs();\n\t\tconst DATASET_START = NOW.subtract(days, 'days');\n\t\tconst ALGORITHM_CHANGE_DAY = DATASET_START.add(45, 'days');\n\t\tconst REENGAGEMENT_START = DATASET_START.add(30, 'days');\n\n\t\t// ─── EVENT-LEVEL HOOKS ───────────────────────────────────────────\n\n\t\tif (type === \"event\") {\n\t\t\tconst EVENT_TIME = dayjs(record.time);\n\n\t\t\t// Hook #3: ALGORITHM CHANGE - Day 45 flips feed to explore\n\t\t\tif (record.event === \"post viewed\") {\n\t\t\t\tif (EVENT_TIME.isAfter(ALGORITHM_CHANGE_DAY)) {\n\t\t\t\t\t// After day 45: 70% explore, 15% feed\n\t\t\t\t\tif (chance.bool({ likelihood: 70 })) {\n\t\t\t\t\t\trecord.source = \"explore\";\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Before day 45: 70% feed, 15% explore (reinforce default)\n\t\t\t\t\tif (chance.bool({ likelihood: 70 })) {\n\t\t\t\t\t\trecord.source = \"feed\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Hook #4: ENGAGEMENT BAIT - High hashtag posts get short view durations\n\t\t\tif (record.event === \"post viewed\") {\n\t\t\t\t// 20% of post views are engagement-bait content\n\t\t\t\tif (chance.bool({ likelihood: 20 })) {\n\t\t\t\t\trecord.view_duration_sec = chance.integer({ min: 1, max: 5 });\n\t\t\t\t\trecord.engagement_bait = true;\n\t\t\t\t} else {\n\t\t\t\t\trecord.engagement_bait = false;\n\t\t\t\t}\n\n\t\t\t\t// Hook #5: NOTIFICATION RE-ENGAGEMENT - Trending drives views after day 30\n\t\t\t\tif (EVENT_TIME.isAfter(REENGAGEMENT_START) && chance.bool({ likelihood: 30 })) {\n\t\t\t\t\trecord.source = \"notification\";\n\t\t\t\t\trecord.trending_reengagement = true;\n\t\t\t\t} else {\n\t\t\t\t\trecord.trending_reengagement = false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Hook #8: WEEKEND CONTENT SURGE - tag weekend content (duplication handled in everything hook)\n\t\t\tif (record.event === \"post created\" || record.event === \"story created\") {\n\t\t\t\tconst dayOfWeek = EVENT_TIME.day(); // 0 = Sunday, 6 = Saturday\n\t\t\t\tif (dayOfWeek === 0 || dayOfWeek === 6) {\n\t\t\t\t\trecord.weekend_surge = true;\n\t\t\t\t} else {\n\t\t\t\t\trecord.weekend_surge = false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// ─── EVERYTHING-LEVEL HOOKS ──────────────────────────────────────\n\n\t\tif (type === \"everything\") {\n\t\t\tconst userEvents = record;\n\t\t\tif (!userEvents || userEvents.length === 0) return record;\n\n\t\t\t// Tracking variables for user patterns\n\t\t\tlet postCreatedCount = 0;\n\t\t\tlet followReceivedCount = 0;\n\t\t\tlet reportSubmittedCount = 0;\n\t\t\tlet hasCreatorSubscription = false;\n\t\t\tlet isViralCreator = false;\n\n\t\t\t// First pass: identify user patterns\n\t\t\tuserEvents.forEach((event) => {\n\t\t\t\tif (event.event === \"post created\") {\n\t\t\t\t\tpostCreatedCount++;\n\t\t\t\t}\n\t\t\t\tif (event.event === \"user followed\") {\n\t\t\t\t\tfollowReceivedCount++;\n\t\t\t\t}\n\t\t\t\tif (event.event === \"report submitted\") {\n\t\t\t\t\treportSubmittedCount++;\n\t\t\t\t}\n\t\t\t\tif (event.event === \"creator subscription started\") {\n\t\t\t\t\thasCreatorSubscription = true;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Hook #1: VIRAL CONTENT CASCADE\n\t\t\t// Users with 10+ posts and 5% random chance are viral creators\n\t\t\tif (postCreatedCount >= 10 && chance.bool({ likelihood: 5 })) {\n\t\t\t\tisViralCreator = true;\n\t\t\t}\n\n\t\t\t// Second pass: set schema defaults then modify/inject based on patterns\n\t\t\tfor (let idx = userEvents.length - 1; idx >= 0; idx--) {\n\t\t\t\tconst event = userEvents[idx];\n\t\t\t\tconst eventTime = dayjs(event.time);\n\n\t\t\t\t// Set schema defaults for conditional properties\n\t\t\t\tif (event.event === \"post created\" || event.event === \"story created\") {\n\t\t\t\t\tif (event.monetized_creator === undefined) event.monetized_creator = false;\n\t\t\t\t\tif (event.follow_back_effect === undefined) event.follow_back_effect = false;\n\t\t\t\t}\n\t\t\t\tif (event.event === \"post viewed\") {\n\t\t\t\t\tif (event.viral_cascade === undefined) event.viral_cascade = false;\n\t\t\t\t}\n\t\t\t\tif (event.toxic_user === undefined) event.toxic_user = false;\n\n\t\t\t\t// Hook #1: VIRAL CONTENT CASCADE\n\t\t\t\t// Viral creators get 10-20x engagement on their posts\n\t\t\t\tif (isViralCreator && event.event === \"post created\") {\n\t\t\t\t\tconst viralViews = chance.integer({ min: 10, max: 20 });\n\t\t\t\t\tconst viralLikes = chance.integer({ min: 10, max: 20 });\n\t\t\t\t\tconst viralShares = chance.integer({ min: 10, max: 20 });\n\t\t\t\t\tconst injected = [];\n\n\t\t\t\t\tfor (let i = 0; i < viralViews; i++) {\n\t\t\t\t\t\tinjected.push({\n\t\t\t\t\t\t\tevent: \"post viewed\",\n\t\t\t\t\t\t\ttime: eventTime.add(chance.integer({ min: 1, max: 180 }), 'minutes').toISOString(),\n\t\t\t\t\t\t\tuser_id: event.user_id,\n\t\t\t\t\t\t\tpost_type: event.post_type || \"text\",\n\t\t\t\t\t\t\tsource: chance.pickone([\"feed\", \"explore\", \"search\"]),\n\t\t\t\t\t\t\tview_duration_sec: chance.integer({ min: 5, max: 90 }),\n\t\t\t\t\t\t\tviral_cascade: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tfor (let i = 0; i < viralLikes; i++) {\n\t\t\t\t\t\tinjected.push({\n\t\t\t\t\t\t\tevent: \"post liked\",\n\t\t\t\t\t\t\ttime: eventTime.add(chance.integer({ min: 2, max: 240 }), 'minutes').toISOString(),\n\t\t\t\t\t\t\tuser_id: event.user_id,\n\t\t\t\t\t\t\tpost_type: event.post_type || \"text\",\n\t\t\t\t\t\t\tviral_cascade: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tfor (let i = 0; i < viralShares; i++) {\n\t\t\t\t\t\tinjected.push({\n\t\t\t\t\t\t\tevent: \"post shared\",\n\t\t\t\t\t\t\ttime: eventTime.add(chance.integer({ min: 5, max: 300 }), 'minutes').toISOString(),\n\t\t\t\t\t\t\tuser_id: event.user_id,\n\t\t\t\t\t\t\tshare_destination: chance.pickone([\"repost\", \"dm\", \"external\", \"copy_link\"]),\n\t\t\t\t\t\t\tviral_cascade: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Splice all injected events after the post created event\n\t\t\t\t\tuserEvents.splice(idx + 1, 0, ...injected);\n\t\t\t\t}\n\n\t\t\t\t// Hook #2: FOLLOW-BACK SNOWBALL\n\t\t\t\t// Users with 5+ follows become prolific creators\n\t\t\t\tif (followReceivedCount >= 5 && event.event === \"post created\") {\n\t\t\t\t\tif (chance.bool({ likelihood: 50 })) {\n\t\t\t\t\t\tconst duplicatePost = {\n\t\t\t\t\t\t\tevent: \"post created\",\n\t\t\t\t\t\t\ttime: eventTime.add(chance.integer({ min: 30, max: 240 }), 'minutes').toISOString(),\n\t\t\t\t\t\t\tuser_id: event.user_id,\n\t\t\t\t\t\t\tpost_type: chance.pickone([\"text\", \"image\", \"video\"]),\n\t\t\t\t\t\t\tcharacter_count: chance.integer({ min: 10, max: 280 }),\n\t\t\t\t\t\t\thas_media: chance.bool({ likelihood: 60 }),\n\t\t\t\t\t\t\thashtag_count: chance.integer({ min: 0, max: 5 }),\n\t\t\t\t\t\t\tfollow_back_effect: true,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst extraComment = {\n\t\t\t\t\t\t\tevent: \"comment posted\",\n\t\t\t\t\t\t\ttime: eventTime.add(chance.integer({ min: 10, max: 120 }), 'minutes').toISOString(),\n\t\t\t\t\t\t\tuser_id: event.user_id,\n\t\t\t\t\t\t\tcomment_length: chance.integer({ min: 5, max: 200 }),\n\t\t\t\t\t\t\thas_mention: chance.bool({ likelihood: 40 }),\n\t\t\t\t\t\t\tfollow_back_effect: true,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tuserEvents.splice(idx + 1, 0, duplicatePost, extraComment);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Hook #6: CREATOR MONETIZATION\n\t\t\t\t// Monetized creators post 3x more frequently\n\t\t\t\tif (hasCreatorSubscription && event.event === \"post created\") {\n\t\t\t\t\t// Triple frequency: add 2 extra posts for each existing one\n\t\t\t\t\tfor (let i = 0; i < 2; i++) {\n\t\t\t\t\t\tconst extraPost = {\n\t\t\t\t\t\t\tevent: \"post created\",\n\t\t\t\t\t\t\ttime: eventTime.add(chance.integer({ min: 1, max: 12 }), 'hours').toISOString(),\n\t\t\t\t\t\t\tuser_id: event.user_id,\n\t\t\t\t\t\t\tpost_type: chance.pickone([\"text\", \"image\", \"video\", \"link\"]),\n\t\t\t\t\t\t\tcharacter_count: chance.integer({ min: 20, max: 280 }),\n\t\t\t\t\t\t\thas_media: chance.bool({ likelihood: 70 }),\n\t\t\t\t\t\t\thashtag_count: chance.integer({ min: 1, max: 8 }),\n\t\t\t\t\t\t\tmonetized_creator: true,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tuserEvents.splice(idx + 1, 0, extraPost);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (hasCreatorSubscription && event.event === \"story created\") {\n\t\t\t\t\t// Also triple story creation\n\t\t\t\t\tfor (let i = 0; i < 2; i++) {\n\t\t\t\t\t\tconst extraStory = {\n\t\t\t\t\t\t\tevent: \"story created\",\n\t\t\t\t\t\t\ttime: eventTime.add(chance.integer({ min: 1, max: 8 }), 'hours').toISOString(),\n\t\t\t\t\t\t\tuser_id: event.user_id,\n\t\t\t\t\t\t\tstory_type: chance.pickone([\"photo\", \"video\", \"text\"]),\n\t\t\t\t\t\t\thas_filter: chance.bool({ likelihood: 60 }),\n\t\t\t\t\t\t\thas_sticker: chance.bool({ likelihood: 40 }),\n\t\t\t\t\t\t\tmonetized_creator: true,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tuserEvents.splice(idx + 1, 0, extraStory);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Monetized creators also check their analytics more (extra post views)\n\t\t\t\tif (hasCreatorSubscription && event.event === \"post viewed\") {\n\t\t\t\t\tif (chance.bool({ likelihood: 25 })) {\n\t\t\t\t\t\tconst analyticsView = {\n\t\t\t\t\t\t\tevent: \"post viewed\",\n\t\t\t\t\t\t\ttime: eventTime.add(chance.integer({ min: 1, max: 30 }), 'minutes').toISOString(),\n\t\t\t\t\t\t\tuser_id: event.user_id,\n\t\t\t\t\t\t\tpost_type: event.post_type || \"text\",\n\t\t\t\t\t\t\tsource: \"profile\",\n\t\t\t\t\t\t\tview_duration_sec: chance.integer({ min: 10, max: 60 }),\n\t\t\t\t\t\t\tmonetized_creator: true,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tuserEvents.splice(idx + 1, 0, analyticsView);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Hook #8: WEEKEND CONTENT SURGE - inject duplicate events for weekend content\n\t\t\tfor (let idx = userEvents.length - 1; idx >= 0; idx--) {\n\t\t\t\tconst event = userEvents[idx];\n\t\t\t\tif (event.weekend_surge && !event.weekend_duplicate) {\n\t\t\t\t\tif (chance.bool({ likelihood: 30 })) {\n\t\t\t\t\t\tconst etime = dayjs(event.time);\n\t\t\t\t\t\tconst dup = {\n\t\t\t\t\t\t\t...event,\n\t\t\t\t\t\t\ttime: etime.add(chance.integer({ min: 1, max: 3 }), 'hours').toISOString(),\n\t\t\t\t\t\t\tweekend_duplicate: true,\n\t\t\t\t\t\t};\n\t\t\t\t\t\tuserEvents.splice(idx + 1, 0, dup);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Hook #7: TOXICITY CHURN\n\t\t\t// Users with 3+ reports lose 60% of activity after day 30\n\t\t\tif (reportSubmittedCount >= 3) {\n\t\t\t\tconst churnCutoff = DATASET_START.add(30, 'days');\n\t\t\t\tfor (let i = userEvents.length - 1; i >= 0; i--) {\n\t\t\t\t\tconst evt = userEvents[i];\n\t\t\t\t\tif (dayjs(evt.time).isAfter(churnCutoff)) {\n\t\t\t\t\t\tif (chance.bool({ likelihood: 60 })) {\n\t\t\t\t\t\t\tuserEvents.splice(i, 1);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tevt.toxic_user = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn record;\n\t}",
1116
+ "timestamp": "2026-04-10T01:39:07.776Z",
1117
+ "version": "4.0"
1118
+ }