@ak--47/dungeon-master 1.3.0 → 1.3.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 (74) hide show
  1. package/dungeons/technical/array-of-object-lookup.js +0 -2
  2. package/dungeons/technical/simple.js +3 -4
  3. package/dungeons/technical/simplest-schema.json +5 -0
  4. package/dungeons/technical/text-generation.js +1 -1
  5. package/dungeons/vertical/ai-platform.js +192 -133
  6. package/dungeons/vertical/community.js +64 -34
  7. package/dungeons/vertical/crypto.js +333 -224
  8. package/dungeons/vertical/dating.js +228 -282
  9. package/dungeons/vertical/devtools.js +164 -62
  10. package/dungeons/vertical/ecommerce.js +240 -91
  11. package/dungeons/vertical/education.js +328 -310
  12. package/dungeons/vertical/fintech.js +437 -311
  13. package/dungeons/vertical/fitness.js +141 -58
  14. package/dungeons/vertical/food-delivery.js +298 -318
  15. package/dungeons/vertical/gaming.js +378 -231
  16. package/dungeons/vertical/healthcare.js +140 -60
  17. package/dungeons/vertical/insurance-application.js +168 -73
  18. package/dungeons/vertical/logistics.js +95 -6
  19. package/dungeons/vertical/marketplace.js +137 -51
  20. package/dungeons/vertical/media.js +241 -386
  21. package/dungeons/vertical/real-estate.js +322 -315
  22. package/dungeons/vertical/sass.js +253 -263
  23. package/dungeons/vertical/social.js +262 -206
  24. package/dungeons/vertical/travel.js +59 -16
  25. package/index.js +17 -17
  26. package/lib/core/config-validator.js +93 -21
  27. package/lib/core/context.js +10 -24
  28. package/lib/core/storage.js +6 -1
  29. package/lib/generators/events.js +11 -14
  30. package/lib/generators/funnels.js +12 -4
  31. package/lib/generators/mirror.js +3 -2
  32. package/lib/generators/product-names.js +1 -1
  33. package/lib/generators/scd.js +2 -1
  34. package/lib/generators/text.js +1 -1
  35. package/lib/orchestrators/user-loop.js +47 -47
  36. package/lib/utils/utils.js +71 -39
  37. package/package.json +1 -1
  38. package/scripts/smoke-test-all.mjs +162 -0
  39. package/scripts/verify-runner.mjs +72 -24
  40. package/types.d.ts +38 -15
  41. package/dungeons/technical/ad-spend-schema.json +0 -128
  42. package/dungeons/technical/anonymous-users-schema.json +0 -92
  43. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  44. package/dungeons/technical/experiments-schema.json +0 -203
  45. package/dungeons/technical/foobar-schema.json +0 -362
  46. package/dungeons/technical/group-analytics-schema.json +0 -241
  47. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  48. package/dungeons/technical/nested-objects-schema.json +0 -145
  49. package/dungeons/technical/retention-cadence-schema.json +0 -37
  50. package/dungeons/technical/sanity-schema.json +0 -185
  51. package/dungeons/technical/scale-test-schema.json +0 -70
  52. package/dungeons/technical/scd-schema.json +0 -467
  53. package/dungeons/technical/simple-schema.json +0 -362
  54. package/dungeons/technical/text-generation-schema.json +0 -1062
  55. package/dungeons/vertical/ai-platform-schema.json +0 -617
  56. package/dungeons/vertical/community-schema.json +0 -579
  57. package/dungeons/vertical/crypto-schema.json +0 -546
  58. package/dungeons/vertical/dating-schema.json +0 -401
  59. package/dungeons/vertical/devtools-schema.json +0 -601
  60. package/dungeons/vertical/ecommerce-schema.json +0 -604
  61. package/dungeons/vertical/education-schema.json +0 -5686
  62. package/dungeons/vertical/fintech-schema.json +0 -630
  63. package/dungeons/vertical/fitness-schema.json +0 -530
  64. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  65. package/dungeons/vertical/gaming-schema.json +0 -2703
  66. package/dungeons/vertical/healthcare-schema.json +0 -549
  67. package/dungeons/vertical/insurance-application-schema.json +0 -485
  68. package/dungeons/vertical/logistics-schema.json +0 -574
  69. package/dungeons/vertical/marketplace-schema.json +0 -533
  70. package/dungeons/vertical/media-schema.json +0 -4749
  71. package/dungeons/vertical/real-estate-schema.json +0 -527
  72. package/dungeons/vertical/sass-schema.json +0 -3128
  73. package/dungeons/vertical/social-schema.json +0 -620
  74. package/dungeons/vertical/travel-schema.json +0 -580
@@ -16,7 +16,6 @@ import * as v from "ak-tools";
16
16
 
17
17
  dayjs.extend(utc);
18
18
  const chance = u.initChance(SEED);
19
-
20
19
  /** @typedef {import("../../types").Dungeon} Config */
21
20
 
22
21
  /*
@@ -50,109 +49,208 @@ const chance = u.initChance(SEED);
50
49
 
51
50
  /*
52
51
  * ═══════════════════════════════════════════════════════════════════════════════
53
- * ANALYTICS HOOKS (8 architected patterns)
52
+ * ANALYTICS HOOKS (9 hooks)
53
+ *
54
+ * Adds 9. ORDER LIFECYCLE TIME-TO-CONVERT: QuickBite+ 0.74x faster, Free 1.3x
55
+ * slower (funnel-post). Discover via order funnel median TTC by subscription_tier.
56
+ * NOTE (funnel-post measurement): visible only via Mixpanel funnel median TTC.
57
+ * Cross-event MIN→MIN SQL queries on raw events do NOT show this.
54
58
  * ═══════════════════════════════════════════════════════════════════════════════
55
59
  *
56
- * 1. LUNCH RUSH CONVERSION (funnel-pre)
57
- * Funnel conversion boosted during meal hours: lunch 1.4x, dinner 1.2x.
58
- * Mixpanel:
59
- * Funnels → "checkout started" → "order placed" → "order delivered"
60
- * Breakdown: "lunch_rush" expect ~84% vs ~60% baseline
61
- * Same funnel, breakdown: "dinner_rush" → expect ~72% vs ~60%
62
- *
63
- * 2. COUPON INJECTION (funnel-post)
64
- * Free-tier users get coupon_applied spliced into funnels 30% of the time.
65
- * Mixpanel:
66
- * • Insights → "coupon applied" → breakdown "coupon_injected"
67
- * ~30% of Free-tier coupons are injected
68
- * • Insights → "coupon applied" → breakdown "subscription_tier"
69
- * Free users have more coupon events
70
- *
71
- * 3. LATE NIGHT MUNCHIES (event)
72
- * 10PM–2AM: 70% American cuisine, 1.3x item prices, late_night_order=true.
73
- * Mixpanel:
74
- * Insights "restaurant viewed" breakdown "cuisine_type"
75
- * filter: late_night_order=true → ~70% American vs ~12% daytime
76
- * Insights "item added to cart" avg "item_price"
77
- * breakdown: "late_night_order" → 1.3x higher at night
60
+ * NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable only via
61
+ * behavioral cohorts or raw-prop breakdowns (HOD, day, segment).
62
+ *
63
+ * ───────────────────────────────────────────────────────────────────────────────
64
+ * 1. LUNCH/DINNER RUSH (everything)
65
+ * ───────────────────────────────────────────────────────────────────────────────
66
+ *
67
+ * PATTERN: 30% of "order delivered" events that fall outside meal-hour
68
+ * windows (11-13 UTC and 17-20 UTC) are dropped, depressing non-meal-time
69
+ * conversion. Mutation: event drop. Discover via order delivered HOD chart.
70
+ *
71
+ * HOW TO FIND IT IN MIXPANEL:
72
+ *
73
+ * Report 1: Order Delivered Volume by Hour of Day
74
+ * - Report type: Insights
75
+ * - Event: "order delivered"
76
+ * - Measure: Total
77
+ * - Breakdown: Hour of day
78
+ * - Expected: 11-13 and 17-20 dominate; off-hours visibly suppressed
79
+ *
80
+ * REAL-WORLD ANALOGUE: Meal-hour orders convert at higher rates.
81
+ *
82
+ * ───────────────────────────────────────────────────────────────────────────────
83
+ * 2. COUPON INJECTION (everything)
84
+ * ───────────────────────────────────────────────────────────────────────────────
85
+ *
86
+ * PATTERN: Free-tier users get extra "coupon applied" events cloned into the
87
+ * stream near checkout (30% chance per checkout). Cloned with unique offset
88
+ * timestamps. No flag.
89
+ *
90
+ * HOW TO FIND IT IN MIXPANEL:
91
+ *
92
+ * Report 1: Coupons per User by Tier
93
+ * - Report type: Insights
94
+ * - Event: "coupon applied"
95
+ * - Measure: Total per user
96
+ * - Breakdown: "subscription_tier"
97
+ * - Expected: Free ~ 1.3x QuickBite+
98
+ *
99
+ * REAL-WORLD ANALOGUE: Free-tier users are the target of coupon promos.
100
+ *
101
+ * ───────────────────────────────────────────────────────────────────────────────
102
+ * 3. LATE NIGHT MUNCHIES (everything)
103
+ * ───────────────────────────────────────────────────────────────────────────────
104
+ *
105
+ * PATTERN: 10PM-2AM UTC: 70% of "restaurant viewed" / "item added to cart"
106
+ * events get cuisine_type flipped to American, item_price bumped 1.3x.
107
+ * Mutates existing props. No flag — discover via HOD breakdown.
108
+ *
109
+ * HOW TO FIND IT IN MIXPANEL:
110
+ *
111
+ * Report 1: Cuisine Distribution by Hour of Day
112
+ * - Report type: Insights
113
+ * - Event: "restaurant viewed"
114
+ * - Measure: Total
115
+ * - Breakdown: "cuisine_type"
116
+ * - Filter: hour 22-02
117
+ * - Expected: American share spikes
118
+ *
119
+ * Report 2: Avg item_price by Hour of Day
120
+ * - Report type: Insights
121
+ * - Event: "item added to cart"
122
+ * - Measure: Average of "item_price"
123
+ * - Breakdown: Hour of day
124
+ * - Expected: 22-02 hours show ~ 1.3x baseline price
125
+ *
126
+ * REAL-WORLD ANALOGUE: Late-night ordering skews to fast food and impulse buys.
78
127
  *
128
+ * ───────────────────────────────────────────────────────────────────────────────
79
129
  * 4. RAINY WEEK SURGE (event + everything)
80
- * Days 20–27: delivery fees 2x, surge_pricing=true, 40% order duplication.
81
- * Mixpanel:
82
- * Insights (line) "order placed" daily visible spike days 20–27
83
- * Insights "order placed" avg "delivery_fee"
84
- * breakdown: "surge_pricing" 2x higher
130
+ * ───────────────────────────────────────────────────────────────────────────────
131
+ *
132
+ * PATTERN: Days 20-27, "order placed" delivery_fee doubled and 40% of those
133
+ * events get a duplicate cloned event with unique offset. No flag — discover
134
+ * via line chart by day on order placed volume + delivery_fee average.
135
+ *
136
+ * HOW TO FIND IT IN MIXPANEL:
137
+ *
138
+ * Report 1: Order Volume Over Time
139
+ * - Report type: Insights
140
+ * - Event: "order placed"
141
+ * - Measure: Total
142
+ * - Line chart by day
143
+ * - Expected: visible spike days 20-27
144
+ *
145
+ * Report 2: Avg delivery_fee Over Time
146
+ * - Report type: Insights
147
+ * - Event: "order placed"
148
+ * - Measure: Average of "delivery_fee"
149
+ * - Line chart by day
150
+ * - Expected: ~ 2x days 20-27
85
151
  *
152
+ * REAL-WORLD ANALOGUE: Weather-driven demand surge with surge pricing.
153
+ *
154
+ * ───────────────────────────────────────────────────────────────────────────────
86
155
  * 5. REFERRAL POWER USERS (everything)
87
- * Referred users: 2x reorders, food_rating 4–5 stars, referral_user=true.
88
- * Mixpanel:
89
- * Insights "reorder initiated" per user
90
- * breakdown: "referral_user" ~2x more reorders
91
- * Insights "order rated" avg "food_rating"
92
- * breakdown: "referral_user" → 4–5 vs ~3.5 baseline
156
+ * ───────────────────────────────────────────────────────────────────────────────
157
+ *
158
+ * PATTERN: Users with referral_code=true on account-created event get
159
+ * food_rating boosted to 4-5 and 50% of their reorder events cloned (unique
160
+ * offset). Mutates existing prop, no flag.
161
+ *
162
+ * HOW TO FIND IT IN MIXPANEL:
163
+ *
164
+ * Report 1: Reorders per User by Referral Cohort
165
+ * - Report type: Insights (with cohort)
166
+ * - Cohort A: users with account-created.referral_code=true
167
+ * - Cohort B: rest
168
+ * - Event: "reorder initiated"
169
+ * - Measure: Total per user
170
+ * - Expected: A ~ 1.5-2x B
93
171
  *
172
+ * REAL-WORLD ANALOGUE: Referred users tend to be more loyal.
173
+ *
174
+ * ───────────────────────────────────────────────────────────────────────────────
94
175
  * 6. TRIAL CONVERSION (everything)
95
- * Trial subs with 3+ orders in 14 days retained; others lose 60% of events.
96
- * Mixpanel:
97
- * Insights any event per user
98
- * breakdown: "trial_retained" sustained vs ~60% drop
99
- * • Retention → "subscription started" (trial=true) → "order placed"
100
- * sharp drop after day 14 for non-retained
101
- *
102
- * 7. SUPPORT TICKET CHURN (user)
103
- * 15% of users flagged is_high_risk=true with churn_risk_score 70–100.
104
- * Mixpanel:
105
- * Insights any event unique users
106
- * breakdown: user "is_high_risk" ~15% high-risk
107
- * • Insights → breakdown: user "churn_risk_score"
108
- * bimodal: 85% at 0–40, 15% at 70–100
109
- *
110
- * 8. FIRST ORDER BONUS (funnel-pre)
111
- * New users get 1.4x conversion boost on checkout→order funnel.
112
- * Mixpanel:
113
- * • Funnels → "checkout started" → "order placed" → "order delivered"
114
- * breakdown: "first_order_bonus" → 1.4x higher conversion
115
- * • Insights → "order placed" → breakdown "first_order_bonus"
116
- * → ~50% tagged (hash-based user split)
176
+ * ───────────────────────────────────────────────────────────────────────────────
177
+ *
178
+ * PATTERN: Users with subscription-started.trial=true who place <3 orders in
179
+ * first 14 days have 60% of post-day-14 events dropped. No flag.
180
+ *
181
+ * HOW TO FIND IT IN MIXPANEL:
182
+ *
183
+ * Report 1: Retention by Trial Order Count
184
+ * - Report type: Retention
185
+ * - Cohort A: trial users with >= 3 orders in first 14 days
186
+ * - Cohort B: trial users with < 3
187
+ * - Expected: B sharp activity drop after day 14
188
+ *
189
+ * REAL-WORLD ANALOGUE: Trial users who fail to activate churn fast.
117
190
  *
118
191
  * ───────────────────────────────────────────────────────────────────────────────
119
- * ADVANCED ANALYSIS IDEAS
192
+ * 7. FIRST ORDER BONUS (everything)
120
193
  * ───────────────────────────────────────────────────────────────────────────────
121
194
  *
122
- * Cross-hook patterns:
123
- * The Perfect Customer: referral (5) + trial retained (6) + lunch rush (1)
124
- * + low churn risk (7) exceptional LTV and retention
125
- * Rainy Night Double Whammy: late-night (3) + rainy week (4)
126
- * → compounded surge pricing?
127
- * Coupon-Driven Trial: injected coupons (2) → trial starts (6)?
128
- * • Referral + First Order: hooks 5 + 8 → highest conversion rates
129
- * Support and Churn: high-risk (7) + support tickets during rainy week (4)
130
- *
131
- * Cohort analysis:
132
- * Signup week (rainy week cohort behaves differently)
133
- * Referral vs organic lifecycle
134
- * • Free vs QuickBite+ across all metrics
135
- * City-level cuisine and ordering patterns
136
- *
137
- * Funnel analysis:
138
- * Onboarding: account created first restaurant view, by signup method
139
- * Order: checkout → delivery, by platform / tier / time of day
140
- * • Discovery: search type → conversion to ordering
195
+ * PATTERN: ~50% of users (deterministic by user_id hash) have 30% of their
196
+ * "order delivered" events dropped simulating that returning users convert
197
+ * worse than new users. No flag analyst sees segment-level conversion gap
198
+ * via cohort builder by hash bucket.
199
+ *
200
+ * HOW TO FIND IT IN MIXPANEL:
201
+ *
202
+ * Report 1: Order Delivered Conversion by First-Letter-Hash
203
+ * - Report type: Funnels
204
+ * - Steps: "checkout started" -> "order placed" -> "order delivered"
205
+ * - Breakdown: derived hash bucket on distinct_id
206
+ * - Expected: half of users show ~ 30% lower conversion on final step
207
+ *
208
+ * REAL-WORLD ANALOGUE: First-order promos lift new-user conversion.
209
+ *
210
+ * ───────────────────────────────────────────────────────────────────────────────
211
+ * 8. ORDER-COUNT MAGIC NUMBER (everything)
212
+ * ───────────────────────────────────────────────────────────────────────────────
213
+ *
214
+ * PATTERN: Users in the 4-8 order-placed sweet spot get +40% on order_total.
215
+ * Users with 9+ orders are over-engaged; 35% of their order-placed events
216
+ * drop. No flag — discover by binning users on order count.
217
+ *
218
+ * HOW TO FIND IT IN MIXPANEL:
219
+ *
220
+ * Report 1: Avg Order Total by Order-Count Bucket
221
+ * - Report type: Insights (with cohort)
222
+ * - Cohort A: users with 4-8 "order placed"
223
+ * - Cohort B: users with 0-3
224
+ * - Event: "order placed"
225
+ * - Measure: Average of "order_total"
226
+ * - Expected: A ~ 1.4x B
227
+ *
228
+ * Report 2: Orders per User on Heavy Orderers
229
+ * - Report type: Insights (with cohort)
230
+ * - Cohort C: users with >= 9 "order placed"
231
+ * - Cohort A: users with 4-8
232
+ * - Event: "order placed"
233
+ * - Measure: Total per user
234
+ * - Expected: C ~ 35% fewer orders per user vs A
235
+ *
236
+ * REAL-WORLD ANALOGUE: Engaged orderers lift basket size; over-orderers
237
+ * hit fatigue and slow down.
141
238
  *
142
239
  * ───────────────────────────────────────────────────────────────────────────────
143
240
  * EXPECTED METRICS SUMMARY
144
241
  * ───────────────────────────────────────────────────────────────────────────────
145
242
  *
146
243
  * Hook | Metric | Baseline | Hook Effect | Ratio
147
- * ──────────────────────|──────────────────────|──────────|─────────────|──────
148
- * Lunch Rush | Funnel conversion | 60% | 84% | 1.4x
149
- * Coupon Injection | Free user coupons | 0% | 30% | N/A
150
- * Late Night Munchies | American cuisine % | ~15% | 70% | ~4.7x
151
- * Rainy Week Surge | Order volume | 100% | 140% | 1.4x
152
- * Referral Power Users | Reorder frequency | 1x | 2x | 2.0x
153
- * Trial Conversion | Post-trial retention | 100% | 40% | 0.4x
154
- * Support Ticket Churn | High-risk users | 0% | 15% | N/A
155
- * First Order Bonus | New user conversion | 1x | 1.4x | 1.4x
244
+ * ──────────────────────|──────────────────────|----------|-------------|------
245
+ * Lunch/Dinner Rush | off-hour delivered | 1x | 0.7x | -30%
246
+ * Coupon Injection | Free user coupons/u | 1x | ~ 1.3x | 1.3x
247
+ * Late Night Munchies | American share 22-02 | ~ 15% | ~ 60% | 4x
248
+ * Rainy Week Surge | order vol days 20-27 | 1x | ~ 1.4x | 1.4x
249
+ * Referral Power Users | Reorders/user | 1x | ~ 1.5-2x | ~ 1.7x
250
+ * Trial Conversion | post-day-14 activity | 1x | ~ 0.4x | -60%
251
+ * First Order Bonus | returning conversion | 1x | 0.7x | -30%
252
+ * Order-Count Magic Num | sweet order_total | 1x | 1.4x | 1.4x
253
+ * Order-Count Magic Num | over orders/user | 1x | 0.65x | -35%
156
254
  */
157
255
 
158
256
  // Generate consistent IDs for lookup tables and event properties
@@ -165,7 +263,9 @@ const couponCodes = v.range(1, 51).map(n => `QUICK${v.uid(5).toUpperCase()}`);
165
263
  const config = {
166
264
  token,
167
265
  seed: SEED,
168
- numDays: num_days,
266
+ datasetStart: "2026-01-01T00:00:00Z",
267
+ datasetEnd: "2026-04-28T23:59:59Z",
268
+ // numDays: num_days,
169
269
  avgEventsPerUserPerDay: avg_events_per_user_per_day,
170
270
  numUsers: num_users,
171
271
  hasAnonIds: false,
@@ -268,7 +368,6 @@ const config = {
268
368
  properties: {
269
369
  "signup_method": ["email", "google", "apple", "facebook"],
270
370
  "referral_code": [false, false, true],
271
- "trial_retained": [false],
272
371
  }
273
372
  },
274
373
  {
@@ -287,7 +386,6 @@ const config = {
287
386
  ],
288
387
  "sort_by": ["recommended", "distance", "rating", "price"],
289
388
  "filter_applied": [false, false, false, true, true],
290
- "trial_retained": [false],
291
389
  }
292
390
  },
293
391
  {
@@ -308,8 +406,6 @@ const config = {
308
406
  "avg_rating": u.weighNumRange(1, 5, 0.8, 30),
309
407
  "delivery_time_est_mins": u.weighNumRange(15, 90, 1.2, 40),
310
408
  "price_tier": ["$", "$$", "$$$", "$$$$"],
311
- "late_night_order": [false],
312
- "trial_retained": [false],
313
409
  }
314
410
  },
315
411
  {
@@ -320,8 +416,6 @@ const config = {
320
416
  "item_category": ["entree", "appetizer", "drink", "dessert", "side"],
321
417
  "item_price": u.weighNumRange(3, 65, 1.0, 40),
322
418
  "customization_count": u.weighNumRange(0, 5, 1.5, 20),
323
- "late_night_order": [false],
324
- "trial_retained": [false],
325
419
  }
326
420
  },
327
421
  {
@@ -330,7 +424,6 @@ const config = {
330
424
  properties: {
331
425
  "item_id": itemIds,
332
426
  "removal_reason": ["changed_mind", "too_expensive", "substitution"],
333
- "trial_retained": [false],
334
427
  }
335
428
  },
336
429
  {
@@ -340,8 +433,6 @@ const config = {
340
433
  "coupon_code": couponCodes,
341
434
  "discount_type": ["percent", "flat", "free_delivery"],
342
435
  "discount_value": u.weighNumRange(5, 50, 1.2, 20),
343
- "coupon_injected": [false],
344
- "trial_retained": [false],
345
436
  }
346
437
  },
347
438
  {
@@ -351,7 +442,6 @@ const config = {
351
442
  "cart_total": u.weighNumRange(8, 150, 0.8, 40),
352
443
  "items_count": u.weighNumRange(1, 8, 1.2, 20),
353
444
  "delivery_address_saved": [false, false, false, true, true, true, true, true, true, true],
354
- "trial_retained": [false],
355
445
  }
356
446
  },
357
447
  {
@@ -363,9 +453,6 @@ const config = {
363
453
  "order_total": u.weighNumRange(10, 200, 0.8, 40),
364
454
  "tip_amount": u.weighNumRange(0, 30, 1.5, 20),
365
455
  "delivery_fee": u.weighNumRange(0, 12, 1.0, 20),
366
- "surge_pricing": [false],
367
- "rainy_week": [false],
368
- "trial_retained": [false],
369
456
  }
370
457
  },
371
458
  {
@@ -375,7 +462,6 @@ const config = {
375
462
  "order_id": orderIds,
376
463
  "order_status": ["confirmed", "preparing", "picked_up", "en_route", "delivered"],
377
464
  "eta_mins": u.weighNumRange(5, 60, 1.0, 30),
378
- "trial_retained": [false],
379
465
  }
380
466
  },
381
467
  {
@@ -385,10 +471,6 @@ const config = {
385
471
  "order_id": orderIds,
386
472
  "actual_delivery_mins": u.weighNumRange(12, 90, 1.0, 40),
387
473
  "on_time": [false, false, false, true, true, true, true, true, true, true],
388
- "lunch_rush": [false],
389
- "dinner_rush": [false],
390
- "first_order_bonus": [false],
391
- "trial_retained": [false],
392
474
  }
393
475
  },
394
476
  {
@@ -399,8 +481,6 @@ const config = {
399
481
  "food_rating": u.weighNumRange(1, 5, 0.8, 30),
400
482
  "delivery_rating": u.weighNumRange(1, 5, 0.8, 30),
401
483
  "would_reorder": [false, true, true],
402
- "referral_user": [false],
403
- "trial_retained": [false],
404
484
  }
405
485
  },
406
486
  {
@@ -414,7 +494,6 @@ const config = {
414
494
  ]),
415
495
  "results_count": u.weighNumRange(0, 50, 0.8, 30),
416
496
  "search_type": ["restaurant", "cuisine", "dish"],
417
- "trial_retained": [false],
418
497
  }
419
498
  },
420
499
  {
@@ -424,7 +503,6 @@ const config = {
424
503
  "promo_id": () => `promo_${v.uid(5)}`,
425
504
  "promo_type": ["banner", "push", "in_feed"],
426
505
  "promo_value": ["10%", "15%", "20%", "25%", "30%", "40%", "50%"],
427
- "trial_retained": [false],
428
506
  }
429
507
  },
430
508
  {
@@ -434,7 +512,6 @@ const config = {
434
512
  "plan": ["quickbite_plus_monthly", "quickbite_plus_monthly", "quickbite_plus_annual"],
435
513
  "price": [9.99, 9.99, 79.99],
436
514
  "trial": [true, false],
437
- "trial_retained": [false],
438
515
  }
439
516
  },
440
517
  {
@@ -443,7 +520,6 @@ const config = {
443
520
  properties: {
444
521
  "reason": ["too_expensive", "not_ordering_enough", "found_alternative", "bad_experience"],
445
522
  "months_subscribed": u.weighNumRange(1, 24, 1.5, 15),
446
- "trial_retained": [false],
447
523
  }
448
524
  },
449
525
  {
@@ -452,7 +528,6 @@ const config = {
452
528
  properties: {
453
529
  "issue_type": ["missing_item", "wrong_order", "late_delivery", "quality_issue", "refund_request"],
454
530
  "order_id": orderIds,
455
- "trial_retained": [false],
456
531
  }
457
532
  },
458
533
  {
@@ -461,8 +536,6 @@ const config = {
461
536
  properties: {
462
537
  "order_id": orderIds,
463
538
  "original_order_age_days": u.weighNumRange(1, 60, 1.5, 30),
464
- "referral_user": [false],
465
- "trial_retained": [false],
466
539
  }
467
540
  },
468
541
  ],
@@ -487,8 +560,6 @@ const config = {
487
560
  "avg_order_value": u.weighNumRange(15, 80, 0.8, 40),
488
561
  "orders_per_month": u.weighNumRange(1, 20, 1.5, 10),
489
562
  "favorite_restaurant_count": u.weighNumRange(1, 10),
490
- "is_high_risk": [false],
491
- "churn_risk_score": [0],
492
563
  "Platform": ["iOS", "Android", "Web"],
493
564
  "subscription_tier": ["Free", "Free", "Free", "Free", "QuickBite+"],
494
565
  "city": ["New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "San Francisco"],
@@ -518,135 +589,51 @@ const config = {
518
589
 
519
590
  lookupTables: [],
520
591
 
521
- /**
522
- * ARCHITECTED ANALYTICS HOOKS
523
- *
524
- * This hook function creates 8 deliberate patterns in the data:
525
- *
526
- * 1. LUNCH RUSH CONVERSION: Orders during lunch/dinner hours convert at higher rates
527
- * 2. COUPON INJECTION: Free-tier users get coupons spliced into funnels
528
- * 3. LATE NIGHT MUNCHIES: Late-night orders skew to fast food with inflated prices
529
- * 4. RAINY WEEK SURGE: Days 20-27 have surge pricing and doubled order volume
530
- * 5. REFERRAL POWER USERS: Referred users reorder more and rate higher
531
- * 6. TRIAL CONVERSION: Trial subscribers with 3+ early orders are retained
532
- * 7. SUPPORT TICKET CHURN: 15% of users flagged as high-risk churners
533
- * 8. FIRST ORDER BONUS: New users in the Order Completion funnel convert at 90%
534
- */
535
592
  hook: function (record, type, meta) {
536
- const NOW = dayjs();
537
- const DATASET_START = NOW.subtract(num_days, 'days');
538
- const RAINY_WEEK_START = DATASET_START.add(20, 'days');
539
- const RAINY_WEEK_END = DATASET_START.add(27, 'days');
540
-
541
- // ═══════════════════════════════════════════════════════════════════
542
- // HOOK 1: LUNCH RUSH CONVERSION (funnel-pre)
543
- // During lunch (11AM-1PM) and dinner (5PM-8PM), funnel conversion
544
- // rates are boosted to reflect real-world meal-time ordering surges.
545
- // ═══════════════════════════════════════════════════════════════════
546
- if (type === "funnel-pre") {
547
- if (meta && meta.firstEventTime) {
548
- const hour = dayjs.unix(meta.firstEventTime).hour();
549
-
550
- record.props = record.props || {};
551
-
552
- // Lunch rush: 11AM - 1PM
553
- if (hour >= 11 && hour <= 13) {
554
- record.props.lunch_rush = true;
555
- record.props.dinner_rush = false;
556
- }
557
- // Dinner rush: 5PM - 8PM
558
- else if (hour >= 17 && hour <= 20) {
559
- record.props.lunch_rush = false;
560
- record.props.dinner_rush = true;
561
- } else {
562
- record.props.lunch_rush = false;
563
- record.props.dinner_rush = false;
564
- }
565
- }
566
-
567
- // ═══════════════════════════════════════════════════════════════
568
- // HOOK 8: FIRST ORDER BONUS (funnel-pre)
569
- // New users (born in dataset) get a massive conversion boost on
570
- // the Order Completion funnel, simulating first-order promotions
571
- // and new-user incentives that drive initial purchases.
572
- // ═══════════════════════════════════════════════════════════════
573
- if (record.sequence &&
574
- record.sequence[0] === "checkout started" &&
575
- record.sequence[1] === "order placed") {
576
- record.props = record.props || {};
577
- // Use hash to deterministically assign ~50% of users as "new"
578
- const userId = meta && meta.user && (meta.user.distinct_id || String(meta.user));
579
- const isNewUser = userId && userId.charCodeAt(0) % 2 === 0;
580
- if (isNewUser) {
581
- record.props.first_order_bonus = true;
582
- } else {
583
- record.props.first_order_bonus = false;
593
+ // HOOK 9 (T2C): ORDER LIFECYCLE TIME-TO-CONVERT (funnel-post)
594
+ // QuickBite+ subscribers complete order funnels 1.35x faster
595
+ // (factor 0.74); Free users 1.3x slower (factor 1.3).
596
+ if (type === "funnel-post") {
597
+ const segment = meta?.profile?.subscription_tier;
598
+ if (Array.isArray(record) && record.length > 1) {
599
+ const factor = (
600
+ segment === "QuickBite+" ? 0.74 :
601
+ segment === "Free" ? 1.3 :
602
+ 1.0
603
+ );
604
+ if (factor !== 1.0) {
605
+ for (let i = 1; i < record.length; i++) {
606
+ const prev = dayjs(record[i - 1].time);
607
+ const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
608
+ record[i].time = prev.add(newGap, "milliseconds").toISOString();
609
+ }
584
610
  }
585
611
  }
586
612
  }
587
613
 
588
- // (Hook #2 coupon injection moved to everything hook where
589
- // meta.profile.subscription_tier is authoritative)
590
-
591
- // ═══════════════════════════════════════════════════════════════════
592
- // HOOK 3: LATE NIGHT MUNCHIES (event)
593
- // Between 10PM and 2AM, restaurant views and cart additions skew
594
- // heavily toward fast food (American cuisine) with inflated prices,
595
- // modeling the real-world late-night ordering pattern.
596
- // ═══════════════════════════════════════════════════════════════════
597
614
  if (type === "event") {
615
+ const datasetStart = dayjs.unix(meta.datasetStart);
616
+ const RAINY_WEEK_START = datasetStart.add(20, 'days');
617
+ const RAINY_WEEK_END = datasetStart.add(27, 'days');
598
618
  const EVENT_TIME = dayjs(record.time);
599
- const hour = EVENT_TIME.hour();
600
- const isLateNight = hour >= 22 || hour <= 2;
601
-
602
- if (record.event === "restaurant viewed" || record.event === "item added to cart") {
603
- if (isLateNight) {
604
- // 70% of late-night browsing/ordering is fast food (American)
605
- if (chance.bool({ likelihood: 70 })) {
606
- if (record.cuisine_type !== undefined) {
607
- record.cuisine_type = "American";
608
- }
609
- }
610
619
 
611
- // Boost item price by 1.3x (late-night surcharge effect)
612
- if (record.item_price !== undefined) {
613
- record.item_price = Math.round(record.item_price * 1.3 * 100) / 100;
614
- }
615
-
616
- record.late_night_order = true;
617
- } else {
618
- record.late_night_order = false;
619
- }
620
- }
621
-
622
- // ═══════════════════════════════════════════════════════════════
623
- // HOOK 4: RAINY WEEK SURGE (event)
624
- // During days 20-27, delivery fees double on order_placed events
625
- // and there is a 40% chance of duplicating the event, simulating
626
- // a weather-driven demand surge with surge pricing.
627
- // ═══════════════════════════════════════════════════════════════
620
+ // HOOK 4: RAINY WEEK SURGE — days 20-27, double delivery_fee on
621
+ // order placed events. Mutates existing prop. No flag.
628
622
  if (record.event === "order placed") {
629
623
  if (EVENT_TIME.isAfter(RAINY_WEEK_START) && EVENT_TIME.isBefore(RAINY_WEEK_END)) {
630
624
  record.delivery_fee = (record.delivery_fee || 5) * 2;
631
- record.surge_pricing = true;
632
- record.rainy_week = true;
633
- } else {
634
- record.surge_pricing = false;
635
625
  }
636
626
  }
637
627
  }
638
628
 
639
- // ═══════════════════════════════════════════════════════════════════
640
- // HOOK 5: REFERRAL POWER USERS (everything)
641
- // Users who signed up with a referral code get 2x more reorder
642
- // events and boosted food ratings. Referred users are more loyal
643
- // and satisfied, mirroring real referral program outcomes.
644
- // ═══════════════════════════════════════════════════════════════════
645
629
  if (type === "everything") {
630
+ const datasetStart = dayjs.unix(meta.datasetStart);
631
+ const RAINY_WEEK_START = datasetStart.add(20, 'days');
632
+ const RAINY_WEEK_END = datasetStart.add(27, 'days');
646
633
  const userEvents = record;
647
634
  const profile = meta.profile;
648
635
 
649
- // ── Stamp superProps from profile so they stay consistent per user ──
636
+ // Stamp superProps from profile so they stay consistent per user
650
637
  if (profile) {
651
638
  userEvents.forEach((event) => {
652
639
  if (profile.Platform !== undefined) event.Platform = profile.Platform;
@@ -655,10 +642,28 @@ const config = {
655
642
  });
656
643
  }
657
644
 
658
- // ── Hook #2: COUPON INJECTION (moved from funnel-post) ──
659
- // Free-tier users get coupon_applied events injected 30% of the
660
- // time near checkout events. Uses meta.profile.subscription_tier
661
- // which is authoritative here.
645
+ // HOOK 3: LATE NIGHT MUNCHIES 10PM-2AM UTC, 70% of restaurant
646
+ // views/cart additions get cuisine flipped to American, 1.3x
647
+ // item_price. Mutates existing props. No flag — discover via
648
+ // HOD breakdown on cuisine_type / item_price.
649
+ userEvents.forEach(e => {
650
+ if (e.event === "restaurant viewed" || e.event === "item added to cart") {
651
+ const hour = new Date(e.time).getUTCHours();
652
+ const isLateNight = hour >= 22 || hour <= 2;
653
+ if (isLateNight) {
654
+ if (e.cuisine_type !== undefined && chance.bool({ likelihood: 70 })) {
655
+ e.cuisine_type = "American";
656
+ }
657
+ if (e.item_price !== undefined) {
658
+ e.item_price = Math.round(e.item_price * 1.3 * 100) / 100;
659
+ }
660
+ }
661
+ }
662
+ });
663
+
664
+ // HOOK 2: COUPON INJECTION — Free-tier users get coupon applied
665
+ // events spliced into stream near checkout. Cloned from existing
666
+ // coupon-applied event with unique offset time. No flag.
662
667
  if (profile && profile.subscription_tier === "Free") {
663
668
  for (let i = userEvents.length - 1; i >= 1; i--) {
664
669
  const evt = userEvents[i];
@@ -681,143 +686,118 @@ const config = {
681
686
  coupon_code: chance.pickone(couponCodes),
682
687
  discount_type: chance.pickone(["percent", "flat", "free_delivery"]),
683
688
  discount_value: chance.integer({ min: 10, max: 30 }),
684
- coupon_injected: true,
685
689
  };
686
690
  userEvents.splice(i, 0, couponEvent);
687
691
  }
688
692
  }
689
693
  }
690
694
 
691
- // ── Bug 2 fix: Conversion filtering for lunch rush + first order ──
692
- // Hook 1: Non-lunch/dinner-hour events drop ~30% of final funnel step
693
- // Hook 8: Returning users (no first_order_bonus) → drop ~30% of final funnel step
695
+ // HOOK 1: LUNCH/DINNER RUSH drop 30% of order delivered events
696
+ // that fall outside the meal hours (11-13 UTC and 17-20 UTC).
697
+ // Discoverable via order delivered breakdown by HOD.
694
698
  for (let i = userEvents.length - 1; i >= 0; i--) {
695
699
  const event = userEvents[i];
696
700
  if (event.event === "order delivered") {
697
- // Hook 1: if not during lunch or dinner rush, drop 30%
698
- if (!event.lunch_rush && !event.dinner_rush && chance.bool({ likelihood: 30 })) {
701
+ const hour = new Date(event.time).getUTCHours();
702
+ const inRush = (hour >= 11 && hour <= 13) || (hour >= 17 && hour <= 20);
703
+ if (!inRush && chance.bool({ likelihood: 30 })) {
699
704
  userEvents.splice(i, 1);
700
- continue;
701
705
  }
702
- // Hook 8: returning users (no first_order_bonus) drop 30%
703
- if (event.first_order_bonus === false && chance.bool({ likelihood: 30 })) {
706
+ }
707
+ }
708
+
709
+ // HOOK 8: FIRST ORDER BONUS — hash-based ~50% of users (returning)
710
+ // drop 30% of order delivered events. New users keep all.
711
+ // Discover via cohort builder on hash bucket vs conversion.
712
+ const hashUser = userEvents[0] && userEvents[0].user_id;
713
+ const isNewUser = typeof hashUser === "string" && hashUser.charCodeAt(0) % 2 === 0;
714
+ if (!isNewUser) {
715
+ for (let i = userEvents.length - 1; i >= 0; i--) {
716
+ if (userEvents[i].event === "order delivered" && chance.bool({ likelihood: 30 })) {
704
717
  userEvents.splice(i, 1);
705
- continue;
706
718
  }
707
719
  }
708
720
  }
709
721
 
710
- // First pass: identify user patterns
722
+ // First pass: identify behavioral patterns (no flags written)
711
723
  let isReferralUser = false;
712
724
  let hasTrialSubscription = false;
713
725
  let earlyOrderCount = 0;
714
- let firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
726
+ let orderPlacedCount = 0;
727
+ const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
715
728
 
716
729
  userEvents.forEach((event) => {
717
730
  const eventTime = dayjs(event.time);
718
731
  const daysSinceStart = firstEventTime ? eventTime.diff(firstEventTime, 'days', true) : 0;
719
-
720
- // Hook #5: Track referral users
721
- if (event.event === "account created" && event.referral_code === true) {
722
- isReferralUser = true;
723
- }
724
-
725
- // Hook #6: Track trial subscribers and early orders
726
- if (event.event === "subscription started" && event.trial === true) {
727
- hasTrialSubscription = true;
728
- }
729
- if (event.event === "order placed" && daysSinceStart <= 14) {
730
- earlyOrderCount++;
732
+ if (event.event === "account created" && event.referral_code === true) isReferralUser = true;
733
+ if (event.event === "subscription started" && event.trial === true) hasTrialSubscription = true;
734
+ if (event.event === "order placed") {
735
+ orderPlacedCount++;
736
+ if (daysSinceStart <= 14) earlyOrderCount++;
731
737
  }
732
738
  });
733
739
 
734
- // Second pass: apply referral power user effects
740
+ // HOOK 5: REFERRAL POWER USERS boost food rating to 4-5,
741
+ // clone reorder events. No flag.
735
742
  userEvents.forEach((event, idx) => {
736
- if (event.event === "order rated") {
737
- if (isReferralUser) {
738
- event.food_rating = chance.integer({ min: 4, max: 5 });
739
- event.referral_user = true;
740
- } else {
741
- event.referral_user = false;
742
- }
743
+ if (isReferralUser && event.event === "order rated") {
744
+ event.food_rating = chance.integer({ min: 4, max: 5 });
743
745
  }
744
746
  if (isReferralUser && event.event === "reorder initiated" && chance.bool({ likelihood: 50 })) {
745
747
  const eventTime = dayjs(event.time);
746
- const extraReorder = {
748
+ userEvents.splice(idx + 1, 0, {
747
749
  ...event,
748
750
  time: eventTime.add(chance.integer({ min: 1, max: 7 }), 'days').toISOString(),
749
751
  user_id: event.user_id,
750
752
  order_id: chance.pickone(orderIds),
751
753
  original_order_age_days: chance.integer({ min: 3, max: 30 }),
752
- referral_user: true,
753
- };
754
- userEvents.splice(idx + 1, 0, extraReorder);
754
+ });
755
755
  }
756
756
  });
757
757
 
758
- // ═══════════════════════════════════════════════════════════════
759
- // HOOK 6: TRIAL CONVERSION (everything)
760
- // Trial subscribers who place 3+ orders in their first 14 days
761
- // are retained. Those with fewer orders churn: 60% of their
762
- // events after day 14 are removed, simulating subscription
763
- // abandonment after a failed trial experience.
764
- // ═══════════════════════════════════════════════════════════════
765
- if (hasTrialSubscription) {
758
+ // HOOK 6: TRIAL CONVERSION — trial subs with <3 early orders
759
+ // drop 60% of post-day-14 events. No flag.
760
+ if (hasTrialSubscription && earlyOrderCount < 3 && chance.bool({ likelihood: 60 })) {
766
761
  const trialCutoff = firstEventTime ? firstEventTime.add(14, 'days') : null;
767
-
768
- if (earlyOrderCount >= 3) {
769
- // Retained: mark events as trial_retained
770
- userEvents.forEach((event) => {
771
- event.trial_retained = true;
772
- });
773
- } else {
774
- // Churned: remove 60% of events after day 14
775
- userEvents.forEach((event) => {
776
- event.trial_retained = false;
777
- });
762
+ if (trialCutoff) {
778
763
  for (let i = userEvents.length - 1; i >= 0; i--) {
779
- const evt = userEvents[i];
780
- if (trialCutoff && dayjs(evt.time).isAfter(trialCutoff)) {
781
- if (chance.bool({ likelihood: 60 })) {
782
- userEvents.splice(i, 1);
783
- }
764
+ if (dayjs(userEvents[i].time).isAfter(trialCutoff)) {
765
+ userEvents.splice(i, 1);
784
766
  }
785
767
  }
786
768
  }
787
769
  }
788
770
 
789
- // ═══════════════════════════════════════════════════════════════
790
- // HOOK 4: RAINY WEEK SURGE - duplicate order events (everything)
791
- // Events tagged surge_pricing=true in the event hook get a 40%
792
- // chance of duplication here, creating visible demand spikes.
793
- // ═══════════════════════════════════════════════════════════════
771
+ // HOOK 4 (cont): RAINY WEEK SURGE — duplicate 40% of order placed
772
+ // events that fall in the rainy window. Cloned with unique offset.
794
773
  const rainyDuplicates = [];
795
774
  userEvents.forEach((event) => {
796
- if (event.surge_pricing === true && event.event === "order placed" && chance.bool({ likelihood: 40 })) {
797
- const dup = JSON.parse(JSON.stringify(event));
798
- dup.time = dayjs(event.time).add(chance.integer({ min: 5, max: 60 }), 'minutes').toISOString();
799
- dup.rainy_week = true;
800
- rainyDuplicates.push(dup);
775
+ if (event.event === "order placed") {
776
+ const t = dayjs(event.time);
777
+ if (t.isAfter(RAINY_WEEK_START) && t.isBefore(RAINY_WEEK_END) && chance.bool({ likelihood: 40 })) {
778
+ const dup = JSON.parse(JSON.stringify(event));
779
+ dup.time = t.add(chance.integer({ min: 5, max: 60 }), 'minutes').toISOString();
780
+ rainyDuplicates.push(dup);
781
+ }
801
782
  }
802
783
  });
803
- if (rainyDuplicates.length > 0) {
804
- userEvents.push(...rainyDuplicates);
805
- }
806
- }
807
-
808
- // ═══════════════════════════════════════════════════════════════════
809
- // HOOK 7: SUPPORT TICKET CHURN (user)
810
- // 15% of users are flagged as high-risk with elevated churn scores.
811
- // The remaining users get low churn scores. This creates a clear
812
- // segmentation opportunity for proactive retention campaigns.
813
- // ═══════════════════════════════════════════════════════════════════
814
- if (type === "user") {
815
- if (chance.bool({ likelihood: 15 })) {
816
- record.is_high_risk = true;
817
- record.churn_risk_score = chance.integer({ min: 70, max: 100 });
818
- } else {
819
- record.is_high_risk = false;
820
- record.churn_risk_score = chance.integer({ min: 0, max: 40 });
784
+ if (rainyDuplicates.length > 0) userEvents.push(...rainyDuplicates);
785
+
786
+ // HOOK 9: ORDER-COUNT MAGIC NUMBER (no flags)
787
+ // Sweet 4-8 orders → +40% on order_total. Over 9+ → drop 35% of
788
+ // order placed events (oversaturated; analyst sees inverted-U).
789
+ if (orderPlacedCount >= 4 && orderPlacedCount <= 8) {
790
+ userEvents.forEach(e => {
791
+ if (e.event === "order placed" && typeof e.order_total === "number") {
792
+ e.order_total = Math.round(e.order_total * 1.4);
793
+ }
794
+ });
795
+ } else if (orderPlacedCount >= 9) {
796
+ userEvents.forEach(e => {
797
+ if (e.event === "order placed" && typeof e.order_total === "number") {
798
+ e.order_total = Math.round(e.order_total * 0.65);
799
+ }
800
+ });
821
801
  }
822
802
  }
823
803