@ak--47/dungeon-master 1.0.0 → 1.2.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.
Files changed (104) hide show
  1. package/README.md +7 -7
  2. package/dungeons/technical/ad-spend-schema.json +128 -0
  3. package/dungeons/technical/ad-spend.js +116 -0
  4. package/dungeons/technical/anonymous-users-schema.json +92 -0
  5. package/dungeons/technical/anonymous-users.js +102 -0
  6. package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
  7. package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +12 -5
  8. package/dungeons/technical/experiments-schema.json +203 -0
  9. package/dungeons/technical/experiments.js +168 -0
  10. package/dungeons/technical/foobar-schema.json +362 -0
  11. package/dungeons/{foobar.js → technical/foobar.js} +3 -3
  12. package/dungeons/technical/group-analytics-schema.json +241 -0
  13. package/dungeons/technical/group-analytics.js +151 -0
  14. package/dungeons/technical/mirror-strategies-schema.json +84 -0
  15. package/dungeons/technical/mirror-strategies.js +126 -0
  16. package/dungeons/technical/nested-objects-schema.json +145 -0
  17. package/dungeons/technical/nested-objects.js +207 -0
  18. package/dungeons/technical/retention-cadence-schema.json +37 -0
  19. package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +2 -3
  20. package/dungeons/technical/sanity-schema.json +185 -0
  21. package/dungeons/{sanity.js → technical/sanity.js} +4 -2
  22. package/dungeons/technical/scale-test-schema.json +70 -0
  23. package/dungeons/technical/scale-test.js +90 -0
  24. package/dungeons/technical/scd-schema.json +467 -0
  25. package/dungeons/{scd.js → technical/scd.js} +20 -10
  26. package/dungeons/technical/simple-schema.json +362 -0
  27. package/dungeons/{simple.js → technical/simple.js} +7 -7
  28. package/dungeons/technical/simplest-schema.json +724 -0
  29. package/dungeons/{simplest.js → technical/simplest.js} +7 -7
  30. package/dungeons/technical/text-generation-schema.json +1062 -0
  31. package/dungeons/{text-generation.js → technical/text-generation.js} +24 -11
  32. package/dungeons/vertical/community-schema.json +579 -0
  33. package/dungeons/vertical/community.js +794 -0
  34. package/dungeons/vertical/devtools-schema.json +601 -0
  35. package/dungeons/vertical/devtools.js +843 -0
  36. package/dungeons/vertical/ecommerce-schema.json +604 -0
  37. package/dungeons/{ecommerce.js → vertical/ecommerce.js} +43 -5
  38. package/dungeons/vertical/education-schema.json +5686 -0
  39. package/dungeons/{education.js → vertical/education.js} +70 -39
  40. package/dungeons/vertical/fintech-schema.json +630 -0
  41. package/dungeons/{fintech.js → vertical/fintech.js} +107 -56
  42. package/dungeons/vertical/fitness-schema.json +530 -0
  43. package/dungeons/vertical/fitness.js +808 -0
  44. package/dungeons/vertical/food-delivery-schema.json +36728 -0
  45. package/dungeons/{food.js → vertical/food-delivery.js} +67 -26
  46. package/dungeons/vertical/gaming-schema.json +438 -0
  47. package/dungeons/{gaming.js → vertical/gaming.js} +29 -16
  48. package/dungeons/vertical/healthcare-schema.json +549 -0
  49. package/dungeons/vertical/healthcare.js +775 -0
  50. package/dungeons/vertical/insurance-application-schema.json +485 -0
  51. package/dungeons/{insurance-application.js → vertical/insurance-application.js} +41 -31
  52. package/dungeons/vertical/logistics-schema.json +574 -0
  53. package/dungeons/vertical/logistics.js +799 -0
  54. package/dungeons/vertical/marketplace-schema.json +533 -0
  55. package/dungeons/vertical/marketplace.js +850 -0
  56. package/dungeons/vertical/media-schema.json +4749 -0
  57. package/dungeons/{media.js → vertical/media.js} +103 -19
  58. package/dungeons/vertical/rpg-schema.json +2491 -0
  59. package/dungeons/{rpg.js → vertical/rpg.js} +87 -60
  60. package/dungeons/vertical/sass-schema.json +3128 -0
  61. package/dungeons/{sass.js → vertical/sass.js} +65 -26
  62. package/dungeons/vertical/social-schema.json +620 -0
  63. package/dungeons/{social.js → vertical/social.js} +76 -17
  64. package/dungeons/vertical/travel-schema.json +580 -0
  65. package/dungeons/vertical/travel.js +742 -0
  66. package/index.js +20 -16
  67. package/lib/core/config-validator.js +286 -1
  68. package/lib/core/context.js +16 -5
  69. package/lib/core/storage.js +2 -0
  70. package/lib/generators/adspend.js +3 -3
  71. package/lib/generators/events.js +141 -14
  72. package/lib/generators/funnels.js +67 -7
  73. package/lib/generators/text.js +2 -2
  74. package/lib/orchestrators/user-loop.js +444 -22
  75. package/lib/templates/soup-presets.js +2 -2
  76. package/lib/utils/utils.js +4 -5
  77. package/package.json +4 -3
  78. package/scripts/dungeon-to-json.mjs +163 -0
  79. package/scripts/extract-dungeon-schema.mjs +343 -0
  80. package/scripts/json-to-dungeon.mjs +200 -0
  81. package/scripts/run-dungeon.mjs +76 -0
  82. package/scripts/run-many.mjs +118 -0
  83. package/scripts/verify-runner.mjs +42 -0
  84. package/types.d.ts +316 -1
  85. package/dungeons/array-of-object-lookup-schema.json +0 -327
  86. package/dungeons/ecommerce-schema.json +0 -462
  87. package/dungeons/education-schema.json +0 -2409
  88. package/dungeons/fintech-schema.json +0 -14034
  89. package/dungeons/foobar-schema.json +0 -403
  90. package/dungeons/food-delivery-schema.json +0 -192
  91. package/dungeons/food-delivery.js +0 -602
  92. package/dungeons/food-schema.json +0 -1152
  93. package/dungeons/gaming-schema.json +0 -1270
  94. package/dungeons/insurance-application-schema.json +0 -204
  95. package/dungeons/media-schema.json +0 -906
  96. package/dungeons/retention-cadence-schema.json +0 -78
  97. package/dungeons/rpg-schema.json +0 -4526
  98. package/dungeons/sanity-schema.json +0 -255
  99. package/dungeons/sass-schema.json +0 -1291
  100. package/dungeons/scd-schema.json +0 -919
  101. package/dungeons/simple-schema.json +0 -608
  102. package/dungeons/simplest-schema.json +0 -1418
  103. package/dungeons/social-schema.json +0 -1118
  104. package/dungeons/text-generation-schema.json +0 -3096
@@ -0,0 +1,850 @@
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 = "dm4-marketplace";
8
+ dayjs.extend(utc);
9
+ const chance = u.initChance(SEED);
10
+ const num_users = 5_000;
11
+ const days = 100;
12
+ const NOW = dayjs();
13
+ const DATASET_START = NOW.subtract(days, "days");
14
+
15
+ /** @typedef {import("../../types").Dungeon} Config */
16
+
17
+ // Generate consistent seller store and listing IDs at module level
18
+ const storeIds = v.range(1, 150).map(() => `STORE_${v.uid(6)}`);
19
+ const listingIds = v.range(1, 500).map(() => `LST_${v.uid(8)}`);
20
+
21
+ /**
22
+ * ═══════════════════════════════════════════════════════════════
23
+ * DATASET OVERVIEW
24
+ * ═══════════════════════════════════════════════════════════════
25
+ *
26
+ * TradeNest — a two-sided marketplace connecting sellers who list
27
+ * products with buyers who search, purchase, and review items.
28
+ *
29
+ * - 5,000 users over 100 days, ~600K events
30
+ * - Two-sided: sellers (power, casual, new) and buyers (frequent, window_shopper)
31
+ * - Core loop: search → view → add to cart → purchase → review
32
+ * - Seller loop: create listing → receive offers → accept → ship
33
+ * - Revenue: marketplace fees on transactions, seller listing fees
34
+ *
35
+ * Advanced Features:
36
+ * - Personas: 5 archetypes spanning both buyer and seller roles
37
+ * - World Events: marketplace fee change (day 45, permanent), viral TikTok moment (day 55, 3 days)
38
+ * - Engagement Decay: exponential with 75-day half-life, 0.1 floor
39
+ * - Attribution: Google Shopping, TikTok, Seller Referral, organic
40
+ * - Geo: US/UK/Australia/Canada with local currencies
41
+ * - Anomalies: whale purchases, viral signup burst
42
+ *
43
+ * Key entities:
44
+ * - store_id: unique seller storefront
45
+ * - listing_id: individual product listing
46
+ * - category: product vertical (electronics, clothing, etc.)
47
+ * - user_type: buyer vs seller role
48
+ */
49
+
50
+ /**
51
+ * ═══════════════════════════════════════════════════════════════
52
+ * ANALYTICS HOOKS (8 hooks)
53
+ * ═══════════════════════════════════════════════════════════════
54
+ *
55
+ * ───────────────────────────────────────────────────────────────
56
+ * 1. FEE CHANGE IMPACT (event hook)
57
+ * ───────────────────────────────────────────────────────────────
58
+ *
59
+ * PATTERN: After day 45 (permanent marketplace fee increase), all
60
+ * "listing created" events get listing_fee multiplied by 1.3x.
61
+ * Simulates the revenue impact of a platform fee adjustment.
62
+ *
63
+ * HOW TO FIND IT IN MIXPANEL:
64
+ *
65
+ * Report 1: Listing Fee Before vs After Fee Change
66
+ * • Report type: Insights
67
+ * • Event: "listing created"
68
+ * • Measure: Average of "listing_fee"
69
+ * • Line chart by week
70
+ * • Expected: Clear step-up around day 45 from ~$15 avg to ~$20 avg
71
+ *
72
+ * Report 2: Fee Change Flag Distribution
73
+ * • Report type: Insights
74
+ * • Event: "listing created"
75
+ * • Measure: Total
76
+ * • Breakdown: "fee_change"
77
+ * • Expected: "increased" appears only after day 45
78
+ *
79
+ * REAL-WORLD ANALOGUE: Marketplace platforms periodically adjust
80
+ * commission/listing fees, impacting seller economics and behavior.
81
+ *
82
+ * ───────────────────────────────────────────────────────────────
83
+ * 2. WEEKEND SHOPPING SURGE (event hook)
84
+ * ───────────────────────────────────────────────────────────────
85
+ *
86
+ * PATTERN: "purchase completed" events on Saturday or Sunday get
87
+ * total_amount multiplied by 1.2x. Weekend shoppers spend more
88
+ * per transaction due to leisure browsing.
89
+ *
90
+ * HOW TO FIND IT IN MIXPANEL:
91
+ *
92
+ * Report 1: Weekend vs Weekday Average Spend
93
+ * • Report type: Insights
94
+ * • Event: "purchase completed"
95
+ * • Measure: Average of "total_amount"
96
+ * • Breakdown: Day of week
97
+ * • Expected: Sat/Sun avg ≈ $72 vs Mon-Fri avg ≈ $60
98
+ *
99
+ * REAL-WORLD ANALOGUE: E-commerce platforms see higher average
100
+ * order values on weekends when buyers browse leisurely.
101
+ *
102
+ * ───────────────────────────────────────────────────────────────
103
+ * 3. SELLER SUCCESS → BUYER TRUST (everything hook)
104
+ * ───────────────────────────────────────────────────────────────
105
+ *
106
+ * PATTERN: Users with user_type "seller" and segment "power_seller"
107
+ * (from meta.profile) get their "purchase completed" events cloned
108
+ * 2x. Power sellers attract more transactions due to trust/reputation.
109
+ *
110
+ * HOW TO FIND IT IN MIXPANEL:
111
+ *
112
+ * Report 1: Purchases per User by Segment
113
+ * • Report type: Insights
114
+ * • Event: "purchase completed"
115
+ * • Measure: Total per user
116
+ * • Breakdown: user property "segment"
117
+ * • Expected: power_seller ≈ 2x purchases vs casual_seller
118
+ *
119
+ * REAL-WORLD ANALOGUE: High-rated sellers with established reputations
120
+ * receive disproportionately more sales on platforms like eBay/Etsy.
121
+ *
122
+ * ───────────────────────────────────────────────────────────────
123
+ * 4. SEARCH-TO-PURCHASE BY CATEGORY (everything hook)
124
+ * ───────────────────────────────────────────────────────────────
125
+ *
126
+ * PATTERN: Users whose events include "item searched" with category
127
+ * "electronics" get cloned "purchase completed" events (electronics
128
+ * buyers have higher conversion). Creates category-specific lift.
129
+ *
130
+ * HOW TO FIND IT IN MIXPANEL:
131
+ *
132
+ * Report 1: Purchase Volume by Category
133
+ * • Report type: Insights
134
+ * • Event: "purchase completed"
135
+ * • Measure: Total
136
+ * • Breakdown: "category" (superProp)
137
+ * • Expected: electronics ≈ 1.5-2x more purchases than clothing/home
138
+ *
139
+ * REAL-WORLD ANALOGUE: Electronics shoppers have higher purchase
140
+ * intent — they research, decide, and buy with less browsing.
141
+ *
142
+ * ───────────────────────────────────────────────────────────────
143
+ * 5. RESPONSE TIME → CONVERSION (everything hook)
144
+ * ───────────────────────────────────────────────────────────────
145
+ *
146
+ * PATTERN: Users whose average response_time_hours on "message sent"
147
+ * events is < 2 hours get additional "offer accepted" events cloned.
148
+ * Fast responders close more deals.
149
+ *
150
+ * HOW TO FIND IT IN MIXPANEL:
151
+ *
152
+ * Report 1: Offer Accepted Count vs Response Time
153
+ * • Report type: Insights
154
+ * • Event: "offer accepted"
155
+ * • Measure: Total per user
156
+ * • Breakdown: user property "response_time_hours"
157
+ * • Expected: Users with < 2hr avg response ≈ 2x offer accepts
158
+ *
159
+ * REAL-WORLD ANALOGUE: Sellers who respond quickly to inquiries
160
+ * close significantly more deals on marketplace platforms.
161
+ *
162
+ * ───────────────────────────────────────────────────────────────
163
+ * 6. NEW SELLER CHURN (everything hook)
164
+ * ───────────────────────────────────────────────────────────────
165
+ *
166
+ * PATTERN: Users with segment "new_seller" and fewer than 10 total
167
+ * events lose 40% of events after day 14. Simulates new sellers
168
+ * who try the platform and abandon it quickly.
169
+ *
170
+ * HOW TO FIND IT IN MIXPANEL:
171
+ *
172
+ * Report 1: New Seller Retention
173
+ * • Report type: Insights
174
+ * • Event: All events
175
+ * • Measure: Total per user
176
+ * • Filter: segment = "new_seller"
177
+ * • Line chart by week
178
+ * • Expected: Sharp drop-off around week 2-3
179
+ *
180
+ * Report 2: Active Users by Segment
181
+ * • Report type: Insights
182
+ * • Event: Any active event
183
+ * • Measure: Uniques
184
+ * • Breakdown: user property "segment"
185
+ * • Expected: new_seller retention ~60% vs power_seller ~99%
186
+ *
187
+ * REAL-WORLD ANALOGUE: Most new sellers on marketplace platforms
188
+ * churn within the first month if they don't get early traction.
189
+ *
190
+ * ───────────────────────────────────────────────────────────────
191
+ * 7. POWER SELLER PROFILES (user hook)
192
+ * ───────────────────────────────────────────────────────────────
193
+ *
194
+ * PATTERN: Users with segment "power_seller" get total_transactions
195
+ * set to 100-500, seller_rating to 4.5-5.0, and store_name to a
196
+ * realistic business name. Enriches profiles for realistic cohort analysis.
197
+ *
198
+ * HOW TO FIND IT IN MIXPANEL:
199
+ *
200
+ * Report 1: Transaction Count Distribution by Segment
201
+ * • Report type: Insights
202
+ * • Measure: Profiles → Breakdown by "total_transactions" (histogram)
203
+ * • Filter: segment = "power_seller"
204
+ * • Expected: power_seller total_transactions 100-500, others ≈ 0
205
+ *
206
+ * Report 2: Seller Rating by Segment
207
+ * • Report type: Insights
208
+ * • Measure: Profiles → Average of "seller_rating"
209
+ * • Breakdown: "segment"
210
+ * • Expected: power_seller ≈ 4.7 avg, casual_seller ≈ 3.5
211
+ *
212
+ * REAL-WORLD ANALOGUE: Top sellers have established storefronts,
213
+ * high transaction volumes, and near-perfect ratings.
214
+ *
215
+ * ───────────────────────────────────────────────────────────────
216
+ * 8. FREQUENT BUYER FUNNEL LIFT (funnel-pre hook)
217
+ * ───────────────────────────────────────────────────────────────
218
+ *
219
+ * PATTERN: Users with segment "frequent_buyer" get 1.3x conversion
220
+ * rate on the browse-to-purchase funnel. Repeat buyers know what
221
+ * they want and convert faster.
222
+ *
223
+ * HOW TO FIND IT IN MIXPANEL:
224
+ *
225
+ * Report 1: Browse-to-Purchase Conversion by Segment
226
+ * • Report type: Funnels
227
+ * • Steps: "item searched" → "item viewed" → "add to cart" → "purchase completed"
228
+ * • Breakdown: user property "segment"
229
+ * • Expected: frequent_buyer ≈ 39% vs window_shopper ≈ 20%
230
+ *
231
+ * REAL-WORLD ANALOGUE: Returning buyers have established trust
232
+ * and familiarity with the platform, converting at higher rates.
233
+ *
234
+ * ═══════════════════════════════════════════════════════════════
235
+ * EXPECTED METRICS SUMMARY
236
+ * ═══════════════════════════════════════════════════════════════
237
+ *
238
+ * Hook | Metric | Baseline | Effect | Ratio
239
+ * ────────────────────────────|─────────────────────|──────────|─────────|──────
240
+ * Fee Change Impact | listing_fee | $15 | $20 | 1.3x
241
+ * Weekend Shopping Surge | total_amount | $60 | $72 | 1.2x
242
+ * Seller Success → Trust | purchases/user | 3 | 6 | 2x
243
+ * Electronics Category Lift | electronics purch. | baseline | 1.5x | 1.5x
244
+ * Response Time → Conversion | offer_accepted/user | 1 | 2 | 2x
245
+ * New Seller Churn | events post-day-14 | 100% | 60% | 0.6x
246
+ * Power Seller Profiles | total_transactions | 0 | 100-500 | --
247
+ * Frequent Buyer Funnel | funnel conversion | 30% | 39% | 1.3x
248
+ */
249
+
250
+ /** @type {Config} */
251
+ const config = {
252
+ token: "",
253
+ seed: SEED,
254
+ numDays: days,
255
+ numEvents: num_users * 120,
256
+ numUsers: num_users,
257
+ hasAnonIds: false,
258
+ hasSessionIds: true,
259
+ format: "json",
260
+ gzip: true,
261
+ alsoInferFunnels: false,
262
+ hasLocation: true,
263
+ hasAndroidDevices: true,
264
+ hasIOSDevices: true,
265
+ hasDesktopDevices: true,
266
+ hasBrowser: false,
267
+ hasCampaigns: false,
268
+ isAnonymous: false,
269
+ hasAdSpend: false,
270
+ percentUsersBornInDataset: 35,
271
+ hasAvatar: true,
272
+ concurrency: 1,
273
+ writeToDisk: false,
274
+ scdProps: {
275
+ seller_tier: {
276
+ values: ["new", "verified", "power", "featured"],
277
+ frequency: "month",
278
+ timing: "fuzzy",
279
+ max: 8
280
+ }
281
+ },
282
+ mirrorProps: {},
283
+ lookupTables: [],
284
+
285
+ // ── Events (18) ──────────────────────────────────────────
286
+ events: [
287
+ {
288
+ event: "account created",
289
+ weight: 1,
290
+ isFirstEvent: true,
291
+ properties: {
292
+ signup_source: ["organic", "google_shopping", "tiktok", "seller_referral", "email_campaign", "word_of_mouth"],
293
+ },
294
+ },
295
+ {
296
+ event: "item searched",
297
+ weight: 8,
298
+ properties: {
299
+ search_query: ["laptop", "vintage jacket", "sneakers", "headphones", "phone case", "gaming console", "watch", "sunglasses", "backpack", "camera"],
300
+ results_count: u.weighNumRange(0, 50, 0.5),
301
+ sort_by: ["relevance", "relevance", "price_low", "price_high", "newest", "best_selling"],
302
+ },
303
+ },
304
+ {
305
+ event: "item viewed",
306
+ weight: 7,
307
+ properties: {
308
+ listing_id: chance.pickone.bind(chance, listingIds),
309
+ item_price: u.weighNumRange(5, 500, 0.3, 45),
310
+ condition: ["new", "new", "like_new", "good", "fair"],
311
+ seller_rating: u.weighNumRange(1, 5, 0.7, 4),
312
+ photos_count: u.weighNumRange(1, 10, 0.5, 3),
313
+ },
314
+ },
315
+ {
316
+ event: "add to cart",
317
+ weight: 5,
318
+ properties: {
319
+ listing_id: chance.pickone.bind(chance, listingIds),
320
+ item_price: u.weighNumRange(5, 500, 0.3, 45),
321
+ quantity: u.weighNumRange(1, 5, 0.2),
322
+ },
323
+ },
324
+ {
325
+ event: "purchase completed",
326
+ weight: 3,
327
+ properties: {
328
+ listing_id: chance.pickone.bind(chance, listingIds),
329
+ total_amount: u.weighNumRange(10, 500, 0.3, 60),
330
+ item_count: u.weighNumRange(1, 5, 0.3),
331
+ payment_method: ["credit_card", "credit_card", "paypal", "apple_pay", "debit"],
332
+ shipping_method: ["standard", "standard", "express", "pickup"],
333
+ is_whale_purchase: [false],
334
+ },
335
+ },
336
+ {
337
+ event: "listing created",
338
+ weight: 4,
339
+ properties: {
340
+ listing_id: chance.pickone.bind(chance, listingIds),
341
+ store_id: chance.pickone.bind(chance, storeIds),
342
+ asking_price: u.weighNumRange(5, 500, 0.3, 50),
343
+ condition: ["new", "new", "like_new", "good", "fair"],
344
+ listing_fee: u.weighNumRange(5, 30, 0.5, 15),
345
+ fee_change: ["none"],
346
+ },
347
+ },
348
+ {
349
+ event: "listing updated",
350
+ weight: 3,
351
+ properties: {
352
+ listing_id: chance.pickone.bind(chance, listingIds),
353
+ field_updated: ["price", "price", "description", "photos", "condition", "shipping"],
354
+ price_change_pct: u.weighNumRange(-30, 30, 0.5),
355
+ },
356
+ },
357
+ {
358
+ event: "offer received",
359
+ weight: 3,
360
+ properties: {
361
+ listing_id: chance.pickone.bind(chance, listingIds),
362
+ offer_amount: u.weighNumRange(5, 400, 0.3, 40),
363
+ offer_pct_of_asking: u.weighNumRange(50, 100, 0.5, 80),
364
+ },
365
+ },
366
+ {
367
+ event: "offer accepted",
368
+ weight: 2,
369
+ properties: {
370
+ listing_id: chance.pickone.bind(chance, listingIds),
371
+ final_price: u.weighNumRange(10, 500, 0.3, 55),
372
+ negotiation_rounds: u.weighNumRange(1, 5, 0.3),
373
+ response_time_hours: u.weighNumRange(0.1, 48, 0.3, 4),
374
+ },
375
+ },
376
+ {
377
+ event: "review submitted",
378
+ weight: 2,
379
+ properties: {
380
+ listing_id: chance.pickone.bind(chance, listingIds),
381
+ rating: u.weighNumRange(1, 5, 0.7, 4),
382
+ review_length: u.weighNumRange(10, 300, 0.5, 80),
383
+ is_verified_purchase: [true, true, true, false],
384
+ },
385
+ },
386
+ {
387
+ event: "seller rated",
388
+ weight: 2,
389
+ properties: {
390
+ store_id: chance.pickone.bind(chance, storeIds),
391
+ seller_rating: u.weighNumRange(1, 5, 0.7, 4),
392
+ would_buy_again: [true, true, true, true, false],
393
+ },
394
+ },
395
+ {
396
+ event: "message sent",
397
+ weight: 5,
398
+ properties: {
399
+ message_type: ["inquiry", "inquiry", "negotiation", "shipping_question", "complaint", "thank_you"],
400
+ recipient_type: ["seller", "seller", "buyer", "support"],
401
+ response_time_hours: u.weighNumRange(0.1, 48, 0.3, 6),
402
+ },
403
+ },
404
+ {
405
+ event: "shipping updated",
406
+ weight: 3,
407
+ properties: {
408
+ status: ["label_created", "in_transit", "in_transit", "out_for_delivery", "delivered", "delivered"],
409
+ carrier: ["usps", "ups", "fedex", "dhl"],
410
+ tracking_viewed: [true, true, false],
411
+ },
412
+ },
413
+ {
414
+ event: "refund requested",
415
+ weight: 1,
416
+ properties: {
417
+ reason: ["not_as_described", "damaged", "wrong_item", "changed_mind", "late_delivery"],
418
+ refund_amount: u.weighNumRange(5, 300, 0.4, 40),
419
+ resolution_status: ["pending", "approved", "approved", "denied"],
420
+ },
421
+ },
422
+ {
423
+ event: "notification received",
424
+ weight: 6,
425
+ properties: {
426
+ notification_type: ["price_drop", "price_drop", "new_offer", "shipping_update", "message_received", "sale_reminder"],
427
+ channel: ["push", "push", "email", "sms"],
428
+ opened: [true, true, true, false],
429
+ },
430
+ },
431
+ {
432
+ event: "profile updated",
433
+ weight: 2,
434
+ properties: {
435
+ field_updated: ["address", "payment_method", "profile_photo", "bio", "phone", "store_description"],
436
+ },
437
+ },
438
+ {
439
+ event: "app session",
440
+ weight: 8,
441
+ properties: {
442
+ session_duration_sec: u.weighNumRange(10, 1800, 0.4, 120),
443
+ pages_viewed: u.weighNumRange(1, 20, 0.5, 4),
444
+ },
445
+ },
446
+ {
447
+ event: "account deactivated",
448
+ weight: 1,
449
+ isChurnEvent: true,
450
+ returnLikelihood: 0.15,
451
+ isStrictEvent: true,
452
+ properties: {
453
+ reason: ["low_sales", "high_fees", "found_alternative", "bad_experience", "no_longer_needed"],
454
+ },
455
+ },
456
+ ],
457
+
458
+ // ── Funnels (5) ──────────────────────────────────────────
459
+ funnels: [
460
+ {
461
+ name: "Buyer Onboarding",
462
+ sequence: ["account created", "item searched", "item viewed", "add to cart"],
463
+ conversionRate: 40,
464
+ order: "sequential",
465
+ isFirstFunnel: true,
466
+ timeToConvert: 72,
467
+ weight: 3,
468
+ },
469
+ {
470
+ name: "Browse to Purchase",
471
+ sequence: ["item searched", "item viewed", "add to cart", "purchase completed"],
472
+ conversionRate: 30,
473
+ order: "sequential",
474
+ timeToConvert: 48,
475
+ weight: 5,
476
+ },
477
+ {
478
+ name: "Seller Listing Flow",
479
+ sequence: ["listing created", "listing updated", "offer received", "offer accepted"],
480
+ conversionRate: 25,
481
+ order: "sequential",
482
+ timeToConvert: 168,
483
+ weight: 3,
484
+ },
485
+ {
486
+ name: "Offer Negotiation",
487
+ sequence: ["offer received", "message sent", "offer accepted"],
488
+ conversionRate: 35,
489
+ order: "sequential",
490
+ timeToConvert: 72,
491
+ weight: 2,
492
+ },
493
+ {
494
+ name: "Review After Purchase",
495
+ sequence: ["purchase completed", "shipping updated", "review submitted"],
496
+ conversionRate: 20,
497
+ order: "sequential",
498
+ timeToConvert: 336,
499
+ weight: 2,
500
+ },
501
+ ],
502
+
503
+ // ── SuperProps ──────────────────────────────────────────
504
+ superProps: {
505
+ platform: ["ios", "android", "web"],
506
+ category: ["electronics", "clothing", "home_garden", "collectibles", "sports", "toys", "automotive"],
507
+ },
508
+
509
+ // ── UserProps ──────────────────────────────────────────
510
+ userProps: {
511
+ user_type: ["buyer"],
512
+ segment: ["window_shopper"],
513
+ seller_rating: u.weighNumRange(0, 5, 0.5),
514
+ total_transactions: [0],
515
+ response_time_hours: u.weighNumRange(0, 48, 0.3),
516
+ store_name: ["none"],
517
+ },
518
+
519
+ // ── Personas ──────────────────────────────────
520
+ personas: [
521
+ {
522
+ name: "power_seller",
523
+ weight: 8,
524
+ eventMultiplier: 5.0,
525
+ conversionModifier: 1.8,
526
+ churnRate: 0.01,
527
+ properties: {
528
+ user_type: "seller",
529
+ segment: "power_seller",
530
+ },
531
+ },
532
+ {
533
+ name: "casual_seller",
534
+ weight: 20,
535
+ eventMultiplier: 1.5,
536
+ conversionModifier: 1.0,
537
+ churnRate: 0.08,
538
+ properties: {
539
+ user_type: "seller",
540
+ segment: "casual_seller",
541
+ },
542
+ },
543
+ {
544
+ name: "frequent_buyer",
545
+ weight: 25,
546
+ eventMultiplier: 2.0,
547
+ conversionModifier: 1.3,
548
+ churnRate: 0.03,
549
+ properties: {
550
+ user_type: "buyer",
551
+ segment: "frequent_buyer",
552
+ },
553
+ },
554
+ {
555
+ name: "window_shopper",
556
+ weight: 35,
557
+ eventMultiplier: 0.5,
558
+ conversionModifier: 0.4,
559
+ churnRate: 0.15,
560
+ properties: {
561
+ user_type: "buyer",
562
+ segment: "window_shopper",
563
+ },
564
+ },
565
+ {
566
+ name: "new_seller",
567
+ weight: 12,
568
+ eventMultiplier: 0.8,
569
+ conversionModifier: 0.5,
570
+ churnRate: 0.3,
571
+ properties: {
572
+ user_type: "seller",
573
+ segment: "new_seller",
574
+ },
575
+ activeWindow: { maxDays: 28 },
576
+ },
577
+ ],
578
+
579
+ // ── World Events ──────────────────────────────
580
+ worldEvents: [
581
+ {
582
+ name: "marketplace_fee_change",
583
+ type: "product_launch",
584
+ startDay: 45,
585
+ duration: null,
586
+ injectProps: { fee_change: "increased" },
587
+ affectsEvents: ["listing created"],
588
+ },
589
+ {
590
+ name: "viral_tiktok_moment",
591
+ type: "campaign",
592
+ startDay: 55,
593
+ duration: 3,
594
+ volumeMultiplier: 3.0,
595
+ affectsEvents: ["item searched", "item viewed", "purchase completed"],
596
+ },
597
+ ],
598
+
599
+ // ── Engagement Decay ──────────────────────────
600
+ engagementDecay: {
601
+ model: "exponential",
602
+ halfLife: 75,
603
+ floor: 0.1,
604
+ },
605
+
606
+ // ── Attribution ──────────────────────────────
607
+ attribution: {
608
+ model: "last_touch",
609
+ window: 7,
610
+ campaigns: [
611
+ {
612
+ name: "google_shopping",
613
+ source: "google",
614
+ medium: "cpc",
615
+ activeDays: [0, 100],
616
+ dailyBudget: [200, 800],
617
+ acquisitionRate: 0.03,
618
+ userPersonaBias: { frequent_buyer: 0.5 },
619
+ },
620
+ {
621
+ name: "tiktok",
622
+ source: "tiktok",
623
+ medium: "social",
624
+ activeDays: [10, 90],
625
+ dailyBudget: [100, 400],
626
+ acquisitionRate: 0.02,
627
+ userPersonaBias: { window_shopper: 0.5 },
628
+ },
629
+ {
630
+ name: "seller_referral",
631
+ source: "referral",
632
+ medium: "referral",
633
+ activeDays: [0, 100],
634
+ dailyBudget: [50, 200],
635
+ acquisitionRate: 0.02,
636
+ userPersonaBias: { power_seller: 0.4 },
637
+ },
638
+ ],
639
+ organicRate: 0.30,
640
+ },
641
+
642
+ // ── Geo ──────────────────────────────────────
643
+ geo: {
644
+ sticky: true,
645
+ regions: [
646
+ {
647
+ name: "us",
648
+ countries: ["US"],
649
+ weight: 50,
650
+ timezoneOffset: -5,
651
+ properties: { currency: "USD", locale: "en-US" },
652
+ },
653
+ {
654
+ name: "uk",
655
+ countries: ["GB"],
656
+ weight: 20,
657
+ timezoneOffset: 0,
658
+ properties: { currency: "GBP", locale: "en-GB" },
659
+ },
660
+ {
661
+ name: "australia",
662
+ countries: ["AU"],
663
+ weight: 15,
664
+ timezoneOffset: 10,
665
+ properties: { currency: "AUD", locale: "en-AU" },
666
+ },
667
+ {
668
+ name: "canada",
669
+ countries: ["CA"],
670
+ weight: 15,
671
+ timezoneOffset: -5,
672
+ properties: { currency: "CAD", locale: "en-CA" },
673
+ },
674
+ ],
675
+ },
676
+
677
+ // ── Anomalies ──────────────────────────────────
678
+ anomalies: [
679
+ {
680
+ type: "extreme_value",
681
+ event: "purchase completed",
682
+ property: "total_amount",
683
+ frequency: 0.002,
684
+ multiplier: 30,
685
+ tag: "whale_purchase",
686
+ },
687
+ {
688
+ type: "coordinated",
689
+ event: "account created",
690
+ day: 68,
691
+ window: 0.02,
692
+ count: 200,
693
+ tag: "viral_signup",
694
+ },
695
+ ],
696
+
697
+ // ── Hook Function ──────────────────────────────────────
698
+ hook: function (record, type, meta) {
699
+ // ── HOOK 7: POWER SELLER PROFILES (user) ─────────────
700
+ // Power sellers get high total_transactions, top seller_rating,
701
+ // and a realistic store name. Casual sellers get mid-range stats.
702
+ if (type === "user") {
703
+ if (record.segment === "power_seller") {
704
+ record.total_transactions = chance.integer({ min: 100, max: 500 });
705
+ record.seller_rating = chance.floating({ min: 4.5, max: 5.0, fixed: 1 });
706
+ record.store_name = chance.pickone([
707
+ "TechVault Pro", "StyleHaven", "GearFactory", "PrimeFinds",
708
+ "TopShelf Goods", "EliteDeals", "QualityFirst Store", "BestBuy Resale",
709
+ ]);
710
+ } else if (record.segment === "casual_seller") {
711
+ record.total_transactions = chance.integer({ min: 5, max: 50 });
712
+ record.seller_rating = chance.floating({ min: 3.0, max: 4.5, fixed: 1 });
713
+ record.store_name = chance.pickone([
714
+ "My Garage Sale", "Closet Cleanout", "Random Finds", "Weekend Seller",
715
+ ]);
716
+ } else if (record.segment === "new_seller") {
717
+ record.total_transactions = chance.integer({ min: 0, max: 3 });
718
+ record.seller_rating = 0;
719
+ record.store_name = "New Store";
720
+ }
721
+ }
722
+
723
+ // ── HOOK 8: FREQUENT BUYER FUNNEL LIFT (funnel-pre) ──
724
+ // Frequent buyers convert 1.3x better through browse-to-purchase.
725
+ if (type === "funnel-pre") {
726
+ if (meta && meta.profile) {
727
+ if (meta.profile.segment === "frequent_buyer") {
728
+ record.conversionRate = Math.min(record.conversionRate * 1.3, 90);
729
+ }
730
+ }
731
+ }
732
+
733
+ // ── HOOK 1: FEE CHANGE IMPACT (event) ────────────────
734
+ // Listings after day 45 get 1.3x higher listing_fee.
735
+ if (type === "event") {
736
+ const FEE_CHANGE_DAY = DATASET_START.add(45, "days");
737
+ if (record.event === "listing created") {
738
+ const eventTime = dayjs(record.time);
739
+ if (eventTime.isAfter(FEE_CHANGE_DAY)) {
740
+ record.listing_fee = Math.floor((record.listing_fee || 15) * 1.3);
741
+ record.fee_change = "increased";
742
+ }
743
+ }
744
+
745
+ // ── HOOK 2: WEEKEND SHOPPING SURGE (event) ───────
746
+ // Purchases on Sat/Sun get 1.2x total_amount.
747
+ if (record.event === "purchase completed") {
748
+ const dayOfWeek = dayjs(record.time).day();
749
+ // Saturday=6, Sunday=0
750
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
751
+ record.total_amount = Math.floor((record.total_amount || 60) * 1.2);
752
+ }
753
+ }
754
+ }
755
+
756
+ // ── EVERYTHING HOOKS ─────────────────────────────────
757
+ if (type === "everything") {
758
+ const events = record;
759
+ if (!events.length) return record;
760
+
761
+ const profile = meta.profile;
762
+
763
+ // ── HOOK 3: SELLER SUCCESS → BUYER TRUST ─────────
764
+ // Power sellers get 2x purchase events (cloned from existing).
765
+ if (profile && profile.segment === "power_seller") {
766
+ const purchases = events.filter(e => e.event === "purchase completed");
767
+ const templatePurchase = purchases[0];
768
+ if (templatePurchase && purchases.length > 0) {
769
+ purchases.forEach(p => {
770
+ if (chance.bool({ likelihood: 65 })) {
771
+ events.push({
772
+ ...templatePurchase,
773
+ time: dayjs(p.time).add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
774
+ user_id: p.user_id,
775
+ total_amount: chance.integer({ min: 15, max: 400 }),
776
+ item_count: chance.integer({ min: 1, max: 4 }),
777
+ });
778
+ }
779
+ });
780
+ }
781
+ }
782
+
783
+ // ── HOOK 4: SEARCH-TO-PURCHASE BY CATEGORY ───────
784
+ // Electronics category gets cloned purchase events (higher conversion).
785
+ const hasElectronicsSearch = events.some(e =>
786
+ e.event === "item searched" && e.category === "electronics"
787
+ );
788
+ if (hasElectronicsSearch) {
789
+ const templatePurchase = events.find(e => e.event === "purchase completed");
790
+ if (templatePurchase) {
791
+ const electronicsSearches = events.filter(e =>
792
+ e.event === "item searched" && e.category === "electronics"
793
+ );
794
+ electronicsSearches.slice(0, 3).forEach(search => {
795
+ if (chance.bool({ likelihood: 40 })) {
796
+ events.push({
797
+ ...templatePurchase,
798
+ time: dayjs(search.time).add(chance.integer({ min: 1, max: 12 }), "hours").toISOString(),
799
+ user_id: search.user_id,
800
+ total_amount: chance.integer({ min: 50, max: 300 }),
801
+ category: "electronics",
802
+ });
803
+ }
804
+ });
805
+ }
806
+ }
807
+
808
+ // ── HOOK 5: RESPONSE TIME → CONVERSION ───────────
809
+ // Sellers with avg response_time < 2 hours get more offer_accepted.
810
+ const messages = events.filter(e => e.event === "message sent" && e.response_time_hours);
811
+ if (messages.length > 0) {
812
+ const avgResponseTime = messages.reduce((sum, m) => sum + m.response_time_hours, 0) / messages.length;
813
+ if (avgResponseTime < 2) {
814
+ const templateOffer = events.find(e => e.event === "offer accepted");
815
+ if (templateOffer) {
816
+ const offers = events.filter(e => e.event === "offer received");
817
+ offers.forEach(offer => {
818
+ if (chance.bool({ likelihood: 50 })) {
819
+ events.push({
820
+ ...templateOffer,
821
+ time: dayjs(offer.time).add(chance.integer({ min: 1, max: 4 }), "hours").toISOString(),
822
+ user_id: offer.user_id,
823
+ response_time_hours: chance.floating({ min: 0.1, max: 2, fixed: 1 }),
824
+ });
825
+ }
826
+ });
827
+ }
828
+ }
829
+ }
830
+
831
+ // ── HOOK 6: NEW SELLER CHURN ─────────────────────
832
+ // New sellers with <10 events lose 40% of events after day 14.
833
+ if (profile && profile.segment === "new_seller" && events.length < 10) {
834
+ const DAY_14 = DATASET_START.add(14, "days");
835
+ for (let i = events.length - 1; i >= 0; i--) {
836
+ const eventTime = dayjs(events[i].time);
837
+ if (eventTime.isAfter(DAY_14) && chance.bool({ likelihood: 40 })) {
838
+ events.splice(i, 1);
839
+ }
840
+ }
841
+ }
842
+
843
+ return record;
844
+ }
845
+
846
+ return record;
847
+ },
848
+ };
849
+
850
+ export default config;