@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,904 +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
|
-
import * as v from "ak-tools";
|
|
8
|
-
/** @typedef {import("../../types").Dungeon} Config */
|
|
9
|
-
|
|
10
|
-
// ── OVERVIEW ──
|
|
11
|
-
/*
|
|
12
|
-
* NAME: Chirp
|
|
13
|
-
* APP: Twitter+Instagram-style social media platform with algorithmic
|
|
14
|
-
* feed, creator monetization, communities, and direct messaging.
|
|
15
|
-
* Power users become "creators" with subscriber tiers; native ads
|
|
16
|
-
* woven into feed and story placements.
|
|
17
|
-
* SCALE: 10,000 users, ~1.4M events, 121 days (2026-01-01 → 2026-05-01)
|
|
18
|
-
* CORE LOOP: account created → profile updated → post created → consume + engage
|
|
19
|
-
*
|
|
20
|
-
* EVENTS (18):
|
|
21
|
-
* post viewed (30) > post liked (18) > story viewed (15) > post created (12)
|
|
22
|
-
* > notification received (12) > comment posted (10) > ad viewed (10)
|
|
23
|
-
* > user followed (8) > dm sent (8) > search performed (7) > post shared (6)
|
|
24
|
-
* > story created (5) > profile updated (3) > user unfollowed (2)
|
|
25
|
-
* > ad clicked (2) > creator subscription started (2) > report submitted (1)
|
|
26
|
-
* > account created (1)
|
|
27
|
-
*
|
|
28
|
-
* FUNNELS (8):
|
|
29
|
-
* - Onboarding: account created → profile updated → post created (70%)
|
|
30
|
-
* - Feed Engagement: post viewed → post liked → comment posted (45%)
|
|
31
|
-
* - Content Cycle: post created → post viewed → post liked → post shared (30%)
|
|
32
|
-
* - Stories: story created → story viewed → dm sent (40%)
|
|
33
|
-
* - Discovery: search performed → post viewed → user followed (35%)
|
|
34
|
-
* - Notifications: notification received → post viewed → post liked (50%)
|
|
35
|
-
* - Creator Monetize: profile updated → creator subscription started → post created (15%)
|
|
36
|
-
* - Ad Moderation: ad viewed → ad clicked → report submitted (20%)
|
|
37
|
-
*
|
|
38
|
-
* USER PROPS: app_version, account_type, follower_count, following_count,
|
|
39
|
-
* bio_length, verified, content_niche
|
|
40
|
-
* SUPER PROPS: app_version, account_type
|
|
41
|
-
* SCD PROPS: account_type (personal/creator/business/verified, monthly fuzzy, max 6),
|
|
42
|
-
* community_status (new/growing/established/featured, monthly fixed,
|
|
43
|
-
* max 6, community_id-scoped)
|
|
44
|
-
* GROUPS: community_id (100 communities)
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
// ── HOOK STORIES ──
|
|
48
|
-
/*
|
|
49
|
-
* NOTE: All cohort effects are HIDDEN — discoverable only via behavioral cohorts
|
|
50
|
-
* (count an event per user, then measure downstream). No cohort flag is stamped
|
|
51
|
-
* on events. Algorithm-change source flips and engagement-bait short durations
|
|
52
|
-
* are raw mutations of config-defined props.
|
|
53
|
-
*
|
|
54
|
-
* -------------------------------------------------------------------------------------
|
|
55
|
-
* 1. VIRAL CONTENT CASCADE (everything)
|
|
56
|
-
* -------------------------------------------------------------------------------------
|
|
57
|
-
*
|
|
58
|
-
* PATTERN: 5% of users with 10+ "post created" events become viral creators.
|
|
59
|
-
* Each of their posts triggers 10-20 cloned post-viewed, post-liked, and
|
|
60
|
-
* post-shared events with unique offset timestamps.
|
|
61
|
-
*
|
|
62
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
63
|
-
*
|
|
64
|
-
* Report 1: Engagement per Post Created (cohort)
|
|
65
|
-
* - Report type: Insights (with cohort)
|
|
66
|
-
* - Cohort A: users with >= 10 "post created"
|
|
67
|
-
* - Cohort B: users with < 10 "post created"
|
|
68
|
-
* - Event: "post viewed"
|
|
69
|
-
* - Measure: Total per user
|
|
70
|
-
* - Compare A vs B
|
|
71
|
-
* - Expected: cohort A ~ 10-20x more views per user than B
|
|
72
|
-
*
|
|
73
|
-
* Report 2: Likes per User by Post-Created Bucket
|
|
74
|
-
* - Report type: Insights (with cohort)
|
|
75
|
-
* - Same cohorts
|
|
76
|
-
* - Event: "post liked"
|
|
77
|
-
* - Measure: Total per user
|
|
78
|
-
* - Expected: A ~ 10-20x more likes per user than B
|
|
79
|
-
*
|
|
80
|
-
* REAL-WORLD ANALOGUE: A small share of creators drive a disproportionate
|
|
81
|
-
* fraction of all platform engagement.
|
|
82
|
-
*
|
|
83
|
-
* -------------------------------------------------------------------------------------
|
|
84
|
-
* 2. FOLLOW-BACK SNOWBALL (everything)
|
|
85
|
-
* -------------------------------------------------------------------------------------
|
|
86
|
-
*
|
|
87
|
-
* PATTERN: Users with 5+ "user followed" events have 50% of their posts
|
|
88
|
-
* duplicated with a 30-240 minute offset, plus an extra comment cloned.
|
|
89
|
-
* No flag — discover by binning users on user-followed count.
|
|
90
|
-
*
|
|
91
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
92
|
-
*
|
|
93
|
-
* Report 1: Posts per User by Follow Activity
|
|
94
|
-
* - Report type: Insights (with cohort)
|
|
95
|
-
* - Cohort A: users with >= 5 "user followed"
|
|
96
|
-
* - Cohort B: users with < 5 "user followed"
|
|
97
|
-
* - Event: "post created"
|
|
98
|
-
* - Measure: Total per user
|
|
99
|
-
* - Expected: A ~ 1.5x posts per user vs B
|
|
100
|
-
*
|
|
101
|
-
* REAL-WORLD ANALOGUE: Users who actively follow many people tend to
|
|
102
|
-
* receive follow-backs and post more frequently.
|
|
103
|
-
*
|
|
104
|
-
* -------------------------------------------------------------------------------------
|
|
105
|
-
* 3. ALGORITHM CHANGE (everything)
|
|
106
|
-
* -------------------------------------------------------------------------------------
|
|
107
|
-
*
|
|
108
|
-
* PATTERN: On day 45, the dominant `source` for "post viewed" flips from
|
|
109
|
-
* "feed" (70% pre) to "explore" (70% post). Mutates an existing config-
|
|
110
|
-
* defined prop — no flag.
|
|
111
|
-
*
|
|
112
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
113
|
-
*
|
|
114
|
-
* Report 1: Source Distribution Over Time
|
|
115
|
-
* - Report type: Insights
|
|
116
|
-
* - Event: "post viewed"
|
|
117
|
-
* - Measure: Total
|
|
118
|
-
* - Breakdown: "source"
|
|
119
|
-
* - Line chart by day
|
|
120
|
-
* - Expected: feed dominates pre-day-45; explore dominates post-day-45
|
|
121
|
-
*
|
|
122
|
-
* REAL-WORLD ANALOGUE: Algorithmic feed redesigns shift content discovery
|
|
123
|
-
* from chronological to interest-based.
|
|
124
|
-
*
|
|
125
|
-
* -------------------------------------------------------------------------------------
|
|
126
|
-
* 4. ENGAGEMENT BAIT (event)
|
|
127
|
-
* -------------------------------------------------------------------------------------
|
|
128
|
-
*
|
|
129
|
-
* PATTERN: 20% of "post viewed" events get view_duration_sec collapsed
|
|
130
|
-
* to 1-5 seconds. No flag — analyst sees a bimodal duration distribution.
|
|
131
|
-
*
|
|
132
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
133
|
-
*
|
|
134
|
-
* Report 1: View Duration Distribution
|
|
135
|
-
* - Report type: Insights
|
|
136
|
-
* - Event: "post viewed"
|
|
137
|
-
* - Measure: Distribution of "view_duration_sec"
|
|
138
|
-
* - Expected: ~ 20% of values cluster at 1-5 sec; rest at 5-120 sec
|
|
139
|
-
*
|
|
140
|
-
* REAL-WORLD ANALOGUE: Clickbait posts collect impressions but bounce
|
|
141
|
-
* quality is awful, dragging down avg watch time.
|
|
142
|
-
*
|
|
143
|
-
* -------------------------------------------------------------------------------------
|
|
144
|
-
* 5. NOTIFICATION RE-ENGAGEMENT (everything)
|
|
145
|
-
* -------------------------------------------------------------------------------------
|
|
146
|
-
*
|
|
147
|
-
* PATTERN: After day 30, 30% of "post viewed" events have source flipped
|
|
148
|
-
* to "notification". Mutates the existing config-defined `source` prop.
|
|
149
|
-
*
|
|
150
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
151
|
-
*
|
|
152
|
-
* Report 1: Notification-Sourced Views Over Time
|
|
153
|
-
* - Report type: Insights
|
|
154
|
-
* - Event: "post viewed"
|
|
155
|
-
* - Measure: Total
|
|
156
|
-
* - Filter: source = "notification"
|
|
157
|
-
* - Line chart by day
|
|
158
|
-
* - Expected: near-zero before day 30, then ~ 30% of views
|
|
159
|
-
*
|
|
160
|
-
* REAL-WORLD ANALOGUE: Push notifications about trending content are a
|
|
161
|
-
* primary lever for waking up dormant users.
|
|
162
|
-
*
|
|
163
|
-
* -------------------------------------------------------------------------------------
|
|
164
|
-
* 6. CREATOR MONETIZATION (everything)
|
|
165
|
-
* -------------------------------------------------------------------------------------
|
|
166
|
-
*
|
|
167
|
-
* PATTERN: Users with any "creator subscription started" event get 2 extra
|
|
168
|
-
* cloned posts and stories per original (3x rate), plus 25% extra cloned
|
|
169
|
-
* post-viewed events from `source="profile"`. No flag — discover via
|
|
170
|
-
* cohort builder.
|
|
171
|
-
*
|
|
172
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
173
|
-
*
|
|
174
|
-
* Report 1: Posts per User — Subscribers vs Not
|
|
175
|
-
* - Report type: Insights (with cohort)
|
|
176
|
-
* - Cohort A: users with >= 1 "creator subscription started"
|
|
177
|
-
* - Cohort B: users with 0
|
|
178
|
-
* - Event: "post created"
|
|
179
|
-
* - Measure: Total per user
|
|
180
|
-
* - Expected: A ~ 3x posts per user
|
|
181
|
-
*
|
|
182
|
-
* REAL-WORLD ANALOGUE: Creators with paying subscribers publish more often.
|
|
183
|
-
*
|
|
184
|
-
* -------------------------------------------------------------------------------------
|
|
185
|
-
* 7. TOXICITY CHURN (everything)
|
|
186
|
-
* -------------------------------------------------------------------------------------
|
|
187
|
-
*
|
|
188
|
-
* PATTERN: Users with 2+ "report submitted" events lose 60% of activity
|
|
189
|
-
* after day 30. No flag — discover via retention or per-user activity drop.
|
|
190
|
-
*
|
|
191
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
192
|
-
*
|
|
193
|
-
* Report 1: Retention by Toxicity
|
|
194
|
-
* - Report type: Retention
|
|
195
|
-
* - Cohort A: users with >= 2 "report submitted"
|
|
196
|
-
* - Cohort B: rest
|
|
197
|
-
* - Expected: A ~ 40% retention vs B ~ 80%
|
|
198
|
-
*
|
|
199
|
-
* REAL-WORLD ANALOGUE: Repeated reporters are signaling dissatisfaction
|
|
200
|
-
* and often quietly churn.
|
|
201
|
-
*
|
|
202
|
-
* -------------------------------------------------------------------------------------
|
|
203
|
-
* 8. WEEKEND CONTENT SURGE (everything)
|
|
204
|
-
* -------------------------------------------------------------------------------------
|
|
205
|
-
*
|
|
206
|
-
* PATTERN: 30% of Sat/Sun "post created" and "story created" events get a
|
|
207
|
-
* duplicate cloned 1-3 hours later. No flag — discover via day-of-week chart.
|
|
208
|
-
*
|
|
209
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
210
|
-
*
|
|
211
|
-
* Report 1: Posts by Day of Week
|
|
212
|
-
* - Report type: Insights
|
|
213
|
-
* - Event: "post created"
|
|
214
|
-
* - Measure: Total
|
|
215
|
-
* - Breakdown: Day of week
|
|
216
|
-
* - Expected: Sat/Sun ~ 1.3x weekday bars
|
|
217
|
-
*
|
|
218
|
-
* REAL-WORLD ANALOGUE: Weekend leisure time produces a natural creation surge.
|
|
219
|
-
*
|
|
220
|
-
* -------------------------------------------------------------------------------------
|
|
221
|
-
* 9. POST-CREATED MAGIC NUMBER (everything)
|
|
222
|
-
* -------------------------------------------------------------------------------------
|
|
223
|
-
*
|
|
224
|
-
* PATTERN: Users in the 3-7 post-created sweet spot get +40% comment_length
|
|
225
|
-
* on their comment-posted events (richer engagement). Users with 8+ posts
|
|
226
|
-
* are over-engaged (burnout); ~30% of their post-liked and comment-posted
|
|
227
|
-
* events are dropped. No flag — discover by binning users on post count.
|
|
228
|
-
*
|
|
229
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
230
|
-
*
|
|
231
|
-
* Report 1: Comment Length by Post Bucket
|
|
232
|
-
* - Report type: Insights (with cohort)
|
|
233
|
-
* - Cohort A: users with 3-7 "post created"
|
|
234
|
-
* - Cohort B: users with 0-2 "post created"
|
|
235
|
-
* - Event: "comment posted"
|
|
236
|
-
* - Measure: Average of "comment_length"
|
|
237
|
-
* - Expected: A ~ 1.4x B
|
|
238
|
-
*
|
|
239
|
-
* Report 2: Engagement Drop on Heavy Posters
|
|
240
|
-
* - Report type: Insights (with cohort)
|
|
241
|
-
* - Cohort C: users with >= 8 "post created"
|
|
242
|
-
* - Cohort A: users with 3-7 "post created"
|
|
243
|
-
* - Event: "post liked" + "comment posted" (combined per user)
|
|
244
|
-
* - Measure: Total per user
|
|
245
|
-
* - Expected: C ~ 30% fewer engagement events per user vs A
|
|
246
|
-
*
|
|
247
|
-
* REAL-WORLD ANALOGUE: Moderate posters write thoughtful comments; the
|
|
248
|
-
* over-prolific tend to spam and burn through audience patience.
|
|
249
|
-
*
|
|
250
|
-
* -------------------------------------------------------------------------------------
|
|
251
|
-
* 10. ONBOARDING TIME-TO-CONVERT (funnel-post)
|
|
252
|
-
* -------------------------------------------------------------------------------------
|
|
253
|
-
*
|
|
254
|
-
* PATTERN: Creator and business account_type users complete the
|
|
255
|
-
* onboarding funnel (account created -> profile updated -> post created)
|
|
256
|
-
* 1.4x faster (factor 0.71). Personal account users complete it 1.25x
|
|
257
|
-
* slower (factor 1.25). The hook intercepts funnel-post arrays, computes
|
|
258
|
-
* the time gap between consecutive steps, and scales each gap by the
|
|
259
|
-
* account-type-specific factor before rewriting the step timestamps.
|
|
260
|
-
*
|
|
261
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
262
|
-
*
|
|
263
|
-
* Report 1: Onboarding Funnel Median TTC by Account Type
|
|
264
|
-
* - Report type: Funnels
|
|
265
|
-
* - Steps: "account created" -> "profile updated" -> "post created"
|
|
266
|
-
* - Measure: Median time to convert
|
|
267
|
-
* - Breakdown: "account_type" (superProp)
|
|
268
|
-
* - Expected: creator/business ~ 0.71x baseline; personal ~ 1.25x baseline
|
|
269
|
-
*
|
|
270
|
-
* NOTE (funnel-post measurement): visible only via Mixpanel funnel median
|
|
271
|
-
* TTC. Cross-event MIN→MIN SQL queries on raw events do NOT show this.
|
|
272
|
-
*
|
|
273
|
-
* REAL-WORLD ANALOGUE: Creators and businesses arrive with clear intent
|
|
274
|
-
* and complete profile setup faster; personal users browse casually and
|
|
275
|
-
* take longer to commit to their first post.
|
|
276
|
-
*
|
|
277
|
-
* =====================================================================================
|
|
278
|
-
* EXPECTED METRICS SUMMARY
|
|
279
|
-
* =====================================================================================
|
|
280
|
-
*
|
|
281
|
-
* Hook | Metric | Baseline | Hook Effect | Ratio
|
|
282
|
-
* --------------------------|-------------------------|----------|-------------|------
|
|
283
|
-
* Viral Content Cascade | Engagement per user | 1x | ~ 15x | ~ 15x
|
|
284
|
-
* Follow-Back Snowball | Posts per user | 1x | ~ 1.5x | 1.5x
|
|
285
|
-
* Algorithm Change | feed vs explore (post) | 70/15 | 15/70 | flip
|
|
286
|
-
* Engagement Bait | View duration distrib | unimodal | bimodal | n/a
|
|
287
|
-
* Notification Re-engage | source=notification % | ~ 10% | ~ 30% | 3x
|
|
288
|
-
* Creator Monetization | Content rate (sub vs not)| 1x | ~ 3x | 3x
|
|
289
|
-
* Toxicity Churn | Post-day-30 activity | 1x | ~ 0.4x | -60%
|
|
290
|
-
* Weekend Surge | Weekend vs weekday | 1x | ~ 1.3x | 1.3x
|
|
291
|
-
* Post-Created Magic Number | sweet comment_length | 1x | ~ 1.4x | 1.4x
|
|
292
|
-
* Post-Created Magic Number | over engagement/user | 1x | ~ 0.7x | -30%
|
|
293
|
-
* Onboarding TTC | median TTC by acct type | 1x | 0.71/1.25x | ~ 1.8x range
|
|
294
|
-
* =====================================================================================
|
|
295
|
-
*/
|
|
296
|
-
|
|
297
|
-
// ── SCALE ──
|
|
298
|
-
const SEED = "harness-social";
|
|
299
|
-
const NUM_USERS = 10_000;
|
|
300
|
-
const DATASET_START = "2026-01-01T00:00:00Z";
|
|
301
|
-
const DATASET_END = "2026-05-01T23:59:59Z";
|
|
302
|
-
const EVENTS_PER_DAY = 1.2;
|
|
303
|
-
const token = process.env.MP_TOKEN || "your-mixpanel-token";
|
|
304
|
-
|
|
305
|
-
const chance = u.initChance(SEED);
|
|
306
|
-
|
|
307
|
-
// ── KNOBS (tweak these to reshape stories) ──
|
|
308
|
-
const VIRAL_POST_THRESHOLD = 10;
|
|
309
|
-
const VIRAL_USER_LIKELIHOOD = 5;
|
|
310
|
-
const VIRAL_CLONES_MIN = 60;
|
|
311
|
-
const VIRAL_CLONES_MAX = 120;
|
|
312
|
-
|
|
313
|
-
const FOLLOW_SNOWBALL_THRESHOLD = 5;
|
|
314
|
-
const FOLLOW_SNOWBALL_LIKELIHOOD = 50;
|
|
315
|
-
|
|
316
|
-
const ALGORITHM_CHANGE_DAY = 45;
|
|
317
|
-
const ALGORITHM_FLIP_LIKELIHOOD = 70;
|
|
318
|
-
|
|
319
|
-
const ENGAGEMENT_BAIT_LIKELIHOOD = 20;
|
|
320
|
-
|
|
321
|
-
const REENGAGEMENT_START_DAY = 30;
|
|
322
|
-
const REENGAGEMENT_LIKELIHOOD = 30;
|
|
323
|
-
|
|
324
|
-
const CREATOR_POST_CLONES = 2;
|
|
325
|
-
const CREATOR_STORY_CLONES = 2;
|
|
326
|
-
const CREATOR_VIEW_CLONE_LIKELIHOOD = 25;
|
|
327
|
-
|
|
328
|
-
const TOXICITY_THRESHOLD = 2;
|
|
329
|
-
const TOXICITY_CUTOFF_DAYS = 30;
|
|
330
|
-
const TOXICITY_DROP_LIKELIHOOD = 60;
|
|
331
|
-
|
|
332
|
-
const WEEKEND_CLONE_LIKELIHOOD = 30;
|
|
333
|
-
|
|
334
|
-
const POST_SWEET_MIN = 3;
|
|
335
|
-
const POST_SWEET_MAX = 7;
|
|
336
|
-
const POST_OVER_THRESHOLD = 8;
|
|
337
|
-
const POST_SWEET_COMMENT_BOOST = 1.4;
|
|
338
|
-
const POST_OVER_COMMENT_FACTOR = 0.7;
|
|
339
|
-
|
|
340
|
-
const ONBOARDING_TTC_FAST = 0.71;
|
|
341
|
-
const ONBOARDING_TTC_SLOW = 1.25;
|
|
342
|
-
|
|
343
|
-
// ── DATA ARRAYS ──
|
|
344
|
-
// Generate consistent post IDs for lookup tables
|
|
345
|
-
const postIds = v.range(1, 1001).map(n => `post_${v.uid(8)}`);
|
|
346
|
-
|
|
347
|
-
// ── HELPER FUNCTIONS ──
|
|
348
|
-
function handleFunnelPostHooks(record, meta) {
|
|
349
|
-
// H10: ONBOARDING TIME-TO-CONVERT — creator/business 0.71x faster,
|
|
350
|
-
// personal 1.25x slower. Scales inter-step gaps via timestamp rewrite.
|
|
351
|
-
const segment = meta?.profile?.account_type;
|
|
352
|
-
if (Array.isArray(record) && record.length > 1) {
|
|
353
|
-
const factor = (
|
|
354
|
-
segment === "creator" || segment === "business" ? ONBOARDING_TTC_FAST :
|
|
355
|
-
segment === "personal" ? ONBOARDING_TTC_SLOW :
|
|
356
|
-
1.0
|
|
357
|
-
);
|
|
358
|
-
if (factor !== 1.0) {
|
|
359
|
-
for (let i = 1; i < record.length; i++) {
|
|
360
|
-
const prev = dayjs(record[i - 1].time);
|
|
361
|
-
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
362
|
-
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return record;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function handleEverythingHooks(record, meta) {
|
|
370
|
-
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
371
|
-
const userEvents = record;
|
|
372
|
-
if (!userEvents || userEvents.length === 0) return record;
|
|
373
|
-
|
|
374
|
-
// Stamp superProps from profile for consistency
|
|
375
|
-
const profile = meta.profile;
|
|
376
|
-
userEvents.forEach(e => {
|
|
377
|
-
e.app_version = profile.app_version;
|
|
378
|
-
e.account_type = profile.account_type;
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
// First pass: identify behavioral patterns (no flags written)
|
|
382
|
-
let postCreatedCount = 0;
|
|
383
|
-
let followReceivedCount = 0;
|
|
384
|
-
let reportSubmittedCount = 0;
|
|
385
|
-
let hasCreatorSubscription = false;
|
|
386
|
-
let isViralCreator = false;
|
|
387
|
-
|
|
388
|
-
userEvents.forEach((event) => {
|
|
389
|
-
if (event.event === "post created") postCreatedCount++;
|
|
390
|
-
if (event.event === "user followed") followReceivedCount++;
|
|
391
|
-
if (event.event === "report submitted") reportSubmittedCount++;
|
|
392
|
-
if (event.event === "creator subscription started") hasCreatorSubscription = true;
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
if (postCreatedCount >= VIRAL_POST_THRESHOLD && chance.bool({ likelihood: VIRAL_USER_LIKELIHOOD })) {
|
|
396
|
-
isViralCreator = true;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Second pass: inject cloned events (no behavioral cohort flags)
|
|
400
|
-
for (let idx = userEvents.length - 1; idx >= 0; idx--) {
|
|
401
|
-
const event = userEvents[idx];
|
|
402
|
-
const eventTime = dayjs(event.time);
|
|
403
|
-
|
|
404
|
-
// H1: VIRAL CONTENT CASCADE — clone 60-120 view/like/share per post.
|
|
405
|
-
// Discovery: bin users by post-created count, observe per-user view/like/share volume.
|
|
406
|
-
if (isViralCreator && event.event === "post created") {
|
|
407
|
-
const viralViews = chance.integer({ min: VIRAL_CLONES_MIN, max: VIRAL_CLONES_MAX });
|
|
408
|
-
const viralLikes = chance.integer({ min: VIRAL_CLONES_MIN, max: VIRAL_CLONES_MAX });
|
|
409
|
-
const viralShares = chance.integer({ min: VIRAL_CLONES_MIN, max: VIRAL_CLONES_MAX });
|
|
410
|
-
const injected = [];
|
|
411
|
-
|
|
412
|
-
const viewTemplate = userEvents.find(e => e.event === "post viewed");
|
|
413
|
-
const likeTemplate = userEvents.find(e => e.event === "post liked");
|
|
414
|
-
const shareTemplate = userEvents.find(e => e.event === "post shared");
|
|
415
|
-
|
|
416
|
-
for (let i = 0; i < viralViews; i++) {
|
|
417
|
-
injected.push({
|
|
418
|
-
...(viewTemplate || event),
|
|
419
|
-
event: "post viewed",
|
|
420
|
-
time: eventTime.add(chance.integer({ min: 1, max: 180 }), 'minutes').toISOString(),
|
|
421
|
-
user_id: event.user_id,
|
|
422
|
-
post_type: event.post_type || "text",
|
|
423
|
-
source: chance.pickone(["feed", "explore", "search"]),
|
|
424
|
-
view_duration_sec: chance.integer({ min: 5, max: 90 }),
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
for (let i = 0; i < viralLikes; i++) {
|
|
428
|
-
injected.push({
|
|
429
|
-
...(likeTemplate || event),
|
|
430
|
-
event: "post liked",
|
|
431
|
-
time: eventTime.add(chance.integer({ min: 2, max: 240 }), 'minutes').toISOString(),
|
|
432
|
-
user_id: event.user_id,
|
|
433
|
-
post_type: event.post_type || "text",
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
for (let i = 0; i < viralShares; i++) {
|
|
437
|
-
injected.push({
|
|
438
|
-
...(shareTemplate || event),
|
|
439
|
-
event: "post shared",
|
|
440
|
-
time: eventTime.add(chance.integer({ min: 5, max: 300 }), 'minutes').toISOString(),
|
|
441
|
-
user_id: event.user_id,
|
|
442
|
-
share_destination: chance.pickone(["repost", "dm", "external", "copy_link"]),
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
userEvents.splice(idx + 1, 0, ...injected);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// H2: FOLLOW-BACK SNOWBALL — extra post + comment per user-followed cluster.
|
|
450
|
-
// Discovery: cohort users with >=5 user-followed events, compare posts/user.
|
|
451
|
-
if (followReceivedCount >= FOLLOW_SNOWBALL_THRESHOLD && event.event === "post created") {
|
|
452
|
-
if (chance.bool({ likelihood: FOLLOW_SNOWBALL_LIKELIHOOD })) {
|
|
453
|
-
const commentTemplate = userEvents.find(e => e.event === "comment posted");
|
|
454
|
-
const duplicatePost = {
|
|
455
|
-
...event,
|
|
456
|
-
time: eventTime.add(chance.integer({ min: 30, max: 240 }), 'minutes').toISOString(),
|
|
457
|
-
user_id: event.user_id,
|
|
458
|
-
post_type: chance.pickone(["text", "image", "video"]),
|
|
459
|
-
character_count: chance.integer({ min: 10, max: 280 }),
|
|
460
|
-
has_media: chance.bool({ likelihood: 60 }),
|
|
461
|
-
hashtag_count: chance.integer({ min: 0, max: 5 }),
|
|
462
|
-
};
|
|
463
|
-
const extraComment = {
|
|
464
|
-
...(commentTemplate || event),
|
|
465
|
-
event: "comment posted",
|
|
466
|
-
time: eventTime.add(chance.integer({ min: 10, max: 120 }), 'minutes').toISOString(),
|
|
467
|
-
user_id: event.user_id,
|
|
468
|
-
comment_length: chance.integer({ min: 5, max: 200 }),
|
|
469
|
-
has_mention: chance.bool({ likelihood: 40 }),
|
|
470
|
-
};
|
|
471
|
-
userEvents.splice(idx + 1, 0, duplicatePost, extraComment);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// H6: CREATOR MONETIZATION — 3x post/story rate for subscribers.
|
|
476
|
-
// Discovery: cohort users with creator-subscription-started event, compare posts/user.
|
|
477
|
-
if (hasCreatorSubscription && event.event === "post created") {
|
|
478
|
-
for (let i = 0; i < CREATOR_POST_CLONES; i++) {
|
|
479
|
-
const extraPost = {
|
|
480
|
-
...event,
|
|
481
|
-
time: eventTime.add(chance.integer({ min: 1, max: 12 }), 'hours').toISOString(),
|
|
482
|
-
user_id: event.user_id,
|
|
483
|
-
post_type: chance.pickone(["text", "image", "video", "link"]),
|
|
484
|
-
character_count: chance.integer({ min: 20, max: 280 }),
|
|
485
|
-
has_media: chance.bool({ likelihood: 70 }),
|
|
486
|
-
hashtag_count: chance.integer({ min: 1, max: 8 }),
|
|
487
|
-
};
|
|
488
|
-
userEvents.splice(idx + 1, 0, extraPost);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
if (hasCreatorSubscription && event.event === "story created") {
|
|
492
|
-
for (let i = 0; i < CREATOR_STORY_CLONES; i++) {
|
|
493
|
-
const extraStory = {
|
|
494
|
-
...event,
|
|
495
|
-
time: eventTime.add(chance.integer({ min: 1, max: 8 }), 'hours').toISOString(),
|
|
496
|
-
user_id: event.user_id,
|
|
497
|
-
story_type: chance.pickone(["photo", "video", "text"]),
|
|
498
|
-
has_filter: chance.bool({ likelihood: 60 }),
|
|
499
|
-
has_sticker: chance.bool({ likelihood: 40 }),
|
|
500
|
-
};
|
|
501
|
-
userEvents.splice(idx + 1, 0, extraStory);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
if (hasCreatorSubscription && event.event === "post viewed") {
|
|
505
|
-
if (chance.bool({ likelihood: CREATOR_VIEW_CLONE_LIKELIHOOD })) {
|
|
506
|
-
const analyticsView = {
|
|
507
|
-
...event,
|
|
508
|
-
time: eventTime.add(chance.integer({ min: 1, max: 30 }), 'minutes').toISOString(),
|
|
509
|
-
user_id: event.user_id,
|
|
510
|
-
post_type: event.post_type || "text",
|
|
511
|
-
source: "profile",
|
|
512
|
-
view_duration_sec: chance.integer({ min: 10, max: 60 }),
|
|
513
|
-
};
|
|
514
|
-
userEvents.splice(idx + 1, 0, analyticsView);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// H8: WEEKEND CONTENT SURGE — duplicate weekend posts/stories with offset.
|
|
520
|
-
// Discovery: line chart by day-of-week shows Sat/Sun bump.
|
|
521
|
-
for (let idx = userEvents.length - 1; idx >= 0; idx--) {
|
|
522
|
-
const event = userEvents[idx];
|
|
523
|
-
if (event.event === "post created" || event.event === "story created") {
|
|
524
|
-
const dow = new Date(event.time).getUTCDay();
|
|
525
|
-
if ((dow === 0 || dow === 6) && chance.bool({ likelihood: WEEKEND_CLONE_LIKELIHOOD })) {
|
|
526
|
-
const etime = dayjs(event.time);
|
|
527
|
-
const dup = {
|
|
528
|
-
...event,
|
|
529
|
-
time: etime.add(chance.integer({ min: 1, max: 3 }), 'hours').toISOString(),
|
|
530
|
-
};
|
|
531
|
-
userEvents.splice(idx + 1, 0, dup);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// H3: ALGORITHM CHANGE — day 45 flips feed → explore on post viewed.
|
|
537
|
-
// H4: ENGAGEMENT BAIT — 20% of post-viewed events get crushed duration.
|
|
538
|
-
// H5: NOTIFICATION RE-ENGAGEMENT — after day 30, 30% of views → notification.
|
|
539
|
-
// All three run AFTER injection passes so they apply to cloned events too.
|
|
540
|
-
const algorithmChangeDay = datasetStart.add(ALGORITHM_CHANGE_DAY, 'days');
|
|
541
|
-
const reengagementStart = datasetStart.add(REENGAGEMENT_START_DAY, 'days');
|
|
542
|
-
userEvents.forEach(e => {
|
|
543
|
-
if (e.event === "post viewed") {
|
|
544
|
-
const eventTime = dayjs(e.time);
|
|
545
|
-
|
|
546
|
-
// H3: Algorithm Change
|
|
547
|
-
if (eventTime.isAfter(algorithmChangeDay)) {
|
|
548
|
-
if (chance.bool({ likelihood: ALGORITHM_FLIP_LIKELIHOOD })) {
|
|
549
|
-
e.source = "explore";
|
|
550
|
-
}
|
|
551
|
-
} else {
|
|
552
|
-
if (chance.bool({ likelihood: ALGORITHM_FLIP_LIKELIHOOD })) {
|
|
553
|
-
e.source = "feed";
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// H4: Engagement Bait — 20% crushed view duration
|
|
558
|
-
if (chance.bool({ likelihood: ENGAGEMENT_BAIT_LIKELIHOOD })) {
|
|
559
|
-
e.view_duration_sec = chance.integer({ min: 1, max: 5 });
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// H5: Notification Re-engagement (runs after #3 so can override)
|
|
563
|
-
if (eventTime.isAfter(reengagementStart) && chance.bool({ likelihood: REENGAGEMENT_LIKELIHOOD })) {
|
|
564
|
-
e.source = "notification";
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
// H7: TOXICITY CHURN — drop 60% of activity after day 30 for high reporters.
|
|
570
|
-
// Discovery: cohort users with >=2 report-submitted events, observe retention drop.
|
|
571
|
-
if (reportSubmittedCount >= TOXICITY_THRESHOLD) {
|
|
572
|
-
const churnCutoff = datasetStart.add(TOXICITY_CUTOFF_DAYS, 'days');
|
|
573
|
-
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
574
|
-
const evt = userEvents[i];
|
|
575
|
-
if (dayjs(evt.time).isAfter(churnCutoff) && chance.bool({ likelihood: TOXICITY_DROP_LIKELIHOOD })) {
|
|
576
|
-
userEvents.splice(i, 1);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// H9: POST-CREATED MAGIC NUMBER (no flags)
|
|
582
|
-
// Sweet 3-7 posts → +40% on comment_length on the user's comment events.
|
|
583
|
-
// Over 8+ → -30% comment_length (engagement burnout).
|
|
584
|
-
if (postCreatedCount >= POST_SWEET_MIN && postCreatedCount <= POST_SWEET_MAX) {
|
|
585
|
-
userEvents.forEach(e => {
|
|
586
|
-
if (e.event === 'comment posted' && typeof e.comment_length === 'number') {
|
|
587
|
-
e.comment_length = Math.round(e.comment_length * POST_SWEET_COMMENT_BOOST);
|
|
588
|
-
}
|
|
589
|
-
});
|
|
590
|
-
} else if (postCreatedCount >= POST_OVER_THRESHOLD) {
|
|
591
|
-
userEvents.forEach(e => {
|
|
592
|
-
if (e.event === 'comment posted' && typeof e.comment_length === 'number') {
|
|
593
|
-
e.comment_length = Math.round(e.comment_length * POST_OVER_COMMENT_FACTOR);
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
return record;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// ── CONFIG ──
|
|
602
|
-
/** @type {Config} */
|
|
603
|
-
const config = {
|
|
604
|
-
version: 2,
|
|
605
|
-
seed: SEED,
|
|
606
|
-
datasetStart: DATASET_START,
|
|
607
|
-
datasetEnd: DATASET_END,
|
|
608
|
-
soup: { dayOfWeekWeights: [1.0, 1.0, 1.0, 1.0, 1.0, 1.2, 1.2] },
|
|
609
|
-
avgEventsPerUserPerDay: EVENTS_PER_DAY,
|
|
610
|
-
numUsers: NUM_USERS,
|
|
611
|
-
format: "json",
|
|
612
|
-
gzip: true,
|
|
613
|
-
credentials: {
|
|
614
|
-
token,
|
|
615
|
-
},
|
|
616
|
-
switches: {
|
|
617
|
-
hasSessionIds: true,
|
|
618
|
-
alsoInferFunnels: false,
|
|
619
|
-
hasLocation: true,
|
|
620
|
-
hasAndroidDevices: true,
|
|
621
|
-
hasIOSDevices: true,
|
|
622
|
-
hasDesktopDevices: true,
|
|
623
|
-
hasBrowser: false,
|
|
624
|
-
hasCampaigns: false,
|
|
625
|
-
isAnonymous: false,
|
|
626
|
-
hasAdSpend: false,
|
|
627
|
-
hasAvatar: true,
|
|
628
|
-
},
|
|
629
|
-
identity: {
|
|
630
|
-
avgDevicePerUser: 2,
|
|
631
|
-
},
|
|
632
|
-
concurrency: 1,
|
|
633
|
-
writeToDisk: false,
|
|
634
|
-
scdProps: {
|
|
635
|
-
account_type: {
|
|
636
|
-
values: ["personal", "creator", "business", "verified"],
|
|
637
|
-
frequency: "month",
|
|
638
|
-
timing: "fuzzy",
|
|
639
|
-
max: 6
|
|
640
|
-
},
|
|
641
|
-
community_status: {
|
|
642
|
-
values: ["new", "growing", "established", "featured"],
|
|
643
|
-
frequency: "month",
|
|
644
|
-
timing: "fixed",
|
|
645
|
-
max: 6,
|
|
646
|
-
type: "community_id"
|
|
647
|
-
}
|
|
648
|
-
},
|
|
649
|
-
|
|
650
|
-
funnels: [
|
|
651
|
-
{
|
|
652
|
-
sequence: ["account created", "profile updated", "post created"],
|
|
653
|
-
isFirstFunnel: true,
|
|
654
|
-
conversionRate: 70,
|
|
655
|
-
timeToConvert: 0.5,
|
|
656
|
-
},
|
|
657
|
-
{
|
|
658
|
-
// Feed consumption: view → like → comment (most common loop)
|
|
659
|
-
sequence: ["post viewed", "post liked", "comment posted"],
|
|
660
|
-
conversionRate: 45,
|
|
661
|
-
timeToConvert: 0.5,
|
|
662
|
-
weight: 6,
|
|
663
|
-
},
|
|
664
|
-
{
|
|
665
|
-
// Content creation cycle: create → views → engagement
|
|
666
|
-
sequence: ["post created", "post viewed", "post liked", "post shared"],
|
|
667
|
-
conversionRate: 30,
|
|
668
|
-
timeToConvert: 3,
|
|
669
|
-
weight: 3,
|
|
670
|
-
},
|
|
671
|
-
{
|
|
672
|
-
// Stories engagement
|
|
673
|
-
sequence: ["story created", "story viewed", "dm sent"],
|
|
674
|
-
conversionRate: 40,
|
|
675
|
-
timeToConvert: 1,
|
|
676
|
-
weight: 3,
|
|
677
|
-
},
|
|
678
|
-
{
|
|
679
|
-
// Discovery and follow loop
|
|
680
|
-
sequence: ["search performed", "post viewed", "user followed"],
|
|
681
|
-
conversionRate: 35,
|
|
682
|
-
timeToConvert: 1,
|
|
683
|
-
weight: 2,
|
|
684
|
-
},
|
|
685
|
-
{
|
|
686
|
-
// Notifications driving re-engagement
|
|
687
|
-
sequence: ["notification received", "post viewed", "post liked"],
|
|
688
|
-
conversionRate: 50,
|
|
689
|
-
timeToConvert: 0.5,
|
|
690
|
-
weight: 2,
|
|
691
|
-
},
|
|
692
|
-
{
|
|
693
|
-
// Profile management and creator monetization
|
|
694
|
-
sequence: ["profile updated", "creator subscription started", "post created"],
|
|
695
|
-
conversionRate: 15,
|
|
696
|
-
timeToConvert: 24,
|
|
697
|
-
weight: 1,
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
// Ad interaction and moderation
|
|
701
|
-
sequence: ["ad viewed", "ad clicked", "report submitted"],
|
|
702
|
-
conversionRate: 20,
|
|
703
|
-
timeToConvert: 2,
|
|
704
|
-
weight: 1,
|
|
705
|
-
},
|
|
706
|
-
],
|
|
707
|
-
|
|
708
|
-
events: [
|
|
709
|
-
{
|
|
710
|
-
event: "account created",
|
|
711
|
-
weight: 1,
|
|
712
|
-
isFirstEvent: true,
|
|
713
|
-
isAuthEvent: true,
|
|
714
|
-
properties: {
|
|
715
|
-
"signup_method": ["email", "google", "apple", "sso"],
|
|
716
|
-
"referred_by": ["organic", "friend", "ad", "influencer"],
|
|
717
|
-
}
|
|
718
|
-
},
|
|
719
|
-
{
|
|
720
|
-
event: "post created",
|
|
721
|
-
weight: 12,
|
|
722
|
-
isStrictEvent: false,
|
|
723
|
-
properties: {
|
|
724
|
-
"post_type": ["text", "image", "video", "poll", "link"],
|
|
725
|
-
"character_count": u.weighNumRange(1, 280),
|
|
726
|
-
"has_media": [false, false, false, true, true],
|
|
727
|
-
"hashtag_count": u.weighNumRange(0, 10, 0.5),
|
|
728
|
-
}
|
|
729
|
-
},
|
|
730
|
-
{
|
|
731
|
-
event: "post viewed",
|
|
732
|
-
weight: 30,
|
|
733
|
-
isStrictEvent: false,
|
|
734
|
-
properties: {
|
|
735
|
-
"post_type": ["text", "image", "video", "poll", "link"],
|
|
736
|
-
"view_duration_sec": u.weighNumRange(1, 120, 0.3, 5),
|
|
737
|
-
"source": ["feed", "explore", "search", "profile", "notification"],
|
|
738
|
-
}
|
|
739
|
-
},
|
|
740
|
-
{
|
|
741
|
-
event: "post liked",
|
|
742
|
-
weight: 18,
|
|
743
|
-
isStrictEvent: false,
|
|
744
|
-
properties: {
|
|
745
|
-
"post_type": ["text", "image", "video", "poll", "link"],
|
|
746
|
-
}
|
|
747
|
-
},
|
|
748
|
-
{
|
|
749
|
-
event: "post shared",
|
|
750
|
-
weight: 6,
|
|
751
|
-
isStrictEvent: false,
|
|
752
|
-
properties: {
|
|
753
|
-
"share_destination": ["repost", "dm", "external", "copy_link"],
|
|
754
|
-
}
|
|
755
|
-
},
|
|
756
|
-
{
|
|
757
|
-
event: "comment posted",
|
|
758
|
-
weight: 10,
|
|
759
|
-
isStrictEvent: false,
|
|
760
|
-
properties: {
|
|
761
|
-
"comment_length": u.weighNumRange(1, 500, 0.3, 20),
|
|
762
|
-
"has_mention": [true, false, false],
|
|
763
|
-
}
|
|
764
|
-
},
|
|
765
|
-
{
|
|
766
|
-
event: "user followed",
|
|
767
|
-
weight: 8,
|
|
768
|
-
isStrictEvent: false,
|
|
769
|
-
properties: {
|
|
770
|
-
"discovery_source": ["suggested", "search", "post", "profile", "mutual"],
|
|
771
|
-
}
|
|
772
|
-
},
|
|
773
|
-
{
|
|
774
|
-
event: "user unfollowed",
|
|
775
|
-
weight: 2,
|
|
776
|
-
properties: {
|
|
777
|
-
"reason": ["content_quality", "too_frequent", "lost_interest", "offensive"],
|
|
778
|
-
}
|
|
779
|
-
},
|
|
780
|
-
{
|
|
781
|
-
event: "story viewed",
|
|
782
|
-
weight: 15,
|
|
783
|
-
isStrictEvent: false,
|
|
784
|
-
properties: {
|
|
785
|
-
"story_type": ["photo", "video", "text"],
|
|
786
|
-
"view_duration_sec": u.weighNumRange(1, 30, 0.5, 5),
|
|
787
|
-
"completed": [false, false, true, true, true],
|
|
788
|
-
}
|
|
789
|
-
},
|
|
790
|
-
{
|
|
791
|
-
event: "story created",
|
|
792
|
-
weight: 5,
|
|
793
|
-
isStrictEvent: false,
|
|
794
|
-
properties: {
|
|
795
|
-
"story_type": ["photo", "video", "text"],
|
|
796
|
-
"has_filter": [true, false],
|
|
797
|
-
"has_sticker": [false, false, true],
|
|
798
|
-
}
|
|
799
|
-
},
|
|
800
|
-
{
|
|
801
|
-
event: "search performed",
|
|
802
|
-
weight: 7,
|
|
803
|
-
properties: {
|
|
804
|
-
"search_type": ["users", "hashtags", "posts"],
|
|
805
|
-
"results_count": u.weighNumRange(0, 50, 0.5, 10),
|
|
806
|
-
}
|
|
807
|
-
},
|
|
808
|
-
{
|
|
809
|
-
event: "notification received",
|
|
810
|
-
weight: 12,
|
|
811
|
-
properties: {
|
|
812
|
-
"notification_type": ["like", "follow", "comment", "mention", "trending"],
|
|
813
|
-
"clicked": [false, false, false, true, true],
|
|
814
|
-
}
|
|
815
|
-
},
|
|
816
|
-
{
|
|
817
|
-
event: "dm sent",
|
|
818
|
-
weight: 8,
|
|
819
|
-
isStrictEvent: false,
|
|
820
|
-
properties: {
|
|
821
|
-
"message_type": ["text", "image", "voice", "link"],
|
|
822
|
-
"conversation_length": u.weighNumRange(1, 100),
|
|
823
|
-
}
|
|
824
|
-
},
|
|
825
|
-
{
|
|
826
|
-
event: "ad viewed",
|
|
827
|
-
weight: 10,
|
|
828
|
-
properties: {
|
|
829
|
-
"ad_format": ["feed_native", "story", "banner", "video"],
|
|
830
|
-
"ad_category": ["retail", "tech", "food", "finance", "entertainment"],
|
|
831
|
-
"view_duration_sec": u.weighNumRange(1, 30, 0.3),
|
|
832
|
-
}
|
|
833
|
-
},
|
|
834
|
-
{
|
|
835
|
-
event: "ad clicked",
|
|
836
|
-
weight: 2,
|
|
837
|
-
properties: {
|
|
838
|
-
"ad_format": ["feed_native", "story", "banner", "video"],
|
|
839
|
-
"ad_category": ["retail", "tech", "food", "finance", "entertainment"],
|
|
840
|
-
}
|
|
841
|
-
},
|
|
842
|
-
{
|
|
843
|
-
event: "report submitted",
|
|
844
|
-
weight: 1,
|
|
845
|
-
properties: {
|
|
846
|
-
"report_type": ["spam", "harassment", "misinformation", "hate_speech", "other"],
|
|
847
|
-
"content_type": ["post", "comment", "user", "dm"],
|
|
848
|
-
}
|
|
849
|
-
},
|
|
850
|
-
{
|
|
851
|
-
event: "profile updated",
|
|
852
|
-
weight: 3,
|
|
853
|
-
properties: {
|
|
854
|
-
"field_updated": ["bio", "avatar", "display_name", "privacy_settings", "interests"],
|
|
855
|
-
}
|
|
856
|
-
},
|
|
857
|
-
{
|
|
858
|
-
event: "creator subscription started",
|
|
859
|
-
weight: 2,
|
|
860
|
-
properties: {
|
|
861
|
-
"tier": ["basic", "premium", "vip"],
|
|
862
|
-
"price_usd": [4.99, 9.99, 19.99],
|
|
863
|
-
}
|
|
864
|
-
},
|
|
865
|
-
],
|
|
866
|
-
|
|
867
|
-
superProps: {
|
|
868
|
-
app_version: ["4.0", "4.1", "4.2", "4.3", "5.0"],
|
|
869
|
-
account_type: ["personal", "creator", "business"],
|
|
870
|
-
},
|
|
871
|
-
|
|
872
|
-
userProps: {
|
|
873
|
-
app_version: ["4.0", "4.1", "4.2", "4.3", "5.0"],
|
|
874
|
-
account_type: ["personal", "creator", "business"],
|
|
875
|
-
"follower_count": u.weighNumRange(0, 10000, 0.2, 50),
|
|
876
|
-
"following_count": u.weighNumRange(0, 5000, 0.3, 100),
|
|
877
|
-
"bio_length": u.weighNumRange(0, 160),
|
|
878
|
-
"verified": [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true],
|
|
879
|
-
"content_niche": ["lifestyle", "tech", "food", "fitness", "travel", "comedy", "news", "art"],
|
|
880
|
-
},
|
|
881
|
-
|
|
882
|
-
groupKeys: [
|
|
883
|
-
["community_id", 100, ["post created", "comment posted", "post liked", "post shared"]],
|
|
884
|
-
],
|
|
885
|
-
|
|
886
|
-
groupProps: {
|
|
887
|
-
community_id: {
|
|
888
|
-
"name": () => `${chance.word()} ${chance.pickone(["Hub", "Circle", "Squad", "Zone", "Space"])}`,
|
|
889
|
-
"member_count": u.weighNumRange(50, 5000, 0.3, 200),
|
|
890
|
-
"category": ["technology", "entertainment", "sports", "politics", "art", "science"],
|
|
891
|
-
"is_moderated": [false, false, false, true, true, true, true, true, true, true],
|
|
892
|
-
}
|
|
893
|
-
},
|
|
894
|
-
|
|
895
|
-
lookupTables: [],
|
|
896
|
-
|
|
897
|
-
hook(record, type, meta) {
|
|
898
|
-
if (type === "funnel-post") return handleFunnelPostHooks(record, meta);
|
|
899
|
-
if (type === "everything") return handleEverythingHooks(record, meta);
|
|
900
|
-
return record;
|
|
901
|
-
}
|
|
902
|
-
};
|
|
903
|
-
|
|
904
|
-
export default config;
|