@ak--47/dungeon-master 1.5.4 → 1.6.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.
- package/.claude/skills/analyze-soup/SKILL.md +21 -12
- package/.claude/skills/create-dungeon/SKILL.md +65 -5
- package/.claude/skills/create-project/SKILL.md +1 -1
- package/.claude/skills/create-project/provision.mjs +57 -10
- package/.claude/skills/powertools/SKILL.md +75 -0
- package/.claude/skills/powertools/pt.mjs +91 -0
- package/.claude/skills/powertools/snapshot-project.mjs +124 -0
- package/.claude/skills/verify-dungeon/SKILL.md +56 -25
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
- package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
- package/.claude/skills/write-hooks/SKILL.md +156 -18
- package/CHANGELOG.md +224 -0
- package/HOOKS.md +566 -112
- package/README.md +30 -9
- package/docs/guides/1.3.0-upgrade-guide.md +262 -0
- package/docs/guides/1.3.1-upgrade-guide.md +328 -0
- package/docs/guides/1.4.0-upgrade-guide.md +130 -0
- package/docs/guides/1.4.1-upgrade-guide.md +125 -0
- package/docs/guides/1.4.3-upgrade-guide.md +200 -0
- package/docs/guides/1.4.4-upgrade-guide.md +95 -0
- package/docs/guides/1.5.0-upgrade-guide.md +426 -0
- package/docs/guides/1.5.1-upgrade-guide.md +178 -0
- package/docs/guides/1.5.3-upgrade-guide.md +117 -0
- package/docs/guides/1.6.0-upgrade-guide.md +204 -0
- package/dungeons/technical/stories-verify.js +152 -0
- package/dungeons/vertical/README.md +154 -0
- package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
- package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
- package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
- package/dungeons/vertical/community/community.js +1589 -0
- package/dungeons/vertical/community/community.sql +216 -0
- package/dungeons/vertical/community/community.verify.mjs +72 -0
- package/dungeons/vertical/crypto/crypto.js +1483 -0
- package/dungeons/vertical/crypto/crypto.sql +194 -0
- package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
- package/dungeons/vertical/dating/dating.js +1540 -0
- package/dungeons/vertical/dating/dating.sql +288 -0
- package/dungeons/vertical/dating/dating.verify.mjs +72 -0
- package/dungeons/vertical/devtools/devtools.js +1524 -0
- package/dungeons/vertical/devtools/devtools.sql +161 -0
- package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
- package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
- package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
- package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
- package/dungeons/vertical/education/education.js +1763 -0
- package/dungeons/vertical/education/education.sql +243 -0
- package/dungeons/vertical/education/education.verify.mjs +75 -0
- package/dungeons/vertical/fintech/fintech.js +1410 -0
- package/dungeons/vertical/fintech/fintech.sql +175 -0
- package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
- package/dungeons/vertical/fitness/fitness.js +1495 -0
- package/dungeons/vertical/fitness/fitness.sql +206 -0
- package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
- package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
- package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
- package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
- package/dungeons/vertical/gaming/gaming.js +1741 -0
- package/dungeons/vertical/gaming/gaming.sql +249 -0
- package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
- package/dungeons/vertical/healthcare/healthcare.js +1488 -0
- package/dungeons/vertical/healthcare/healthcare.sql +193 -0
- package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
- package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
- package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
- package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
- package/dungeons/vertical/logistics/logistics.js +1541 -0
- package/dungeons/vertical/logistics/logistics.sql +205 -0
- package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
- package/dungeons/vertical/marketplace/marketplace.js +1438 -0
- package/dungeons/vertical/marketplace/marketplace.sql +148 -0
- package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
- package/dungeons/vertical/{media.js → media/media.js} +557 -58
- package/dungeons/vertical/media/media.sql +173 -0
- package/dungeons/vertical/media/media.verify.mjs +74 -0
- package/dungeons/vertical/real-estate/real-estate.js +1629 -0
- package/dungeons/vertical/real-estate/real-estate.sql +167 -0
- package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
- package/dungeons/vertical/sass/sass.js +1733 -0
- package/dungeons/vertical/sass/sass.sql +173 -0
- package/dungeons/vertical/sass/sass.verify.mjs +74 -0
- package/dungeons/vertical/social/social.js +1456 -0
- package/dungeons/vertical/social/social.sql +160 -0
- package/dungeons/vertical/social/social.verify.mjs +76 -0
- package/dungeons/vertical/streaming/streaming.js +677 -0
- package/dungeons/vertical/streaming/streaming.sql +101 -0
- package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
- package/dungeons/vertical/support-desk/support-desk.js +741 -0
- package/dungeons/vertical/support-desk/support-desk.sql +137 -0
- package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
- package/dungeons/vertical/travel/travel.js +1603 -0
- package/dungeons/vertical/travel/travel.sql +236 -0
- package/dungeons/vertical/travel/travel.verify.mjs +72 -0
- package/index.js +7 -3
- package/lib/core/config-validator.js +37 -2
- package/lib/core/context.js +4 -2
- package/lib/core/dungeon-loader.js +20 -15
- package/lib/core/dungeon-to-json.js +22 -4
- package/lib/core/storage.js +3 -3
- package/lib/generators/funnels.js +36 -9
- package/lib/hook-helpers/cohort.js +53 -0
- package/lib/hook-helpers/index.js +8 -0
- package/lib/hook-helpers/mutate.js +1 -1
- package/lib/hook-helpers/shape.js +319 -0
- package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
- package/lib/hook-patterns/attributed-by-source.js +60 -58
- package/lib/hook-patterns/frequency-by-frequency.js +14 -4
- package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
- package/lib/hook-patterns/index.js +1 -1
- package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
- package/lib/orchestrators/user-loop.js +41 -0
- package/lib/templates/story-spec.schema.json +175 -0
- package/lib/utils/json-evaluator.js +1 -1
- package/lib/utils/utils.js +151 -59
- package/lib/verify/coerce.js +186 -0
- package/lib/verify/counting.js +270 -32
- package/lib/verify/emulate-breakdown.js +1285 -203
- package/lib/verify/first-time.js +89 -0
- package/lib/verify/flows.js +679 -0
- package/lib/verify/formula.js +259 -0
- package/lib/verify/funnel-engine.js +697 -113
- package/lib/verify/index.js +32 -4
- package/lib/verify/sessionize.js +275 -0
- package/lib/verify/story-runner.js +483 -0
- package/lib/verify/verify-dungeon.js +59 -37
- package/package.json +2 -1
- package/scripts/extract-dungeon-schema.mjs +2 -2
- package/scripts/run-many.mjs +2 -2
- package/scripts/verify-runner.mjs +2 -2
- package/scripts/verify-stories.mjs +244 -0
- package/types.d.ts +298 -30
- package/dungeons/vertical/ai-platform.js +0 -818
- package/dungeons/vertical/community.js +0 -966
- package/dungeons/vertical/crypto.js +0 -895
- package/dungeons/vertical/dating.js +0 -830
- package/dungeons/vertical/devtools.js +0 -1050
- package/dungeons/vertical/ecommerce.js +0 -811
- package/dungeons/vertical/education.js +0 -964
- package/dungeons/vertical/fintech.js +0 -946
- package/dungeons/vertical/fitness.js +0 -935
- package/dungeons/vertical/food-delivery.js +0 -934
- package/dungeons/vertical/gaming.js +0 -1120
- package/dungeons/vertical/healthcare.js +0 -945
- package/dungeons/vertical/insurance-application.js +0 -1039
- package/dungeons/vertical/logistics.js +0 -986
- package/dungeons/vertical/marketplace.js +0 -1019
- package/dungeons/vertical/real-estate.js +0 -872
- package/dungeons/vertical/sass.js +0 -995
- package/dungeons/vertical/social.js +0 -904
- package/dungeons/vertical/travel.js +0 -930
|
@@ -1,935 +0,0 @@
|
|
|
1
|
-
// ── IMPORTS ──
|
|
2
|
-
import dayjs from "dayjs";
|
|
3
|
-
import utc from "dayjs/plugin/utc.js";
|
|
4
|
-
dayjs.extend(utc);
|
|
5
|
-
import "dotenv/config";
|
|
6
|
-
import * as u from "../../lib/utils/utils.js";
|
|
7
|
-
/** @typedef {import("../../types").Dungeon} Config */
|
|
8
|
-
|
|
9
|
-
// ── OVERVIEW ──
|
|
10
|
-
/*
|
|
11
|
-
* NAME: FitQuest
|
|
12
|
-
* APP: Fitness & wellness app for workout tracking, meal planning,
|
|
13
|
-
* social fitness challenges, and AI coaching. Core loop: sign
|
|
14
|
-
* up → plan workout → complete workout → track progress.
|
|
15
|
-
* Revenue: free / monthly ($12.99, 7-day trial) / annual
|
|
16
|
-
* ($99.99) / family ($149.99).
|
|
17
|
-
* SCALE: 10,000 users, ~1.4M events, 121 days (2026-01-01 → 2026-05-01)
|
|
18
|
-
* CORE LOOP: account created → workout planned → workout completed → progress checked
|
|
19
|
-
*
|
|
20
|
-
* EVENTS (18):
|
|
21
|
-
* workout completed (8) > app session (8) > meal logged (7) > workout planned (6)
|
|
22
|
-
* > progress checked (5) > notification received (5) > leaderboard viewed (4)
|
|
23
|
-
* > nutrition plan viewed (4) > challenge joined (3) > coach session (3)
|
|
24
|
-
* > heart rate recorded (3) > achievement unlocked (2) > friend added (2)
|
|
25
|
-
* > challenge completed (2) > subscription managed (2) > profile updated (2)
|
|
26
|
-
* > account created (1) > account deactivated (1)
|
|
27
|
-
*
|
|
28
|
-
* FUNNELS (5):
|
|
29
|
-
* - Onboarding: account created → profile updated → workout planned → workout completed (45%)
|
|
30
|
-
* - Workout Loop: workout planned → workout completed → progress checked (45%, reentry)
|
|
31
|
-
* - Social Engagement: friend added → leaderboard viewed → challenge joined (35%)
|
|
32
|
-
* - Challenge Completion: challenge joined → workout completed → challenge completed → achievement unlocked (30%)
|
|
33
|
-
* - Coaching Path: coach session → workout planned → workout completed → progress checked (50%)
|
|
34
|
-
*
|
|
35
|
-
* USER PROPS: fitness_level, segment, streak_days, total_workouts, preferred_workout, goal, Platform, workout_type, subscription_tier
|
|
36
|
-
* SUPER PROPS: Platform, workout_type, subscription_tier
|
|
37
|
-
* SCD PROPS: fitness_level (beginner/intermediate/advanced/elite, monthly fuzzy, max 8)
|
|
38
|
-
* GROUPS: none
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
// ── HOOK STORIES ──
|
|
42
|
-
/*
|
|
43
|
-
* NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable
|
|
44
|
-
* via raw-prop breakdowns (HOD, day, segment) or behavioral cohorts.
|
|
45
|
-
*
|
|
46
|
-
* ───────────────────────────────────────────────────────────────
|
|
47
|
-
* 1. MORNING WORKOUT BOOST (everything)
|
|
48
|
-
* ───────────────────────────────────────────────────────────────
|
|
49
|
-
*
|
|
50
|
-
* PATTERN: Workouts in 5-9 UTC get calories_burned 1.3x. Mutates
|
|
51
|
-
* raw prop. No flag.
|
|
52
|
-
*
|
|
53
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
54
|
-
*
|
|
55
|
-
* Report 1: Avg Calories Burned by Hour of Day
|
|
56
|
-
* - Event: "workout completed"
|
|
57
|
-
* - Measure: Average of "calories_burned"
|
|
58
|
-
* - Breakdown: hour of day
|
|
59
|
-
* - Expected: 5-9 hours show ~ 1.3x baseline
|
|
60
|
-
*
|
|
61
|
-
* REAL-WORLD ANALOGUE: Morning workouts get a metabolic boost.
|
|
62
|
-
*
|
|
63
|
-
* ───────────────────────────────────────────────────────────────
|
|
64
|
-
* 2. POST-LAUNCH AI COACHING LIFT (everything hook)
|
|
65
|
-
* ───────────────────────────────────────────────────────────────
|
|
66
|
-
*
|
|
67
|
-
* PATTERN: After day 35 (ai_coach feature launch), workouts
|
|
68
|
-
* with coaching_mode="ai_assisted" get 1.2x duration_minutes.
|
|
69
|
-
* AI coaching helps users push through longer sessions.
|
|
70
|
-
*
|
|
71
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
72
|
-
*
|
|
73
|
-
* Report 1: AI Coaching Duration Lift
|
|
74
|
-
* • Report type: Insights
|
|
75
|
-
* • Event: "workout completed"
|
|
76
|
-
* • Measure: Average of "duration_minutes"
|
|
77
|
-
* • Breakdown: "coaching_mode"
|
|
78
|
-
* • Filter: time after day 35
|
|
79
|
-
* • Expected: ai_assisted ≈ 48 min vs self_guided ≈ 40 min
|
|
80
|
-
*
|
|
81
|
-
* Report 2: AI Coaching Adoption Over Time
|
|
82
|
-
* • Report type: Insights
|
|
83
|
-
* • Event: "workout completed"
|
|
84
|
-
* • Measure: Total
|
|
85
|
-
* • Breakdown: "coaching_mode"
|
|
86
|
-
* • Line chart by week
|
|
87
|
-
* • Expected: ai_assisted grows from 0 after day 35
|
|
88
|
-
*
|
|
89
|
-
* REAL-WORLD ANALOGUE: AI-powered coaching features increase
|
|
90
|
-
* session duration as users get real-time form and pacing guidance.
|
|
91
|
-
*
|
|
92
|
-
* ───────────────────────────────────────────────────────────────
|
|
93
|
-
* 3. STREAK RETENTION (everything hook)
|
|
94
|
-
* ───────────────────────────────────────────────────────────────
|
|
95
|
-
*
|
|
96
|
-
* PATTERN: Users with >=2 workout events get streak_days set
|
|
97
|
-
* to their actual workout count on their profile, and receive
|
|
98
|
-
* achievement clones with super-linear scaling: 1 per workout
|
|
99
|
-
* for workouts 2-4, then 4 per workout beyond that. This
|
|
100
|
-
* amplifies the gap so athlete/casual ratio reaches 2x+.
|
|
101
|
-
*
|
|
102
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
103
|
-
*
|
|
104
|
-
* Report 1: Achievement Events by Workout Volume
|
|
105
|
-
* • Report type: Insights
|
|
106
|
-
* • Event: "achievement unlocked"
|
|
107
|
-
* • Measure: Total per user
|
|
108
|
-
* • Breakdown: user property "segment"
|
|
109
|
-
* • Expected: athlete and coach segments show 2-3x more
|
|
110
|
-
* achievements than casual and resolver segments
|
|
111
|
-
*
|
|
112
|
-
* Report 2: Streak Days Distribution
|
|
113
|
-
* • Report type: Insights
|
|
114
|
-
* • Event: "profile updated"
|
|
115
|
-
* • Measure: Average of user property "streak_days"
|
|
116
|
-
* • Breakdown: user property "segment"
|
|
117
|
-
* • Expected: athlete/coach ≈ 30-50 streaks, casual ≈ 10-15
|
|
118
|
-
*
|
|
119
|
-
* REAL-WORLD ANALOGUE: Gamification streaks are the #1 retention
|
|
120
|
-
* driver in fitness apps — users who hit milestones stay longer.
|
|
121
|
-
*
|
|
122
|
-
* ───────────────────────────────────────────────────────────────
|
|
123
|
-
* 4. SOCIAL CHALLENGE COMPLETION (everything hook)
|
|
124
|
-
* ───────────────────────────────────────────────────────────────
|
|
125
|
-
*
|
|
126
|
-
* PATTERN: Users with "friend added" events complete 1.5x more
|
|
127
|
-
* "challenge completed" events (cloned from existing ones).
|
|
128
|
-
* Social users are more accountable.
|
|
129
|
-
*
|
|
130
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
131
|
-
*
|
|
132
|
-
* Report 1: Challenge Completion Rate by Social Activity
|
|
133
|
-
* • Report type: Insights
|
|
134
|
-
* • Event: "challenge completed"
|
|
135
|
-
* • Measure: Total per user
|
|
136
|
-
* • Filter: users who did "friend added" at least once
|
|
137
|
-
* • Compare to: users who never did "friend added"
|
|
138
|
-
* • Expected: social users ≈ 1.5x more challenge completions
|
|
139
|
-
*
|
|
140
|
-
* REAL-WORLD ANALOGUE: Social accountability is a proven
|
|
141
|
-
* motivator — users with friends complete more challenges.
|
|
142
|
-
*
|
|
143
|
-
* ───────────────────────────────────────────────────────────────
|
|
144
|
-
* 5. RESOLVER CHURN CLIFF (everything hook)
|
|
145
|
-
* ───────────────────────────────────────────────────────────────
|
|
146
|
-
*
|
|
147
|
-
* PATTERN: Users in the "resolver" segment with <8 events
|
|
148
|
-
* lose 70% of their events after day 14. Simulates the classic
|
|
149
|
-
* New Year's resolution drop-off.
|
|
150
|
-
*
|
|
151
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
152
|
-
*
|
|
153
|
-
* Report 1: Resolver Retention Drop
|
|
154
|
-
* • Report type: Insights
|
|
155
|
-
* • Event: All events
|
|
156
|
-
* • Measure: Total
|
|
157
|
-
* • Filter: segment = "resolver"
|
|
158
|
-
* • Line chart by week
|
|
159
|
-
* • Expected: Sharp cliff after week 2, ~70% drop in volume
|
|
160
|
-
*
|
|
161
|
-
* Report 2: Segment Retention Comparison
|
|
162
|
-
* • Report type: Retention
|
|
163
|
-
* • Starting event: "account created"
|
|
164
|
-
* • Return event: Any active event
|
|
165
|
-
* • Breakdown: user property "segment"
|
|
166
|
-
* • Expected: resolver retention drops to <30% by week 3
|
|
167
|
-
*
|
|
168
|
-
* REAL-WORLD ANALOGUE: 80% of New Year's gym memberships are
|
|
169
|
-
* abandoned by mid-February — the "resolution cliff."
|
|
170
|
-
*
|
|
171
|
-
* ───────────────────────────────────────────────────────────────
|
|
172
|
-
* 6. COACH SESSION QUALITY (everything hook)
|
|
173
|
-
* ───────────────────────────────────────────────────────────────
|
|
174
|
-
*
|
|
175
|
-
* PATTERN: Users with "coach session" events get higher
|
|
176
|
-
* satisfaction_score (4.0-5.0) on those events. Coached users
|
|
177
|
-
* rate their experience higher.
|
|
178
|
-
*
|
|
179
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
180
|
-
*
|
|
181
|
-
* Report 1: Satisfaction by Session Type
|
|
182
|
-
* • Report type: Insights
|
|
183
|
-
* • Event: "coach session"
|
|
184
|
-
* • Measure: Average of "satisfaction_score"
|
|
185
|
-
* • Expected: ≈ 4.3 avg (vs baseline 3.0 for non-coached)
|
|
186
|
-
*
|
|
187
|
-
* REAL-WORLD ANALOGUE: Personal coaching sessions have higher
|
|
188
|
-
* satisfaction scores because of personalized attention.
|
|
189
|
-
*
|
|
190
|
-
* ───────────────────────────────────────────────────────────────
|
|
191
|
-
* 7. COACH PROFILE ENRICHMENT (user hook)
|
|
192
|
-
* ───────────────────────────────────────────────────────────────
|
|
193
|
-
*
|
|
194
|
-
* PATTERN: Users in the "coach" segment get total_workouts
|
|
195
|
-
* boosted to 200-500 and streak_days to 60-365. Coaches are
|
|
196
|
-
* power users who lead by example.
|
|
197
|
-
*
|
|
198
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
199
|
-
*
|
|
200
|
-
* Report 1: Workout Volume by Segment
|
|
201
|
-
* • Report type: Insights
|
|
202
|
-
* • Event: "workout completed"
|
|
203
|
-
* • Measure: Average of user property "total_workouts"
|
|
204
|
-
* • Breakdown: user property "segment"
|
|
205
|
-
* • Expected: coach ≈ 350 vs athlete ≈ 0 (default) vs casual ≈ 0
|
|
206
|
-
*
|
|
207
|
-
* Report 2: Streak Distribution by Segment
|
|
208
|
-
* • Report type: Insights
|
|
209
|
-
* • Measure: Average of user property "streak_days"
|
|
210
|
-
* • Breakdown: user property "segment"
|
|
211
|
-
* • Expected: coach ≈ 200 vs others ≈ 0 (pre-hook default)
|
|
212
|
-
*
|
|
213
|
-
* REAL-WORLD ANALOGUE: Fitness coaches maintain extreme workout
|
|
214
|
-
* consistency to build credibility with their clients.
|
|
215
|
-
*
|
|
216
|
-
* ───────────────────────────────────────────────────────────────
|
|
217
|
-
* 8. ANNUAL SUBSCRIBER WORKOUT FUNNEL LIFT (everything hook)
|
|
218
|
-
* ───────────────────────────────────────────────────────────────
|
|
219
|
-
*
|
|
220
|
-
* PATTERN: Free/monthly-tier users lose ~30% of "progress checked"
|
|
221
|
-
* events (last step of the Workout Loop funnel), simulating lower
|
|
222
|
-
* follow-through. Annual/family subscribers retain all events.
|
|
223
|
-
*
|
|
224
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
225
|
-
*
|
|
226
|
-
* Report 1: Workout Funnel Conversion by Tier
|
|
227
|
-
* • Report type: Funnels
|
|
228
|
-
* • Steps: "workout planned" → "workout completed" → "progress checked"
|
|
229
|
-
* • Breakdown: "subscription_tier" (superProp)
|
|
230
|
-
* • Expected: annual ≈ 63% vs free ≈ 45% conversion
|
|
231
|
-
*
|
|
232
|
-
* REAL-WORLD ANALOGUE: Annual gym memberships have higher
|
|
233
|
-
* utilization — sunk cost + commitment drives consistency.
|
|
234
|
-
*
|
|
235
|
-
* ───────────────────────────────────────────────────────────────
|
|
236
|
-
* 9. WORKOUT LOOP TIME-TO-CONVERT (funnel-post)
|
|
237
|
-
* ───────────────────────────────────────────────────────────────
|
|
238
|
-
*
|
|
239
|
-
* PATTERN: Annual + family subscribers complete the Workout Loop
|
|
240
|
-
* funnel 1.3x faster (factor 0.77); Free users 1.25x slower (factor 1.25).
|
|
241
|
-
*
|
|
242
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
243
|
-
*
|
|
244
|
-
* Report 1: Workout Loop Median Time-to-Convert by Subscription
|
|
245
|
-
* - Funnels > "workout planned" -> "workout completed" -> "progress checked"
|
|
246
|
-
* - Measure: Median time to convert
|
|
247
|
-
* - Breakdown: subscription_plan
|
|
248
|
-
* - Expected: annual ~ 0.77x; free ~ 1.25x
|
|
249
|
-
*
|
|
250
|
-
* NOTE (funnel-post measurement): visible only via Mixpanel funnel
|
|
251
|
-
* median TTC. Cross-event MIN→MIN SQL queries on raw events do NOT
|
|
252
|
-
* show this — funnel-post adjusts gaps within funnel instances, not
|
|
253
|
-
* across the user's full event history.
|
|
254
|
-
*
|
|
255
|
-
* ───────────────────────────────────────────────────────────────
|
|
256
|
-
* 10. WORKOUT-COUNT MAGIC NUMBER (everything)
|
|
257
|
-
* ───────────────────────────────────────────────────────────────
|
|
258
|
-
*
|
|
259
|
-
* PATTERN: Sweet 12-14 workouts/user → +35% on workout
|
|
260
|
-
* duration_minutes (peak progression). Over 15+ → drop 65% of
|
|
261
|
-
* post-day-30 non-workout, non-progress events (overtraining
|
|
262
|
-
* churn). Preserves workout + progress events so H8 funnel
|
|
263
|
-
* lift isn't diluted. No flag.
|
|
264
|
-
*
|
|
265
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
266
|
-
*
|
|
267
|
-
* Report 1: Avg Workout Duration by Workout-Count Bucket
|
|
268
|
-
* - Cohort A: users with 12-14 "workout completed"
|
|
269
|
-
* - Cohort B: users with 0-11
|
|
270
|
-
* - Event: "workout completed"
|
|
271
|
-
* - Measure: Average of "duration_minutes"
|
|
272
|
-
* - Expected: A ~ 1.35x B
|
|
273
|
-
*
|
|
274
|
-
* Report 2: D30+ Activity on Heavy Workout Cohort
|
|
275
|
-
* - Cohort C: users with >= 15 "workout completed"
|
|
276
|
-
* - Cohort A: users with 12-14
|
|
277
|
-
* - Event: any event
|
|
278
|
-
* - Measure: post-d30/pre-d30 ratio per user
|
|
279
|
-
* - Expected: C ~ 70% lower post/pre ratio than A (overtraining churn)
|
|
280
|
-
*
|
|
281
|
-
* REAL-WORLD ANALOGUE: Sweet-spot training drives progression;
|
|
282
|
-
* over-training causes injury and burnout.
|
|
283
|
-
*
|
|
284
|
-
* ═══════════════════════════════════════════════════════════════
|
|
285
|
-
* EXPECTED METRICS SUMMARY
|
|
286
|
-
* ═══════════════════════════════════════════════════════════════
|
|
287
|
-
*
|
|
288
|
-
* Hook | Metric | Baseline | Effect | Ratio
|
|
289
|
-
* ----------------------------|---------------------|----------|-----------|------
|
|
290
|
-
* Morning Workout Boost | calories_burned 5-9 | 1x | 1.3x | 1.3x
|
|
291
|
-
* AI Coaching Lift | duration_minutes | 1x | 1.2x | 1.2x
|
|
292
|
-
* Streak Retention | achievements/user | 1x | 2-3x | 2-3x
|
|
293
|
-
* Social Challenge Completion | challenges/user | 1x | 1.5x | 1.5x
|
|
294
|
-
* Resolver Churn Cliff | events after day 14 | 1x | 0.3x | -70%
|
|
295
|
-
* Coach Session Quality | satisfaction_score | 3.0 | 4.3 | 1.4x
|
|
296
|
-
* Coach Profile Enrichment | total_workouts | 0 | 350 | n/a
|
|
297
|
-
* Annual Funnel Lift | funnel conversion | 45% | 63% | 1.4x
|
|
298
|
-
* Workout Loop T2C | median min by tier | 1x | 0.77/1.25x| ~ 1.6x range
|
|
299
|
-
* Workout Magic Number | sweet duration_min | 1x | 1.35x | 1.35x
|
|
300
|
-
* Workout Magic Number | over D30+ post/pre | 1x | 0.29x | -71%
|
|
301
|
-
*/
|
|
302
|
-
|
|
303
|
-
// ── SCALE ──
|
|
304
|
-
const SEED = "dm4-fitness";
|
|
305
|
-
const NUM_USERS = 10_000;
|
|
306
|
-
const DATASET_START = "2026-01-01T00:00:00Z";
|
|
307
|
-
const DATASET_END = "2026-05-01T23:59:59Z";
|
|
308
|
-
const EVENTS_PER_DAY = 1.2;
|
|
309
|
-
const token = process.env.MP_TOKEN || "your-mixpanel-token";
|
|
310
|
-
|
|
311
|
-
const chance = u.initChance(SEED);
|
|
312
|
-
|
|
313
|
-
// ── KNOBS (tweak these to reshape stories) ──
|
|
314
|
-
const MORNING_HOUR_START = 5;
|
|
315
|
-
const MORNING_HOUR_END = 9;
|
|
316
|
-
const MORNING_CALORIE_MULT = 1.3;
|
|
317
|
-
|
|
318
|
-
const AI_LAUNCH_DAY = 35;
|
|
319
|
-
const AI_ADOPTION_LIKELIHOOD = 40;
|
|
320
|
-
const AI_DURATION_MULT = 1.2;
|
|
321
|
-
|
|
322
|
-
const GROUP_LAUNCH_DAY = 55;
|
|
323
|
-
const GROUP_ADOPTION_LIKELIHOOD = 30;
|
|
324
|
-
|
|
325
|
-
const STREAK_MIN_WORKOUTS = 2;
|
|
326
|
-
const STREAK_LINEAR_CAP = 3; // workouts 2-4 (count - 1 capped to 3) → 1 achievement each
|
|
327
|
-
const STREAK_SUPER_LINEAR_MULT = 4; // workouts 5+ → 4 achievements each
|
|
328
|
-
|
|
329
|
-
const SOCIAL_FRIEND_THRESHOLD = 3;
|
|
330
|
-
const SOCIAL_CHALLENGE_CLONE_FACTOR = 0.5;
|
|
331
|
-
|
|
332
|
-
const RESOLVER_EVENT_THRESHOLD = 30;
|
|
333
|
-
const RESOLVER_CLIFF_DAYS = 14;
|
|
334
|
-
const RESOLVER_DROP_LIKELIHOOD = 70;
|
|
335
|
-
|
|
336
|
-
const COACH_SESSION_SATISFACTION_MIN = 4.0;
|
|
337
|
-
const COACH_SESSION_SATISFACTION_MAX = 5.0;
|
|
338
|
-
|
|
339
|
-
const COACH_TOTAL_WORKOUTS_MIN = 200;
|
|
340
|
-
const COACH_TOTAL_WORKOUTS_MAX = 500;
|
|
341
|
-
const COACH_STREAK_DAYS_MIN = 60;
|
|
342
|
-
const COACH_STREAK_DAYS_MAX = 365;
|
|
343
|
-
|
|
344
|
-
const ANNUAL_FUNNEL_FREE_DROP_LIKELIHOOD = 30;
|
|
345
|
-
|
|
346
|
-
const TTC_ANNUAL_FACTOR = 0.77;
|
|
347
|
-
const TTC_FREE_FACTOR = 1.25;
|
|
348
|
-
|
|
349
|
-
const WORKOUT_SWEET_MIN = 12;
|
|
350
|
-
const WORKOUT_SWEET_MAX = 14;
|
|
351
|
-
const WORKOUT_OVER_THRESHOLD = 15;
|
|
352
|
-
const WORKOUT_DURATION_BOOST = 1.35;
|
|
353
|
-
const WORKOUT_OVER_CUTOFF_DAYS = 30;
|
|
354
|
-
const WORKOUT_OVER_DROP_LIKELIHOOD = 65;
|
|
355
|
-
|
|
356
|
-
// ── HELPER FUNCTIONS ──
|
|
357
|
-
function handleUserHooks(record) {
|
|
358
|
-
// H7: COACH PROFILE ENRICHMENT — coach segment users get high
|
|
359
|
-
// total_workouts + streak_days. Also assign subscription_tier by segment.
|
|
360
|
-
if (record.segment === "coach") {
|
|
361
|
-
record.total_workouts = chance.integer({ min: COACH_TOTAL_WORKOUTS_MIN, max: COACH_TOTAL_WORKOUTS_MAX });
|
|
362
|
-
record.streak_days = chance.integer({ min: COACH_STREAK_DAYS_MIN, max: COACH_STREAK_DAYS_MAX });
|
|
363
|
-
}
|
|
364
|
-
// Subscription tier: athletes/coaches → annual/family; social → monthly; casual/resolver → mostly free
|
|
365
|
-
if (record.segment === "athlete") {
|
|
366
|
-
record.subscription_tier = chance.pickone(["annual", "annual", "family", "monthly"]);
|
|
367
|
-
} else if (record.segment === "coach") {
|
|
368
|
-
record.subscription_tier = chance.pickone(["annual", "family", "family"]);
|
|
369
|
-
} else if (record.segment === "social") {
|
|
370
|
-
record.subscription_tier = chance.pickone(["monthly", "monthly", "annual", "free"]);
|
|
371
|
-
} else if (record.segment === "resolver") {
|
|
372
|
-
record.subscription_tier = chance.pickone(["free", "free", "free", "monthly"]);
|
|
373
|
-
} else {
|
|
374
|
-
record.subscription_tier = chance.pickone(["free", "free", "monthly"]);
|
|
375
|
-
}
|
|
376
|
-
return record;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function handleFunnelPostHooks(record, meta) {
|
|
380
|
-
// H9: WORKOUT LOOP TIME-TO-CONVERT — Annual/family complete 1.3x faster
|
|
381
|
-
// (factor 0.77); Free 1.25x slower (factor 1.25).
|
|
382
|
-
const tier = meta?.profile?.subscription_tier;
|
|
383
|
-
if (Array.isArray(record) && record.length > 1) {
|
|
384
|
-
const factor = (
|
|
385
|
-
tier === "annual" || tier === "family" ? TTC_ANNUAL_FACTOR :
|
|
386
|
-
tier === "free" ? TTC_FREE_FACTOR :
|
|
387
|
-
1.0
|
|
388
|
-
);
|
|
389
|
-
if (factor !== 1.0) {
|
|
390
|
-
for (let i = 1; i < record.length; i++) {
|
|
391
|
-
const prev = dayjs(record[i - 1].time);
|
|
392
|
-
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
393
|
-
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return record;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function handleEverythingHooks(record, meta) {
|
|
401
|
-
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
402
|
-
let events = record;
|
|
403
|
-
if (!events.length) return record;
|
|
404
|
-
|
|
405
|
-
// ── SUPERPROP STAMPING ──────────────────────────
|
|
406
|
-
// Stamp superProps from profile so they are consistent per user.
|
|
407
|
-
if (meta && meta.profile) {
|
|
408
|
-
const p = meta.profile;
|
|
409
|
-
events.forEach(e => {
|
|
410
|
-
if (p.Platform) e.Platform = p.Platform;
|
|
411
|
-
if (p.workout_type) e.workout_type = p.workout_type;
|
|
412
|
-
if (p.subscription_tier) e.subscription_tier = p.subscription_tier;
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// HOOK 1: MORNING WORKOUT BOOST — 5AM-9AM UTC workouts get
|
|
417
|
-
// calories_burned 1.3x. No flag — analyst breaks down by HOD.
|
|
418
|
-
events.forEach(e => {
|
|
419
|
-
if (e.event === "workout completed") {
|
|
420
|
-
const hour = new Date(e.time).getUTCHours();
|
|
421
|
-
if (hour >= MORNING_HOUR_START && hour < MORNING_HOUR_END && e.calories_burned) {
|
|
422
|
-
e.calories_burned = Math.floor(e.calories_burned * MORNING_CALORIE_MULT);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
// ── HOOK 2: POST-LAUNCH AI COACHING LIFT ────────────
|
|
428
|
-
// After day 35, ~40% of workouts switch to ai_assisted coaching,
|
|
429
|
-
// then ai_assisted workouts get 1.2x duration.
|
|
430
|
-
const AI_LAUNCH = datasetStart.add(AI_LAUNCH_DAY, "days");
|
|
431
|
-
events.forEach(e => {
|
|
432
|
-
if ((e.event === "workout completed" || e.event === "workout planned") &&
|
|
433
|
-
dayjs(e.time).isAfter(AI_LAUNCH)) {
|
|
434
|
-
// Adopt ai_assisted for ~40% of post-launch workouts
|
|
435
|
-
if (chance.bool({ likelihood: AI_ADOPTION_LIKELIHOOD })) {
|
|
436
|
-
e.coaching_mode = "ai_assisted";
|
|
437
|
-
}
|
|
438
|
-
// AI-assisted workouts get 1.2x duration
|
|
439
|
-
if (e.coaching_mode === "ai_assisted") {
|
|
440
|
-
if (e.duration_minutes) {
|
|
441
|
-
e.duration_minutes = Math.floor(e.duration_minutes * AI_DURATION_MULT);
|
|
442
|
-
}
|
|
443
|
-
if (e.planned_duration_minutes) {
|
|
444
|
-
e.planned_duration_minutes = Math.floor(e.planned_duration_minutes * AI_DURATION_MULT);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
// ── GROUP CHALLENGES ADOPTION ────────────────────
|
|
451
|
-
// After day 55, ~30% of challenge events switch to group mode.
|
|
452
|
-
const GROUP_LAUNCH = datasetStart.add(GROUP_LAUNCH_DAY, "days");
|
|
453
|
-
events.forEach(e => {
|
|
454
|
-
if ((e.event === "challenge joined" || e.event === "workout completed") &&
|
|
455
|
-
dayjs(e.time).isAfter(GROUP_LAUNCH) &&
|
|
456
|
-
chance.bool({ likelihood: GROUP_ADOPTION_LIKELIHOOD })) {
|
|
457
|
-
e.challenge_mode = "group";
|
|
458
|
-
}
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
// ── HOOK 8: ANNUAL SUBSCRIBER CONVERSION FILTER ─
|
|
462
|
-
// Free/monthly-tier users drop ~30% of "progress checked"
|
|
463
|
-
// (last step of Workout Loop funnel) to simulate lower conversion.
|
|
464
|
-
if (meta && meta.profile) {
|
|
465
|
-
const tier = meta.profile.subscription_tier;
|
|
466
|
-
if (tier !== "annual" && tier !== "family" && chance.bool({ likelihood: ANNUAL_FUNNEL_FREE_DROP_LIKELIHOOD })) {
|
|
467
|
-
record = record.filter(e => e.event !== "progress checked");
|
|
468
|
-
events = record;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// ── HOOK 3: STREAK RETENTION ─────────────────────
|
|
473
|
-
// Users with >=2 workouts get streak_days updated and
|
|
474
|
-
// cloned achievement events. Achievements scale super-
|
|
475
|
-
// linearly: 1 per workout for workouts 2-4, then 4 per
|
|
476
|
-
// workout beyond that.
|
|
477
|
-
const workoutEvents = events.filter(e => e.event === "workout completed");
|
|
478
|
-
if (workoutEvents.length >= STREAK_MIN_WORKOUTS) {
|
|
479
|
-
// Update profile streak_days via a profile update event
|
|
480
|
-
if (meta && meta.profile) {
|
|
481
|
-
meta.profile.streak_days = workoutEvents.length;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Super-linear achievement scaling:
|
|
485
|
-
// workouts 2-4: 1 achievement each
|
|
486
|
-
// workouts 5+: 4 achievements each
|
|
487
|
-
const templateAchievement = events.find(e => e.event === "achievement unlocked");
|
|
488
|
-
if (templateAchievement) {
|
|
489
|
-
let achievementCount = Math.min(workoutEvents.length - 1, STREAK_LINEAR_CAP); // 1 each for workouts 2-4
|
|
490
|
-
if (workoutEvents.length > 4) {
|
|
491
|
-
achievementCount += (workoutEvents.length - 4) * STREAK_SUPER_LINEAR_MULT; // 4 each for workouts 5+
|
|
492
|
-
}
|
|
493
|
-
for (let a = 0; a < achievementCount; a++) {
|
|
494
|
-
const srcIdx = Math.min(a, workoutEvents.length - 1);
|
|
495
|
-
const sourceEvent = workoutEvents[srcIdx];
|
|
496
|
-
events.push({
|
|
497
|
-
...templateAchievement,
|
|
498
|
-
time: dayjs(sourceEvent.time).add(chance.integer({ min: 1, max: 60 }), "minutes").toISOString(),
|
|
499
|
-
user_id: sourceEvent.user_id,
|
|
500
|
-
achievement_type: "streak_milestone",
|
|
501
|
-
streak_days_at_unlock: a + 2,
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// ── HOOK 4: SOCIAL CHALLENGE COMPLETION ──────────
|
|
508
|
-
// Users with >=3 friend_added events get 1.5x challenge completions.
|
|
509
|
-
const friendCount = events.filter(e => e.event === "friend added").length;
|
|
510
|
-
if (friendCount >= SOCIAL_FRIEND_THRESHOLD) {
|
|
511
|
-
const templateChallenge = events.find(e => e.event === "challenge completed");
|
|
512
|
-
if (templateChallenge) {
|
|
513
|
-
const challengeCompletions = events.filter(e => e.event === "challenge completed");
|
|
514
|
-
const extraCount = Math.max(1, Math.floor(challengeCompletions.length * SOCIAL_CHALLENGE_CLONE_FACTOR));
|
|
515
|
-
for (let i = 0; i < extraCount; i++) {
|
|
516
|
-
const source = challengeCompletions[i % challengeCompletions.length];
|
|
517
|
-
events.push({
|
|
518
|
-
...templateChallenge,
|
|
519
|
-
time: dayjs(source.time).add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
|
|
520
|
-
user_id: source.user_id,
|
|
521
|
-
challenge_type: source.challenge_type,
|
|
522
|
-
completion_pct: chance.integer({ min: 80, max: 100 }),
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// ── HOOK 5: RESOLVER CHURN CLIFF ─────────────────
|
|
529
|
-
// Resolver segment users with <30 events lose 70% after day 14.
|
|
530
|
-
if (meta && meta.profile && meta.profile.segment === "resolver" && events.length < RESOLVER_EVENT_THRESHOLD) {
|
|
531
|
-
const CHURN_CLIFF = datasetStart.add(RESOLVER_CLIFF_DAYS, "days");
|
|
532
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
533
|
-
const eventTime = dayjs(events[i].time);
|
|
534
|
-
if (eventTime.isAfter(CHURN_CLIFF) && chance.bool({ likelihood: RESOLVER_DROP_LIKELIHOOD })) {
|
|
535
|
-
events.splice(i, 1);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// HOOK 6: COACH SESSION QUALITY — coach-session satisfaction 4-5.
|
|
541
|
-
const hasCoachSessions = events.some(e => e.event === "coach session");
|
|
542
|
-
if (hasCoachSessions) {
|
|
543
|
-
events.forEach(e => {
|
|
544
|
-
if (e.event === "coach session") {
|
|
545
|
-
e.satisfaction_score = chance.floating({ min: COACH_SESSION_SATISFACTION_MIN, max: COACH_SESSION_SATISFACTION_MAX, fixed: 1 });
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// HOOK 10: WORKOUT-COUNT MAGIC NUMBER (no flags)
|
|
551
|
-
// Sweet 12-14 workouts → +35% on workout duration_minutes (peak
|
|
552
|
-
// progression). Over 15+ → drop 65% of post-day-30 non-workout
|
|
553
|
-
// events (overtraining → churn). Workout events are preserved
|
|
554
|
-
// so the bucket categorization stays consistent.
|
|
555
|
-
const workoutCount = events.filter(e => e.event === "workout completed").length;
|
|
556
|
-
if (workoutCount >= WORKOUT_SWEET_MIN && workoutCount <= WORKOUT_SWEET_MAX) {
|
|
557
|
-
events.forEach(e => {
|
|
558
|
-
if (e.event === "workout completed" && typeof e.duration_minutes === "number") {
|
|
559
|
-
e.duration_minutes = Math.round(e.duration_minutes * WORKOUT_DURATION_BOOST);
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
|
-
} else if (workoutCount >= WORKOUT_OVER_THRESHOLD) {
|
|
563
|
-
const day30 = datasetStart.add(WORKOUT_OVER_CUTOFF_DAYS, "days");
|
|
564
|
-
const preserveEvents = new Set(["workout completed", "progress checked"]);
|
|
565
|
-
for (let i = events.length - 1; i >= 0; i--) {
|
|
566
|
-
if (!preserveEvents.has(events[i].event) &&
|
|
567
|
-
dayjs(events[i].time).isAfter(day30) && chance.bool({ likelihood: WORKOUT_OVER_DROP_LIKELIHOOD })) {
|
|
568
|
-
events.splice(i, 1);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
return record;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// ── CONFIG ──
|
|
577
|
-
/** @type {Config} */
|
|
578
|
-
const config = {
|
|
579
|
-
version: 2,
|
|
580
|
-
seed: SEED,
|
|
581
|
-
datasetStart: DATASET_START,
|
|
582
|
-
datasetEnd: DATASET_END,
|
|
583
|
-
avgEventsPerUserPerDay: EVENTS_PER_DAY,
|
|
584
|
-
numUsers: NUM_USERS,
|
|
585
|
-
format: "json",
|
|
586
|
-
gzip: true,
|
|
587
|
-
credentials: {
|
|
588
|
-
token,
|
|
589
|
-
},
|
|
590
|
-
switches: {
|
|
591
|
-
hasSessionIds: true,
|
|
592
|
-
alsoInferFunnels: false,
|
|
593
|
-
hasLocation: true,
|
|
594
|
-
hasAndroidDevices: true,
|
|
595
|
-
hasIOSDevices: true,
|
|
596
|
-
hasDesktopDevices: false,
|
|
597
|
-
hasBrowser: false,
|
|
598
|
-
hasCampaigns: false,
|
|
599
|
-
isAnonymous: false,
|
|
600
|
-
hasAdSpend: false,
|
|
601
|
-
hasAvatar: true,
|
|
602
|
-
},
|
|
603
|
-
identity: {
|
|
604
|
-
avgDevicePerUser: 3,
|
|
605
|
-
},
|
|
606
|
-
concurrency: 1,
|
|
607
|
-
writeToDisk: false,
|
|
608
|
-
scdProps: {
|
|
609
|
-
fitness_level: {
|
|
610
|
-
values: ["beginner", "intermediate", "advanced", "elite"],
|
|
611
|
-
frequency: "month",
|
|
612
|
-
timing: "fuzzy",
|
|
613
|
-
max: 8
|
|
614
|
-
}
|
|
615
|
-
},
|
|
616
|
-
mirrorProps: {},
|
|
617
|
-
lookupTables: [],
|
|
618
|
-
|
|
619
|
-
// ── Events (18) ──────────────────────────────────────────
|
|
620
|
-
events: [
|
|
621
|
-
{
|
|
622
|
-
event: "account created",
|
|
623
|
-
weight: 1,
|
|
624
|
-
isFirstEvent: true,
|
|
625
|
-
isAuthEvent: true,
|
|
626
|
-
properties: {
|
|
627
|
-
referral_source: ["organic", "friend_invite", "app_store", "social_media", "search"],
|
|
628
|
-
},
|
|
629
|
-
},
|
|
630
|
-
{
|
|
631
|
-
event: "workout completed",
|
|
632
|
-
weight: 8,
|
|
633
|
-
isStrictEvent: false,
|
|
634
|
-
properties: {
|
|
635
|
-
duration_minutes: u.weighNumRange(10, 90, 0.5, 40),
|
|
636
|
-
calories_burned: u.weighNumRange(50, 800, 0.4, 300),
|
|
637
|
-
heart_rate_avg: u.weighNumRange(80, 185, 0.5, 130),
|
|
638
|
-
satisfaction_score: u.weighNumRange(1, 5, 0.7, 3),
|
|
639
|
-
coaching_mode: ["self_guided"],
|
|
640
|
-
challenge_mode: ["solo"],
|
|
641
|
-
},
|
|
642
|
-
},
|
|
643
|
-
{
|
|
644
|
-
event: "workout planned",
|
|
645
|
-
weight: 6,
|
|
646
|
-
isStrictEvent: false,
|
|
647
|
-
properties: {
|
|
648
|
-
planned_duration_minutes: u.weighNumRange(15, 90, 0.5, 45),
|
|
649
|
-
day_of_week: ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"],
|
|
650
|
-
coaching_mode: ["self_guided"],
|
|
651
|
-
},
|
|
652
|
-
},
|
|
653
|
-
{
|
|
654
|
-
event: "meal logged",
|
|
655
|
-
weight: 7,
|
|
656
|
-
properties: {
|
|
657
|
-
meal_type: ["breakfast", "lunch", "dinner", "snack"],
|
|
658
|
-
calories: u.weighNumRange(50, 1200, 0.5, 400),
|
|
659
|
-
protein_g: u.weighNumRange(0, 60, 0.5, 20),
|
|
660
|
-
meal_quality: ["healthy", "healthy", "balanced", "balanced", "indulgent"],
|
|
661
|
-
},
|
|
662
|
-
},
|
|
663
|
-
{
|
|
664
|
-
event: "challenge joined",
|
|
665
|
-
weight: 3,
|
|
666
|
-
isStrictEvent: false,
|
|
667
|
-
properties: {
|
|
668
|
-
challenge_type: ["steps", "calories", "streak", "strength", "team_relay"],
|
|
669
|
-
duration_days: [7, 14, 21, 30],
|
|
670
|
-
participants: u.weighNumRange(2, 50, 0.3, 10),
|
|
671
|
-
challenge_mode: ["solo"],
|
|
672
|
-
},
|
|
673
|
-
},
|
|
674
|
-
{
|
|
675
|
-
event: "challenge completed",
|
|
676
|
-
weight: 2,
|
|
677
|
-
isStrictEvent: false,
|
|
678
|
-
properties: {
|
|
679
|
-
challenge_type: ["steps", "calories", "streak", "strength", "team_relay"],
|
|
680
|
-
final_rank: u.weighNumRange(1, 50, 0.3, 10),
|
|
681
|
-
completion_pct: u.weighNumRange(50, 100, 0.7, 85),
|
|
682
|
-
challenge_mode: ["solo"],
|
|
683
|
-
},
|
|
684
|
-
},
|
|
685
|
-
{
|
|
686
|
-
event: "achievement unlocked",
|
|
687
|
-
weight: 2,
|
|
688
|
-
isStrictEvent: false,
|
|
689
|
-
properties: {
|
|
690
|
-
achievement_type: ["streak_milestone", "weight_goal", "distance_record", "calories_target", "social_champion", "first_workout"],
|
|
691
|
-
streak_days_at_unlock: u.weighNumRange(1, 100, 0.3, 10),
|
|
692
|
-
},
|
|
693
|
-
},
|
|
694
|
-
{
|
|
695
|
-
event: "friend added",
|
|
696
|
-
weight: 2,
|
|
697
|
-
isStrictEvent: false,
|
|
698
|
-
properties: {
|
|
699
|
-
source: ["search", "contacts", "challenge", "suggestion", "qr_code"],
|
|
700
|
-
},
|
|
701
|
-
},
|
|
702
|
-
{
|
|
703
|
-
event: "leaderboard viewed",
|
|
704
|
-
weight: 4,
|
|
705
|
-
properties: {
|
|
706
|
-
leaderboard_type: ["friends", "global", "challenge", "local"],
|
|
707
|
-
user_rank: u.weighNumRange(1, 500, 0.3, 50),
|
|
708
|
-
},
|
|
709
|
-
},
|
|
710
|
-
{
|
|
711
|
-
event: "progress checked",
|
|
712
|
-
weight: 5,
|
|
713
|
-
isStrictEvent: false,
|
|
714
|
-
properties: {
|
|
715
|
-
metric_viewed: ["weight", "body_fat", "steps", "calories_burned", "workout_count", "streaks"],
|
|
716
|
-
trend_direction: ["improving", "improving", "stable", "declining"],
|
|
717
|
-
time_range: ["week", "month", "3_months", "year"],
|
|
718
|
-
},
|
|
719
|
-
},
|
|
720
|
-
{
|
|
721
|
-
event: "coach session",
|
|
722
|
-
weight: 3,
|
|
723
|
-
isStrictEvent: false,
|
|
724
|
-
properties: {
|
|
725
|
-
session_type: ["live_video", "chat", "plan_review", "form_check"],
|
|
726
|
-
duration_minutes: u.weighNumRange(10, 60, 0.5, 30),
|
|
727
|
-
satisfaction_score: u.weighNumRange(1, 5, 0.6, 3),
|
|
728
|
-
coach_speciality: ["strength", "cardio", "nutrition", "yoga", "general"],
|
|
729
|
-
},
|
|
730
|
-
},
|
|
731
|
-
{
|
|
732
|
-
event: "nutrition plan viewed",
|
|
733
|
-
weight: 4,
|
|
734
|
-
properties: {
|
|
735
|
-
plan_type: ["weight_loss", "muscle_gain", "maintenance", "custom"],
|
|
736
|
-
adherence_pct: u.weighNumRange(0, 100, 0.5, 60),
|
|
737
|
-
},
|
|
738
|
-
},
|
|
739
|
-
{
|
|
740
|
-
event: "heart rate recorded",
|
|
741
|
-
weight: 3,
|
|
742
|
-
properties: {
|
|
743
|
-
bpm: u.weighNumRange(50, 200, 0.5, 110),
|
|
744
|
-
activity_state: ["resting", "warmup", "active", "peak", "cooldown"],
|
|
745
|
-
device: ["watch", "chest_strap", "phone_sensor"],
|
|
746
|
-
},
|
|
747
|
-
},
|
|
748
|
-
{
|
|
749
|
-
event: "app session",
|
|
750
|
-
weight: 8,
|
|
751
|
-
properties: {
|
|
752
|
-
session_duration_sec: u.weighNumRange(10, 1800, 0.4, 120),
|
|
753
|
-
pages_viewed: u.weighNumRange(1, 15, 0.5, 3),
|
|
754
|
-
},
|
|
755
|
-
},
|
|
756
|
-
{
|
|
757
|
-
event: "notification received",
|
|
758
|
-
weight: 5,
|
|
759
|
-
properties: {
|
|
760
|
-
notification_type: ["workout_reminder", "workout_reminder", "challenge_update", "friend_activity", "streak_warning", "coaching_tip"],
|
|
761
|
-
channel: ["push", "push", "email", "sms"],
|
|
762
|
-
opened: [true, true, true, false],
|
|
763
|
-
},
|
|
764
|
-
},
|
|
765
|
-
{
|
|
766
|
-
event: "subscription managed",
|
|
767
|
-
weight: 2,
|
|
768
|
-
properties: {
|
|
769
|
-
action: ["viewed_plans", "started_trial", "upgraded", "downgraded", "cancelled", "renewed"],
|
|
770
|
-
plan_viewed: ["free", "monthly", "annual", "family"],
|
|
771
|
-
},
|
|
772
|
-
},
|
|
773
|
-
{
|
|
774
|
-
event: "profile updated",
|
|
775
|
-
weight: 2,
|
|
776
|
-
isStrictEvent: false,
|
|
777
|
-
properties: {
|
|
778
|
-
field_updated: ["weight", "height", "goal", "avatar", "workout_preferences", "notification_settings"],
|
|
779
|
-
},
|
|
780
|
-
},
|
|
781
|
-
{
|
|
782
|
-
event: "account deactivated",
|
|
783
|
-
weight: 1,
|
|
784
|
-
isChurnEvent: true,
|
|
785
|
-
returnLikelihood: 0.15,
|
|
786
|
-
isStrictEvent: true,
|
|
787
|
-
properties: {
|
|
788
|
-
reason: ["lost_motivation", "cost", "switched_app", "injury", "achieved_goal"],
|
|
789
|
-
},
|
|
790
|
-
},
|
|
791
|
-
],
|
|
792
|
-
|
|
793
|
-
// ── Funnels (5) ──────────────────────────────────────────
|
|
794
|
-
funnels: [
|
|
795
|
-
{
|
|
796
|
-
name: "Onboarding",
|
|
797
|
-
sequence: ["account created", "profile updated", "workout planned", "workout completed"],
|
|
798
|
-
conversionRate: 45,
|
|
799
|
-
order: "sequential",
|
|
800
|
-
isFirstFunnel: true,
|
|
801
|
-
timeToConvert: 72,
|
|
802
|
-
weight: 3,
|
|
803
|
-
},
|
|
804
|
-
{
|
|
805
|
-
name: "Workout Loop",
|
|
806
|
-
sequence: ["workout planned", "workout completed", "progress checked"],
|
|
807
|
-
conversionRate: 45,
|
|
808
|
-
order: "sequential",
|
|
809
|
-
timeToConvert: 48,
|
|
810
|
-
weight: 5,
|
|
811
|
-
reentry: true,
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
name: "Social Engagement",
|
|
815
|
-
sequence: ["friend added", "leaderboard viewed", "challenge joined"],
|
|
816
|
-
conversionRate: 35,
|
|
817
|
-
order: "sequential",
|
|
818
|
-
timeToConvert: 96,
|
|
819
|
-
weight: 3,
|
|
820
|
-
},
|
|
821
|
-
{
|
|
822
|
-
name: "Challenge Completion",
|
|
823
|
-
sequence: ["challenge joined", "workout completed", "challenge completed", "achievement unlocked"],
|
|
824
|
-
conversionRate: 30,
|
|
825
|
-
order: "sequential",
|
|
826
|
-
timeToConvert: 336,
|
|
827
|
-
weight: 2,
|
|
828
|
-
},
|
|
829
|
-
{
|
|
830
|
-
name: "Coaching Path",
|
|
831
|
-
sequence: ["coach session", "workout planned", "workout completed", "progress checked"],
|
|
832
|
-
conversionRate: 50,
|
|
833
|
-
order: "sequential",
|
|
834
|
-
timeToConvert: 72,
|
|
835
|
-
weight: 2,
|
|
836
|
-
},
|
|
837
|
-
],
|
|
838
|
-
|
|
839
|
-
// ── SuperProps ──────────────────────────────────────────
|
|
840
|
-
superProps: {
|
|
841
|
-
Platform: ["ios", "ios", "android"],
|
|
842
|
-
workout_type: ["strength", "cardio", "yoga", "hiit", "running", "cycling"],
|
|
843
|
-
subscription_tier: ["free"],
|
|
844
|
-
},
|
|
845
|
-
|
|
846
|
-
// ── UserProps ──────────────────────────────────────────
|
|
847
|
-
userProps: {
|
|
848
|
-
fitness_level: ["beginner"],
|
|
849
|
-
segment: ["casual"],
|
|
850
|
-
streak_days: [0],
|
|
851
|
-
total_workouts: u.weighNumRange(0, 0, 0.5),
|
|
852
|
-
preferred_workout: ["strength", "cardio", "yoga", "hiit", "running", "cycling"],
|
|
853
|
-
goal: ["weight_loss", "muscle_gain", "endurance", "flexibility", "general_health"],
|
|
854
|
-
Platform: ["ios", "ios", "android"],
|
|
855
|
-
workout_type: ["strength", "cardio", "yoga", "hiit", "running", "cycling"],
|
|
856
|
-
subscription_tier: ["free"],
|
|
857
|
-
},
|
|
858
|
-
|
|
859
|
-
// ── Personas ──────────────────────────────────
|
|
860
|
-
personas: [
|
|
861
|
-
{
|
|
862
|
-
name: "athlete",
|
|
863
|
-
weight: 10,
|
|
864
|
-
eventMultiplier: 4.0,
|
|
865
|
-
conversionModifier: 1.5,
|
|
866
|
-
churnRate: 0.02,
|
|
867
|
-
properties: {
|
|
868
|
-
fitness_level: "advanced",
|
|
869
|
-
segment: "athlete",
|
|
870
|
-
},
|
|
871
|
-
},
|
|
872
|
-
{
|
|
873
|
-
name: "casual_exerciser",
|
|
874
|
-
weight: 40,
|
|
875
|
-
eventMultiplier: 1.0,
|
|
876
|
-
conversionModifier: 0.8,
|
|
877
|
-
churnRate: 0.08,
|
|
878
|
-
properties: {
|
|
879
|
-
fitness_level: "intermediate",
|
|
880
|
-
segment: "casual",
|
|
881
|
-
},
|
|
882
|
-
},
|
|
883
|
-
{
|
|
884
|
-
name: "new_year_resolver",
|
|
885
|
-
weight: 25,
|
|
886
|
-
eventMultiplier: 0.6,
|
|
887
|
-
conversionModifier: 0.5,
|
|
888
|
-
churnRate: 0.4,
|
|
889
|
-
properties: {
|
|
890
|
-
fitness_level: "beginner",
|
|
891
|
-
segment: "resolver",
|
|
892
|
-
},
|
|
893
|
-
activeWindow: { maxDays: 30 },
|
|
894
|
-
},
|
|
895
|
-
{
|
|
896
|
-
name: "social_motivator",
|
|
897
|
-
weight: 15,
|
|
898
|
-
eventMultiplier: 2.0,
|
|
899
|
-
conversionModifier: 1.2,
|
|
900
|
-
churnRate: 0.05,
|
|
901
|
-
properties: {
|
|
902
|
-
fitness_level: "intermediate",
|
|
903
|
-
segment: "social",
|
|
904
|
-
},
|
|
905
|
-
},
|
|
906
|
-
{
|
|
907
|
-
name: "coach",
|
|
908
|
-
weight: 10,
|
|
909
|
-
eventMultiplier: 3.0,
|
|
910
|
-
conversionModifier: 1.5,
|
|
911
|
-
churnRate: 0.01,
|
|
912
|
-
properties: {
|
|
913
|
-
fitness_level: "expert",
|
|
914
|
-
segment: "coach",
|
|
915
|
-
},
|
|
916
|
-
},
|
|
917
|
-
],
|
|
918
|
-
|
|
919
|
-
// ── Engagement Decay ──────────────────────────
|
|
920
|
-
engagementDecay: {
|
|
921
|
-
model: "step",
|
|
922
|
-
halfLife: 30,
|
|
923
|
-
floor: 0.1,
|
|
924
|
-
reactivationChance: 0.02,
|
|
925
|
-
},
|
|
926
|
-
|
|
927
|
-
hook(record, type, meta) {
|
|
928
|
-
if (type === "user") return handleUserHooks(record);
|
|
929
|
-
if (type === "funnel-post") return handleFunnelPostHooks(record, meta);
|
|
930
|
-
if (type === "everything") return handleEverythingHooks(record, meta);
|
|
931
|
-
return record;
|
|
932
|
-
},
|
|
933
|
-
};
|
|
934
|
-
|
|
935
|
-
export default config;
|