@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.
- package/README.md +7 -7
- package/dungeons/technical/ad-spend-schema.json +128 -0
- package/dungeons/technical/ad-spend.js +116 -0
- package/dungeons/technical/anonymous-users-schema.json +92 -0
- package/dungeons/technical/anonymous-users.js +102 -0
- package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
- package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +12 -5
- package/dungeons/technical/experiments-schema.json +203 -0
- package/dungeons/technical/experiments.js +168 -0
- package/dungeons/technical/foobar-schema.json +362 -0
- package/dungeons/{foobar.js → technical/foobar.js} +3 -3
- package/dungeons/technical/group-analytics-schema.json +241 -0
- package/dungeons/technical/group-analytics.js +151 -0
- package/dungeons/technical/mirror-strategies-schema.json +84 -0
- package/dungeons/technical/mirror-strategies.js +126 -0
- package/dungeons/technical/nested-objects-schema.json +145 -0
- package/dungeons/technical/nested-objects.js +207 -0
- package/dungeons/technical/retention-cadence-schema.json +37 -0
- package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +2 -3
- package/dungeons/technical/sanity-schema.json +185 -0
- package/dungeons/{sanity.js → technical/sanity.js} +4 -2
- package/dungeons/technical/scale-test-schema.json +70 -0
- package/dungeons/technical/scale-test.js +90 -0
- package/dungeons/technical/scd-schema.json +467 -0
- package/dungeons/{scd.js → technical/scd.js} +20 -10
- package/dungeons/technical/simple-schema.json +362 -0
- package/dungeons/{simple.js → technical/simple.js} +7 -7
- package/dungeons/technical/simplest-schema.json +724 -0
- package/dungeons/{simplest.js → technical/simplest.js} +7 -7
- package/dungeons/technical/text-generation-schema.json +1062 -0
- package/dungeons/{text-generation.js → technical/text-generation.js} +24 -11
- package/dungeons/vertical/community-schema.json +579 -0
- package/dungeons/vertical/community.js +794 -0
- package/dungeons/vertical/devtools-schema.json +601 -0
- package/dungeons/vertical/devtools.js +843 -0
- package/dungeons/vertical/ecommerce-schema.json +604 -0
- package/dungeons/{ecommerce.js → vertical/ecommerce.js} +43 -5
- package/dungeons/vertical/education-schema.json +5686 -0
- package/dungeons/{education.js → vertical/education.js} +70 -39
- package/dungeons/vertical/fintech-schema.json +630 -0
- package/dungeons/{fintech.js → vertical/fintech.js} +107 -56
- package/dungeons/vertical/fitness-schema.json +530 -0
- package/dungeons/vertical/fitness.js +808 -0
- package/dungeons/vertical/food-delivery-schema.json +36728 -0
- package/dungeons/{food.js → vertical/food-delivery.js} +67 -26
- package/dungeons/vertical/gaming-schema.json +438 -0
- package/dungeons/{gaming.js → vertical/gaming.js} +29 -16
- package/dungeons/vertical/healthcare-schema.json +549 -0
- package/dungeons/vertical/healthcare.js +775 -0
- package/dungeons/vertical/insurance-application-schema.json +485 -0
- package/dungeons/{insurance-application.js → vertical/insurance-application.js} +41 -31
- package/dungeons/vertical/logistics-schema.json +574 -0
- package/dungeons/vertical/logistics.js +799 -0
- package/dungeons/vertical/marketplace-schema.json +533 -0
- package/dungeons/vertical/marketplace.js +850 -0
- package/dungeons/vertical/media-schema.json +4749 -0
- package/dungeons/{media.js → vertical/media.js} +103 -19
- package/dungeons/vertical/rpg-schema.json +2491 -0
- package/dungeons/{rpg.js → vertical/rpg.js} +87 -60
- package/dungeons/vertical/sass-schema.json +3128 -0
- package/dungeons/{sass.js → vertical/sass.js} +65 -26
- package/dungeons/vertical/social-schema.json +620 -0
- package/dungeons/{social.js → vertical/social.js} +76 -17
- package/dungeons/vertical/travel-schema.json +580 -0
- package/dungeons/vertical/travel.js +742 -0
- package/index.js +20 -16
- package/lib/core/config-validator.js +286 -1
- package/lib/core/context.js +16 -5
- package/lib/core/storage.js +2 -0
- package/lib/generators/adspend.js +3 -3
- package/lib/generators/events.js +141 -14
- package/lib/generators/funnels.js +67 -7
- package/lib/generators/text.js +2 -2
- package/lib/orchestrators/user-loop.js +444 -22
- package/lib/templates/soup-presets.js +2 -2
- package/lib/utils/utils.js +4 -5
- package/package.json +4 -3
- package/scripts/dungeon-to-json.mjs +163 -0
- package/scripts/extract-dungeon-schema.mjs +343 -0
- package/scripts/json-to-dungeon.mjs +200 -0
- package/scripts/run-dungeon.mjs +76 -0
- package/scripts/run-many.mjs +118 -0
- package/scripts/verify-runner.mjs +42 -0
- package/types.d.ts +316 -1
- package/dungeons/array-of-object-lookup-schema.json +0 -327
- package/dungeons/ecommerce-schema.json +0 -462
- package/dungeons/education-schema.json +0 -2409
- package/dungeons/fintech-schema.json +0 -14034
- package/dungeons/foobar-schema.json +0 -403
- package/dungeons/food-delivery-schema.json +0 -192
- package/dungeons/food-delivery.js +0 -602
- package/dungeons/food-schema.json +0 -1152
- package/dungeons/gaming-schema.json +0 -1270
- package/dungeons/insurance-application-schema.json +0 -204
- package/dungeons/media-schema.json +0 -906
- package/dungeons/retention-cadence-schema.json +0 -78
- package/dungeons/rpg-schema.json +0 -4526
- package/dungeons/sanity-schema.json +0 -255
- package/dungeons/sass-schema.json +0 -1291
- package/dungeons/scd-schema.json +0 -919
- package/dungeons/simple-schema.json +0 -608
- package/dungeons/simplest-schema.json +0 -1418
- package/dungeons/social-schema.json +0 -1118
- 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;
|