make-mp-data 2.1.11 → 3.0.2

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 (71) hide show
  1. package/README.md +31 -0
  2. package/dungeons/adspend.js +35 -1
  3. package/dungeons/anon.js +25 -1
  4. package/dungeons/array-of-object-lookup.js +201 -0
  5. package/dungeons/benchmark-heavy.js +241 -0
  6. package/dungeons/benchmark-light.js +141 -0
  7. package/dungeons/big.js +10 -9
  8. package/dungeons/business.js +60 -12
  9. package/dungeons/complex.js +35 -1
  10. package/dungeons/copilot.js +383 -0
  11. package/dungeons/education.js +1005 -0
  12. package/dungeons/experiments.js +18 -4
  13. package/dungeons/fintech.js +976 -0
  14. package/dungeons/foobar.js +32 -0
  15. package/dungeons/food.js +988 -0
  16. package/dungeons/funnels.js +38 -1
  17. package/dungeons/gaming.js +26 -5
  18. package/dungeons/media.js +861 -270
  19. package/dungeons/mil.js +31 -3
  20. package/dungeons/mirror.js +33 -1
  21. package/dungeons/retention-cadence.js +211 -0
  22. package/dungeons/rpg.js +1178 -0
  23. package/dungeons/sanity.js +32 -2
  24. package/dungeons/sass.js +923 -0
  25. package/dungeons/scd.js +47 -1
  26. package/dungeons/simple.js +29 -14
  27. package/dungeons/social.js +928 -0
  28. package/dungeons/streaming.js +373 -0
  29. package/dungeons/strict-event-test.js +30 -0
  30. package/dungeons/student-teacher.js +19 -5
  31. package/dungeons/text-generation.js +120 -84
  32. package/dungeons/too-big-events.js +203 -0
  33. package/dungeons/{userAgent.js → user-agent.js} +23 -2
  34. package/entry.js +5 -4
  35. package/index.js +41 -54
  36. package/lib/core/config-validator.js +122 -7
  37. package/lib/core/context.js +7 -14
  38. package/lib/core/storage.js +57 -25
  39. package/lib/generators/adspend.js +12 -12
  40. package/lib/generators/events.js +6 -5
  41. package/lib/generators/funnels.js +32 -10
  42. package/lib/generators/product-lookup.js +262 -0
  43. package/lib/generators/product-names.js +195 -0
  44. package/lib/generators/profiles.js +3 -3
  45. package/lib/generators/scd.js +13 -3
  46. package/lib/generators/text.js +17 -4
  47. package/lib/orchestrators/mixpanel-sender.js +244 -204
  48. package/lib/orchestrators/user-loop.js +54 -16
  49. package/lib/templates/phrases.js +473 -16
  50. package/lib/templates/schema.d.ts +173 -0
  51. package/lib/templates/verbose-schema.js +140 -206
  52. package/lib/utils/chart.js +210 -0
  53. package/lib/utils/function-registry.js +285 -0
  54. package/lib/utils/json-evaluator.js +172 -0
  55. package/lib/utils/logger.js +34 -0
  56. package/lib/utils/utils.js +41 -4
  57. package/package.json +12 -21
  58. package/types.d.ts +15 -5
  59. package/dungeons/ai-chat-analytics-ed.js +0 -274
  60. package/dungeons/money2020-ed-also.js +0 -277
  61. package/dungeons/money2020-ed.js +0 -579
  62. package/lib/generators/text-bak-old.js +0 -1121
  63. package/lib/orchestrators/worker-manager.js +0 -203
  64. package/lib/templates/hooks-instructions.txt +0 -434
  65. package/lib/templates/phrases-bak.js +0 -925
  66. package/lib/templates/prompt (old).txt +0 -98
  67. package/lib/templates/schema-instructions.txt +0 -155
  68. package/lib/templates/scratch-dungeon-template.js +0 -116
  69. package/lib/templates/textQuickTest.js +0 -172
  70. package/lib/utils/ai.js +0 -120
  71. package/lib/utils/project.js +0 -166
@@ -0,0 +1,988 @@
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: "",
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
+ // Use hash to deterministically assign ~50% of users as "new"
451
+ const userId = meta && meta.user && (meta.user.distinct_id || String(meta.user));
452
+ const isNewUser = userId && userId.charCodeAt(0) % 2 === 0;
453
+ if (isNewUser) {
454
+ record.conversionRate = 0.90;
455
+ record.props.first_order_bonus = true;
456
+ } else {
457
+ record.props.first_order_bonus = false;
458
+ }
459
+ }
460
+ }
461
+
462
+ // ═══════════════════════════════════════════════════════════════════
463
+ // HOOK 2: COUPON INJECTION (funnel-post)
464
+ // Free-tier users get coupon_applied events spliced into their
465
+ // funnel sequences 30% of the time, simulating promotional nudges
466
+ // that push non-subscribers toward conversion.
467
+ // ═══════════════════════════════════════════════════════════════════
468
+ if (type === "funnel-post") {
469
+ if (Array.isArray(record) && record.length >= 2) {
470
+ // Check if user is Free tier from first event's super props
471
+ const firstEvent = record[0];
472
+ const isFreeUser = firstEvent && firstEvent.subscription_tier === "Free";
473
+
474
+ if (isFreeUser && chance.bool({ likelihood: 30 })) {
475
+ // Pick a random insertion point between funnel steps
476
+ const insertIdx = chance.integer({ min: 1, max: record.length - 1 });
477
+ const prevEvent = record[insertIdx - 1];
478
+ const nextEvent = record[insertIdx];
479
+ const midTime = dayjs(prevEvent.time).add(
480
+ dayjs(nextEvent.time).diff(dayjs(prevEvent.time)) / 2,
481
+ 'milliseconds'
482
+ ).toISOString();
483
+
484
+ const couponEvent = {
485
+ event: "coupon applied",
486
+ time: midTime,
487
+ user_id: firstEvent.user_id,
488
+ coupon_code: chance.pickone(couponCodes),
489
+ discount_type: chance.pickone(["percent", "flat", "free_delivery"]),
490
+ discount_value: chance.integer({ min: 10, max: 30 }),
491
+ coupon_injected: true,
492
+ };
493
+ record.splice(insertIdx, 0, couponEvent);
494
+ }
495
+ }
496
+ }
497
+
498
+ // ═══════════════════════════════════════════════════════════════════
499
+ // HOOK 3: LATE NIGHT MUNCHIES (event)
500
+ // Between 10PM and 2AM, restaurant views and cart additions skew
501
+ // heavily toward fast food (American cuisine) with inflated prices,
502
+ // modeling the real-world late-night ordering pattern.
503
+ // ═══════════════════════════════════════════════════════════════════
504
+ if (type === "event") {
505
+ const EVENT_TIME = dayjs(record.time);
506
+ const hour = EVENT_TIME.hour();
507
+ const isLateNight = hour >= 22 || hour <= 2;
508
+
509
+ if (record.event === "restaurant viewed" || record.event === "item added to cart") {
510
+ if (isLateNight) {
511
+ // 70% of late-night browsing/ordering is fast food (American)
512
+ if (chance.bool({ likelihood: 70 })) {
513
+ if (record.cuisine_type !== undefined) {
514
+ record.cuisine_type = "American";
515
+ }
516
+ }
517
+
518
+ // Boost item price by 1.3x (late-night surcharge effect)
519
+ if (record.item_price !== undefined) {
520
+ record.item_price = Math.round(record.item_price * 1.3 * 100) / 100;
521
+ }
522
+
523
+ record.late_night_order = true;
524
+ } else {
525
+ record.late_night_order = false;
526
+ }
527
+ }
528
+
529
+ // ═══════════════════════════════════════════════════════════════
530
+ // HOOK 4: RAINY WEEK SURGE (event)
531
+ // During days 20-27, delivery fees double on order_placed events
532
+ // and there is a 40% chance of duplicating the event, simulating
533
+ // a weather-driven demand surge with surge pricing.
534
+ // ═══════════════════════════════════════════════════════════════
535
+ if (record.event === "order placed") {
536
+ if (EVENT_TIME.isAfter(RAINY_WEEK_START) && EVENT_TIME.isBefore(RAINY_WEEK_END)) {
537
+ record.delivery_fee = (record.delivery_fee || 5) * 2;
538
+ record.surge_pricing = true;
539
+
540
+ // 40% chance to duplicate the event (more orders during rain)
541
+ if (chance.bool({ likelihood: 40 })) {
542
+ const duplicateEvent = {
543
+ event: "order placed",
544
+ time: EVENT_TIME.add(chance.integer({ min: 5, max: 60 }), 'minutes').toISOString(),
545
+ user_id: record.user_id,
546
+ order_id: chance.pickone(orderIds),
547
+ payment_method: chance.pickone(["credit_card", "apple_pay", "google_pay"]),
548
+ order_total: chance.integer({ min: 15, max: 120 }),
549
+ tip_amount: chance.integer({ min: 0, max: 20 }),
550
+ delivery_fee: chance.integer({ min: 6, max: 24 }),
551
+ surge_pricing: true,
552
+ rainy_week: true,
553
+ };
554
+ return [record, duplicateEvent];
555
+ }
556
+ } else {
557
+ record.surge_pricing = false;
558
+ }
559
+ }
560
+ }
561
+
562
+ // ═══════════════════════════════════════════════════════════════════
563
+ // HOOK 5: REFERRAL POWER USERS (everything)
564
+ // Users who signed up with a referral code get 2x more reorder
565
+ // events and boosted food ratings. Referred users are more loyal
566
+ // and satisfied, mirroring real referral program outcomes.
567
+ // ═══════════════════════════════════════════════════════════════════
568
+ if (type === "everything") {
569
+ const userEvents = record;
570
+
571
+ // First pass: identify user patterns
572
+ let isReferralUser = false;
573
+ let hasTrialSubscription = false;
574
+ let earlyOrderCount = 0;
575
+ let firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
576
+
577
+ userEvents.forEach((event) => {
578
+ const eventTime = dayjs(event.time);
579
+ const daysSinceStart = firstEventTime ? eventTime.diff(firstEventTime, 'days', true) : 0;
580
+
581
+ // Hook #5: Track referral users
582
+ if (event.event === "account created" && event.referral_code === true) {
583
+ isReferralUser = true;
584
+ }
585
+
586
+ // Hook #6: Track trial subscribers and early orders
587
+ if (event.event === "subscription started" && event.trial === true) {
588
+ hasTrialSubscription = true;
589
+ }
590
+ if (event.event === "order placed" && daysSinceStart <= 14) {
591
+ earlyOrderCount++;
592
+ }
593
+ });
594
+
595
+ // Second pass: apply referral power user effects
596
+ userEvents.forEach((event, idx) => {
597
+ if (event.event === "order rated") {
598
+ if (isReferralUser) {
599
+ event.food_rating = chance.integer({ min: 4, max: 5 });
600
+ event.referral_user = true;
601
+ } else {
602
+ event.referral_user = false;
603
+ }
604
+ }
605
+ if (isReferralUser && event.event === "reorder initiated" && chance.bool({ likelihood: 50 })) {
606
+ const eventTime = dayjs(event.time);
607
+ const extraReorder = {
608
+ event: "reorder initiated",
609
+ time: eventTime.add(chance.integer({ min: 1, max: 7 }), 'days').toISOString(),
610
+ user_id: event.user_id,
611
+ order_id: chance.pickone(orderIds),
612
+ original_order_age_days: chance.integer({ min: 3, max: 30 }),
613
+ referral_user: true,
614
+ };
615
+ userEvents.splice(idx + 1, 0, extraReorder);
616
+ }
617
+ });
618
+
619
+ // ═══════════════════════════════════════════════════════════════
620
+ // HOOK 6: TRIAL CONVERSION (everything)
621
+ // Trial subscribers who place 3+ orders in their first 14 days
622
+ // are retained. Those with fewer orders churn: 60% of their
623
+ // events after day 14 are removed, simulating subscription
624
+ // abandonment after a failed trial experience.
625
+ // ═══════════════════════════════════════════════════════════════
626
+ if (hasTrialSubscription) {
627
+ const trialCutoff = firstEventTime ? firstEventTime.add(14, 'days') : null;
628
+
629
+ if (earlyOrderCount >= 3) {
630
+ // Retained: mark events as trial_retained
631
+ userEvents.forEach((event) => {
632
+ event.trial_retained = true;
633
+ });
634
+ } else {
635
+ // Churned: remove 60% of events after day 14
636
+ userEvents.forEach((event) => {
637
+ event.trial_retained = false;
638
+ });
639
+ for (let i = userEvents.length - 1; i >= 0; i--) {
640
+ const evt = userEvents[i];
641
+ if (trialCutoff && dayjs(evt.time).isAfter(trialCutoff)) {
642
+ if (chance.bool({ likelihood: 60 })) {
643
+ userEvents.splice(i, 1);
644
+ }
645
+ }
646
+ }
647
+ }
648
+ }
649
+ }
650
+
651
+ // ═══════════════════════════════════════════════════════════════════
652
+ // HOOK 7: SUPPORT TICKET CHURN (user)
653
+ // 15% of users are flagged as high-risk with elevated churn scores.
654
+ // The remaining users get low churn scores. This creates a clear
655
+ // segmentation opportunity for proactive retention campaigns.
656
+ // ═══════════════════════════════════════════════════════════════════
657
+ if (type === "user") {
658
+ if (chance.bool({ likelihood: 15 })) {
659
+ record.is_high_risk = true;
660
+ record.churn_risk_score = chance.integer({ min: 70, max: 100 });
661
+ } else {
662
+ record.is_high_risk = false;
663
+ record.churn_risk_score = chance.integer({ min: 0, max: 40 });
664
+ }
665
+ }
666
+
667
+ return record;
668
+ }
669
+ };
670
+
671
+ export default config;
672
+
673
+ /**
674
+ * ═══════════════════════════════════════════════════════════════════════════════
675
+ * NEEDLE IN A HAYSTACK - QUICKBITE FOOD DELIVERY ANALYTICS
676
+ * ═══════════════════════════════════════════════════════════════════════════════
677
+ *
678
+ * A food delivery app dungeon with 8 deliberately architected analytics
679
+ * insights hidden in the data. This dungeon is designed to showcase advanced
680
+ * product analytics patterns and demonstrate how to find "needles" (meaningful
681
+ * insights) in "haystacks" (large datasets).
682
+ *
683
+ * ═══════════════════════════════════════════════════════════════════════════════
684
+ * DATASET OVERVIEW
685
+ * ═══════════════════════════════════════════════════════════════════════════════
686
+ *
687
+ * - 5,000 users over 100 days
688
+ * - 360K events across 17 event types
689
+ * - 3 funnels (onboarding, order completion, discovery to order)
690
+ * - Group analytics (restaurants)
691
+ * - Lookup tables (menu items, coupon codes)
692
+ * - Subscription tiers (Free, QuickBite+)
693
+ *
694
+ * ═══════════════════════════════════════════════════════════════════════════════
695
+ * THE 8 ARCHITECTED HOOKS
696
+ * ═══════════════════════════════════════════════════════════════════════════════
697
+ *
698
+ * Each hook creates a specific, discoverable analytics insight that simulates
699
+ * real-world product behavior patterns.
700
+ *
701
+ * ───────────────────────────────────────────────────────────────────────────────
702
+ * 1. LUNCH RUSH CONVERSION (funnel-pre)
703
+ * ───────────────────────────────────────────────────────────────────────────────
704
+ *
705
+ * PATTERN: Funnel conversion rates are boosted during meal-time hours.
706
+ * Lunch (11AM-1PM) gets a 1.4x multiplier; dinner (5PM-8PM) gets 1.2x.
707
+ * Events during these windows carry lunch_rush or dinner_rush properties.
708
+ *
709
+ * HOW TO FIND IT:
710
+ * - Break down funnel conversion by hour of day
711
+ * - Compare conversion rates during 11AM-1PM vs. off-peak hours
712
+ * - Filter events where lunch_rush = true or dinner_rush = true
713
+ *
714
+ * EXPECTED INSIGHT: Lunch hour funnels convert ~40% better than baseline.
715
+ * Dinner hour funnels convert ~20% better. Clear time-of-day pattern in
716
+ * the checkout-to-order-to-delivery pipeline.
717
+ *
718
+ * REAL-WORLD ANALOGUE: Food delivery apps see massive demand spikes during
719
+ * traditional meal times. Users ordering during these windows have higher
720
+ * intent and thus higher conversion rates.
721
+ *
722
+ * ───────────────────────────────────────────────────────────────────────────────
723
+ * 2. COUPON INJECTION (funnel-post)
724
+ * ───────────────────────────────────────────────────────────────────────────────
725
+ *
726
+ * PATTERN: Free-tier users get coupon_applied events spliced into their
727
+ * funnel sequences 30% of the time. These injected coupons carry the
728
+ * coupon_injected: true property to distinguish them from organic usage.
729
+ *
730
+ * HOW TO FIND IT:
731
+ * - Segment by subscription_tier = "Free"
732
+ * - Look for coupon_applied events with coupon_injected = true
733
+ * - Compare funnel completion rates for Free users with/without coupons
734
+ *
735
+ * EXPECTED INSIGHT: ~30% of Free users receive promotional coupons mid-funnel.
736
+ * These users should show different downstream conversion behavior, simulating
737
+ * how promotional nudges affect the purchase funnel.
738
+ *
739
+ * REAL-WORLD ANALOGUE: Apps frequently inject promotional offers (push
740
+ * notifications, in-app banners) to non-paying users during key funnel
741
+ * moments to boost conversion.
742
+ *
743
+ * ───────────────────────────────────────────────────────────────────────────────
744
+ * 3. LATE NIGHT MUNCHIES (event)
745
+ * ───────────────────────────────────────────────────────────────────────────────
746
+ *
747
+ * PATTERN: Between 10PM and 2AM, restaurant views and cart additions skew
748
+ * 70% toward American (fast food) cuisine. Item prices are boosted by 1.3x.
749
+ * Events carry late_night_order: true.
750
+ *
751
+ * HOW TO FIND IT:
752
+ * - Break down restaurant_viewed by hour of day and cuisine_type
753
+ * - Compare cuisine distribution at 10PM-2AM vs. daytime
754
+ * - Filter late_night_order = true and compare average item_price
755
+ *
756
+ * EXPECTED INSIGHT: Late-night orders are overwhelmingly fast food with
757
+ * higher average prices. The cuisine distribution at night is dramatically
758
+ * different from daytime patterns.
759
+ *
760
+ * REAL-WORLD ANALOGUE: Late-night food delivery is dominated by fast food
761
+ * and comfort food. Many platforms charge late-night surcharges or see
762
+ * naturally inflated basket sizes during these hours.
763
+ *
764
+ * ───────────────────────────────────────────────────────────────────────────────
765
+ * 4. RAINY WEEK SURGE (event)
766
+ * ───────────────────────────────────────────────────────────────────────────────
767
+ *
768
+ * PATTERN: During days 20-27 of the dataset, a simulated rainy week causes:
769
+ * - Delivery fees double on order_placed events
770
+ * - surge_pricing: true flag added
771
+ * - 40% chance of duplicate order events (demand surge)
772
+ *
773
+ * HOW TO FIND IT:
774
+ * - Chart order_placed event count by day
775
+ * - Filter days 20-27 and compare delivery_fee averages
776
+ * - Look for surge_pricing = true and rainy_week = true properties
777
+ *
778
+ * EXPECTED INSIGHT: Clear spike in order volume around days 20-27, with
779
+ * doubled delivery fees and surge pricing markers. The demand surge is
780
+ * visible as a ~40% increase in order_placed event volume.
781
+ *
782
+ * REAL-WORLD ANALOGUE: Weather events drive significant demand surges for
783
+ * food delivery. Platforms respond with surge pricing on delivery fees,
784
+ * and order volume increases as people avoid going out.
785
+ *
786
+ * ───────────────────────────────────────────────────────────────────────────────
787
+ * 5. REFERRAL POWER USERS (everything)
788
+ * ───────────────────────────────────────────────────────────────────────────────
789
+ *
790
+ * PATTERN: Users whose account_created event has referral_code = true:
791
+ * - Get 2x more reorder_initiated events (higher loyalty)
792
+ * - Have food_rating boosted to 4-5 (higher satisfaction)
793
+ * - Carry referral_user: true on affected events
794
+ *
795
+ * HOW TO FIND IT:
796
+ * - Segment users by: referral_code = true on account_created
797
+ * - Compare: reorder_initiated event count per user
798
+ * - Compare: average food_rating on order_rated events
799
+ *
800
+ * EXPECTED INSIGHT: Referred users reorder roughly 2x more often and rate
801
+ * food 4-5 stars consistently. They represent a higher-LTV, more-satisfied
802
+ * segment of the user base.
803
+ *
804
+ * REAL-WORLD ANALOGUE: Referral programs consistently produce higher-quality
805
+ * users. Referred customers have lower acquisition costs, higher retention,
806
+ * and higher satisfaction because they come with built-in social proof.
807
+ *
808
+ * ───────────────────────────────────────────────────────────────────────────────
809
+ * 6. TRIAL CONVERSION (everything)
810
+ * ───────────────────────────────────────────────────────────────────────────────
811
+ *
812
+ * PATTERN: Users with subscription_started where trial = true are evaluated:
813
+ * - If they place 3+ orders in their first 14 days: retained (all events kept)
814
+ * - If fewer than 3 orders: churned (60% of events after day 14 removed)
815
+ * - Retained users carry trial_retained: true
816
+ *
817
+ * HOW TO FIND IT:
818
+ * - Segment users by: subscription_started with trial = true
819
+ * - Count order_placed events within first 14 days per user
820
+ * - Compare: event volume after day 14 for 3+ orders vs. <3 orders
821
+ * - Look for trial_retained = true property
822
+ *
823
+ * EXPECTED INSIGHT: Trial users who place 3+ early orders show sustained
824
+ * engagement. Those who don't show a dramatic drop-off after day 14, with
825
+ * 60% fewer events. The "magic number" for trial conversion is 3 orders.
826
+ *
827
+ * REAL-WORLD ANALOGUE: Subscription services often find a "magic number"
828
+ * of early actions that predict long-term retention. For food delivery,
829
+ * this is typically a threshold of orders during the trial period.
830
+ *
831
+ * ───────────────────────────────────────────────────────────────────────────────
832
+ * 7. SUPPORT TICKET CHURN (user)
833
+ * ───────────────────────────────────────────────────────────────────────────────
834
+ *
835
+ * PATTERN: 15% of users are flagged as is_high_risk = true with a
836
+ * churn_risk_score between 70-100. The remaining 85% get scores of 0-40.
837
+ * This creates a binary segmentation opportunity for retention teams.
838
+ *
839
+ * HOW TO FIND IT:
840
+ * - Segment users by: is_high_risk = true
841
+ * - Compare: churn_risk_score distribution
842
+ * - Cross-reference: support_ticket event frequency for high-risk users
843
+ * - Compare: retention and ordering behavior by risk segment
844
+ *
845
+ * EXPECTED INSIGHT: 15% of users have churn scores above 70, creating a
846
+ * clear at-risk segment. These users likely correlate with higher support
847
+ * ticket rates and lower order frequency.
848
+ *
849
+ * REAL-WORLD ANALOGUE: Customer health scoring is used by subscription
850
+ * businesses to identify at-risk users for proactive retention outreach.
851
+ * ML models in production generate similar risk scores from behavioral data.
852
+ *
853
+ * ───────────────────────────────────────────────────────────────────────────────
854
+ * 8. FIRST ORDER BONUS (funnel-pre)
855
+ * ───────────────────────────────────────────────────────────────────────────────
856
+ *
857
+ * PATTERN: For the Order Completion funnel (checkout started -> order placed
858
+ * -> order delivered), new users (born in dataset) get their conversion rate
859
+ * boosted to 90%. Events carry first_order_bonus: true.
860
+ *
861
+ * HOW TO FIND IT:
862
+ * - Segment the Order Completion funnel by new vs. existing users
863
+ * - Compare: conversion rates for new users vs. returning users
864
+ * - Filter: first_order_bonus = true
865
+ *
866
+ * EXPECTED INSIGHT: New users convert at ~90% through the order funnel,
867
+ * dramatically higher than the 60% baseline. This simulates first-order
868
+ * promotions (free delivery, $10 off) that most food delivery apps offer.
869
+ *
870
+ * REAL-WORLD ANALOGUE: Every major food delivery app offers aggressive
871
+ * first-order incentives. These dramatically boost initial conversion but
872
+ * the real question is whether those users return (see Hook #6).
873
+ *
874
+ * ═══════════════════════════════════════════════════════════════════════════════
875
+ * ADVANCED ANALYSIS IDEAS
876
+ * ═══════════════════════════════════════════════════════════════════════════════
877
+ *
878
+ * CROSS-HOOK PATTERNS:
879
+ *
880
+ * 1. The Perfect Customer: Users who:
881
+ * - Signed up via referral (Hook #5)
882
+ * - Started a trial and placed 3+ early orders (Hook #6)
883
+ * - Order during lunch rush (Hook #1)
884
+ * - Have low churn risk score (Hook #7)
885
+ * These users should have exceptional LTV and retention metrics.
886
+ *
887
+ * 2. Rainy Night Double Whammy: Do late-night orders during rainy week
888
+ * (Hook #3 + Hook #4) show compounded surge pricing effects?
889
+ *
890
+ * 3. Coupon-Driven Trial Conversion: Do free users who receive injected
891
+ * coupons (Hook #2) eventually start trials (Hook #6)?
892
+ *
893
+ * 4. Referral + First Order: Referred users (Hook #5) who also get the
894
+ * first order bonus (Hook #8) should have the highest conversion rates.
895
+ *
896
+ * 5. Support Tickets and Churn Risk: Do high-risk users (Hook #7) file
897
+ * more support tickets, and does this correlate with rainy week
898
+ * late deliveries (Hook #4)?
899
+ *
900
+ * COHORT ANALYSIS:
901
+ *
902
+ * - Cohort by signup week: Users who started during rainy week (days 20-27)
903
+ * should show different ordering patterns
904
+ * - Cohort by referral source: Referred vs. organic user lifecycle comparison
905
+ * - Cohort by subscription tier: Free vs. QuickBite+ across all metrics
906
+ * - Cohort by city: Do different cities show different cuisine preferences
907
+ * and ordering patterns?
908
+ *
909
+ * FUNNEL ANALYSIS:
910
+ *
911
+ * - Onboarding Funnel: How quickly do users go from account creation to
912
+ * first restaurant view? Break down by signup method.
913
+ * - Order Funnel: Compare checkout-to-delivery completion by platform,
914
+ * subscription tier, and time of day.
915
+ * - Discovery Funnel: How does search type (restaurant vs. cuisine vs. dish)
916
+ * affect downstream conversion to ordering?
917
+ *
918
+ * ═══════════════════════════════════════════════════════════════════════════════
919
+ * EXPECTED METRICS SUMMARY
920
+ * ═══════════════════════════════════════════════════════════════════════════════
921
+ *
922
+ * Hook | Metric | Baseline | Hook Effect | Ratio
923
+ * ──────────────────────|──────────────────────|──────────|─────────────|──────
924
+ * Lunch Rush | Funnel conversion | 60% | 84% | 1.4x
925
+ * Coupon Injection | Free user coupons | 0% | 30% | N/A
926
+ * Late Night Munchies | American cuisine % | ~15% | 70% | ~4.7x
927
+ * Rainy Week Surge | Order volume | 100% | 140% | 1.4x
928
+ * Referral Power Users | Reorder frequency | 1x | 2x | 2.0x
929
+ * Trial Conversion | Post-trial retention | 100% | 40% | 0.4x
930
+ * Support Ticket Churn | High-risk users | 0% | 15% | N/A
931
+ * First Order Bonus | New user conversion | 60% | 90% | 1.5x
932
+ *
933
+ * ═══════════════════════════════════════════════════════════════════════════════
934
+ * HOW TO RUN THIS DUNGEON
935
+ * ═══════════════════════════════════════════════════════════════════════════════
936
+ *
937
+ * From the dm4 root directory:
938
+ *
939
+ * npm start
940
+ *
941
+ * Or programmatically:
942
+ *
943
+ * import generate from './index.js';
944
+ * import config from './dungeons/harness-food.js';
945
+ * const results = await generate(config);
946
+ *
947
+ * OUTPUT FILES (with writeToDisk: false, format: "json", gzip: true):
948
+ *
949
+ * - needle-haystack-food__events.json.gz - All event data
950
+ * - needle-haystack-food__user_profiles.json.gz - User profiles
951
+ * - needle-haystack-food__group_profiles.json.gz - Restaurant profiles
952
+ * - needle-haystack-food__item_id_lookup.json.gz - Menu item catalog
953
+ * - needle-haystack-food__coupon_code_lookup.json.gz - Coupon catalog
954
+ *
955
+ * ═══════════════════════════════════════════════════════════════════════════════
956
+ * TESTING YOUR ANALYTICS PLATFORM
957
+ * ═══════════════════════════════════════════════════════════════════════════════
958
+ *
959
+ * This dungeon is perfect for testing:
960
+ *
961
+ * 1. Time-of-Day Analysis: Can you detect the lunch and dinner rush patterns?
962
+ * 2. Promotional Impact: Can you measure the effect of injected coupons?
963
+ * 3. Behavioral Shifts: Can you discover the late-night cuisine preferences?
964
+ * 4. Anomaly Detection: Can you spot the rainy week demand surge?
965
+ * 5. Referral Analysis: Can you quantify the referral user advantage?
966
+ * 6. Trial Optimization: Can you find the "magic number" of early orders?
967
+ * 7. Churn Prediction: Can you identify high-risk users before they leave?
968
+ * 8. New User Funnels: Can you measure the first-order bonus impact?
969
+ *
970
+ * ═══════════════════════════════════════════════════════════════════════════════
971
+ * WHY "NEEDLE IN A HAYSTACK"?
972
+ * ═══════════════════════════════════════════════════════════════════════════════
973
+ *
974
+ * Each hook is a "needle" - a meaningful, actionable insight hidden in a
975
+ * "haystack" of 360K events. The challenge is:
976
+ *
977
+ * 1. FINDING the needles (discovery)
978
+ * 2. VALIDATING they're real patterns (statistical significance)
979
+ * 3. UNDERSTANDING why they matter (business impact)
980
+ * 4. ACTING on them (product decisions)
981
+ *
982
+ * This mirrors real-world product analytics: your data contains valuable insights,
983
+ * but you need the right tools and skills to find them.
984
+ *
985
+ * Happy Hunting!
986
+ *
987
+ * ═══════════════════════════════════════════════════════════════════════════════
988
+ */