@ak--47/dungeon-master 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/dungeons/technical/array-of-object-lookup.js +0 -2
- package/dungeons/technical/hook-helpers-verify.js +89 -0
- package/dungeons/technical/identity-model-verify.js +47 -0
- package/dungeons/technical/pattern-aggregate-by-bin.js +41 -0
- package/dungeons/technical/pattern-attributed-by-source.js +42 -0
- package/dungeons/technical/pattern-frequency-by-frequency.js +40 -0
- package/dungeons/technical/pattern-funnel-frequency.js +54 -0
- package/dungeons/technical/pattern-ttc-by-segment.js +45 -0
- package/dungeons/technical/simple.js +3 -4
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/text-generation.js +1 -1
- package/dungeons/vertical/ai-platform.js +216 -164
- package/dungeons/vertical/community.js +74 -41
- package/dungeons/vertical/crypto.js +343 -233
- package/dungeons/vertical/dating.js +246 -292
- package/dungeons/vertical/devtools.js +179 -70
- package/dungeons/vertical/ecommerce.js +275 -122
- package/dungeons/vertical/education.js +345 -312
- package/dungeons/vertical/fintech.js +445 -314
- package/dungeons/vertical/fitness.js +199 -165
- package/dungeons/vertical/food-delivery.js +309 -330
- package/dungeons/vertical/gaming.js +387 -241
- package/dungeons/vertical/healthcare.js +149 -66
- package/dungeons/vertical/insurance-application.js +173 -75
- package/dungeons/vertical/logistics.js +107 -12
- package/dungeons/vertical/marketplace.js +171 -76
- package/dungeons/vertical/media.js +259 -402
- package/dungeons/vertical/real-estate.js +336 -318
- package/dungeons/vertical/sass.js +294 -288
- package/dungeons/vertical/social.js +289 -234
- package/dungeons/vertical/travel.js +66 -20
- package/index.js +17 -17
- package/lib/core/config-validator.js +226 -175
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +57 -104
- package/lib/generators/funnels.js +212 -93
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/hook-helpers/_internal.js +23 -0
- package/lib/hook-helpers/cohort.js +124 -0
- package/lib/hook-helpers/identity.js +56 -0
- package/lib/hook-helpers/index.js +44 -0
- package/lib/hook-helpers/inject.js +99 -0
- package/lib/hook-helpers/mutate.js +151 -0
- package/lib/hook-helpers/timing.js +99 -0
- package/lib/hook-patterns/aggregate-per-user-by-bin.js +38 -0
- package/lib/hook-patterns/attributed-by-source.js +72 -0
- package/lib/hook-patterns/frequency-by-frequency.js +46 -0
- package/lib/hook-patterns/funnel-frequency-breakdown.js +73 -0
- package/lib/hook-patterns/index.js +14 -0
- package/lib/hook-patterns/time-to-convert-by-segment.js +41 -0
- package/lib/orchestrators/user-loop.js +143 -293
- package/lib/utils/utils.js +97 -52
- package/lib/verify/emulate-breakdown.js +281 -0
- package/lib/verify/index.js +12 -0
- package/lib/verify/verify-dungeon.js +61 -0
- package/package.json +6 -4
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +434 -225
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/vertical/ai-platform-schema.json +0 -617
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/crypto-schema.json +0 -546
- package/dungeons/vertical/dating-schema.json +0 -401
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -2703
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/real-estate-schema.json +0 -527
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
// ── TWEAK THESE ──
|
|
2
2
|
const SEED = "simple is best";
|
|
3
|
-
const num_days =
|
|
4
|
-
const num_users =
|
|
3
|
+
const num_days = 120;
|
|
4
|
+
const num_users = 42_000;
|
|
5
5
|
const avg_events_per_user_per_day = 0.37;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
8
8
|
// ── env overrides ──
|
|
9
9
|
if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
|
|
10
10
|
|
|
11
|
-
import Chance from 'chance';
|
|
12
|
-
let chance = new Chance();
|
|
13
11
|
import dayjs from "dayjs";
|
|
14
12
|
import utc from "dayjs/plugin/utc.js";
|
|
15
13
|
dayjs.extend(utc);
|
|
16
14
|
import { uid, comma } from 'ak-tools';
|
|
17
|
-
import { weighNumRange, date, integer, weighChoices, decimal } from "../../lib/utils/utils.js";
|
|
15
|
+
import { weighNumRange, date, integer, weighChoices, decimal, initChance } from "../../lib/utils/utils.js";
|
|
16
|
+
const chance = initChance(SEED);
|
|
18
17
|
|
|
19
18
|
/** @typedef {import("../../types").Dungeon} Config */
|
|
20
19
|
const itemCategories = ["Books", "Movies", "Music", "Games", "Electronics", "Computers", "Smart Home", "Home", "Garden", "Pet", "Beauty", "Health", "Toys", "Kids", "Baby", "Handmade", "Sports", "Outdoors", "Automotive", "Industrial", "Entertainment", "Art", "Food", "Appliances", "Office", "Wedding", "Software"];
|
|
@@ -50,47 +49,199 @@ const spiritAnimals = ["duck", "dog", "otter", "penguin", "cat", "elephant", "li
|
|
|
50
49
|
|
|
51
50
|
/*
|
|
52
51
|
* ============================================================================
|
|
53
|
-
* ANALYTICS HOOKS
|
|
52
|
+
* ANALYTICS HOOKS (7 hooks)
|
|
53
|
+
*
|
|
54
|
+
* Adds 7. SIGNUP FLOW TIME-TO-CONVERT: gold/platinum loyalty 0.71x faster,
|
|
55
|
+
* bronze 1.3x slower (funnel-post). Discover via funnel median time-to-convert
|
|
56
|
+
* by loyalty_tier breakdown.
|
|
57
|
+
* NOTE (funnel-post measurement): visible only via Mixpanel funnel median TTC.
|
|
58
|
+
* Cross-event MIN→MIN SQL queries on raw events do NOT show this.
|
|
59
|
+
* ============================================================================
|
|
60
|
+
*
|
|
61
|
+
* ----------------------------------------------------------------------------
|
|
62
|
+
* Hook 1: Signup Flow Improvement (event + everything)
|
|
63
|
+
* ----------------------------------------------------------------------------
|
|
64
|
+
*
|
|
65
|
+
* PATTERN: 7 days ago, signup_flow switches from "v1" to "v2". Before
|
|
66
|
+
* that date, 50% of sign ups are tagged _drop and removed in the
|
|
67
|
+
* everything hook, simulating a broken flow that got fixed.
|
|
68
|
+
*
|
|
69
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
70
|
+
*
|
|
71
|
+
* Report 1: Sign Ups by Flow Version
|
|
72
|
+
* - Report type: Insights
|
|
73
|
+
* - Event: "sign up"
|
|
74
|
+
* - Measure: Total
|
|
75
|
+
* - Breakdown: "signup_flow"
|
|
76
|
+
* - Line chart by day
|
|
77
|
+
* - Expected: v1 disappears at day -7; v2 takes over and roughly 2x daily volume
|
|
78
|
+
*
|
|
79
|
+
* Report 2: Pre vs Post Volume
|
|
80
|
+
* - Report type: Insights
|
|
81
|
+
* - Event: "sign up"
|
|
82
|
+
* - Measure: Total
|
|
83
|
+
* - Compare date ranges (last 7 days vs prior 7 days)
|
|
84
|
+
* - Expected: ~ 2x signups in the post-fix window
|
|
85
|
+
*
|
|
86
|
+
* REAL-WORLD ANALOGUE: A broken signup flow silently halved conversions
|
|
87
|
+
* until a release shipped a fix; daily signups roughly doubled overnight.
|
|
88
|
+
*
|
|
89
|
+
* ----------------------------------------------------------------------------
|
|
90
|
+
* Hook 2: Watch Time Inflection (event)
|
|
91
|
+
* ----------------------------------------------------------------------------
|
|
92
|
+
*
|
|
93
|
+
* PATTERN: 30 days ago, watch time shifts. Before that date, watchTimeSec
|
|
94
|
+
* is reduced by 25-79%. After, it is increased by 25-79%. Creates a
|
|
95
|
+
* clear before/after inflection.
|
|
96
|
+
*
|
|
97
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
98
|
+
*
|
|
99
|
+
* Report 1: Avg Watch Time Over Time
|
|
100
|
+
* - Report type: Insights
|
|
101
|
+
* - Event: "watch video"
|
|
102
|
+
* - Measure: Average of "watchTimeSec"
|
|
103
|
+
* - Line chart by day
|
|
104
|
+
* - Expected: clear upward inflection ~ 30 days ago; watch time ~ doubles
|
|
105
|
+
*
|
|
106
|
+
* Report 2: Watch Time Distribution Pre vs Post
|
|
107
|
+
* - Report type: Insights
|
|
108
|
+
* - Event: "watch video"
|
|
109
|
+
* - Measure: Average of "watchTimeSec"
|
|
110
|
+
* - Compare date ranges (last 30 days vs prior 30 days)
|
|
111
|
+
* - Expected: post-inflection ~ 2x avg watch time
|
|
112
|
+
*
|
|
113
|
+
* REAL-WORLD ANALOGUE: An algorithm or UX change (autoplay, recs)
|
|
114
|
+
* inflects average watch duration sharply on a release date.
|
|
115
|
+
*
|
|
116
|
+
* ----------------------------------------------------------------------------
|
|
117
|
+
* Hook 3: Toys + Shoes Cart Correlation (event)
|
|
118
|
+
* ----------------------------------------------------------------------------
|
|
119
|
+
*
|
|
120
|
+
* PATTERN: Checkout carts with toys get a shoes item injected (and vice
|
|
121
|
+
* versa). Carts with neither toys nor shoes have all item prices
|
|
122
|
+
* discounted to 75-90% of normal.
|
|
123
|
+
*
|
|
124
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
125
|
+
*
|
|
126
|
+
* Report 1: Cart Category Co-occurrence
|
|
127
|
+
* - Report type: Insights
|
|
128
|
+
* - Event: "checkout"
|
|
129
|
+
* - Measure: Total
|
|
130
|
+
* - Breakdown: "category" (flattened item category)
|
|
131
|
+
* - Expected: toys and shoes appear at high co-occurrence rate
|
|
132
|
+
*
|
|
133
|
+
* Report 2: Cart Value by Category Mix
|
|
134
|
+
* - Report type: Insights
|
|
135
|
+
* - Event: "checkout"
|
|
136
|
+
* - Measure: Average of "amount"
|
|
137
|
+
* - Breakdown: "category"
|
|
138
|
+
* - Expected: carts lacking both toys and shoes have lower avg amount
|
|
139
|
+
*
|
|
140
|
+
* REAL-WORLD ANALOGUE: Family shoppers buying for kids tend to bundle
|
|
141
|
+
* toys with shoes; a market-basket pattern that retailers exploit.
|
|
142
|
+
*
|
|
143
|
+
* ----------------------------------------------------------------------------
|
|
144
|
+
* Hook 4: Quality to Watch Time Correlation (event)
|
|
145
|
+
* ----------------------------------------------------------------------------
|
|
146
|
+
*
|
|
147
|
+
* PATTERN: Video quality multiplies watchTimeSec: 2160p=1.5x, 1440p=1.4x,
|
|
148
|
+
* 1080p=1.3x, 720p=1.15x, 480p=1.0x, 360p=0.85x, 240p=0.7x.
|
|
149
|
+
*
|
|
150
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
151
|
+
*
|
|
152
|
+
* Report 1: Avg Watch Time by Quality
|
|
153
|
+
* - Report type: Insights
|
|
154
|
+
* - Event: "watch video"
|
|
155
|
+
* - Measure: Average of "watchTimeSec"
|
|
156
|
+
* - Breakdown: "quality"
|
|
157
|
+
* - Expected: monotonic increase from 240p to 2160p
|
|
158
|
+
*
|
|
159
|
+
* Report 2: Quality Distribution
|
|
160
|
+
* - Report type: Insights
|
|
161
|
+
* - Event: "watch video"
|
|
162
|
+
* - Measure: Total
|
|
163
|
+
* - Breakdown: "quality"
|
|
164
|
+
* - Expected: viewers split across all quality tiers; HD tiers earn more time
|
|
165
|
+
*
|
|
166
|
+
* REAL-WORLD ANALOGUE: Higher-resolution streams correlate with longer
|
|
167
|
+
* sessions because they signal a better connection and more invested viewer.
|
|
168
|
+
*
|
|
169
|
+
* ----------------------------------------------------------------------------
|
|
170
|
+
* Hook 5: Item Flattening (event)
|
|
171
|
+
* ----------------------------------------------------------------------------
|
|
172
|
+
*
|
|
173
|
+
* PATTERN: Events with an item array property get the first item's
|
|
174
|
+
* fields (category, amount, slug, etc.) flattened onto the event record
|
|
175
|
+
* as top-level properties — making them available for direct breakdown
|
|
176
|
+
* in reports.
|
|
177
|
+
*
|
|
178
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
179
|
+
*
|
|
180
|
+
* Report 1: View Items by Category
|
|
181
|
+
* - Report type: Insights
|
|
182
|
+
* - Event: "view item"
|
|
183
|
+
* - Measure: Total
|
|
184
|
+
* - Breakdown: "category"
|
|
185
|
+
* - Expected: clean breakdown across category values without nested-property issues
|
|
186
|
+
*
|
|
187
|
+
* Report 2: Add to Cart Avg Amount by Category
|
|
188
|
+
* - Report type: Insights
|
|
189
|
+
* - Event: "add to cart"
|
|
190
|
+
* - Measure: Average of "amount"
|
|
191
|
+
* - Breakdown: "category"
|
|
192
|
+
* - Expected: per-category averages render correctly off flat properties
|
|
193
|
+
*
|
|
194
|
+
* REAL-WORLD ANALOGUE: Analytics teams routinely flatten nested cart
|
|
195
|
+
* objects onto events so downstream tools can group/filter without joins.
|
|
196
|
+
*
|
|
197
|
+
* ----------------------------------------------------------------------------
|
|
198
|
+
* Hook 6: View-Item Magic Number (everything)
|
|
199
|
+
* ----------------------------------------------------------------------------
|
|
200
|
+
*
|
|
201
|
+
* PATTERN: Users who view 3-8 items in the dataset window are in the
|
|
202
|
+
* "considered buyer" sweet spot — every cart item amount and total_value
|
|
203
|
+
* gets boosted ~25%. Users who view 9 or more items are over-engaged
|
|
204
|
+
* window-shoppers; ~30% of their checkout events are dropped (decision
|
|
205
|
+
* paralysis / browse without buy). No flag is stamped — discoverable
|
|
206
|
+
* only by binning users on view-item COUNT and comparing avg cart total.
|
|
207
|
+
*
|
|
208
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
209
|
+
*
|
|
210
|
+
* Report 1: Avg Cart Total by View-Item Bucket
|
|
211
|
+
* - Report type: Insights (with cohort)
|
|
212
|
+
* - Cohort A: users with 3-8 "view item" events
|
|
213
|
+
* - Cohort B: users with 0-2 "view item" events
|
|
214
|
+
* - Event: "checkout"
|
|
215
|
+
* - Measure: Average of "amount" (sum across cart items if exposed)
|
|
216
|
+
* - Compare cohort A vs cohort B
|
|
217
|
+
* - Expected: cohort A ~ 1.25x higher cart total than B
|
|
218
|
+
*
|
|
219
|
+
* Report 2: Checkouts per User by Browse Intensity
|
|
220
|
+
* - Report type: Insights (with cohort)
|
|
221
|
+
* - Cohort C: users with >= 9 "view item" events
|
|
222
|
+
* - Cohort A: users with 3-8 "view item" events
|
|
223
|
+
* - Event: "checkout"
|
|
224
|
+
* - Measure: Total events per user
|
|
225
|
+
* - Compare cohort C vs cohort A
|
|
226
|
+
* - Expected: cohort C has ~ 30% fewer checkouts per user
|
|
227
|
+
*
|
|
228
|
+
* REAL-WORLD ANALOGUE: A focused buyer who reviews a handful of items
|
|
229
|
+
* tends to convert at higher cart value; an excessive browser is a
|
|
230
|
+
* tire-kicker who abandons more often.
|
|
231
|
+
*
|
|
232
|
+
* ============================================================================
|
|
233
|
+
* EXPECTED METRICS SUMMARY
|
|
54
234
|
* ============================================================================
|
|
55
235
|
*
|
|
56
|
-
* Hook
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* Hook 2: Watch Time Inflection
|
|
66
|
-
* Type: event
|
|
67
|
-
* What: 30 days ago, watch time shifts. Before: watchTimeSec reduced by 25-79%.
|
|
68
|
-
* After: increased by 25-79%. Creates a clear before/after inflection.
|
|
69
|
-
* Mixpanel report:
|
|
70
|
-
* - Insights > line chart > "watch video" with AVG(watchTimeSec) by day
|
|
71
|
-
* - Expect: clear upward inflection ~30 days ago; watch time roughly doubles
|
|
72
|
-
*
|
|
73
|
-
* Hook 3: Toys + Shoes Cart Correlation
|
|
74
|
-
* Type: event
|
|
75
|
-
* What: Checkout carts with toys get shoes injected (and vice versa). Carts
|
|
76
|
-
* with neither toys nor shoes have all item prices discounted to 75-90%.
|
|
77
|
-
* Mixpanel report:
|
|
78
|
-
* - Insights > bar chart > "checkout" broken down by cart[].category
|
|
79
|
-
* - Expect: toys and shoes co-occur at high rate; carts without both have lower values
|
|
80
|
-
*
|
|
81
|
-
* Hook 4: Quality → Watch Time Correlation
|
|
82
|
-
* Type: event
|
|
83
|
-
* What: Video quality multiplies watchTimeSec: 2160p=1.5x, 1440p=1.4x,
|
|
84
|
-
* 1080p=1.3x, 720p=1.15x, 480p=1.0x, 360p=0.85x, 240p=0.7x.
|
|
85
|
-
* Mixpanel report:
|
|
86
|
-
* - Insights > bar chart > "watch video" with AVG(watchTimeSec), breakdown by quality
|
|
87
|
-
* - Expect: monotonic increase from 240p to 2160p
|
|
88
|
-
*
|
|
89
|
-
* Hook 5: Item Flattening
|
|
90
|
-
* Type: event
|
|
91
|
-
* What: Events with an item array property get the first item's fields
|
|
92
|
-
* flattened onto the event record, making category/amount/slug available
|
|
93
|
-
* as top-level properties for easier breakdown in reports.
|
|
236
|
+
* Hook | Metric | Baseline | Hook Effect | Ratio
|
|
237
|
+
* ---------------------------|-------------------------|------------|-------------|------
|
|
238
|
+
* Signup Flow Improvement | Daily sign ups | 1x | ~ 2x | 2x
|
|
239
|
+
* Watch Time Inflection | Avg watchTimeSec | 1x | ~ 2x | 2x
|
|
240
|
+
* Toys + Shoes Correlation | toys/shoes co-occurrence| ~ 7% | ~ 50%+ | ~ 7x
|
|
241
|
+
* Quality -> Watch Time | Avg watchTimeSec (240p->2160p) | 0.7x | 1.5x | ~ 2.1x
|
|
242
|
+
* Item Flattening | category breakdown | nested | flat | n/a
|
|
243
|
+
* View-Item Magic Number | sweet (3-8) cart total | 1x | ~ 1.25x | 1.25x
|
|
244
|
+
* View-Item Magic Number | over (9+) checkouts/user| 1x | ~ 0.7x | -30%
|
|
94
245
|
* ============================================================================
|
|
95
246
|
*/
|
|
96
247
|
|
|
@@ -140,15 +291,19 @@ function makeProducts(maxItems = 5) {
|
|
|
140
291
|
|
|
141
292
|
/** @type {import('../types.d.ts').Dungeon} */
|
|
142
293
|
const config = {
|
|
294
|
+
version: 2,
|
|
143
295
|
token,
|
|
144
296
|
seed: SEED,
|
|
145
|
-
|
|
297
|
+
datasetStart: "2026-01-01T00:00:00Z",
|
|
298
|
+
datasetEnd: "2026-05-01T23:59:59Z",
|
|
299
|
+
// numDays: num_days,
|
|
146
300
|
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
147
301
|
numUsers: num_users,
|
|
148
302
|
format: 'json', //csv or json
|
|
149
303
|
region: "US",
|
|
150
|
-
hasAnonIds:
|
|
151
|
-
|
|
304
|
+
hasAnonIds: true,
|
|
305
|
+
avgDevicePerUser: 2,
|
|
306
|
+
hasSessionIds: true,
|
|
152
307
|
hasAdSpend: false,
|
|
153
308
|
hasLocation: true,
|
|
154
309
|
hasAndroidDevices: true,
|
|
@@ -166,7 +321,7 @@ const config = {
|
|
|
166
321
|
properties: {
|
|
167
322
|
currency: ["USD", "CAD", "EUR", "BTC", "ETH", "JPY"],
|
|
168
323
|
coupon: weighChoices(["none", "none", "none", "none", "10%OFF", "20%OFF", "10%OFF", "20%OFF", "30%OFF", "40%OFF", "50%OFF"]),
|
|
169
|
-
cart: makeProducts()
|
|
324
|
+
cart: makeProducts(),
|
|
170
325
|
}
|
|
171
326
|
},
|
|
172
327
|
{
|
|
@@ -252,6 +407,7 @@ const config = {
|
|
|
252
407
|
event: "sign up",
|
|
253
408
|
weight: 1,
|
|
254
409
|
isFirstEvent: true,
|
|
410
|
+
isAuthEvent: true,
|
|
255
411
|
properties: {
|
|
256
412
|
signupMethod: ["email", "google", "facebook", "twitter", "linkedin", "github"],
|
|
257
413
|
referral: weighChoices(["none", "none", "none", "friend", "ad", "ad", "ad", "friend", "friend", "friend", "friend"]),
|
|
@@ -336,43 +492,34 @@ const config = {
|
|
|
336
492
|
lookupTables: [],
|
|
337
493
|
hook: function (record, type, meta) {
|
|
338
494
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
495
|
+
// Hook 7 (T2C): SIGNUP FLOW TIME-TO-CONVERT (funnel-post)
|
|
496
|
+
// Gold/platinum loyalty tier users complete the Signup Flow funnel
|
|
497
|
+
// 1.4x faster (factor 0.71); bronze users 1.3x slower (factor 1.3).
|
|
498
|
+
if (type === "funnel-post") {
|
|
499
|
+
const segment = meta?.profile?.loyalty_tier;
|
|
500
|
+
if (Array.isArray(record) && record.length > 1) {
|
|
501
|
+
const factor = (
|
|
502
|
+
segment === "gold" || segment === "platinum" ? 0.71 :
|
|
503
|
+
segment === "bronze" ? 1.3 :
|
|
504
|
+
1.0
|
|
505
|
+
);
|
|
506
|
+
if (factor !== 1.0) {
|
|
507
|
+
for (let i = 1; i < record.length; i++) {
|
|
508
|
+
const prev = dayjs(record[i - 1].time);
|
|
509
|
+
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
510
|
+
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
344
515
|
|
|
516
|
+
if (type === "event") {
|
|
345
517
|
// unflattering 'items'
|
|
346
518
|
if (record.item && Array.isArray(record.item)) {
|
|
347
519
|
record = { ...record, ...record.item[0] };
|
|
348
520
|
delete record.item;
|
|
349
521
|
}
|
|
350
522
|
|
|
351
|
-
if (record.event === 'sign up') {
|
|
352
|
-
record.signup_flow = "v1";
|
|
353
|
-
if (eventTime.isBefore(DAY_SIGNUPS_IMPROVED)) {
|
|
354
|
-
// tag 50% for removal (filtered in "everything" hook)
|
|
355
|
-
if (chance.bool({ likelihood: 50 })) {
|
|
356
|
-
record._drop = true;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
if (eventTime.isAfter(DAY_SIGNUPS_IMPROVED)) {
|
|
360
|
-
record.signup_flow = "v2";
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (record.event === 'watch video') {
|
|
365
|
-
const factor = decimal(0.25, 0.79);
|
|
366
|
-
if (eventTime.isBefore(DAY_WATCH_TIME_WENT_UP)) {
|
|
367
|
-
record.watchTimeSec = Math.round(record.watchTimeSec * (1 - factor));
|
|
368
|
-
}
|
|
369
|
-
if (eventTime.isAfter(DAY_WATCH_TIME_WENT_UP)) {
|
|
370
|
-
// increase watch time by 33%
|
|
371
|
-
record.watchTimeSec = Math.round(record.watchTimeSec * (1 + factor));
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
376
523
|
// toys + shoes frequently purchases together (and are higher cart values)
|
|
377
524
|
if (record.event === 'checkout' && Array.isArray(record.cart)) {
|
|
378
525
|
const hasToys = record.cart.some(item => item.category === 'toys');
|
|
@@ -434,53 +581,59 @@ const config = {
|
|
|
434
581
|
e.theme = profile.theme;
|
|
435
582
|
});
|
|
436
583
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// dark mode leads to faster purchase conversion
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
// people who pay in bitcoin tend to buy more electronics and gadgets
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
//custom themes purchase more:
|
|
449
|
-
// const numCustomMode = record.filter(a => a.theme === 'custom').length;
|
|
450
|
-
// const numLightMode = record.filter(a => a.theme === 'light').length;
|
|
451
|
-
// const numDarkMode = record.filter(a => a.theme === 'dark').length;
|
|
452
|
-
// if (numCustomMode > numLightMode || numCustomMode > numDarkMode) {
|
|
453
|
-
// //triple their checkout events
|
|
454
|
-
// const checkoutEvents = record.filter(a => a.event === 'checkout');
|
|
455
|
-
// const newCheckouts = checkoutEvents.map(a => {
|
|
456
|
-
// const randomInt = integer(-48, 48);
|
|
457
|
-
// const newCheckout = {
|
|
458
|
-
// ...a,
|
|
459
|
-
// time: dayjs(a.time).add(randomInt, 'hour').toISOString(),
|
|
460
|
-
// event: "checkout",
|
|
461
|
-
// amount: a.amount * 2,
|
|
462
|
-
// coupon: "50%OFF"
|
|
463
|
-
// };
|
|
464
|
-
// return newCheckout;
|
|
465
|
-
// });
|
|
466
|
-
// record.push(...newCheckouts);
|
|
467
|
-
// }
|
|
468
|
-
|
|
469
|
-
// //users who watch low quality videos churn more:
|
|
470
|
-
// const loQuality = ["480p", "360p", "240p"];
|
|
471
|
-
// const lowQualityWatches = record.filter(a => a.event === 'watch video' && loQuality.includes(a.quality));
|
|
472
|
-
// const highQualityWatches = record.filter(a => a.event === 'watch video' && !loQuality.includes(a.quality));
|
|
473
|
-
// if (lowQualityWatches.length > highQualityWatches.length) {
|
|
474
|
-
// if (flip()) {
|
|
475
|
-
// // find midpoint of records
|
|
476
|
-
// const midpoint = Math.floor(record.length / 2);
|
|
477
|
-
// record = record.slice(0, midpoint);
|
|
478
|
-
|
|
479
|
-
// }
|
|
480
|
-
// }
|
|
584
|
+
const datasetEnd = dayjs.unix(meta.datasetEnd);
|
|
585
|
+
const DAY_SIGNUPS_IMPROVED = datasetEnd.subtract(7, 'day');
|
|
586
|
+
const DAY_WATCH_TIME_WENT_UP = datasetEnd.subtract(30, 'day');
|
|
481
587
|
|
|
588
|
+
// Hook 1: Signup flow improvement — pre-fix signups (v1) get 50% dropped
|
|
589
|
+
record.forEach(e => {
|
|
590
|
+
if (e.event !== 'sign up') return;
|
|
591
|
+
const eventTime = dayjs(e.time);
|
|
592
|
+
e.signup_flow = "v1";
|
|
593
|
+
if (eventTime.isBefore(DAY_SIGNUPS_IMPROVED)) {
|
|
594
|
+
if (chance.bool({ likelihood: 50 })) {
|
|
595
|
+
e._drop = true;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (eventTime.isAfter(DAY_SIGNUPS_IMPROVED)) {
|
|
599
|
+
e.signup_flow = "v2";
|
|
600
|
+
}
|
|
601
|
+
});
|
|
482
602
|
|
|
603
|
+
// Hook 2: Watch time inflection — before 30 days ago: reduce, after: increase
|
|
604
|
+
record.forEach(e => {
|
|
605
|
+
if (e.event !== 'watch video') return;
|
|
606
|
+
const eventTime = dayjs(e.time);
|
|
607
|
+
const factor = decimal(0.25, 0.79);
|
|
608
|
+
if (eventTime.isBefore(DAY_WATCH_TIME_WENT_UP)) {
|
|
609
|
+
e.watchTimeSec = Math.round(e.watchTimeSec * (1 - factor));
|
|
610
|
+
}
|
|
611
|
+
if (eventTime.isAfter(DAY_WATCH_TIME_WENT_UP)) {
|
|
612
|
+
e.watchTimeSec = Math.round(e.watchTimeSec * (1 + factor));
|
|
613
|
+
}
|
|
614
|
+
});
|
|
483
615
|
|
|
616
|
+
// Hook 6: View-Item Magic Number (behavioral, no flags)
|
|
617
|
+
// Users in 3-8 view-item sweet spot show ~25% higher cart amounts.
|
|
618
|
+
// Users >=9 are over-engaged window-shoppers; ~30% of their checkouts drop.
|
|
619
|
+
const viewItemCount = record.filter(e => e.event === 'view item').length;
|
|
620
|
+
if (viewItemCount >= 3 && viewItemCount <= 8) {
|
|
621
|
+
record.forEach(e => {
|
|
622
|
+
if (e.event === 'checkout' && Array.isArray(e.cart)) {
|
|
623
|
+
e.cart = e.cart.map(it => ({
|
|
624
|
+
...it,
|
|
625
|
+
amount: typeof it.amount === 'number' ? Math.round(it.amount * 1.25) : it.amount,
|
|
626
|
+
total_value: typeof it.total_value === 'number' ? Math.round(it.total_value * 1.25) : it.total_value
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
} else if (viewItemCount >= 9) {
|
|
631
|
+
for (let i = record.length - 1; i >= 0; i--) {
|
|
632
|
+
if (record[i].event === 'checkout' && chance.bool({ likelihood: 30 })) {
|
|
633
|
+
record.splice(i, 1);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
484
637
|
|
|
485
638
|
}
|
|
486
639
|
|