make-mp-data 2.1.11 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +31 -0
  2. package/dungeons/adspend.js +2 -2
  3. package/dungeons/ai-chat-analytics-ed.js +3 -2
  4. package/dungeons/anon.js +2 -2
  5. package/dungeons/array-of-object-loopup.js +181 -0
  6. package/dungeons/benchmark-heavy.js +241 -0
  7. package/dungeons/benchmark-light.js +141 -0
  8. package/dungeons/big.js +9 -8
  9. package/dungeons/business.js +2 -1
  10. package/dungeons/clinch-agi.js +632 -0
  11. package/dungeons/complex.js +3 -2
  12. package/dungeons/copilot.js +383 -0
  13. package/dungeons/ecommerce-store.js +0 -0
  14. package/dungeons/experiments.js +5 -4
  15. package/dungeons/foobar.js +1 -1
  16. package/dungeons/funnels.js +2 -2
  17. package/dungeons/gaming.js +3 -2
  18. package/dungeons/harness/harness-education.js +988 -0
  19. package/dungeons/harness/harness-fintech.js +976 -0
  20. package/dungeons/harness/harness-food.js +985 -0
  21. package/dungeons/harness/harness-gaming.js +1178 -0
  22. package/dungeons/harness/harness-media.js +961 -0
  23. package/dungeons/harness/harness-sass.js +923 -0
  24. package/dungeons/harness/harness-social.js +928 -0
  25. package/dungeons/kurby.js +211 -0
  26. package/dungeons/media.js +5 -4
  27. package/dungeons/mil.js +4 -3
  28. package/dungeons/mirror.js +2 -2
  29. package/dungeons/money2020-ed.js +8 -7
  30. package/dungeons/sanity.js +3 -2
  31. package/dungeons/scd.js +3 -2
  32. package/dungeons/simple.js +30 -15
  33. package/dungeons/strict-event-test.js +30 -0
  34. package/dungeons/student-teacher.js +3 -2
  35. package/dungeons/text-generation.js +84 -85
  36. package/dungeons/too-big-events.js +166 -0
  37. package/dungeons/uday-schema.json +220 -0
  38. package/dungeons/userAgent.js +4 -3
  39. package/index.js +41 -54
  40. package/lib/core/config-validator.js +122 -7
  41. package/lib/core/context.js +7 -14
  42. package/lib/core/storage.js +57 -25
  43. package/lib/generators/adspend.js +12 -12
  44. package/lib/generators/events.js +6 -5
  45. package/lib/generators/funnels.js +32 -10
  46. package/lib/generators/product-lookup.js +262 -0
  47. package/lib/generators/product-names.js +195 -0
  48. package/lib/generators/profiles.js +3 -3
  49. package/lib/generators/scd.js +13 -3
  50. package/lib/generators/text.js +17 -4
  51. package/lib/orchestrators/mixpanel-sender.js +244 -204
  52. package/lib/orchestrators/user-loop.js +54 -16
  53. package/lib/templates/funnels-instructions.txt +272 -0
  54. package/lib/templates/hook-examples.json +187 -0
  55. package/lib/templates/hooks-instructions.txt +295 -8
  56. package/lib/templates/phrases.js +473 -16
  57. package/lib/templates/refine-instructions.txt +485 -0
  58. package/lib/templates/schema-instructions.txt +239 -109
  59. package/lib/templates/schema.d.ts +173 -0
  60. package/lib/templates/verbose-schema.js +140 -206
  61. package/lib/utils/ai.js +853 -77
  62. package/lib/utils/chart.js +210 -0
  63. package/lib/utils/function-registry.js +285 -0
  64. package/lib/utils/json-evaluator.js +172 -0
  65. package/lib/utils/logger.js +38 -0
  66. package/lib/utils/mixpanel.js +101 -0
  67. package/lib/utils/project.js +3 -2
  68. package/lib/utils/utils.js +41 -4
  69. package/package.json +15 -21
  70. package/types.d.ts +15 -5
  71. package/lib/generators/text-bak-old.js +0 -1121
  72. package/lib/orchestrators/worker-manager.js +0 -203
  73. package/lib/templates/phrases-bak.js +0 -925
  74. package/lib/templates/prompt (old).txt +0 -98
  75. package/lib/templates/scratch-dungeon-template.js +0 -116
  76. package/lib/templates/textQuickTest.js +0 -172
@@ -0,0 +1,985 @@
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-food";
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
+ * NEEDLE IN A HAYSTACK - FOOD DELIVERY APP DESIGN
17
+ *
18
+ * QuickBite - A food delivery platform connecting hungry customers with local restaurants.
19
+ * Think DoorDash/Uber Eats: users browse restaurants, build carts, place orders, track
20
+ * deliveries in real-time, and rate their experiences afterward.
21
+ *
22
+ * CORE USER LOOP:
23
+ * Users sign up (email, Google, Apple, Facebook) and immediately enter a discovery flow.
24
+ * They browse restaurants by cuisine, sort by distance/rating/price, search for specific
25
+ * dishes, and eventually land on a restaurant page. From there, they build a cart by adding
26
+ * items (entrees, appetizers, drinks, desserts, sides) with customizations. Checkout
27
+ * captures payment, tip, and delivery details. After ordering, users track their delivery
28
+ * through multiple statuses (confirmed, preparing, picked up, en route, delivered).
29
+ *
30
+ * RESTAURANT ECOSYSTEM:
31
+ * 200 restaurants across cuisine types: American, Italian, Chinese, Japanese, Mexican,
32
+ * Indian, Thai, and Mediterranean. Restaurants span four price tiers ($, $$, $$$, $$$$)
33
+ * with varying delivery times and ratings. This models a realistic marketplace with
34
+ * restaurant-level analytics via group profiles.
35
+ *
36
+ * DISCOVERY & SEARCH:
37
+ * Two paths to finding food: browsing (filtering by cuisine, sorting by rating/distance/price)
38
+ * and searching (by restaurant name, cuisine type, or specific dish). The search-to-order
39
+ * funnel captures intent-driven behavior vs. casual browsing.
40
+ *
41
+ * MONETIZATION MODEL:
42
+ * - Delivery fees ($0-$12, waived for QuickBite+ subscribers)
43
+ * - QuickBite+ subscription ($9.99/month or $79.99/year) for free delivery and perks
44
+ * - Promotions and coupons drive trial and reactivation
45
+ * - Restaurant-promoted listings (not modeled as user events)
46
+ *
47
+ * CART BEHAVIOR:
48
+ * Cart events (add/remove) capture the deliberation process. Removal reasons
49
+ * (changed_mind, too_expensive, substitution) reveal price sensitivity and UX friction.
50
+ * Customization counts show engagement depth with individual items.
51
+ *
52
+ * ORDER LIFECYCLE:
53
+ * order placed -> order tracked (multiple status updates) -> order delivered -> order rated
54
+ * This models the full post-purchase experience. On-time delivery, actual vs. estimated
55
+ * delivery time, and food/delivery ratings capture service quality metrics.
56
+ *
57
+ * SUPPORT & RETENTION:
58
+ * Support tickets (missing items, wrong orders, late delivery, quality issues, refund
59
+ * requests) model service failures. Reorder events capture repeat behavior and loyalty.
60
+ * The ratio of support tickets to orders is a key service quality indicator.
61
+ *
62
+ * WHY THESE EVENTS/PROPERTIES?
63
+ * - Events model the complete food delivery loop: discovery -> consideration -> purchase -> fulfillment -> retention
64
+ * - Properties enable cohort analysis: cuisine preferences, price sensitivity, platform usage, subscription status
65
+ * - Funnels reveal friction: where do users drop off between browsing and ordering?
66
+ * - Time-based patterns (lunch rush, late night) simulate real delivery demand curves
67
+ * - Subscription tier (Free vs QuickBite+) creates a natural A/B comparison for monetization analysis
68
+ * - Support and rating events drive churn prediction and service quality monitoring
69
+ * - The "needle in haystack" hooks simulate real product insights hidden in production data
70
+ */
71
+
72
+ // Generate consistent IDs for lookup tables and event properties
73
+ const restaurantIds = v.range(1, 201).map(n => `rest_${v.uid(6)}`);
74
+ const itemIds = v.range(1, 301).map(n => `item_${v.uid(7)}`);
75
+ const orderIds = v.range(1, 5001).map(n => `order_${v.uid(8)}`);
76
+ const couponCodes = v.range(1, 51).map(n => `QUICK${v.uid(5).toUpperCase()}`);
77
+
78
+ /** @type {Config} */
79
+ const config = {
80
+ token: "9ffdb0649ebbea4ff7a742725166b06f",
81
+ seed: SEED,
82
+ numDays: days,
83
+ numEvents: num_users * 120,
84
+ numUsers: num_users,
85
+ hasAnonIds: false,
86
+ hasSessionIds: true,
87
+ format: "json",
88
+ gzip: true,
89
+ alsoInferFunnels: false,
90
+ hasLocation: true,
91
+ hasAndroidDevices: true,
92
+ hasIOSDevices: true,
93
+ hasDesktopDevices: true,
94
+ hasBrowser: false,
95
+ hasCampaigns: false,
96
+ isAnonymous: false,
97
+ hasAdSpend: false,
98
+ percentUsersBornInDataset: 50,
99
+ hasAvatar: true,
100
+ makeChart: false,
101
+ batchSize: 2_500_000,
102
+ concurrency: 10,
103
+ writeToDisk: false,
104
+ scdProps: {},
105
+
106
+ funnels: [
107
+ {
108
+ sequence: ["account created", "restaurant browsed", "restaurant viewed"],
109
+ isFirstFunnel: true,
110
+ conversionRate: 80,
111
+ timeToConvert: 0.5,
112
+ },
113
+ {
114
+ // Browse and discover: most common action on food delivery apps
115
+ sequence: ["restaurant browsed", "restaurant viewed", "item added to cart"],
116
+ conversionRate: 55,
117
+ timeToConvert: 1,
118
+ weight: 5,
119
+ props: { "restaurant_id": u.pickAWinner(restaurantIds) },
120
+ },
121
+ {
122
+ // Search-driven ordering
123
+ sequence: ["search performed", "restaurant viewed", "item added to cart", "checkout started"],
124
+ conversionRate: 45,
125
+ timeToConvert: 2,
126
+ weight: 3,
127
+ },
128
+ {
129
+ // Full order lifecycle: checkout to delivery
130
+ sequence: ["checkout started", "order placed", "order tracked", "order delivered"],
131
+ conversionRate: 65,
132
+ timeToConvert: 2,
133
+ weight: 4,
134
+ props: { "order_id": u.pickAWinner(orderIds) },
135
+ },
136
+ {
137
+ // Post-order: rate and reorder
138
+ sequence: ["order delivered", "order rated", "reorder initiated"],
139
+ conversionRate: 40,
140
+ timeToConvert: 24,
141
+ weight: 2,
142
+ },
143
+ {
144
+ // Browsing promos and coupons
145
+ sequence: ["promotion viewed", "coupon applied", "checkout started"],
146
+ conversionRate: 50,
147
+ timeToConvert: 1,
148
+ weight: 2,
149
+ },
150
+ {
151
+ // Support flow
152
+ sequence: ["support ticket", "order rated"],
153
+ conversionRate: 45,
154
+ timeToConvert: 6,
155
+ weight: 1,
156
+ },
157
+ {
158
+ // Subscription management
159
+ sequence: ["subscription started", "order placed", "subscription cancelled"],
160
+ conversionRate: 20,
161
+ timeToConvert: 48,
162
+ weight: 1,
163
+ },
164
+ ],
165
+
166
+ events: [
167
+ {
168
+ event: "account created",
169
+ weight: 1,
170
+ isFirstEvent: true,
171
+ properties: {
172
+ "signup_method": u.pickAWinner(["email", "google", "apple", "facebook"]),
173
+ "referral_code": u.pickAWinner([true, false], 0.3),
174
+ }
175
+ },
176
+ {
177
+ event: "restaurant browsed",
178
+ weight: 18,
179
+ properties: {
180
+ "cuisine_type": u.pickAWinner([
181
+ "American",
182
+ "Italian",
183
+ "Chinese",
184
+ "Japanese",
185
+ "Mexican",
186
+ "Indian",
187
+ "Thai",
188
+ "Mediterranean"
189
+ ]),
190
+ "sort_by": u.pickAWinner(["recommended", "distance", "rating", "price"]),
191
+ "filter_applied": u.pickAWinner([true, false], 0.4),
192
+ }
193
+ },
194
+ {
195
+ event: "restaurant viewed",
196
+ weight: 15,
197
+ properties: {
198
+ "restaurant_id": u.pickAWinner(restaurantIds),
199
+ "cuisine_type": u.pickAWinner([
200
+ "American",
201
+ "Italian",
202
+ "Chinese",
203
+ "Japanese",
204
+ "Mexican",
205
+ "Indian",
206
+ "Thai",
207
+ "Mediterranean"
208
+ ]),
209
+ "avg_rating": u.weighNumRange(1, 5, 0.8, 30),
210
+ "delivery_time_est_mins": u.weighNumRange(15, 90, 1.2, 40),
211
+ "price_tier": u.pickAWinner(["$", "$$", "$$$", "$$$$"]),
212
+ }
213
+ },
214
+ {
215
+ event: "item added to cart",
216
+ weight: 14,
217
+ properties: {
218
+ "item_id": u.pickAWinner(itemIds),
219
+ "item_category": u.pickAWinner(["entree", "appetizer", "drink", "dessert", "side"]),
220
+ "item_price": u.weighNumRange(3, 65, 1.0, 40),
221
+ "customization_count": u.weighNumRange(0, 5, 1.5, 20),
222
+ }
223
+ },
224
+ {
225
+ event: "item removed from cart",
226
+ weight: 5,
227
+ properties: {
228
+ "item_id": u.pickAWinner(itemIds),
229
+ "removal_reason": u.pickAWinner(["changed_mind", "too_expensive", "substitution"]),
230
+ }
231
+ },
232
+ {
233
+ event: "coupon applied",
234
+ weight: 4,
235
+ properties: {
236
+ "coupon_code": u.pickAWinner(couponCodes),
237
+ "discount_type": u.pickAWinner(["percent", "flat", "free_delivery"]),
238
+ "discount_value": u.weighNumRange(5, 50, 1.2, 20),
239
+ }
240
+ },
241
+ {
242
+ event: "checkout started",
243
+ weight: 12,
244
+ properties: {
245
+ "cart_total": u.weighNumRange(8, 150, 0.8, 40),
246
+ "items_count": u.weighNumRange(1, 8, 1.2, 20),
247
+ "delivery_address_saved": u.pickAWinner([true, false], 0.7),
248
+ }
249
+ },
250
+ {
251
+ event: "order placed",
252
+ weight: 10,
253
+ properties: {
254
+ "order_id": u.pickAWinner(orderIds),
255
+ "payment_method": u.pickAWinner(["credit_card", "apple_pay", "google_pay", "paypal", "cash"]),
256
+ "order_total": u.weighNumRange(10, 200, 0.8, 40),
257
+ "tip_amount": u.weighNumRange(0, 30, 1.5, 20),
258
+ "delivery_fee": u.weighNumRange(0, 12, 1.0, 20),
259
+ }
260
+ },
261
+ {
262
+ event: "order tracked",
263
+ weight: 13,
264
+ properties: {
265
+ "order_id": u.pickAWinner(orderIds),
266
+ "order_status": u.pickAWinner(["confirmed", "preparing", "picked_up", "en_route", "delivered"]),
267
+ "eta_mins": u.weighNumRange(5, 60, 1.0, 30),
268
+ }
269
+ },
270
+ {
271
+ event: "order delivered",
272
+ weight: 9,
273
+ properties: {
274
+ "order_id": u.pickAWinner(orderIds),
275
+ "actual_delivery_mins": u.weighNumRange(12, 90, 1.0, 40),
276
+ "on_time": u.pickAWinner([true, false], 0.7),
277
+ }
278
+ },
279
+ {
280
+ event: "order rated",
281
+ weight: 7,
282
+ properties: {
283
+ "order_id": u.pickAWinner(orderIds),
284
+ "food_rating": u.weighNumRange(1, 5, 0.8, 30),
285
+ "delivery_rating": u.weighNumRange(1, 5, 0.8, 30),
286
+ "would_reorder": u.pickAWinner([true, false], 0.65),
287
+ }
288
+ },
289
+ {
290
+ event: "search performed",
291
+ weight: 11,
292
+ properties: {
293
+ "search_query": () => chance.pickone([
294
+ "pizza", "sushi", "burger", "tacos", "pad thai",
295
+ "chicken", "salad", "ramen", "pasta", "sandwich",
296
+ "wings", "curry", "pho", "burritos", "steak"
297
+ ]),
298
+ "results_count": u.weighNumRange(0, 50, 0.8, 30),
299
+ "search_type": u.pickAWinner(["restaurant", "cuisine", "dish"]),
300
+ }
301
+ },
302
+ {
303
+ event: "promotion viewed",
304
+ weight: 8,
305
+ properties: {
306
+ "promo_id": () => `promo_${v.uid(5)}`,
307
+ "promo_type": u.pickAWinner(["banner", "push", "in_feed"]),
308
+ "promo_value": u.pickAWinner(["10%", "15%", "20%", "25%", "30%", "40%", "50%"]),
309
+ }
310
+ },
311
+ {
312
+ event: "subscription started",
313
+ weight: 2,
314
+ properties: {
315
+ "plan": u.pickAWinner(["quickbite_plus_monthly", "quickbite_plus_monthly", "quickbite_plus_annual"]),
316
+ "price": u.pickAWinner([9.99, 9.99, 79.99]),
317
+ "trial": u.pickAWinner([true, false], 0.5),
318
+ }
319
+ },
320
+ {
321
+ event: "subscription cancelled",
322
+ weight: 1,
323
+ properties: {
324
+ "reason": u.pickAWinner(["too_expensive", "not_ordering_enough", "found_alternative", "bad_experience"]),
325
+ "months_subscribed": u.weighNumRange(1, 24, 1.5, 15),
326
+ }
327
+ },
328
+ {
329
+ event: "support ticket",
330
+ weight: 3,
331
+ properties: {
332
+ "issue_type": u.pickAWinner(["missing_item", "wrong_order", "late_delivery", "quality_issue", "refund_request"]),
333
+ "order_id": u.pickAWinner(orderIds),
334
+ }
335
+ },
336
+ {
337
+ event: "reorder initiated",
338
+ weight: 6,
339
+ properties: {
340
+ "order_id": u.pickAWinner(orderIds),
341
+ "original_order_age_days": u.weighNumRange(1, 60, 1.5, 30),
342
+ }
343
+ },
344
+ ],
345
+
346
+ superProps: {
347
+ platform: u.pickAWinner(["iOS", "Android", "Web"]),
348
+ subscription_tier: u.pickAWinner(["Free", "Free", "Free", "Free", "QuickBite+"]),
349
+ city: u.pickAWinner(["New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "San Francisco"]),
350
+ },
351
+
352
+ userProps: {
353
+ "preferred_cuisine": u.pickAWinner([
354
+ "American",
355
+ "Italian",
356
+ "Chinese",
357
+ "Japanese",
358
+ "Mexican",
359
+ "Indian",
360
+ "Thai",
361
+ "Mediterranean"
362
+ ]),
363
+ "avg_order_value": u.weighNumRange(15, 80, 0.8, 40),
364
+ "orders_per_month": u.weighNumRange(1, 20, 1.5, 10),
365
+ "favorite_restaurant_count": u.weighNumRange(1, 10),
366
+ },
367
+
368
+ groupKeys: [
369
+ ["restaurant_id", 200, ["restaurant viewed", "order placed", "order rated"]],
370
+ ],
371
+
372
+ groupProps: {
373
+ restaurant_id: {
374
+ "name": () => `${chance.pickone(["The", "Big", "Lucky", "Golden", "Fresh", "Urban"])} ${chance.pickone(["Kitchen", "Grill", "Bowl", "Wok", "Bistro", "Plate", "Table", "Fork"])}`,
375
+ "cuisine": u.pickAWinner([
376
+ "American",
377
+ "Italian",
378
+ "Chinese",
379
+ "Japanese",
380
+ "Mexican",
381
+ "Indian",
382
+ "Thai",
383
+ "Mediterranean"
384
+ ]),
385
+ "avg_rating": u.weighNumRange(1, 5, 0.8, 30),
386
+ "delivery_radius_mi": u.weighNumRange(1, 15, 1.0, 10),
387
+ }
388
+ },
389
+
390
+ lookupTables: [],
391
+
392
+ /**
393
+ * ARCHITECTED ANALYTICS HOOKS
394
+ *
395
+ * This hook function creates 8 deliberate patterns in the data:
396
+ *
397
+ * 1. LUNCH RUSH CONVERSION: Orders during lunch/dinner hours convert at higher rates
398
+ * 2. COUPON INJECTION: Free-tier users get coupons spliced into funnels
399
+ * 3. LATE NIGHT MUNCHIES: Late-night orders skew to fast food with inflated prices
400
+ * 4. RAINY WEEK SURGE: Days 20-27 have surge pricing and doubled order volume
401
+ * 5. REFERRAL POWER USERS: Referred users reorder more and rate higher
402
+ * 6. TRIAL CONVERSION: Trial subscribers with 3+ early orders are retained
403
+ * 7. SUPPORT TICKET CHURN: 15% of users flagged as high-risk churners
404
+ * 8. FIRST ORDER BONUS: New users in the Order Completion funnel convert at 90%
405
+ */
406
+ hook: function (record, type, meta) {
407
+ const NOW = dayjs();
408
+ const DATASET_START = NOW.subtract(days, 'days');
409
+ const RAINY_WEEK_START = DATASET_START.add(20, 'days');
410
+ const RAINY_WEEK_END = DATASET_START.add(27, 'days');
411
+
412
+ // ═══════════════════════════════════════════════════════════════════
413
+ // HOOK 1: LUNCH RUSH CONVERSION (funnel-pre)
414
+ // During lunch (11AM-1PM) and dinner (5PM-8PM), funnel conversion
415
+ // rates are boosted to reflect real-world meal-time ordering surges.
416
+ // ═══════════════════════════════════════════════════════════════════
417
+ if (type === "funnel-pre") {
418
+ if (meta && meta.firstEventTime) {
419
+ const hour = dayjs(meta.firstEventTime).hour();
420
+
421
+ record.props = record.props || {};
422
+
423
+ // Lunch rush: 11AM - 1PM
424
+ if (hour >= 11 && hour <= 13) {
425
+ record.conversionRate = record.conversionRate * 1.4;
426
+ record.props.lunch_rush = true;
427
+ record.props.dinner_rush = false;
428
+ }
429
+ // Dinner rush: 5PM - 8PM
430
+ else if (hour >= 17 && hour <= 20) {
431
+ record.conversionRate = record.conversionRate * 1.2;
432
+ record.props.lunch_rush = false;
433
+ record.props.dinner_rush = true;
434
+ } else {
435
+ record.props.lunch_rush = false;
436
+ record.props.dinner_rush = false;
437
+ }
438
+ }
439
+
440
+ // ═══════════════════════════════════════════════════════════════
441
+ // HOOK 8: FIRST ORDER BONUS (funnel-pre)
442
+ // New users (born in dataset) get a massive conversion boost on
443
+ // the Order Completion funnel, simulating first-order promotions
444
+ // and new-user incentives that drive initial purchases.
445
+ // ═══════════════════════════════════════════════════════════════
446
+ if (record.sequence &&
447
+ record.sequence[0] === "checkout started" &&
448
+ record.sequence[1] === "order placed") {
449
+ record.props = record.props || {};
450
+ if (meta && meta.userIsBornInDataset) {
451
+ record.conversionRate = 0.90;
452
+ record.props.first_order_bonus = true;
453
+ } else {
454
+ record.props.first_order_bonus = false;
455
+ }
456
+ }
457
+ }
458
+
459
+ // ═══════════════════════════════════════════════════════════════════
460
+ // HOOK 2: COUPON INJECTION (funnel-post)
461
+ // Free-tier users get coupon_applied events spliced into their
462
+ // funnel sequences 30% of the time, simulating promotional nudges
463
+ // that push non-subscribers toward conversion.
464
+ // ═══════════════════════════════════════════════════════════════════
465
+ if (type === "funnel-post") {
466
+ if (Array.isArray(record) && record.length >= 2) {
467
+ // Check if user is Free tier from first event's super props
468
+ const firstEvent = record[0];
469
+ const isFreeUser = firstEvent && firstEvent.subscription_tier === "Free";
470
+
471
+ if (isFreeUser && chance.bool({ likelihood: 30 })) {
472
+ // Pick a random insertion point between funnel steps
473
+ const insertIdx = chance.integer({ min: 1, max: record.length - 1 });
474
+ const prevEvent = record[insertIdx - 1];
475
+ const nextEvent = record[insertIdx];
476
+ const midTime = dayjs(prevEvent.time).add(
477
+ dayjs(nextEvent.time).diff(dayjs(prevEvent.time)) / 2,
478
+ 'milliseconds'
479
+ ).toISOString();
480
+
481
+ const couponEvent = {
482
+ event: "coupon applied",
483
+ time: midTime,
484
+ user_id: firstEvent.user_id,
485
+ coupon_code: chance.pickone(couponCodes),
486
+ discount_type: chance.pickone(["percent", "flat", "free_delivery"]),
487
+ discount_value: chance.integer({ min: 10, max: 30 }),
488
+ coupon_injected: true,
489
+ };
490
+ record.splice(insertIdx, 0, couponEvent);
491
+ }
492
+ }
493
+ }
494
+
495
+ // ═══════════════════════════════════════════════════════════════════
496
+ // HOOK 3: LATE NIGHT MUNCHIES (event)
497
+ // Between 10PM and 2AM, restaurant views and cart additions skew
498
+ // heavily toward fast food (American cuisine) with inflated prices,
499
+ // modeling the real-world late-night ordering pattern.
500
+ // ═══════════════════════════════════════════════════════════════════
501
+ if (type === "event") {
502
+ const EVENT_TIME = dayjs(record.time);
503
+ const hour = EVENT_TIME.hour();
504
+ const isLateNight = hour >= 22 || hour <= 2;
505
+
506
+ if (record.event === "restaurant viewed" || record.event === "item added to cart") {
507
+ if (isLateNight) {
508
+ // 70% of late-night browsing/ordering is fast food (American)
509
+ if (chance.bool({ likelihood: 70 })) {
510
+ if (record.cuisine_type !== undefined) {
511
+ record.cuisine_type = "American";
512
+ }
513
+ }
514
+
515
+ // Boost item price by 1.3x (late-night surcharge effect)
516
+ if (record.item_price !== undefined) {
517
+ record.item_price = Math.round(record.item_price * 1.3 * 100) / 100;
518
+ }
519
+
520
+ record.late_night_order = true;
521
+ } else {
522
+ record.late_night_order = false;
523
+ }
524
+ }
525
+
526
+ // ═══════════════════════════════════════════════════════════════
527
+ // HOOK 4: RAINY WEEK SURGE (event)
528
+ // During days 20-27, delivery fees double on order_placed events
529
+ // and there is a 40% chance of duplicating the event, simulating
530
+ // a weather-driven demand surge with surge pricing.
531
+ // ═══════════════════════════════════════════════════════════════
532
+ if (record.event === "order placed") {
533
+ if (EVENT_TIME.isAfter(RAINY_WEEK_START) && EVENT_TIME.isBefore(RAINY_WEEK_END)) {
534
+ record.delivery_fee = (record.delivery_fee || 5) * 2;
535
+ record.surge_pricing = true;
536
+
537
+ // 40% chance to duplicate the event (more orders during rain)
538
+ if (chance.bool({ likelihood: 40 })) {
539
+ const duplicateEvent = {
540
+ event: "order placed",
541
+ time: EVENT_TIME.add(chance.integer({ min: 5, max: 60 }), 'minutes').toISOString(),
542
+ user_id: record.user_id,
543
+ order_id: chance.pickone(orderIds),
544
+ payment_method: chance.pickone(["credit_card", "apple_pay", "google_pay"]),
545
+ order_total: chance.integer({ min: 15, max: 120 }),
546
+ tip_amount: chance.integer({ min: 0, max: 20 }),
547
+ delivery_fee: chance.integer({ min: 6, max: 24 }),
548
+ surge_pricing: true,
549
+ rainy_week: true,
550
+ };
551
+ return [record, duplicateEvent];
552
+ }
553
+ } else {
554
+ record.surge_pricing = false;
555
+ }
556
+ }
557
+ }
558
+
559
+ // ═══════════════════════════════════════════════════════════════════
560
+ // HOOK 5: REFERRAL POWER USERS (everything)
561
+ // Users who signed up with a referral code get 2x more reorder
562
+ // events and boosted food ratings. Referred users are more loyal
563
+ // and satisfied, mirroring real referral program outcomes.
564
+ // ═══════════════════════════════════════════════════════════════════
565
+ if (type === "everything") {
566
+ const userEvents = record;
567
+
568
+ // First pass: identify user patterns
569
+ let isReferralUser = false;
570
+ let hasTrialSubscription = false;
571
+ let earlyOrderCount = 0;
572
+ let firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
573
+
574
+ userEvents.forEach((event) => {
575
+ const eventTime = dayjs(event.time);
576
+ const daysSinceStart = firstEventTime ? eventTime.diff(firstEventTime, 'days', true) : 0;
577
+
578
+ // Hook #5: Track referral users
579
+ if (event.event === "account created" && event.referral_code === true) {
580
+ isReferralUser = true;
581
+ }
582
+
583
+ // Hook #6: Track trial subscribers and early orders
584
+ if (event.event === "subscription started" && event.trial === true) {
585
+ hasTrialSubscription = true;
586
+ }
587
+ if (event.event === "order placed" && daysSinceStart <= 14) {
588
+ earlyOrderCount++;
589
+ }
590
+ });
591
+
592
+ // Second pass: apply referral power user effects
593
+ userEvents.forEach((event, idx) => {
594
+ if (event.event === "order rated") {
595
+ if (isReferralUser) {
596
+ event.food_rating = chance.integer({ min: 4, max: 5 });
597
+ event.referral_user = true;
598
+ } else {
599
+ event.referral_user = false;
600
+ }
601
+ }
602
+ if (isReferralUser && event.event === "reorder initiated" && chance.bool({ likelihood: 50 })) {
603
+ const eventTime = dayjs(event.time);
604
+ const extraReorder = {
605
+ event: "reorder initiated",
606
+ time: eventTime.add(chance.integer({ min: 1, max: 7 }), 'days').toISOString(),
607
+ user_id: event.user_id,
608
+ order_id: chance.pickone(orderIds),
609
+ original_order_age_days: chance.integer({ min: 3, max: 30 }),
610
+ referral_user: true,
611
+ };
612
+ userEvents.splice(idx + 1, 0, extraReorder);
613
+ }
614
+ });
615
+
616
+ // ═══════════════════════════════════════════════════════════════
617
+ // HOOK 6: TRIAL CONVERSION (everything)
618
+ // Trial subscribers who place 3+ orders in their first 14 days
619
+ // are retained. Those with fewer orders churn: 60% of their
620
+ // events after day 14 are removed, simulating subscription
621
+ // abandonment after a failed trial experience.
622
+ // ═══════════════════════════════════════════════════════════════
623
+ if (hasTrialSubscription) {
624
+ const trialCutoff = firstEventTime ? firstEventTime.add(14, 'days') : null;
625
+
626
+ if (earlyOrderCount >= 3) {
627
+ // Retained: mark events as trial_retained
628
+ userEvents.forEach((event) => {
629
+ event.trial_retained = true;
630
+ });
631
+ } else {
632
+ // Churned: remove 60% of events after day 14
633
+ userEvents.forEach((event) => {
634
+ event.trial_retained = false;
635
+ });
636
+ for (let i = userEvents.length - 1; i >= 0; i--) {
637
+ const evt = userEvents[i];
638
+ if (trialCutoff && dayjs(evt.time).isAfter(trialCutoff)) {
639
+ if (chance.bool({ likelihood: 60 })) {
640
+ userEvents.splice(i, 1);
641
+ }
642
+ }
643
+ }
644
+ }
645
+ }
646
+ }
647
+
648
+ // ═══════════════════════════════════════════════════════════════════
649
+ // HOOK 7: SUPPORT TICKET CHURN (user)
650
+ // 15% of users are flagged as high-risk with elevated churn scores.
651
+ // The remaining users get low churn scores. This creates a clear
652
+ // segmentation opportunity for proactive retention campaigns.
653
+ // ═══════════════════════════════════════════════════════════════════
654
+ if (type === "user") {
655
+ if (chance.bool({ likelihood: 15 })) {
656
+ record.is_high_risk = true;
657
+ record.churn_risk_score = chance.integer({ min: 70, max: 100 });
658
+ } else {
659
+ record.is_high_risk = false;
660
+ record.churn_risk_score = chance.integer({ min: 0, max: 40 });
661
+ }
662
+ }
663
+
664
+ return record;
665
+ }
666
+ };
667
+
668
+ export default config;
669
+
670
+ /**
671
+ * ═══════════════════════════════════════════════════════════════════════════════
672
+ * NEEDLE IN A HAYSTACK - QUICKBITE FOOD DELIVERY ANALYTICS
673
+ * ═══════════════════════════════════════════════════════════════════════════════
674
+ *
675
+ * A food delivery app dungeon with 8 deliberately architected analytics
676
+ * insights hidden in the data. This dungeon is designed to showcase advanced
677
+ * product analytics patterns and demonstrate how to find "needles" (meaningful
678
+ * insights) in "haystacks" (large datasets).
679
+ *
680
+ * ═══════════════════════════════════════════════════════════════════════════════
681
+ * DATASET OVERVIEW
682
+ * ═══════════════════════════════════════════════════════════════════════════════
683
+ *
684
+ * - 5,000 users over 100 days
685
+ * - 360K events across 17 event types
686
+ * - 3 funnels (onboarding, order completion, discovery to order)
687
+ * - Group analytics (restaurants)
688
+ * - Lookup tables (menu items, coupon codes)
689
+ * - Subscription tiers (Free, QuickBite+)
690
+ *
691
+ * ═══════════════════════════════════════════════════════════════════════════════
692
+ * THE 8 ARCHITECTED HOOKS
693
+ * ═══════════════════════════════════════════════════════════════════════════════
694
+ *
695
+ * Each hook creates a specific, discoverable analytics insight that simulates
696
+ * real-world product behavior patterns.
697
+ *
698
+ * ───────────────────────────────────────────────────────────────────────────────
699
+ * 1. LUNCH RUSH CONVERSION (funnel-pre)
700
+ * ───────────────────────────────────────────────────────────────────────────────
701
+ *
702
+ * PATTERN: Funnel conversion rates are boosted during meal-time hours.
703
+ * Lunch (11AM-1PM) gets a 1.4x multiplier; dinner (5PM-8PM) gets 1.2x.
704
+ * Events during these windows carry lunch_rush or dinner_rush properties.
705
+ *
706
+ * HOW TO FIND IT:
707
+ * - Break down funnel conversion by hour of day
708
+ * - Compare conversion rates during 11AM-1PM vs. off-peak hours
709
+ * - Filter events where lunch_rush = true or dinner_rush = true
710
+ *
711
+ * EXPECTED INSIGHT: Lunch hour funnels convert ~40% better than baseline.
712
+ * Dinner hour funnels convert ~20% better. Clear time-of-day pattern in
713
+ * the checkout-to-order-to-delivery pipeline.
714
+ *
715
+ * REAL-WORLD ANALOGUE: Food delivery apps see massive demand spikes during
716
+ * traditional meal times. Users ordering during these windows have higher
717
+ * intent and thus higher conversion rates.
718
+ *
719
+ * ───────────────────────────────────────────────────────────────────────────────
720
+ * 2. COUPON INJECTION (funnel-post)
721
+ * ───────────────────────────────────────────────────────────────────────────────
722
+ *
723
+ * PATTERN: Free-tier users get coupon_applied events spliced into their
724
+ * funnel sequences 30% of the time. These injected coupons carry the
725
+ * coupon_injected: true property to distinguish them from organic usage.
726
+ *
727
+ * HOW TO FIND IT:
728
+ * - Segment by subscription_tier = "Free"
729
+ * - Look for coupon_applied events with coupon_injected = true
730
+ * - Compare funnel completion rates for Free users with/without coupons
731
+ *
732
+ * EXPECTED INSIGHT: ~30% of Free users receive promotional coupons mid-funnel.
733
+ * These users should show different downstream conversion behavior, simulating
734
+ * how promotional nudges affect the purchase funnel.
735
+ *
736
+ * REAL-WORLD ANALOGUE: Apps frequently inject promotional offers (push
737
+ * notifications, in-app banners) to non-paying users during key funnel
738
+ * moments to boost conversion.
739
+ *
740
+ * ───────────────────────────────────────────────────────────────────────────────
741
+ * 3. LATE NIGHT MUNCHIES (event)
742
+ * ───────────────────────────────────────────────────────────────────────────────
743
+ *
744
+ * PATTERN: Between 10PM and 2AM, restaurant views and cart additions skew
745
+ * 70% toward American (fast food) cuisine. Item prices are boosted by 1.3x.
746
+ * Events carry late_night_order: true.
747
+ *
748
+ * HOW TO FIND IT:
749
+ * - Break down restaurant_viewed by hour of day and cuisine_type
750
+ * - Compare cuisine distribution at 10PM-2AM vs. daytime
751
+ * - Filter late_night_order = true and compare average item_price
752
+ *
753
+ * EXPECTED INSIGHT: Late-night orders are overwhelmingly fast food with
754
+ * higher average prices. The cuisine distribution at night is dramatically
755
+ * different from daytime patterns.
756
+ *
757
+ * REAL-WORLD ANALOGUE: Late-night food delivery is dominated by fast food
758
+ * and comfort food. Many platforms charge late-night surcharges or see
759
+ * naturally inflated basket sizes during these hours.
760
+ *
761
+ * ───────────────────────────────────────────────────────────────────────────────
762
+ * 4. RAINY WEEK SURGE (event)
763
+ * ───────────────────────────────────────────────────────────────────────────────
764
+ *
765
+ * PATTERN: During days 20-27 of the dataset, a simulated rainy week causes:
766
+ * - Delivery fees double on order_placed events
767
+ * - surge_pricing: true flag added
768
+ * - 40% chance of duplicate order events (demand surge)
769
+ *
770
+ * HOW TO FIND IT:
771
+ * - Chart order_placed event count by day
772
+ * - Filter days 20-27 and compare delivery_fee averages
773
+ * - Look for surge_pricing = true and rainy_week = true properties
774
+ *
775
+ * EXPECTED INSIGHT: Clear spike in order volume around days 20-27, with
776
+ * doubled delivery fees and surge pricing markers. The demand surge is
777
+ * visible as a ~40% increase in order_placed event volume.
778
+ *
779
+ * REAL-WORLD ANALOGUE: Weather events drive significant demand surges for
780
+ * food delivery. Platforms respond with surge pricing on delivery fees,
781
+ * and order volume increases as people avoid going out.
782
+ *
783
+ * ───────────────────────────────────────────────────────────────────────────────
784
+ * 5. REFERRAL POWER USERS (everything)
785
+ * ───────────────────────────────────────────────────────────────────────────────
786
+ *
787
+ * PATTERN: Users whose account_created event has referral_code = true:
788
+ * - Get 2x more reorder_initiated events (higher loyalty)
789
+ * - Have food_rating boosted to 4-5 (higher satisfaction)
790
+ * - Carry referral_user: true on affected events
791
+ *
792
+ * HOW TO FIND IT:
793
+ * - Segment users by: referral_code = true on account_created
794
+ * - Compare: reorder_initiated event count per user
795
+ * - Compare: average food_rating on order_rated events
796
+ *
797
+ * EXPECTED INSIGHT: Referred users reorder roughly 2x more often and rate
798
+ * food 4-5 stars consistently. They represent a higher-LTV, more-satisfied
799
+ * segment of the user base.
800
+ *
801
+ * REAL-WORLD ANALOGUE: Referral programs consistently produce higher-quality
802
+ * users. Referred customers have lower acquisition costs, higher retention,
803
+ * and higher satisfaction because they come with built-in social proof.
804
+ *
805
+ * ───────────────────────────────────────────────────────────────────────────────
806
+ * 6. TRIAL CONVERSION (everything)
807
+ * ───────────────────────────────────────────────────────────────────────────────
808
+ *
809
+ * PATTERN: Users with subscription_started where trial = true are evaluated:
810
+ * - If they place 3+ orders in their first 14 days: retained (all events kept)
811
+ * - If fewer than 3 orders: churned (60% of events after day 14 removed)
812
+ * - Retained users carry trial_retained: true
813
+ *
814
+ * HOW TO FIND IT:
815
+ * - Segment users by: subscription_started with trial = true
816
+ * - Count order_placed events within first 14 days per user
817
+ * - Compare: event volume after day 14 for 3+ orders vs. <3 orders
818
+ * - Look for trial_retained = true property
819
+ *
820
+ * EXPECTED INSIGHT: Trial users who place 3+ early orders show sustained
821
+ * engagement. Those who don't show a dramatic drop-off after day 14, with
822
+ * 60% fewer events. The "magic number" for trial conversion is 3 orders.
823
+ *
824
+ * REAL-WORLD ANALOGUE: Subscription services often find a "magic number"
825
+ * of early actions that predict long-term retention. For food delivery,
826
+ * this is typically a threshold of orders during the trial period.
827
+ *
828
+ * ───────────────────────────────────────────────────────────────────────────────
829
+ * 7. SUPPORT TICKET CHURN (user)
830
+ * ───────────────────────────────────────────────────────────────────────────────
831
+ *
832
+ * PATTERN: 15% of users are flagged as is_high_risk = true with a
833
+ * churn_risk_score between 70-100. The remaining 85% get scores of 0-40.
834
+ * This creates a binary segmentation opportunity for retention teams.
835
+ *
836
+ * HOW TO FIND IT:
837
+ * - Segment users by: is_high_risk = true
838
+ * - Compare: churn_risk_score distribution
839
+ * - Cross-reference: support_ticket event frequency for high-risk users
840
+ * - Compare: retention and ordering behavior by risk segment
841
+ *
842
+ * EXPECTED INSIGHT: 15% of users have churn scores above 70, creating a
843
+ * clear at-risk segment. These users likely correlate with higher support
844
+ * ticket rates and lower order frequency.
845
+ *
846
+ * REAL-WORLD ANALOGUE: Customer health scoring is used by subscription
847
+ * businesses to identify at-risk users for proactive retention outreach.
848
+ * ML models in production generate similar risk scores from behavioral data.
849
+ *
850
+ * ───────────────────────────────────────────────────────────────────────────────
851
+ * 8. FIRST ORDER BONUS (funnel-pre)
852
+ * ───────────────────────────────────────────────────────────────────────────────
853
+ *
854
+ * PATTERN: For the Order Completion funnel (checkout started -> order placed
855
+ * -> order delivered), new users (born in dataset) get their conversion rate
856
+ * boosted to 90%. Events carry first_order_bonus: true.
857
+ *
858
+ * HOW TO FIND IT:
859
+ * - Segment the Order Completion funnel by new vs. existing users
860
+ * - Compare: conversion rates for new users vs. returning users
861
+ * - Filter: first_order_bonus = true
862
+ *
863
+ * EXPECTED INSIGHT: New users convert at ~90% through the order funnel,
864
+ * dramatically higher than the 60% baseline. This simulates first-order
865
+ * promotions (free delivery, $10 off) that most food delivery apps offer.
866
+ *
867
+ * REAL-WORLD ANALOGUE: Every major food delivery app offers aggressive
868
+ * first-order incentives. These dramatically boost initial conversion but
869
+ * the real question is whether those users return (see Hook #6).
870
+ *
871
+ * ═══════════════════════════════════════════════════════════════════════════════
872
+ * ADVANCED ANALYSIS IDEAS
873
+ * ═══════════════════════════════════════════════════════════════════════════════
874
+ *
875
+ * CROSS-HOOK PATTERNS:
876
+ *
877
+ * 1. The Perfect Customer: Users who:
878
+ * - Signed up via referral (Hook #5)
879
+ * - Started a trial and placed 3+ early orders (Hook #6)
880
+ * - Order during lunch rush (Hook #1)
881
+ * - Have low churn risk score (Hook #7)
882
+ * These users should have exceptional LTV and retention metrics.
883
+ *
884
+ * 2. Rainy Night Double Whammy: Do late-night orders during rainy week
885
+ * (Hook #3 + Hook #4) show compounded surge pricing effects?
886
+ *
887
+ * 3. Coupon-Driven Trial Conversion: Do free users who receive injected
888
+ * coupons (Hook #2) eventually start trials (Hook #6)?
889
+ *
890
+ * 4. Referral + First Order: Referred users (Hook #5) who also get the
891
+ * first order bonus (Hook #8) should have the highest conversion rates.
892
+ *
893
+ * 5. Support Tickets and Churn Risk: Do high-risk users (Hook #7) file
894
+ * more support tickets, and does this correlate with rainy week
895
+ * late deliveries (Hook #4)?
896
+ *
897
+ * COHORT ANALYSIS:
898
+ *
899
+ * - Cohort by signup week: Users who started during rainy week (days 20-27)
900
+ * should show different ordering patterns
901
+ * - Cohort by referral source: Referred vs. organic user lifecycle comparison
902
+ * - Cohort by subscription tier: Free vs. QuickBite+ across all metrics
903
+ * - Cohort by city: Do different cities show different cuisine preferences
904
+ * and ordering patterns?
905
+ *
906
+ * FUNNEL ANALYSIS:
907
+ *
908
+ * - Onboarding Funnel: How quickly do users go from account creation to
909
+ * first restaurant view? Break down by signup method.
910
+ * - Order Funnel: Compare checkout-to-delivery completion by platform,
911
+ * subscription tier, and time of day.
912
+ * - Discovery Funnel: How does search type (restaurant vs. cuisine vs. dish)
913
+ * affect downstream conversion to ordering?
914
+ *
915
+ * ═══════════════════════════════════════════════════════════════════════════════
916
+ * EXPECTED METRICS SUMMARY
917
+ * ═══════════════════════════════════════════════════════════════════════════════
918
+ *
919
+ * Hook | Metric | Baseline | Hook Effect | Ratio
920
+ * ──────────────────────|──────────────────────|──────────|─────────────|──────
921
+ * Lunch Rush | Funnel conversion | 60% | 84% | 1.4x
922
+ * Coupon Injection | Free user coupons | 0% | 30% | N/A
923
+ * Late Night Munchies | American cuisine % | ~15% | 70% | ~4.7x
924
+ * Rainy Week Surge | Order volume | 100% | 140% | 1.4x
925
+ * Referral Power Users | Reorder frequency | 1x | 2x | 2.0x
926
+ * Trial Conversion | Post-trial retention | 100% | 40% | 0.4x
927
+ * Support Ticket Churn | High-risk users | 0% | 15% | N/A
928
+ * First Order Bonus | New user conversion | 60% | 90% | 1.5x
929
+ *
930
+ * ═══════════════════════════════════════════════════════════════════════════════
931
+ * HOW TO RUN THIS DUNGEON
932
+ * ═══════════════════════════════════════════════════════════════════════════════
933
+ *
934
+ * From the dm4 root directory:
935
+ *
936
+ * npm start
937
+ *
938
+ * Or programmatically:
939
+ *
940
+ * import generate from './index.js';
941
+ * import config from './dungeons/harness-food.js';
942
+ * const results = await generate(config);
943
+ *
944
+ * OUTPUT FILES (with writeToDisk: true, format: "json", gzip: true):
945
+ *
946
+ * - needle-haystack-food__events.json.gz - All event data
947
+ * - needle-haystack-food__user_profiles.json.gz - User profiles
948
+ * - needle-haystack-food__group_profiles.json.gz - Restaurant profiles
949
+ * - needle-haystack-food__item_id_lookup.json.gz - Menu item catalog
950
+ * - needle-haystack-food__coupon_code_lookup.json.gz - Coupon catalog
951
+ *
952
+ * ═══════════════════════════════════════════════════════════════════════════════
953
+ * TESTING YOUR ANALYTICS PLATFORM
954
+ * ═══════════════════════════════════════════════════════════════════════════════
955
+ *
956
+ * This dungeon is perfect for testing:
957
+ *
958
+ * 1. Time-of-Day Analysis: Can you detect the lunch and dinner rush patterns?
959
+ * 2. Promotional Impact: Can you measure the effect of injected coupons?
960
+ * 3. Behavioral Shifts: Can you discover the late-night cuisine preferences?
961
+ * 4. Anomaly Detection: Can you spot the rainy week demand surge?
962
+ * 5. Referral Analysis: Can you quantify the referral user advantage?
963
+ * 6. Trial Optimization: Can you find the "magic number" of early orders?
964
+ * 7. Churn Prediction: Can you identify high-risk users before they leave?
965
+ * 8. New User Funnels: Can you measure the first-order bonus impact?
966
+ *
967
+ * ═══════════════════════════════════════════════════════════════════════════════
968
+ * WHY "NEEDLE IN A HAYSTACK"?
969
+ * ═══════════════════════════════════════════════════════════════════════════════
970
+ *
971
+ * Each hook is a "needle" - a meaningful, actionable insight hidden in a
972
+ * "haystack" of 360K events. The challenge is:
973
+ *
974
+ * 1. FINDING the needles (discovery)
975
+ * 2. VALIDATING they're real patterns (statistical significance)
976
+ * 3. UNDERSTANDING why they matter (business impact)
977
+ * 4. ACTING on them (product decisions)
978
+ *
979
+ * This mirrors real-world product analytics: your data contains valuable insights,
980
+ * but you need the right tools and skills to find them.
981
+ *
982
+ * Happy Hunting!
983
+ *
984
+ * ═══════════════════════════════════════════════════════════════════════════════
985
+ */