@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,7 +1,7 @@
|
|
|
1
1
|
// ── TWEAK THESE ──
|
|
2
2
|
const SEED = "dm4-insurance";
|
|
3
|
-
const num_days =
|
|
4
|
-
const num_users =
|
|
3
|
+
const num_days = 120;
|
|
4
|
+
const num_users = 15_000;
|
|
5
5
|
const avg_events_per_user_per_day = 1.2;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
@@ -52,100 +52,148 @@ const chance = u.initChance(SEED);
|
|
|
52
52
|
|
|
53
53
|
/*
|
|
54
54
|
* ===================================================================
|
|
55
|
-
* ANALYTICS HOOKS
|
|
55
|
+
* ANALYTICS HOOKS (5 hooks)
|
|
56
|
+
*
|
|
57
|
+
* Adds 5. APPLICATION COMPLETION TIME-TO-CONVERT: business 0.74x faster,
|
|
58
|
+
* family 1.3x slower (funnel-post). Discover via funnel median TTC by account_type.
|
|
59
|
+
* NOTE (funnel-post measurement): visible only via Mixpanel funnel median TTC.
|
|
60
|
+
* Cross-event MIN→MIN SQL queries on raw events do NOT show this.
|
|
56
61
|
* ===================================================================
|
|
57
62
|
*
|
|
63
|
+
* NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable
|
|
64
|
+
* via raw-prop breakdowns (date, app_version, issue_category) or
|
|
65
|
+
* behavioral cohorts.
|
|
66
|
+
*
|
|
58
67
|
* -------------------------------------------------------------------
|
|
59
|
-
* 1. VERSION STAMPING (everything
|
|
68
|
+
* 1. VERSION STAMPING (everything)
|
|
60
69
|
* -------------------------------------------------------------------
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* All users shift simultaneously on release dates:
|
|
70
|
+
*
|
|
71
|
+
* PATTERN: Every event gets a deterministic app_version based on its
|
|
72
|
+
* final timestamp. All users shift simultaneously on release dates:
|
|
65
73
|
* - Days 0-30: v2.10
|
|
66
74
|
* - Days 30-60: v2.11
|
|
67
75
|
* - Days 60-90: v2.12
|
|
68
76
|
* - Last 10 days: v2.13
|
|
77
|
+
* app_version is a real product property already in superProps.
|
|
78
|
+
*
|
|
79
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
80
|
+
*
|
|
81
|
+
* Report 1: Event Volume by App Version
|
|
82
|
+
* - Report type: Insights
|
|
83
|
+
* - Event: "page viewed"
|
|
84
|
+
* - Measure: Total
|
|
85
|
+
* - Breakdown: "app_version"
|
|
86
|
+
* - Line chart by day
|
|
87
|
+
* - Expected: clean cutovers between versions, no overlap
|
|
69
88
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* 2. Chart event volume over time, colored by app_version
|
|
73
|
-
* 3. Confirm: no overlap between versions (deterministic cutover)
|
|
89
|
+
* REAL-WORLD ANALOGUE: Forced auto-update SaaS apps cut all users over
|
|
90
|
+
* on release day, producing crisp version bands.
|
|
74
91
|
*
|
|
75
92
|
* -------------------------------------------------------------------
|
|
76
|
-
* 2. SUPPORT TICKET VOLUME DROP (everything
|
|
93
|
+
* 2. SUPPORT TICKET VOLUME DROP (everything)
|
|
77
94
|
* -------------------------------------------------------------------
|
|
78
|
-
* Before v2.13, support ticket volume is high — each user gets 2-3
|
|
79
|
-
* extra tickets injected with bug-related categories (form_crash,
|
|
80
|
-
* login_error, page_timeout, payment_failure). After v2.13, tickets
|
|
81
|
-
* are progressively removed (30% on day 1 → 85% on day 10).
|
|
82
95
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
96
|
+
* PATTERN: Pre-v2.13: each user gets 2-3 extra cloned support-ticket
|
|
97
|
+
* created events with bug-related issue_category values (form_crash,
|
|
98
|
+
* login_error, page_timeout, payment_failure — added to event config).
|
|
99
|
+
* Post-v2.13: tickets progressively removed (30% day 1 → 85% day 10).
|
|
100
|
+
* No flag — discover via issue_category breakdown over time.
|
|
101
|
+
*
|
|
102
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
103
|
+
*
|
|
104
|
+
* Report 1: Ticket Volume Over Time
|
|
105
|
+
* - Report type: Insights
|
|
106
|
+
* - Event: "support ticket created"
|
|
107
|
+
* - Measure: Total
|
|
108
|
+
* - Breakdown: "app_version"
|
|
109
|
+
* - Line chart by day
|
|
110
|
+
* - Expected: high volume on v2.12, sharp drop on v2.13
|
|
111
|
+
*
|
|
112
|
+
* Report 2: Bug Category Share Pre vs Post v2.13
|
|
113
|
+
* - Report type: Insights
|
|
114
|
+
* - Event: "support ticket created"
|
|
115
|
+
* - Measure: Total
|
|
116
|
+
* - Breakdown: "issue_category"
|
|
117
|
+
* - Compare pre-v2.13 vs v2.13 date ranges
|
|
118
|
+
* - Expected: form_crash / login_error / page_timeout / payment_failure
|
|
119
|
+
* dominate pre-v2.13, vanish post-v2.13
|
|
120
|
+
*
|
|
121
|
+
* REAL-WORLD ANALOGUE: A UX-fix release reduces ticket volume overnight.
|
|
89
122
|
*
|
|
90
123
|
* -------------------------------------------------------------------
|
|
91
|
-
* 3. APPLICATION CONVERSION BOOST (everything
|
|
124
|
+
* 3. APPLICATION CONVERSION BOOST (everything)
|
|
92
125
|
* -------------------------------------------------------------------
|
|
93
|
-
* Before v2.13, 40% of "application approved" and "policy activated"
|
|
94
|
-
* events are removed, simulating a broken application flow. After
|
|
95
|
-
* v2.13, all events are preserved — creating a visible conversion jump.
|
|
96
126
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* 3. Compare conversion rates: pre-v2.13 ~60% of post-v2.13
|
|
101
|
-
* 4. Insights > "application approved" count over time — step change
|
|
127
|
+
* PATTERN: Pre-v2.13: ~95% of users have ALL their application approved +
|
|
128
|
+
* policy activated events dropped (per-user gating). Post-v2.13: kept.
|
|
129
|
+
* No flag — discover via funnel by app_version or volume line chart over time.
|
|
102
130
|
*
|
|
103
|
-
*
|
|
104
|
-
* EXPECTED METRICS SUMMARY
|
|
105
|
-
* ===================================================================
|
|
131
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
106
132
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
133
|
+
* Report 1: Application Funnel by Version
|
|
134
|
+
* - Report type: Funnels
|
|
135
|
+
* - Steps: "application submitted" -> "application approved" -> "policy activated"
|
|
136
|
+
* - Breakdown: "app_version"
|
|
137
|
+
* - Expected: post-v2.13 ~ 1.3x pre-v2.13 conversion rate
|
|
138
|
+
* (~58% v2.13 vs ~44% v2.12; some dilution because users span versions)
|
|
112
139
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
140
|
+
* REAL-WORLD ANALOGUE: A buggy multi-step form fixed by release.
|
|
141
|
+
*
|
|
142
|
+
* -------------------------------------------------------------------
|
|
143
|
+
* 4. APPLICATION-STEP MAGIC NUMBER (everything)
|
|
144
|
+
* -------------------------------------------------------------------
|
|
145
|
+
*
|
|
146
|
+
* PATTERN: Users with 8-14 application step completed events sit in the
|
|
147
|
+
* sweet spot — approved_premium boosted +35%. Users with 15+ steps
|
|
148
|
+
* are over-engaged (likely fraud or signal review); 40% of their
|
|
149
|
+
* application approved events drop. No flag.
|
|
116
150
|
*
|
|
117
|
-
*
|
|
118
|
-
* application conversion by app_version to show v2.13's dual impact.
|
|
151
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
119
152
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
153
|
+
* Report 1: Avg Approved Premium by Step Bucket
|
|
154
|
+
* - Report type: Insights (with cohort)
|
|
155
|
+
* - Cohort A: users with 8-14 "application step completed"
|
|
156
|
+
* - Cohort B: users with 0-7
|
|
157
|
+
* - Event: "application approved"
|
|
158
|
+
* - Measure: Average of "approved_premium"
|
|
159
|
+
* - Expected: A ~ 1.35x B
|
|
123
160
|
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
161
|
+
* Report 2: Approvals per User on Heavy Step-Completers
|
|
162
|
+
* - Report type: Insights (with cohort)
|
|
163
|
+
* - Cohort C: users with >= 15 "application step completed"
|
|
164
|
+
* - Cohort A: users with 8-14
|
|
165
|
+
* - Event: "application approved"
|
|
166
|
+
* - Measure: Total per user
|
|
167
|
+
* - Expected: C ~ 40% fewer approvals per user vs A
|
|
126
168
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
169
|
+
* REAL-WORLD ANALOGUE: Engaged applicants get higher premiums approved;
|
|
170
|
+
* over-engaged ones look like fraud and get flagged.
|
|
171
|
+
*
|
|
172
|
+
* ===================================================================
|
|
173
|
+
* EXPECTED METRICS SUMMARY
|
|
174
|
+
* ===================================================================
|
|
129
175
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
176
|
+
* Hook | Metric | Baseline | Effect | Ratio
|
|
177
|
+
* ------------------------|-------------------------|----------|---------|------
|
|
178
|
+
* Version Stamping | Events per version | n/a | clean | bands
|
|
179
|
+
* Support Ticket Volume | tickets v2.12 -> v2.13 | 1x | ~ 0.3x | -70%
|
|
180
|
+
* Application Conversion | approval rate pre/post | 1x | ~ 1.3x | step-up
|
|
181
|
+
* Step-Count Magic Number | sweet approved_premium | 1x | 1.35x | 1.35x
|
|
182
|
+
* Step-Count Magic Number | over approvals/user | 1x | 0.6x | -40%
|
|
132
183
|
*/
|
|
133
184
|
|
|
134
|
-
// ── Time constants for hook calculations ──
|
|
135
|
-
const NOW = dayjs();
|
|
136
|
-
const DATASET_START = NOW.subtract(num_days, "days");
|
|
137
|
-
const V211_DATE = DATASET_START.add(30, "days");
|
|
138
|
-
const V212_DATE = DATASET_START.add(60, "days");
|
|
139
|
-
const V213_DATE = NOW.subtract(10, "days");
|
|
140
|
-
|
|
141
185
|
/** @type {Config} */
|
|
142
186
|
const config = {
|
|
187
|
+
version: 2,
|
|
143
188
|
token,
|
|
144
189
|
seed: SEED,
|
|
145
|
-
|
|
190
|
+
datasetStart: "2026-01-01T00:00:00Z",
|
|
191
|
+
datasetEnd: "2026-05-01T23:59:59Z",
|
|
192
|
+
// numDays: num_days,
|
|
146
193
|
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
147
194
|
numUsers: num_users,
|
|
148
|
-
hasAnonIds:
|
|
195
|
+
hasAnonIds: true,
|
|
196
|
+
avgDevicePerUser: 2,
|
|
149
197
|
hasSessionIds: true,
|
|
150
198
|
format: "json",
|
|
151
199
|
gzip: true,
|
|
@@ -179,6 +227,7 @@ const config = {
|
|
|
179
227
|
event: "account created",
|
|
180
228
|
weight: 1,
|
|
181
229
|
isFirstEvent: true,
|
|
230
|
+
isAuthEvent: true,
|
|
182
231
|
properties: {
|
|
183
232
|
signup_source: ["web", "mobile", "agent_referral", "partner"],
|
|
184
233
|
account_type: ["individual", "family", "business"],
|
|
@@ -328,10 +377,13 @@ const config = {
|
|
|
328
377
|
"coverage",
|
|
329
378
|
"technical",
|
|
330
379
|
"policy_change",
|
|
380
|
+
"form_crash",
|
|
381
|
+
"login_error",
|
|
382
|
+
"page_timeout",
|
|
383
|
+
"payment_failure",
|
|
331
384
|
],
|
|
332
385
|
priority: ["low", "medium", "medium", "high"],
|
|
333
386
|
channel: ["chat", "phone", "email", "web_form"],
|
|
334
|
-
pre_release_bug: [false],
|
|
335
387
|
},
|
|
336
388
|
},
|
|
337
389
|
{
|
|
@@ -477,12 +529,39 @@ const config = {
|
|
|
477
529
|
* are left intact, making the conversion visibly jump up.
|
|
478
530
|
*/
|
|
479
531
|
hook: function (record, type, meta) {
|
|
532
|
+
// HOOK 5 (T2C): APPLICATION COMPLETION TIME-TO-CONVERT (funnel-post)
|
|
533
|
+
// Business accounts complete the Application Completion funnel 1.35x
|
|
534
|
+
// faster (factor 0.74); family accounts 1.3x slower (factor 1.3).
|
|
535
|
+
if (type === "funnel-post") {
|
|
536
|
+
const profile = meta?.profile;
|
|
537
|
+
const accountType = profile && profile.account_type;
|
|
538
|
+
if (Array.isArray(record) && record.length > 1) {
|
|
539
|
+
const factor = (
|
|
540
|
+
accountType === "business" ? 0.74 :
|
|
541
|
+
accountType === "family" ? 1.3 :
|
|
542
|
+
1.0
|
|
543
|
+
);
|
|
544
|
+
if (factor !== 1.0) {
|
|
545
|
+
for (let i = 1; i < record.length; i++) {
|
|
546
|
+
const prev = dayjs(record[i - 1].time);
|
|
547
|
+
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
548
|
+
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
480
554
|
// =============================================================
|
|
481
555
|
// Hooks #1-#3 all run in the "everything" hook so that version
|
|
482
556
|
// stamping sees final timestamps (funnel events adjust time
|
|
483
557
|
// AFTER the event hook runs).
|
|
484
558
|
// =============================================================
|
|
485
559
|
if (type === "everything") {
|
|
560
|
+
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
561
|
+
const datasetEnd = dayjs.unix(meta.datasetEnd);
|
|
562
|
+
const V211_DATE = datasetStart.add(30, "days");
|
|
563
|
+
const V212_DATE = datasetStart.add(60, "days");
|
|
564
|
+
const V213_DATE = datasetEnd.subtract(10, "days");
|
|
486
565
|
const userEvents = record;
|
|
487
566
|
if (userEvents.length === 0) return record;
|
|
488
567
|
|
|
@@ -570,7 +649,6 @@ const config = {
|
|
|
570
649
|
"email",
|
|
571
650
|
"web_form",
|
|
572
651
|
]),
|
|
573
|
-
pre_release_bug: true,
|
|
574
652
|
});
|
|
575
653
|
}
|
|
576
654
|
}
|
|
@@ -602,16 +680,36 @@ const config = {
|
|
|
602
680
|
}
|
|
603
681
|
|
|
604
682
|
// ─── Hook #3: APPLICATION CONVERSION BOOST ───
|
|
605
|
-
// PRE-V2.13: Remove
|
|
606
|
-
//
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
683
|
+
// PRE-V2.13: Remove ALL application approved + policy activated events
|
|
684
|
+
// for ~80% of users (per-user gating, not per-event). This produces
|
|
685
|
+
// visible funnel completion gap pre-v2.13 vs post-v2.13.
|
|
686
|
+
if (chance.bool({ likelihood: 95 })) {
|
|
687
|
+
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
688
|
+
const evt = userEvents[i];
|
|
689
|
+
if (
|
|
690
|
+
(evt.event === "application approved" ||
|
|
691
|
+
evt.event === "policy activated") &&
|
|
692
|
+
dayjs(evt.time).isBefore(V213_DATE)
|
|
693
|
+
) {
|
|
694
|
+
userEvents.splice(i, 1);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Hook 4: APPLICATION-STEP MAGIC NUMBER (no flags)
|
|
700
|
+
// Sweet 8-14 application step completed → +35% on approved_premium
|
|
701
|
+
// for application approved events. Over 15+ → drop 40% of
|
|
702
|
+
// application approved events (fraud filter triggers).
|
|
703
|
+
const stepCount = userEvents.filter(e => e.event === "application step completed").length;
|
|
704
|
+
if (stepCount >= 8 && stepCount <= 14) {
|
|
705
|
+
userEvents.forEach(e => {
|
|
706
|
+
if (e.event === "application approved" && typeof e.approved_premium === "number") {
|
|
707
|
+
e.approved_premium = Math.round(e.approved_premium * 1.35);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
} else if (stepCount >= 15) {
|
|
711
|
+
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
712
|
+
if (userEvents[i].event === "application approved" && chance.bool({ likelihood: 40 })) {
|
|
615
713
|
userEvents.splice(i, 1);
|
|
616
714
|
}
|
|
617
715
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ── TWEAK THESE ──
|
|
2
2
|
const SEED = "dm4-logistics";
|
|
3
|
-
const num_days =
|
|
4
|
-
const num_users =
|
|
3
|
+
const num_days = 120;
|
|
4
|
+
const num_users = 10_000;
|
|
5
5
|
const avg_events_per_user_per_day = 1.2;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
@@ -16,9 +16,6 @@ import * as v from "ak-tools";
|
|
|
16
16
|
|
|
17
17
|
dayjs.extend(utc);
|
|
18
18
|
const chance = u.initChance(SEED);
|
|
19
|
-
const NOW = dayjs();
|
|
20
|
-
const DATASET_START = NOW.subtract(num_days, "days");
|
|
21
|
-
|
|
22
19
|
/** @typedef {import("../../types").Dungeon} Config */
|
|
23
20
|
|
|
24
21
|
// Generate consistent warehouse/supplier IDs at module level
|
|
@@ -34,7 +31,7 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
|
|
|
34
31
|
* businesses to track stock levels, purchase orders, suppliers,
|
|
35
32
|
* shipments, and quality inspections across multiple warehouses.
|
|
36
33
|
*
|
|
37
|
-
* -
|
|
34
|
+
* - 10,000 users over 120 days
|
|
38
35
|
* - Multi-tier system: enterprise (15%), mid-market (35%), small business (40%), trial (10%)
|
|
39
36
|
* - Core loop: sign up -> check inventory -> create PO -> receive order -> track shipment
|
|
40
37
|
* - Revenue: free_trial / starter ($49) / professional ($199) / enterprise ($599)
|
|
@@ -56,14 +53,19 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
|
|
|
56
53
|
|
|
57
54
|
/**
|
|
58
55
|
* ===================================================================
|
|
59
|
-
* ANALYTICS HOOKS (
|
|
56
|
+
* ANALYTICS HOOKS (10 hooks)
|
|
57
|
+
*
|
|
58
|
+
* Adds 10. ONBOARDING TIME-TO-CONVERT: enterprise 0.71x faster, small_business
|
|
59
|
+
* 1.3x slower (funnel-post). Discover via Onboarding funnel median TTC by company_tier.
|
|
60
|
+
* NOTE (funnel-post measurement): visible only via Mixpanel funnel median TTC.
|
|
61
|
+
* Cross-event MIN→MIN SQL queries on raw events do NOT show this.
|
|
60
62
|
* ===================================================================
|
|
61
63
|
*
|
|
62
64
|
* -------------------------------------------------------------------
|
|
63
65
|
* 1. MONTH-END REPORTING SURGE (event hook)
|
|
64
66
|
* -------------------------------------------------------------------
|
|
65
67
|
*
|
|
66
|
-
* PATTERN: Reports generated on calendar days 28-31 have
|
|
68
|
+
* PATTERN: Reports generated on calendar days 28-31 have 2.5x the
|
|
67
69
|
* report_pages. Simulates end-of-month compliance and audit reporting.
|
|
68
70
|
*
|
|
69
71
|
* HOW TO FIND IT IN MIXPANEL:
|
|
@@ -123,6 +125,15 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
|
|
|
123
125
|
* - Breakdown: user property "company_tier"
|
|
124
126
|
* - Expected: enterprise ratio ~0.9x vs small_business baseline
|
|
125
127
|
*
|
|
128
|
+
* Report 3: Stockout-to-Inventory-Check Ratio (NORMALIZED — recommended)
|
|
129
|
+
* - Report type: Insights (formula)
|
|
130
|
+
* - A = "stockout alert" total, B = "inventory checked" total
|
|
131
|
+
* - Formula: A / B
|
|
132
|
+
* - Breakdown: user property "company_tier"
|
|
133
|
+
* - Expected: enterprise ratio ~ 0.9x SMB ratio (10% reduction visible)
|
|
134
|
+
* - WHY THIS METRIC: per-user counts are dominated by persona event multipliers
|
|
135
|
+
* (enterprise has 5x baseline activity); ratio normalizes that out.
|
|
136
|
+
*
|
|
126
137
|
* REAL-WORLD ANALOGUE: Enterprise operations have dedicated
|
|
127
138
|
* procurement teams and predictive analytics reducing stockouts.
|
|
128
139
|
*
|
|
@@ -243,6 +254,41 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
|
|
|
243
254
|
* REAL-WORLD ANALOGUE: Small businesses lack dedicated IT teams,
|
|
244
255
|
* leading to incomplete integration setup and lower alert adoption.
|
|
245
256
|
*
|
|
257
|
+
* -------------------------------------------------------------------
|
|
258
|
+
* 9. INVENTORY-CHECK MAGIC NUMBER (everything hook)
|
|
259
|
+
* -------------------------------------------------------------------
|
|
260
|
+
*
|
|
261
|
+
* PATTERN: Users with 5-15 "inventory checked" events sit in the
|
|
262
|
+
* "engaged-but-focused" sweet spot — every "purchase order created"
|
|
263
|
+
* event gets quantity boosted ~25%. Users with 16 or more inventory
|
|
264
|
+
* checks are over-engaged (paralysis); ~30% of their "purchase order
|
|
265
|
+
* created" events are dropped. No flag is stamped — discoverable only
|
|
266
|
+
* by binning users on inventory-check COUNT and comparing PO totals.
|
|
267
|
+
*
|
|
268
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
269
|
+
*
|
|
270
|
+
* Report 1: PO Quantity by Inventory-Check Bucket
|
|
271
|
+
* - Report type: Insights (with cohort)
|
|
272
|
+
* - Cohort A: users with 5-15 "inventory checked" events
|
|
273
|
+
* - Cohort B: users with 0-4 "inventory checked" events
|
|
274
|
+
* - Event: "purchase order created"
|
|
275
|
+
* - Measure: Average of "quantity"
|
|
276
|
+
* - Compare cohort A vs cohort B
|
|
277
|
+
* - Expected: cohort A ~ 1.25x higher quantity than B
|
|
278
|
+
*
|
|
279
|
+
* Report 2: POs per User by Browse Intensity
|
|
280
|
+
* - Report type: Insights (with cohort)
|
|
281
|
+
* - Cohort C: users with >= 16 "inventory checked" events
|
|
282
|
+
* - Cohort A: users with 5-15 "inventory checked" events
|
|
283
|
+
* - Event: "purchase order created"
|
|
284
|
+
* - Measure: Total events per user
|
|
285
|
+
* - Compare cohort C vs cohort A
|
|
286
|
+
* - Expected: cohort C has ~ 30% fewer POs per user
|
|
287
|
+
*
|
|
288
|
+
* REAL-WORLD ANALOGUE: A focused operations team that monitors
|
|
289
|
+
* stock just enough places larger, more confident orders; an
|
|
290
|
+
* obsessive checker is paralysed and orders less.
|
|
291
|
+
*
|
|
246
292
|
* ===================================================================
|
|
247
293
|
* EXPECTED METRICS SUMMARY
|
|
248
294
|
* ===================================================================
|
|
@@ -256,17 +302,23 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
|
|
|
256
302
|
* Alert Fatigue | response_time_hours | 4h | 8-12h | 2-3x
|
|
257
303
|
* Trial Churn | events after wk 1 | 5 | 2.5 | 0.5x
|
|
258
304
|
* Enterprise Profiles | warehouse_count | 3 | 10 | 3.3x
|
|
259
|
-
* Small-Biz Conversion Drop
|
|
305
|
+
* Small-Biz Conversion Drop | funnel conversion | 30% | 20% | 0.65x
|
|
306
|
+
* Inventory-Check Magic Num | sweet PO quantity | 1x | 1.25x | 1.25x
|
|
307
|
+
* Inventory-Check Magic Num | over POs/user | 1x | 0.7x | -30%
|
|
260
308
|
*/
|
|
261
309
|
|
|
262
310
|
/** @type {Config} */
|
|
263
311
|
const config = {
|
|
312
|
+
version: 2,
|
|
264
313
|
token,
|
|
265
314
|
seed: SEED,
|
|
266
|
-
|
|
315
|
+
datasetStart: "2026-01-01T00:00:00Z",
|
|
316
|
+
datasetEnd: "2026-05-01T23:59:59Z",
|
|
317
|
+
// numDays: num_days,
|
|
267
318
|
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
268
319
|
numUsers: num_users,
|
|
269
|
-
hasAnonIds:
|
|
320
|
+
hasAnonIds: true,
|
|
321
|
+
avgDevicePerUser: 2,
|
|
270
322
|
hasSessionIds: true,
|
|
271
323
|
format: "json",
|
|
272
324
|
gzip: true,
|
|
@@ -299,6 +351,7 @@ const config = {
|
|
|
299
351
|
event: "account created",
|
|
300
352
|
weight: 1,
|
|
301
353
|
isFirstEvent: true,
|
|
354
|
+
isAuthEvent: true,
|
|
302
355
|
properties: {
|
|
303
356
|
referral_source: ["organic", "partner_referral", "google_search", "trade_show", "linkedin"],
|
|
304
357
|
},
|
|
@@ -691,6 +744,27 @@ const config = {
|
|
|
691
744
|
|
|
692
745
|
// -- Hook Function ------------------------------------------------
|
|
693
746
|
hook: function (record, type, meta) {
|
|
747
|
+
// HOOK 10 (T2C): ONBOARDING TIME-TO-CONVERT (funnel-post)
|
|
748
|
+
// Enterprise tier completes Onboarding funnel 1.4x faster (factor 0.71);
|
|
749
|
+
// small_business 1.3x slower (factor 1.3).
|
|
750
|
+
if (type === "funnel-post") {
|
|
751
|
+
const segment = meta?.profile?.company_tier;
|
|
752
|
+
if (Array.isArray(record) && record.length > 1) {
|
|
753
|
+
const factor = (
|
|
754
|
+
segment === "enterprise" ? 0.71 :
|
|
755
|
+
segment === "small_business" || segment === "trial" ? 1.3 :
|
|
756
|
+
1.0
|
|
757
|
+
);
|
|
758
|
+
if (factor !== 1.0) {
|
|
759
|
+
for (let i = 1; i < record.length; i++) {
|
|
760
|
+
const prev = dayjs(record[i - 1].time);
|
|
761
|
+
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
762
|
+
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
694
768
|
// -- HOOK 7: ENTERPRISE PROFILES (user) -----------------------
|
|
695
769
|
// Enterprise ops get large warehouse_count and employee_count.
|
|
696
770
|
if (type === "user") {
|
|
@@ -700,6 +774,9 @@ const config = {
|
|
|
700
774
|
} else if (record.company_tier === "mid_market") {
|
|
701
775
|
record.warehouse_count = chance.integer({ min: 2, max: 6 });
|
|
702
776
|
record.employee_count = chance.integer({ min: 20, max: 200 });
|
|
777
|
+
} else if (record.company_tier === "small_business") {
|
|
778
|
+
record.warehouse_count = chance.integer({ min: 1, max: 3 });
|
|
779
|
+
record.employee_count = chance.integer({ min: 5, max: 80 });
|
|
703
780
|
} else if (record.company_tier === "trial") {
|
|
704
781
|
record.warehouse_count = 1;
|
|
705
782
|
record.employee_count = chance.integer({ min: 1, max: 10 });
|
|
@@ -747,7 +824,7 @@ const config = {
|
|
|
747
824
|
if (e.event === 'report generated') {
|
|
748
825
|
const dayOfMonth = new Date(e.time).getUTCDate();
|
|
749
826
|
if (dayOfMonth >= 28) {
|
|
750
|
-
e.report_pages = Math.floor((e.report_pages || 20) * 2);
|
|
827
|
+
e.report_pages = Math.floor((e.report_pages || 20) * 2.5);
|
|
751
828
|
}
|
|
752
829
|
}
|
|
753
830
|
}
|
|
@@ -823,6 +900,24 @@ const config = {
|
|
|
823
900
|
}
|
|
824
901
|
}
|
|
825
902
|
|
|
903
|
+
// -- HOOK 9: INVENTORY-CHECK MAGIC NUMBER (no flags) ------
|
|
904
|
+
// Sweet 5-15 inventory checks → +25% PO quantity.
|
|
905
|
+
// Over 16+ → drop 45% of PO created events.
|
|
906
|
+
const invCheckCount = record.filter(e => e.event === 'inventory checked').length;
|
|
907
|
+
if (invCheckCount >= 5 && invCheckCount <= 15) {
|
|
908
|
+
record.forEach(e => {
|
|
909
|
+
if (e.event === 'purchase order created' && typeof e.quantity === 'number') {
|
|
910
|
+
e.quantity = Math.round(e.quantity * 1.25);
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
} else if (invCheckCount >= 16) {
|
|
914
|
+
for (let i = record.length - 1; i >= 0; i--) {
|
|
915
|
+
if (record[i].event === 'purchase order created' && chance.bool({ likelihood: 45 })) {
|
|
916
|
+
record.splice(i, 1);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
826
921
|
return record;
|
|
827
922
|
}
|
|
828
923
|
|