@ak--47/dungeon-master 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +7 -7
  2. package/dungeons/technical/ad-spend-schema.json +128 -0
  3. package/dungeons/technical/ad-spend.js +116 -0
  4. package/dungeons/technical/anonymous-users-schema.json +92 -0
  5. package/dungeons/technical/anonymous-users.js +102 -0
  6. package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
  7. package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +12 -5
  8. package/dungeons/technical/experiments-schema.json +203 -0
  9. package/dungeons/technical/experiments.js +168 -0
  10. package/dungeons/technical/foobar-schema.json +362 -0
  11. package/dungeons/{foobar.js → technical/foobar.js} +3 -3
  12. package/dungeons/technical/group-analytics-schema.json +241 -0
  13. package/dungeons/technical/group-analytics.js +151 -0
  14. package/dungeons/technical/mirror-strategies-schema.json +84 -0
  15. package/dungeons/technical/mirror-strategies.js +126 -0
  16. package/dungeons/technical/nested-objects-schema.json +145 -0
  17. package/dungeons/technical/nested-objects.js +207 -0
  18. package/dungeons/technical/retention-cadence-schema.json +37 -0
  19. package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +2 -3
  20. package/dungeons/technical/sanity-schema.json +185 -0
  21. package/dungeons/{sanity.js → technical/sanity.js} +4 -2
  22. package/dungeons/technical/scale-test-schema.json +70 -0
  23. package/dungeons/technical/scale-test.js +90 -0
  24. package/dungeons/technical/scd-schema.json +467 -0
  25. package/dungeons/{scd.js → technical/scd.js} +20 -10
  26. package/dungeons/technical/simple-schema.json +362 -0
  27. package/dungeons/{simple.js → technical/simple.js} +7 -7
  28. package/dungeons/technical/simplest-schema.json +724 -0
  29. package/dungeons/{simplest.js → technical/simplest.js} +7 -7
  30. package/dungeons/technical/text-generation-schema.json +1062 -0
  31. package/dungeons/{text-generation.js → technical/text-generation.js} +24 -11
  32. package/dungeons/vertical/community-schema.json +579 -0
  33. package/dungeons/vertical/community.js +794 -0
  34. package/dungeons/vertical/devtools-schema.json +601 -0
  35. package/dungeons/vertical/devtools.js +843 -0
  36. package/dungeons/vertical/ecommerce-schema.json +604 -0
  37. package/dungeons/{ecommerce.js → vertical/ecommerce.js} +43 -5
  38. package/dungeons/vertical/education-schema.json +5686 -0
  39. package/dungeons/{education.js → vertical/education.js} +70 -39
  40. package/dungeons/vertical/fintech-schema.json +630 -0
  41. package/dungeons/{fintech.js → vertical/fintech.js} +107 -56
  42. package/dungeons/vertical/fitness-schema.json +530 -0
  43. package/dungeons/vertical/fitness.js +808 -0
  44. package/dungeons/vertical/food-delivery-schema.json +36728 -0
  45. package/dungeons/{food.js → vertical/food-delivery.js} +67 -26
  46. package/dungeons/vertical/gaming-schema.json +438 -0
  47. package/dungeons/{gaming.js → vertical/gaming.js} +29 -16
  48. package/dungeons/vertical/healthcare-schema.json +549 -0
  49. package/dungeons/vertical/healthcare.js +775 -0
  50. package/dungeons/vertical/insurance-application-schema.json +485 -0
  51. package/dungeons/{insurance-application.js → vertical/insurance-application.js} +41 -31
  52. package/dungeons/vertical/logistics-schema.json +574 -0
  53. package/dungeons/vertical/logistics.js +799 -0
  54. package/dungeons/vertical/marketplace-schema.json +533 -0
  55. package/dungeons/vertical/marketplace.js +850 -0
  56. package/dungeons/vertical/media-schema.json +4749 -0
  57. package/dungeons/{media.js → vertical/media.js} +103 -19
  58. package/dungeons/vertical/rpg-schema.json +2491 -0
  59. package/dungeons/{rpg.js → vertical/rpg.js} +87 -60
  60. package/dungeons/vertical/sass-schema.json +3128 -0
  61. package/dungeons/{sass.js → vertical/sass.js} +65 -26
  62. package/dungeons/vertical/social-schema.json +620 -0
  63. package/dungeons/{social.js → vertical/social.js} +76 -17
  64. package/dungeons/vertical/travel-schema.json +580 -0
  65. package/dungeons/vertical/travel.js +742 -0
  66. package/index.js +20 -16
  67. package/lib/core/config-validator.js +286 -1
  68. package/lib/core/context.js +16 -5
  69. package/lib/core/storage.js +2 -0
  70. package/lib/generators/adspend.js +3 -3
  71. package/lib/generators/events.js +141 -14
  72. package/lib/generators/funnels.js +67 -7
  73. package/lib/generators/text.js +2 -2
  74. package/lib/orchestrators/user-loop.js +444 -22
  75. package/lib/templates/soup-presets.js +2 -2
  76. package/lib/utils/utils.js +4 -5
  77. package/package.json +4 -3
  78. package/scripts/dungeon-to-json.mjs +163 -0
  79. package/scripts/extract-dungeon-schema.mjs +343 -0
  80. package/scripts/json-to-dungeon.mjs +200 -0
  81. package/scripts/run-dungeon.mjs +76 -0
  82. package/scripts/run-many.mjs +118 -0
  83. package/scripts/verify-runner.mjs +42 -0
  84. package/types.d.ts +316 -1
  85. package/dungeons/array-of-object-lookup-schema.json +0 -327
  86. package/dungeons/ecommerce-schema.json +0 -462
  87. package/dungeons/education-schema.json +0 -2409
  88. package/dungeons/fintech-schema.json +0 -14034
  89. package/dungeons/foobar-schema.json +0 -403
  90. package/dungeons/food-delivery-schema.json +0 -192
  91. package/dungeons/food-delivery.js +0 -602
  92. package/dungeons/food-schema.json +0 -1152
  93. package/dungeons/gaming-schema.json +0 -1270
  94. package/dungeons/insurance-application-schema.json +0 -204
  95. package/dungeons/media-schema.json +0 -906
  96. package/dungeons/retention-cadence-schema.json +0 -78
  97. package/dungeons/rpg-schema.json +0 -4526
  98. package/dungeons/sanity-schema.json +0 -255
  99. package/dungeons/sass-schema.json +0 -1291
  100. package/dungeons/scd-schema.json +0 -919
  101. package/dungeons/simple-schema.json +0 -608
  102. package/dungeons/simplest-schema.json +0 -1418
  103. package/dungeons/social-schema.json +0 -1118
  104. package/dungeons/text-generation-schema.json +0 -3096
@@ -0,0 +1,808 @@
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
+
6
+ const SEED = "dm4-fitness";
7
+ dayjs.extend(utc);
8
+ const chance = u.initChance(SEED);
9
+ const num_users = 5_000;
10
+ const days = 100;
11
+ const NOW = dayjs();
12
+ const DATASET_START = NOW.subtract(days, "days");
13
+
14
+ /** @typedef {import("../../types").Dungeon} Config */
15
+
16
+ /**
17
+ * ═══════════════════════════════════════════════════════════════
18
+ * DATASET OVERVIEW
19
+ * ═══════════════════════════════════════════════════════════════
20
+ *
21
+ * FitQuest — a fitness & wellness app for workout tracking, meal
22
+ * planning, social fitness challenges, and AI coaching.
23
+ *
24
+ * - 5,000 users over 100 days, ~600K events
25
+ * - 5 personas: athlete (10%), casual_exerciser (40%),
26
+ * new_year_resolver (25%), social_motivator (15%), coach (10%)
27
+ * - Core loop: sign up → plan workout → complete workout → track progress
28
+ * - Revenue: free, monthly ($12.99, 7-day trial), annual ($99.99), family ($149.99)
29
+ *
30
+ * Advanced Features:
31
+ * - Personas: 5 archetypes with distinct engagement and churn profiles
32
+ * - Subscription: 4-tier revenue lifecycle with trial conversion
33
+ * - Attribution: 5 campaign sources with persona biases
34
+ * - Engagement Decay: step model with 30-day half-life
35
+ * - Features: ai_coach (day 35) and group_challenges (day 55)
36
+ *
37
+ * Key entities:
38
+ * - workout_type: strength/cardio/yoga/hiit/running/cycling
39
+ * - fitness_level: beginner/intermediate/advanced/expert
40
+ * - coaching_mode: self_guided vs ai_assisted (driven by Feature rollout)
41
+ * - challenge_mode: solo vs group (driven by Feature rollout)
42
+ */
43
+
44
+ /**
45
+ * ═══════════════════════════════════════════════════════════════
46
+ * ANALYTICS HOOKS (8 hooks)
47
+ * ═══════════════════════════════════════════════════════════════
48
+ *
49
+ * ───────────────────────────────────────────────────────────────
50
+ * 1. MORNING WORKOUT BOOST (event hook)
51
+ * ───────────────────────────────────────────────────────────────
52
+ *
53
+ * PATTERN: Workouts completed between 5AM-9AM get 1.3x
54
+ * calories_burned. Simulates the metabolic boost from fasted
55
+ * morning exercise.
56
+ *
57
+ * HOW TO FIND IT IN MIXPANEL:
58
+ *
59
+ * Report 1: Morning vs Non-Morning Calorie Burn
60
+ * • Report type: Insights
61
+ * • Event: "workout completed"
62
+ * • Measure: Average of "calories_burned"
63
+ * • Breakdown: "is_morning_workout"
64
+ * • Expected: is_morning_workout=true should show ~1.3x avg
65
+ * calories vs false (true ≈ 390, false ≈ 300)
66
+ *
67
+ * REAL-WORLD ANALOGUE: Fitness apps promote morning workouts
68
+ * as more effective for fat loss, driving engagement.
69
+ *
70
+ * ───────────────────────────────────────────────────────────────
71
+ * 2. POST-LAUNCH AI COACHING LIFT (event hook)
72
+ * ───────────────────────────────────────────────────────────────
73
+ *
74
+ * PATTERN: After day 35 (ai_coach feature launch), workouts
75
+ * with coaching_mode="ai_assisted" get 1.2x duration_minutes.
76
+ * AI coaching helps users push through longer sessions.
77
+ *
78
+ * HOW TO FIND IT IN MIXPANEL:
79
+ *
80
+ * Report 1: AI Coaching Duration Lift
81
+ * • Report type: Insights
82
+ * • Event: "workout completed"
83
+ * • Measure: Average of "duration_minutes"
84
+ * • Breakdown: "coaching_mode"
85
+ * • Filter: time after day 35
86
+ * • Expected: ai_assisted ≈ 48 min vs self_guided ≈ 40 min
87
+ *
88
+ * Report 2: AI Coaching Adoption Over Time
89
+ * • Report type: Insights
90
+ * • Event: "workout completed"
91
+ * • Measure: Total
92
+ * • Breakdown: "coaching_mode"
93
+ * • Line chart by week
94
+ * • Expected: ai_assisted grows from 0 after day 35
95
+ *
96
+ * REAL-WORLD ANALOGUE: AI-powered coaching features increase
97
+ * session duration as users get real-time form and pacing guidance.
98
+ *
99
+ * ───────────────────────────────────────────────────────────────
100
+ * 3. STREAK RETENTION (everything hook)
101
+ * ───────────────────────────────────────────────────────────────
102
+ *
103
+ * PATTERN: Users with >10 workout events get streak_days set
104
+ * to their actual workout count on their profile, and receive
105
+ * cloned "achievement unlocked" events for milestone streaks.
106
+ *
107
+ * HOW TO FIND IT IN MIXPANEL:
108
+ *
109
+ * Report 1: Achievement Events by Workout Volume
110
+ * • Report type: Insights
111
+ * • Event: "achievement unlocked"
112
+ * • Measure: Total per user
113
+ * • Breakdown: user property "segment"
114
+ * • Expected: athlete and coach segments show 2-3x more
115
+ * achievements than casual and resolver segments
116
+ *
117
+ * Report 2: Streak Days Distribution
118
+ * • Report type: Insights
119
+ * • Event: "profile updated"
120
+ * • Measure: Average of user property "streak_days"
121
+ * • Breakdown: user property "segment"
122
+ * • Expected: athlete/coach ≈ 30-50 streaks, casual ≈ 10-15
123
+ *
124
+ * REAL-WORLD ANALOGUE: Gamification streaks are the #1 retention
125
+ * driver in fitness apps — users who hit milestones stay longer.
126
+ *
127
+ * ───────────────────────────────────────────────────────────────
128
+ * 4. SOCIAL CHALLENGE COMPLETION (everything hook)
129
+ * ───────────────────────────────────────────────────────────────
130
+ *
131
+ * PATTERN: Users with "friend added" events complete 1.5x more
132
+ * "challenge completed" events (cloned from existing ones).
133
+ * Social users are more accountable.
134
+ *
135
+ * HOW TO FIND IT IN MIXPANEL:
136
+ *
137
+ * Report 1: Challenge Completion Rate by Social Activity
138
+ * • Report type: Insights
139
+ * • Event: "challenge completed"
140
+ * • Measure: Total per user
141
+ * • Filter: users who did "friend added" at least once
142
+ * • Compare to: users who never did "friend added"
143
+ * • Expected: social users ≈ 1.5x more challenge completions
144
+ *
145
+ * REAL-WORLD ANALOGUE: Social accountability is a proven
146
+ * motivator — users with friends complete more challenges.
147
+ *
148
+ * ───────────────────────────────────────────────────────────────
149
+ * 5. RESOLVER CHURN CLIFF (everything hook)
150
+ * ───────────────────────────────────────────────────────────────
151
+ *
152
+ * PATTERN: Users in the "resolver" segment with <8 events
153
+ * lose 70% of their events after day 14. Simulates the classic
154
+ * New Year's resolution drop-off.
155
+ *
156
+ * HOW TO FIND IT IN MIXPANEL:
157
+ *
158
+ * Report 1: Resolver Retention Drop
159
+ * • Report type: Insights
160
+ * • Event: All events
161
+ * • Measure: Total
162
+ * • Filter: segment = "resolver"
163
+ * • Line chart by week
164
+ * • Expected: Sharp cliff after week 2, ~70% drop in volume
165
+ *
166
+ * Report 2: Segment Retention Comparison
167
+ * • Report type: Retention
168
+ * • Starting event: "account created"
169
+ * • Return event: Any active event
170
+ * • Breakdown: user property "segment"
171
+ * • Expected: resolver retention drops to <30% by week 3
172
+ *
173
+ * REAL-WORLD ANALOGUE: 80% of New Year's gym memberships are
174
+ * abandoned by mid-February — the "resolution cliff."
175
+ *
176
+ * ───────────────────────────────────────────────────────────────
177
+ * 6. COACH SESSION QUALITY (everything hook)
178
+ * ───────────────────────────────────────────────────────────────
179
+ *
180
+ * PATTERN: Users with "coach session" events get higher
181
+ * satisfaction_score (4.0-5.0) on those events. Coached users
182
+ * rate their experience higher.
183
+ *
184
+ * HOW TO FIND IT IN MIXPANEL:
185
+ *
186
+ * Report 1: Satisfaction by Session Type
187
+ * • Report type: Insights
188
+ * • Event: "coach session"
189
+ * • Measure: Average of "satisfaction_score"
190
+ * • Expected: ≈ 4.3 avg (vs baseline 3.0 for non-coached)
191
+ *
192
+ * REAL-WORLD ANALOGUE: Personal coaching sessions have higher
193
+ * satisfaction scores because of personalized attention.
194
+ *
195
+ * ───────────────────────────────────────────────────────────────
196
+ * 7. COACH PROFILE ENRICHMENT (user hook)
197
+ * ───────────────────────────────────────────────────────────────
198
+ *
199
+ * PATTERN: Users in the "coach" segment get total_workouts
200
+ * boosted to 200-500 and streak_days to 60-365. Coaches are
201
+ * power users who lead by example.
202
+ *
203
+ * HOW TO FIND IT IN MIXPANEL:
204
+ *
205
+ * Report 1: Workout Volume by Segment
206
+ * • Report type: Insights
207
+ * • Event: "workout completed"
208
+ * • Measure: Average of user property "total_workouts"
209
+ * • Breakdown: user property "segment"
210
+ * • Expected: coach ≈ 350 vs athlete ≈ 0 (default) vs casual ≈ 0
211
+ *
212
+ * Report 2: Streak Distribution by Segment
213
+ * • Report type: Insights
214
+ * • Measure: Average of user property "streak_days"
215
+ * • Breakdown: user property "segment"
216
+ * • Expected: coach ≈ 200 vs others ≈ 0 (pre-hook default)
217
+ *
218
+ * REAL-WORLD ANALOGUE: Fitness coaches maintain extreme workout
219
+ * consistency to build credibility with their clients.
220
+ *
221
+ * ───────────────────────────────────────────────────────────────
222
+ * 8. ANNUAL SUBSCRIBER WORKOUT FUNNEL LIFT (funnel-pre hook)
223
+ * ───────────────────────────────────────────────────────────────
224
+ *
225
+ * PATTERN: Users with subscription_tier "annual" or "family"
226
+ * get 1.4x conversion rate through the Workout Loop funnel.
227
+ * Annual commitment correlates with follow-through.
228
+ *
229
+ * HOW TO FIND IT IN MIXPANEL:
230
+ *
231
+ * Report 1: Workout Funnel Conversion by Tier
232
+ * • Report type: Funnels
233
+ * • Steps: "workout planned" → "workout completed" → "progress checked"
234
+ * • Breakdown: "subscription_tier" (superProp)
235
+ * • Expected: annual ≈ 63% vs free ≈ 45% conversion
236
+ *
237
+ * REAL-WORLD ANALOGUE: Annual gym memberships have higher
238
+ * utilization — sunk cost + commitment drives consistency.
239
+ *
240
+ * ═══════════════════════════════════════════════════════════════
241
+ * EXPECTED METRICS SUMMARY
242
+ * ═══════════════════════════════════════════════════════════════
243
+ *
244
+ * Hook | Metric | Baseline | Effect | Ratio
245
+ * ────────────────────────────|─────────────────────|──────────|─────────|──────
246
+ * Morning Workout Boost | calories_burned | 300 | 390 | 1.3x
247
+ * AI Coaching Lift | duration_minutes | 40 | 48 | 1.2x
248
+ * Streak Retention | achievements/user | 1 | 2-3 | 2-3x
249
+ * Social Challenge Completion | challenges/user | 1 | 1.5 | 1.5x
250
+ * Resolver Churn Cliff | events after day 14 | 100% | 30% | 0.3x
251
+ * Coach Session Quality | satisfaction_score | 3.0 | 4.3 | 1.4x
252
+ * Coach Profile Enrichment | total_workouts | 0 | 350 | N/A
253
+ * Annual Funnel Lift | funnel conversion | 45% | 63% | 1.4x
254
+ */
255
+
256
+ /** @type {Config} */
257
+ const config = {
258
+ token: "",
259
+ seed: SEED,
260
+ numDays: days,
261
+ numEvents: num_users * 120,
262
+ numUsers: num_users,
263
+ hasAnonIds: false,
264
+ hasSessionIds: true,
265
+ format: "json",
266
+ gzip: true,
267
+ alsoInferFunnels: false,
268
+ hasLocation: true,
269
+ hasAndroidDevices: true,
270
+ hasIOSDevices: true,
271
+ hasDesktopDevices: false,
272
+ hasBrowser: false,
273
+ hasCampaigns: false,
274
+ isAnonymous: false,
275
+ hasAdSpend: false,
276
+ percentUsersBornInDataset: 35,
277
+ hasAvatar: true,
278
+ concurrency: 1,
279
+ writeToDisk: false,
280
+ scdProps: {
281
+ fitness_level: {
282
+ values: ["beginner", "intermediate", "advanced", "elite"],
283
+ frequency: "month",
284
+ timing: "fuzzy",
285
+ max: 8
286
+ }
287
+ },
288
+ mirrorProps: {},
289
+ lookupTables: [],
290
+
291
+ // ── Events (18) ──────────────────────────────────────────
292
+ events: [
293
+ {
294
+ event: "account created",
295
+ weight: 1,
296
+ isFirstEvent: true,
297
+ properties: {
298
+ referral_source: ["organic", "friend_invite", "app_store", "social_media", "search"],
299
+ },
300
+ },
301
+ {
302
+ event: "workout completed",
303
+ weight: 8,
304
+ properties: {
305
+ duration_minutes: u.weighNumRange(10, 90, 0.5, 40),
306
+ calories_burned: u.weighNumRange(50, 800, 0.4, 300),
307
+ heart_rate_avg: u.weighNumRange(80, 185, 0.5, 130),
308
+ is_morning_workout: [false],
309
+ satisfaction_score: u.weighNumRange(1, 5, 0.7, 3),
310
+ coaching_mode: ["self_guided"],
311
+ challenge_mode: ["solo"],
312
+ },
313
+ },
314
+ {
315
+ event: "workout planned",
316
+ weight: 6,
317
+ properties: {
318
+ planned_duration_minutes: u.weighNumRange(15, 90, 0.5, 45),
319
+ day_of_week: ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"],
320
+ coaching_mode: ["self_guided"],
321
+ },
322
+ },
323
+ {
324
+ event: "meal logged",
325
+ weight: 7,
326
+ properties: {
327
+ meal_type: ["breakfast", "lunch", "dinner", "snack"],
328
+ calories: u.weighNumRange(50, 1200, 0.5, 400),
329
+ protein_g: u.weighNumRange(0, 60, 0.5, 20),
330
+ meal_quality: ["healthy", "healthy", "balanced", "balanced", "indulgent"],
331
+ },
332
+ },
333
+ {
334
+ event: "challenge joined",
335
+ weight: 3,
336
+ properties: {
337
+ challenge_type: ["steps", "calories", "streak", "strength", "team_relay"],
338
+ duration_days: [7, 14, 21, 30],
339
+ participants: u.weighNumRange(2, 50, 0.3, 10),
340
+ challenge_mode: ["solo"],
341
+ },
342
+ },
343
+ {
344
+ event: "challenge completed",
345
+ weight: 2,
346
+ properties: {
347
+ challenge_type: ["steps", "calories", "streak", "strength", "team_relay"],
348
+ final_rank: u.weighNumRange(1, 50, 0.3, 10),
349
+ completion_pct: u.weighNumRange(50, 100, 0.7, 85),
350
+ challenge_mode: ["solo"],
351
+ },
352
+ },
353
+ {
354
+ event: "achievement unlocked",
355
+ weight: 2,
356
+ properties: {
357
+ achievement_type: ["streak_milestone", "weight_goal", "distance_record", "calories_target", "social_champion", "first_workout"],
358
+ streak_days_at_unlock: u.weighNumRange(1, 100, 0.3, 10),
359
+ },
360
+ },
361
+ {
362
+ event: "friend added",
363
+ weight: 2,
364
+ properties: {
365
+ source: ["search", "contacts", "challenge", "suggestion", "qr_code"],
366
+ },
367
+ },
368
+ {
369
+ event: "leaderboard viewed",
370
+ weight: 4,
371
+ properties: {
372
+ leaderboard_type: ["friends", "global", "challenge", "local"],
373
+ user_rank: u.weighNumRange(1, 500, 0.3, 50),
374
+ },
375
+ },
376
+ {
377
+ event: "progress checked",
378
+ weight: 5,
379
+ properties: {
380
+ metric_viewed: ["weight", "body_fat", "steps", "calories_burned", "workout_count", "streaks"],
381
+ trend_direction: ["improving", "improving", "stable", "declining"],
382
+ time_range: ["week", "month", "3_months", "year"],
383
+ },
384
+ },
385
+ {
386
+ event: "coach session",
387
+ weight: 3,
388
+ properties: {
389
+ session_type: ["live_video", "chat", "plan_review", "form_check"],
390
+ duration_minutes: u.weighNumRange(10, 60, 0.5, 30),
391
+ satisfaction_score: u.weighNumRange(1, 5, 0.6, 3),
392
+ coach_speciality: ["strength", "cardio", "nutrition", "yoga", "general"],
393
+ },
394
+ },
395
+ {
396
+ event: "nutrition plan viewed",
397
+ weight: 4,
398
+ properties: {
399
+ plan_type: ["weight_loss", "muscle_gain", "maintenance", "custom"],
400
+ adherence_pct: u.weighNumRange(0, 100, 0.5, 60),
401
+ },
402
+ },
403
+ {
404
+ event: "heart rate recorded",
405
+ weight: 3,
406
+ properties: {
407
+ bpm: u.weighNumRange(50, 200, 0.5, 110),
408
+ activity_state: ["resting", "warmup", "active", "peak", "cooldown"],
409
+ device: ["watch", "chest_strap", "phone_sensor"],
410
+ },
411
+ },
412
+ {
413
+ event: "app session",
414
+ weight: 8,
415
+ properties: {
416
+ session_duration_sec: u.weighNumRange(10, 1800, 0.4, 120),
417
+ pages_viewed: u.weighNumRange(1, 15, 0.5, 3),
418
+ },
419
+ },
420
+ {
421
+ event: "notification received",
422
+ weight: 5,
423
+ properties: {
424
+ notification_type: ["workout_reminder", "workout_reminder", "challenge_update", "friend_activity", "streak_warning", "coaching_tip"],
425
+ channel: ["push", "push", "email", "sms"],
426
+ opened: [true, true, true, false],
427
+ },
428
+ },
429
+ {
430
+ event: "subscription managed",
431
+ weight: 2,
432
+ properties: {
433
+ action: ["viewed_plans", "started_trial", "upgraded", "downgraded", "cancelled", "renewed"],
434
+ plan_viewed: ["free", "monthly", "annual", "family"],
435
+ },
436
+ },
437
+ {
438
+ event: "profile updated",
439
+ weight: 2,
440
+ properties: {
441
+ field_updated: ["weight", "height", "goal", "avatar", "workout_preferences", "notification_settings"],
442
+ },
443
+ },
444
+ {
445
+ event: "account deactivated",
446
+ weight: 1,
447
+ isChurnEvent: true,
448
+ returnLikelihood: 0.15,
449
+ isStrictEvent: true,
450
+ properties: {
451
+ reason: ["lost_motivation", "cost", "switched_app", "injury", "achieved_goal"],
452
+ },
453
+ },
454
+ ],
455
+
456
+ // ── Funnels (5) ──────────────────────────────────────────
457
+ funnels: [
458
+ {
459
+ name: "Onboarding",
460
+ sequence: ["account created", "profile updated", "workout planned", "workout completed"],
461
+ conversionRate: 45,
462
+ order: "sequential",
463
+ isFirstFunnel: true,
464
+ timeToConvert: 72,
465
+ weight: 3,
466
+ },
467
+ {
468
+ name: "Workout Loop",
469
+ sequence: ["workout planned", "workout completed", "progress checked"],
470
+ conversionRate: 45,
471
+ order: "sequential",
472
+ timeToConvert: 48,
473
+ weight: 5,
474
+ },
475
+ {
476
+ name: "Social Engagement",
477
+ sequence: ["friend added", "leaderboard viewed", "challenge joined"],
478
+ conversionRate: 35,
479
+ order: "sequential",
480
+ timeToConvert: 96,
481
+ weight: 3,
482
+ },
483
+ {
484
+ name: "Challenge Completion",
485
+ sequence: ["challenge joined", "workout completed", "challenge completed", "achievement unlocked"],
486
+ conversionRate: 30,
487
+ order: "sequential",
488
+ timeToConvert: 336,
489
+ weight: 2,
490
+ },
491
+ {
492
+ name: "Coaching Path",
493
+ sequence: ["coach session", "workout planned", "workout completed", "progress checked"],
494
+ conversionRate: 50,
495
+ order: "sequential",
496
+ timeToConvert: 72,
497
+ weight: 2,
498
+ },
499
+ ],
500
+
501
+ // ── SuperProps ──────────────────────────────────────────
502
+ superProps: {
503
+ platform: ["ios", "ios", "android"],
504
+ workout_type: ["strength", "cardio", "yoga", "hiit", "running", "cycling"],
505
+ },
506
+
507
+ // ── UserProps ──────────────────────────────────────────
508
+ userProps: {
509
+ fitness_level: ["beginner"],
510
+ segment: ["casual"],
511
+ streak_days: [0],
512
+ total_workouts: u.weighNumRange(0, 0, 0.5),
513
+ preferred_workout: ["strength", "cardio", "yoga", "hiit", "running", "cycling"],
514
+ goal: ["weight_loss", "muscle_gain", "endurance", "flexibility", "general_health"],
515
+ },
516
+
517
+ // ── Personas ──────────────────────────────────
518
+ personas: [
519
+ {
520
+ name: "athlete",
521
+ weight: 10,
522
+ eventMultiplier: 4.0,
523
+ conversionModifier: 1.5,
524
+ churnRate: 0.02,
525
+ properties: {
526
+ fitness_level: "advanced",
527
+ segment: "athlete",
528
+ },
529
+ },
530
+ {
531
+ name: "casual_exerciser",
532
+ weight: 40,
533
+ eventMultiplier: 1.0,
534
+ conversionModifier: 0.8,
535
+ churnRate: 0.08,
536
+ properties: {
537
+ fitness_level: "intermediate",
538
+ segment: "casual",
539
+ },
540
+ },
541
+ {
542
+ name: "new_year_resolver",
543
+ weight: 25,
544
+ eventMultiplier: 0.6,
545
+ conversionModifier: 0.5,
546
+ churnRate: 0.4,
547
+ properties: {
548
+ fitness_level: "beginner",
549
+ segment: "resolver",
550
+ },
551
+ activeWindow: { maxDays: 30 },
552
+ },
553
+ {
554
+ name: "social_motivator",
555
+ weight: 15,
556
+ eventMultiplier: 2.0,
557
+ conversionModifier: 1.2,
558
+ churnRate: 0.05,
559
+ properties: {
560
+ fitness_level: "intermediate",
561
+ segment: "social",
562
+ },
563
+ },
564
+ {
565
+ name: "coach",
566
+ weight: 10,
567
+ eventMultiplier: 3.0,
568
+ conversionModifier: 1.5,
569
+ churnRate: 0.01,
570
+ properties: {
571
+ fitness_level: "expert",
572
+ segment: "coach",
573
+ },
574
+ },
575
+ ],
576
+
577
+ // ── Subscription ──────────────────────────────
578
+ subscription: {
579
+ plans: [
580
+ { name: "free", price: 0, default: true },
581
+ { name: "monthly", price: 12.99, trialDays: 7 },
582
+ { name: "annual", price: 99.99 },
583
+ { name: "family", price: 149.99 },
584
+ ],
585
+ lifecycle: {
586
+ trialToPayRate: 0.30,
587
+ upgradeRate: 0.08,
588
+ downgradeRate: 0.03,
589
+ churnRate: 0.05,
590
+ winBackRate: 0.10,
591
+ winBackDelay: 21,
592
+ paymentFailureRate: 0.02,
593
+ },
594
+ },
595
+
596
+ // ── Attribution ──────────────────────────────
597
+ attribution: {
598
+ model: "last_touch",
599
+ window: 7,
600
+ campaigns: [
601
+ {
602
+ name: "app_store",
603
+ source: "apple",
604
+ medium: "app_store",
605
+ activeDays: [0, 100],
606
+ dailyBudget: [200, 600],
607
+ acquisitionRate: 0.03,
608
+ userPersonaBias: { athlete: 0.5, coach: 0.3 },
609
+ },
610
+ {
611
+ name: "google_play",
612
+ source: "google",
613
+ medium: "app_store",
614
+ activeDays: [0, 100],
615
+ dailyBudget: [150, 500],
616
+ acquisitionRate: 0.02,
617
+ },
618
+ {
619
+ name: "instagram_ads",
620
+ source: "instagram",
621
+ medium: "social",
622
+ activeDays: [5, 90],
623
+ dailyBudget: [200, 800],
624
+ acquisitionRate: 0.03,
625
+ userPersonaBias: { new_year_resolver: 0.5, social_motivator: 0.3 },
626
+ },
627
+ {
628
+ name: "referral_program",
629
+ source: "referral",
630
+ medium: "referral",
631
+ activeDays: [0, 100],
632
+ dailyBudget: [50, 200],
633
+ acquisitionRate: 0.02,
634
+ userPersonaBias: { social_motivator: 0.4 },
635
+ },
636
+ ],
637
+ organicRate: 0.30,
638
+ },
639
+
640
+ // ── Engagement Decay ──────────────────────────
641
+ engagementDecay: {
642
+ model: "step",
643
+ halfLife: 30,
644
+ floor: 0.1,
645
+ reactivationChance: 0.02,
646
+ },
647
+
648
+ // ── Features ──────────────────────────────────
649
+ features: [
650
+ {
651
+ name: "ai_coach",
652
+ launchDay: 35,
653
+ adoptionCurve: "fast",
654
+ property: "coaching_mode",
655
+ values: ["self_guided", "ai_assisted"],
656
+ defaultBefore: "self_guided",
657
+ affectsEvents: ["workout completed", "workout planned"],
658
+ },
659
+ {
660
+ name: "group_challenges",
661
+ launchDay: 55,
662
+ adoptionCurve: { k: 0.1, midpoint: 25 },
663
+ property: "challenge_mode",
664
+ values: ["solo", "group"],
665
+ defaultBefore: "solo",
666
+ affectsEvents: ["challenge joined", "workout completed"],
667
+ },
668
+ ],
669
+
670
+ // ── Hook Function ──────────────────────────────────────
671
+ hook: function (record, type, meta) {
672
+ // ── HOOK 7: COACH PROFILE ENRICHMENT (user) ──────────
673
+ // Coach segment gets boosted total_workouts and streak_days.
674
+ if (type === "user") {
675
+ if (record.segment === "coach") {
676
+ record.total_workouts = chance.integer({ min: 200, max: 500 });
677
+ record.streak_days = chance.integer({ min: 60, max: 365 });
678
+ }
679
+ }
680
+
681
+ // ── HOOK 8: ANNUAL SUBSCRIBER WORKOUT FUNNEL LIFT (funnel-pre) ─
682
+ // Annual and family subscribers convert 1.4x through workout loop.
683
+ if (type === "funnel-pre") {
684
+ if (meta && meta.profile) {
685
+ const tier = meta.profile.subscription_tier;
686
+ if (tier === "annual" || tier === "family") {
687
+ record.conversionRate = Math.min(record.conversionRate * 1.4, 95);
688
+ } else if (tier === "monthly") {
689
+ record.conversionRate = Math.min(record.conversionRate * 1.15, 90);
690
+ }
691
+ }
692
+ }
693
+
694
+ // ── HOOK 1: MORNING WORKOUT BOOST (event) ────────────
695
+ // Workouts between 5AM-9AM get 1.3x calories_burned.
696
+ if (type === "event") {
697
+ if (record.event === "workout completed") {
698
+ const hour = dayjs(record.time).hour();
699
+ if (hour >= 5 && hour < 9) {
700
+ record.is_morning_workout = true;
701
+ if (record.calories_burned) {
702
+ record.calories_burned = Math.floor(record.calories_burned * 1.3);
703
+ }
704
+ }
705
+ }
706
+
707
+ // ── HOOK 2: POST-LAUNCH AI COACHING LIFT (event) ────
708
+ // After day 35, ai_assisted workouts get 1.2x duration.
709
+ if (record.event === "workout completed" || record.event === "workout planned") {
710
+ const AI_LAUNCH = DATASET_START.add(35, "days");
711
+ const eventTime = dayjs(record.time);
712
+ if (eventTime.isAfter(AI_LAUNCH) && record.coaching_mode === "ai_assisted") {
713
+ if (record.duration_minutes) {
714
+ record.duration_minutes = Math.floor(record.duration_minutes * 1.2);
715
+ }
716
+ if (record.planned_duration_minutes) {
717
+ record.planned_duration_minutes = Math.floor(record.planned_duration_minutes * 1.2);
718
+ }
719
+ }
720
+ }
721
+ }
722
+
723
+ // ── EVERYTHING HOOKS ─────────────────────────────────
724
+ if (type === "everything") {
725
+ const events = record;
726
+ if (!events.length) return record;
727
+
728
+ // ── HOOK 3: STREAK RETENTION ─────────────────────
729
+ // Users with >10 workouts get streak_days updated and
730
+ // cloned achievement events for milestones.
731
+ const workoutEvents = events.filter(e => e.event === "workout completed");
732
+ if (workoutEvents.length > 10) {
733
+ // Update profile streak_days via a profile update event
734
+ if (meta && meta.profile) {
735
+ meta.profile.streak_days = workoutEvents.length;
736
+ }
737
+
738
+ // Clone achievement events for streak milestones
739
+ const templateAchievement = events.find(e => e.event === "achievement unlocked");
740
+ if (templateAchievement) {
741
+ const milestones = [10, 25, 50, 75, 100];
742
+ milestones.forEach(m => {
743
+ if (workoutEvents.length >= m) {
744
+ const sourceEvent = workoutEvents[Math.min(m - 1, workoutEvents.length - 1)];
745
+ events.push({
746
+ ...templateAchievement,
747
+ time: dayjs(sourceEvent.time).add(chance.integer({ min: 1, max: 30 }), "minutes").toISOString(),
748
+ user_id: sourceEvent.user_id,
749
+ achievement_type: "streak_milestone",
750
+ streak_days_at_unlock: m,
751
+ });
752
+ }
753
+ });
754
+ }
755
+ }
756
+
757
+ // ── HOOK 4: SOCIAL CHALLENGE COMPLETION ──────────
758
+ // Users with friend_added events get 1.5x challenge completions.
759
+ const hasFriends = events.some(e => e.event === "friend added");
760
+ if (hasFriends) {
761
+ const templateChallenge = events.find(e => e.event === "challenge completed");
762
+ if (templateChallenge) {
763
+ const challengeCompletions = events.filter(e => e.event === "challenge completed");
764
+ const extraCount = Math.floor(challengeCompletions.length * 0.5);
765
+ for (let i = 0; i < extraCount; i++) {
766
+ const source = challengeCompletions[i % challengeCompletions.length];
767
+ events.push({
768
+ ...templateChallenge,
769
+ time: dayjs(source.time).add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
770
+ user_id: source.user_id,
771
+ challenge_type: source.challenge_type,
772
+ completion_pct: chance.integer({ min: 80, max: 100 }),
773
+ });
774
+ }
775
+ }
776
+ }
777
+
778
+ // ── HOOK 5: RESOLVER CHURN CLIFF ─────────────────
779
+ // Resolver segment users with <8 events lose 70% after day 14.
780
+ if (meta && meta.profile && meta.profile.segment === "resolver" && events.length < 8) {
781
+ const CHURN_CLIFF = DATASET_START.add(14, "days");
782
+ for (let i = events.length - 1; i >= 0; i--) {
783
+ const eventTime = dayjs(events[i].time);
784
+ if (eventTime.isAfter(CHURN_CLIFF) && chance.bool({ likelihood: 70 })) {
785
+ events.splice(i, 1);
786
+ }
787
+ }
788
+ }
789
+
790
+ // ── HOOK 6: COACH SESSION QUALITY ────────────────
791
+ // Users with coach_session events get higher satisfaction.
792
+ const hasCoachSessions = events.some(e => e.event === "coach session");
793
+ if (hasCoachSessions) {
794
+ events.forEach(e => {
795
+ if (e.event === "coach session") {
796
+ e.satisfaction_score = chance.floating({ min: 4.0, max: 5.0, fixed: 1 });
797
+ }
798
+ });
799
+ }
800
+
801
+ return record;
802
+ }
803
+
804
+ return record;
805
+ },
806
+ };
807
+
808
+ export default config;