@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.
Files changed (98) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dungeons/technical/array-of-object-lookup.js +0 -2
  3. package/dungeons/technical/hook-helpers-verify.js +89 -0
  4. package/dungeons/technical/identity-model-verify.js +47 -0
  5. package/dungeons/technical/pattern-aggregate-by-bin.js +41 -0
  6. package/dungeons/technical/pattern-attributed-by-source.js +42 -0
  7. package/dungeons/technical/pattern-frequency-by-frequency.js +40 -0
  8. package/dungeons/technical/pattern-funnel-frequency.js +54 -0
  9. package/dungeons/technical/pattern-ttc-by-segment.js +45 -0
  10. package/dungeons/technical/simple.js +3 -4
  11. package/dungeons/technical/simplest-schema.json +5 -0
  12. package/dungeons/technical/text-generation.js +1 -1
  13. package/dungeons/vertical/ai-platform.js +216 -164
  14. package/dungeons/vertical/community.js +74 -41
  15. package/dungeons/vertical/crypto.js +343 -233
  16. package/dungeons/vertical/dating.js +246 -292
  17. package/dungeons/vertical/devtools.js +179 -70
  18. package/dungeons/vertical/ecommerce.js +275 -122
  19. package/dungeons/vertical/education.js +345 -312
  20. package/dungeons/vertical/fintech.js +445 -314
  21. package/dungeons/vertical/fitness.js +199 -165
  22. package/dungeons/vertical/food-delivery.js +309 -330
  23. package/dungeons/vertical/gaming.js +387 -241
  24. package/dungeons/vertical/healthcare.js +149 -66
  25. package/dungeons/vertical/insurance-application.js +173 -75
  26. package/dungeons/vertical/logistics.js +107 -12
  27. package/dungeons/vertical/marketplace.js +171 -76
  28. package/dungeons/vertical/media.js +259 -402
  29. package/dungeons/vertical/real-estate.js +336 -318
  30. package/dungeons/vertical/sass.js +294 -288
  31. package/dungeons/vertical/social.js +289 -234
  32. package/dungeons/vertical/travel.js +66 -20
  33. package/index.js +17 -17
  34. package/lib/core/config-validator.js +226 -175
  35. package/lib/core/context.js +10 -24
  36. package/lib/core/storage.js +6 -1
  37. package/lib/generators/events.js +57 -104
  38. package/lib/generators/funnels.js +212 -93
  39. package/lib/generators/mirror.js +3 -2
  40. package/lib/generators/product-names.js +1 -1
  41. package/lib/generators/scd.js +2 -1
  42. package/lib/generators/text.js +1 -1
  43. package/lib/hook-helpers/_internal.js +23 -0
  44. package/lib/hook-helpers/cohort.js +124 -0
  45. package/lib/hook-helpers/identity.js +56 -0
  46. package/lib/hook-helpers/index.js +44 -0
  47. package/lib/hook-helpers/inject.js +99 -0
  48. package/lib/hook-helpers/mutate.js +151 -0
  49. package/lib/hook-helpers/timing.js +99 -0
  50. package/lib/hook-patterns/aggregate-per-user-by-bin.js +38 -0
  51. package/lib/hook-patterns/attributed-by-source.js +72 -0
  52. package/lib/hook-patterns/frequency-by-frequency.js +46 -0
  53. package/lib/hook-patterns/funnel-frequency-breakdown.js +73 -0
  54. package/lib/hook-patterns/index.js +14 -0
  55. package/lib/hook-patterns/time-to-convert-by-segment.js +41 -0
  56. package/lib/orchestrators/user-loop.js +143 -293
  57. package/lib/utils/utils.js +97 -52
  58. package/lib/verify/emulate-breakdown.js +281 -0
  59. package/lib/verify/index.js +12 -0
  60. package/lib/verify/verify-dungeon.js +61 -0
  61. package/package.json +6 -4
  62. package/scripts/smoke-test-all.mjs +162 -0
  63. package/scripts/verify-runner.mjs +72 -24
  64. package/types.d.ts +434 -225
  65. package/dungeons/technical/ad-spend-schema.json +0 -128
  66. package/dungeons/technical/anonymous-users-schema.json +0 -92
  67. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  68. package/dungeons/technical/experiments-schema.json +0 -203
  69. package/dungeons/technical/foobar-schema.json +0 -362
  70. package/dungeons/technical/group-analytics-schema.json +0 -241
  71. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  72. package/dungeons/technical/nested-objects-schema.json +0 -145
  73. package/dungeons/technical/retention-cadence-schema.json +0 -37
  74. package/dungeons/technical/sanity-schema.json +0 -185
  75. package/dungeons/technical/scale-test-schema.json +0 -70
  76. package/dungeons/technical/scd-schema.json +0 -467
  77. package/dungeons/technical/simple-schema.json +0 -362
  78. package/dungeons/technical/text-generation-schema.json +0 -1062
  79. package/dungeons/vertical/ai-platform-schema.json +0 -617
  80. package/dungeons/vertical/community-schema.json +0 -579
  81. package/dungeons/vertical/crypto-schema.json +0 -546
  82. package/dungeons/vertical/dating-schema.json +0 -401
  83. package/dungeons/vertical/devtools-schema.json +0 -601
  84. package/dungeons/vertical/ecommerce-schema.json +0 -604
  85. package/dungeons/vertical/education-schema.json +0 -5686
  86. package/dungeons/vertical/fintech-schema.json +0 -630
  87. package/dungeons/vertical/fitness-schema.json +0 -530
  88. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  89. package/dungeons/vertical/gaming-schema.json +0 -2703
  90. package/dungeons/vertical/healthcare-schema.json +0 -549
  91. package/dungeons/vertical/insurance-application-schema.json +0 -485
  92. package/dungeons/vertical/logistics-schema.json +0 -574
  93. package/dungeons/vertical/marketplace-schema.json +0 -533
  94. package/dungeons/vertical/media-schema.json +0 -4749
  95. package/dungeons/vertical/real-estate-schema.json +0 -527
  96. package/dungeons/vertical/sass-schema.json +0 -3128
  97. package/dungeons/vertical/social-schema.json +0 -620
  98. 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 = 100;
4
- const num_users = 5_000;
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 hook)
68
+ * 1. VERSION STAMPING (everything)
60
69
  * -------------------------------------------------------------------
61
- * Every event gets a deterministic app_version based on its final
62
- * timestamp. Uses the everything hook (not event hook) because funnel
63
- * events adjust their time AFTER the event hook runs.
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
- * MIXPANEL REPORT:
71
- * 1. Insights > "page viewed" > Breakdown by app_version
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 hook)
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
- * MIXPANEL REPORT:
84
- * 1. Insights > "support ticket created" count over time (line chart)
85
- * 2. Break down by app_version: v2.12 has high volume, v2.13 drops
86
- * 3. Filter issue_category to bug categories (form_crash, etc.)
87
- * 4. Filter pre_release_bug = true for injected tickets only
88
- * 5. Compare weekly ticket volume before vs after v2.13 release
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 hook)
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
- * MIXPANEL REPORT:
98
- * 1. Funnels > application submitted approved policy activated
99
- * 2. Break down by app_version (v2.12 vs v2.13)
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
- * Hook | Metric | Pre-v2.13 | Post-v2.13
108
- * ------------------------|-------------------------|-----------|----------
109
- * Version Stamping | Events per version | ~30d each | 10 days
110
- * Support Ticket Volume | Weekly ticket count | HIGH | ~70% lower
111
- * Application Conversion | Approval rate | ~42% | ~70%
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
- * ADVANCED ANALYSIS IDEAS
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
- * 1. Version Impact Dashboard: Chart both support tickets AND
118
- * application conversion by app_version to show v2.13's dual impact.
151
+ * HOW TO FIND IT IN MIXPANEL:
119
152
  *
120
- * 2. Bug Category Analysis: Which pre_release_bug categories were most
121
- * common? Do they correlate with the application steps where users
122
- * were dropping off?
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
- * 3. Platform Comparison: Did the v2.13 improvement affect all platforms
125
- * equally, or did web/iOS/Android see different magnitudes?
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
- * 4. Insurance Type Breakdown: Are certain insurance types (auto vs home
128
- * vs life) more affected by the conversion improvement?
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
- * 5. Time-to-Approval: Did v2.13 also change the approval_time_hours
131
- * distribution, or just the volume of approvals?
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
- numDays: num_days,
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: false,
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 ~40% of application approved and policy activated
606
- // events, lowering effective conversion before the release.
607
- for (let i = userEvents.length - 1; i >= 0; i--) {
608
- const evt = userEvents[i];
609
- if (
610
- (evt.event === "application approved" ||
611
- evt.event === "policy activated") &&
612
- dayjs(evt.time).isBefore(V213_DATE)
613
- ) {
614
- if (chance.bool({ likelihood: 40 })) {
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 = 100;
4
- const num_users = 5_000;
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
- * - 5,000 users over 100 days, ~600K events
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 (8 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 2x the
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 | funnel conversion | 30% | 20% | 0.65x
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
- numDays: num_days,
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: false,
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