@ak--47/dungeon-master 1.5.4 → 1.6.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 (146) hide show
  1. package/.claude/skills/analyze-soup/SKILL.md +21 -12
  2. package/.claude/skills/create-dungeon/SKILL.md +65 -5
  3. package/.claude/skills/create-project/SKILL.md +1 -1
  4. package/.claude/skills/create-project/provision.mjs +57 -10
  5. package/.claude/skills/verify-dungeon/SKILL.md +56 -25
  6. package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
  7. package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
  8. package/.claude/skills/write-hooks/SKILL.md +156 -18
  9. package/CHANGELOG.md +183 -0
  10. package/HOOKS.md +566 -112
  11. package/README.md +28 -9
  12. package/docs/guides/1.3.0-upgrade-guide.md +262 -0
  13. package/docs/guides/1.3.1-upgrade-guide.md +328 -0
  14. package/docs/guides/1.4.0-upgrade-guide.md +130 -0
  15. package/docs/guides/1.4.1-upgrade-guide.md +125 -0
  16. package/docs/guides/1.4.3-upgrade-guide.md +200 -0
  17. package/docs/guides/1.4.4-upgrade-guide.md +95 -0
  18. package/docs/guides/1.5.0-upgrade-guide.md +426 -0
  19. package/docs/guides/1.5.1-upgrade-guide.md +178 -0
  20. package/docs/guides/1.5.3-upgrade-guide.md +117 -0
  21. package/docs/guides/1.6.0-upgrade-guide.md +204 -0
  22. package/dungeons/technical/stories-verify.js +152 -0
  23. package/dungeons/vertical/README.md +154 -0
  24. package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
  25. package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
  26. package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
  27. package/dungeons/vertical/community/community.js +1589 -0
  28. package/dungeons/vertical/community/community.sql +216 -0
  29. package/dungeons/vertical/community/community.verify.mjs +72 -0
  30. package/dungeons/vertical/crypto/crypto.js +1483 -0
  31. package/dungeons/vertical/crypto/crypto.sql +194 -0
  32. package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
  33. package/dungeons/vertical/dating/dating.js +1540 -0
  34. package/dungeons/vertical/dating/dating.sql +288 -0
  35. package/dungeons/vertical/dating/dating.verify.mjs +72 -0
  36. package/dungeons/vertical/devtools/devtools.js +1524 -0
  37. package/dungeons/vertical/devtools/devtools.sql +161 -0
  38. package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
  39. package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
  40. package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
  41. package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
  42. package/dungeons/vertical/education/education.js +1763 -0
  43. package/dungeons/vertical/education/education.sql +243 -0
  44. package/dungeons/vertical/education/education.verify.mjs +75 -0
  45. package/dungeons/vertical/fintech/fintech.js +1410 -0
  46. package/dungeons/vertical/fintech/fintech.sql +175 -0
  47. package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
  48. package/dungeons/vertical/fitness/fitness.js +1495 -0
  49. package/dungeons/vertical/fitness/fitness.sql +206 -0
  50. package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
  51. package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
  52. package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
  53. package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
  54. package/dungeons/vertical/gaming/gaming.js +1741 -0
  55. package/dungeons/vertical/gaming/gaming.sql +249 -0
  56. package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
  57. package/dungeons/vertical/healthcare/healthcare.js +1488 -0
  58. package/dungeons/vertical/healthcare/healthcare.sql +193 -0
  59. package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
  60. package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
  61. package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
  62. package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
  63. package/dungeons/vertical/logistics/logistics.js +1541 -0
  64. package/dungeons/vertical/logistics/logistics.sql +205 -0
  65. package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
  66. package/dungeons/vertical/marketplace/marketplace.js +1438 -0
  67. package/dungeons/vertical/marketplace/marketplace.sql +148 -0
  68. package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
  69. package/dungeons/vertical/{media.js → media/media.js} +557 -58
  70. package/dungeons/vertical/media/media.sql +173 -0
  71. package/dungeons/vertical/media/media.verify.mjs +74 -0
  72. package/dungeons/vertical/real-estate/real-estate.js +1629 -0
  73. package/dungeons/vertical/real-estate/real-estate.sql +167 -0
  74. package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
  75. package/dungeons/vertical/sass/sass.js +1733 -0
  76. package/dungeons/vertical/sass/sass.sql +173 -0
  77. package/dungeons/vertical/sass/sass.verify.mjs +74 -0
  78. package/dungeons/vertical/social/social.js +1456 -0
  79. package/dungeons/vertical/social/social.sql +160 -0
  80. package/dungeons/vertical/social/social.verify.mjs +76 -0
  81. package/dungeons/vertical/streaming/streaming.js +677 -0
  82. package/dungeons/vertical/streaming/streaming.sql +101 -0
  83. package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
  84. package/dungeons/vertical/support-desk/support-desk.js +741 -0
  85. package/dungeons/vertical/support-desk/support-desk.sql +137 -0
  86. package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
  87. package/dungeons/vertical/travel/travel.js +1603 -0
  88. package/dungeons/vertical/travel/travel.sql +236 -0
  89. package/dungeons/vertical/travel/travel.verify.mjs +72 -0
  90. package/index.js +2 -2
  91. package/lib/core/config-validator.js +37 -2
  92. package/lib/core/context.js +4 -2
  93. package/lib/core/dungeon-loader.js +20 -15
  94. package/lib/core/dungeon-to-json.js +22 -4
  95. package/lib/core/storage.js +3 -3
  96. package/lib/generators/funnels.js +36 -9
  97. package/lib/hook-helpers/cohort.js +53 -0
  98. package/lib/hook-helpers/index.js +8 -0
  99. package/lib/hook-helpers/mutate.js +1 -1
  100. package/lib/hook-helpers/shape.js +319 -0
  101. package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
  102. package/lib/hook-patterns/attributed-by-source.js +60 -58
  103. package/lib/hook-patterns/frequency-by-frequency.js +14 -4
  104. package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
  105. package/lib/hook-patterns/index.js +1 -1
  106. package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
  107. package/lib/orchestrators/user-loop.js +41 -0
  108. package/lib/templates/story-spec.schema.json +175 -0
  109. package/lib/utils/json-evaluator.js +1 -1
  110. package/lib/utils/utils.js +5 -4
  111. package/lib/verify/coerce.js +186 -0
  112. package/lib/verify/counting.js +270 -32
  113. package/lib/verify/emulate-breakdown.js +1285 -203
  114. package/lib/verify/first-time.js +89 -0
  115. package/lib/verify/flows.js +679 -0
  116. package/lib/verify/formula.js +259 -0
  117. package/lib/verify/funnel-engine.js +697 -113
  118. package/lib/verify/index.js +32 -4
  119. package/lib/verify/sessionize.js +275 -0
  120. package/lib/verify/story-runner.js +483 -0
  121. package/lib/verify/verify-dungeon.js +59 -37
  122. package/package.json +2 -1
  123. package/scripts/extract-dungeon-schema.mjs +2 -2
  124. package/scripts/run-many.mjs +2 -2
  125. package/scripts/verify-runner.mjs +2 -2
  126. package/scripts/verify-stories.mjs +244 -0
  127. package/types.d.ts +296 -30
  128. package/dungeons/vertical/ai-platform.js +0 -818
  129. package/dungeons/vertical/community.js +0 -966
  130. package/dungeons/vertical/crypto.js +0 -895
  131. package/dungeons/vertical/dating.js +0 -830
  132. package/dungeons/vertical/devtools.js +0 -1050
  133. package/dungeons/vertical/ecommerce.js +0 -811
  134. package/dungeons/vertical/education.js +0 -964
  135. package/dungeons/vertical/fintech.js +0 -946
  136. package/dungeons/vertical/fitness.js +0 -935
  137. package/dungeons/vertical/food-delivery.js +0 -934
  138. package/dungeons/vertical/gaming.js +0 -1120
  139. package/dungeons/vertical/healthcare.js +0 -945
  140. package/dungeons/vertical/insurance-application.js +0 -1039
  141. package/dungeons/vertical/logistics.js +0 -986
  142. package/dungeons/vertical/marketplace.js +0 -1019
  143. package/dungeons/vertical/real-estate.js +0 -872
  144. package/dungeons/vertical/sass.js +0 -995
  145. package/dungeons/vertical/social.js +0 -904
  146. package/dungeons/vertical/travel.js +0 -930
@@ -1,986 +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: SupplyStack
13
- * APP: B2B warehouse/inventory management SaaS for businesses to track
14
- * stock levels, purchase orders, suppliers, shipments, and quality
15
- * inspections across multiple warehouses. Multi-tier system with
16
- * enterprise, mid-market, small business, and trial customers.
17
- * SCALE: 10,000 users, ~1.4M events, 121 days (2026-01-01 → 2026-05-01)
18
- * CORE LOOP: account created → inventory checked → purchase order → order received → shipment tracked
19
- *
20
- * EVENTS (17):
21
- * inventory checked (8) > app session (7) > shipment tracked (6) > notification received (6)
22
- * > stockout alert (5) > purchase order created (5) > order received (4) > report generated (4)
23
- * > supplier contacted (3) > warehouse transfer (3) > invoice processed (3)
24
- * > integration connected (2) > alert configured (2) > quality inspection (2)
25
- * > account created (1) > support ticket (1) > account deactivated (1)
26
- *
27
- * FUNNELS (5):
28
- * - Onboarding: account created → inventory checked → integration connected → report generated (40%)
29
- * - Order Fulfillment: inventory checked → purchase order created → order received → shipment tracked (35%, reentry)
30
- * - Supplier Management: supplier contacted → purchase order created → invoice processed (45%)
31
- * - Integration Setup: integration connected → report generated → alert configured (30%)
32
- * - Alert Response: stockout alert → purchase order created → order received (50%)
33
- *
34
- * USER PROPS: company_tier, warehouse_count, employee_count, industry, integration_count, Platform, subscription_plan
35
- * SUPER PROPS: Platform, subscription_plan
36
- * SCD PROPS: company_tier (trial/starter/professional/enterprise, monthly fuzzy, max 6)
37
- * GROUPS: none
38
- */
39
-
40
- // ── HOOK STORIES ──
41
- /*
42
- * -------------------------------------------------------------------
43
- * 1. MONTH-END REPORTING SURGE (event hook)
44
- * -------------------------------------------------------------------
45
- *
46
- * PATTERN: Reports generated on calendar days 28-31 have 2.5x the
47
- * report_pages. Simulates end-of-month compliance and audit reporting.
48
- *
49
- * HOW TO FIND IT IN MIXPANEL:
50
- *
51
- * Report 1: Report Volume by Day of Month
52
- * - Report type: Insights
53
- * - Event: "report generated"
54
- * - Measure: Average of "report_pages"
55
- * - Breakdown: Day of month (custom formula or daily chart)
56
- * - Expected: Days 28-31 should show ~2x avg report_pages vs mid-month
57
- * (month-end ~40 pages, mid-month ~20 pages)
58
- *
59
- * REAL-WORLD ANALOGUE: Warehouse managers generate larger, more
60
- * comprehensive reports at month-end for audits and compliance.
61
- *
62
- * -------------------------------------------------------------------
63
- * 2. RUSH ORDER PREMIUM (event hook)
64
- * -------------------------------------------------------------------
65
- *
66
- * PATTERN: Purchase orders with priority "urgent" get 1.5x unit_cost.
67
- * Simulates expedited shipping and rush fulfillment surcharges.
68
- *
69
- * HOW TO FIND IT IN MIXPANEL:
70
- *
71
- * Report 1: Unit Cost by Priority
72
- * - Report type: Insights
73
- * - Event: "purchase order created"
74
- * - Measure: Average of "unit_cost"
75
- * - Breakdown: "priority"
76
- * - Expected: urgent ~$75 vs standard ~$50 (1.5x ratio)
77
- *
78
- * REAL-WORLD ANALOGUE: Rush orders from suppliers incur premium
79
- * pricing for expedited manufacturing and express shipping.
80
- *
81
- * -------------------------------------------------------------------
82
- * 3. REORDER ACCURACY BY TIER (everything hook)
83
- * -------------------------------------------------------------------
84
- *
85
- * PATTERN: Enterprise users have 0.9x stockout rate -- they get 10%
86
- * of their "stockout alert" events removed. Better forecasting and
87
- * larger safety stock reduces out-of-stock incidents.
88
- *
89
- * HOW TO FIND IT IN MIXPANEL:
90
- *
91
- * Report 1: Stockout Rate by Company Tier
92
- * - Report type: Insights
93
- * - Event: "stockout alert"
94
- * - Measure: Total per user
95
- * - Breakdown: user property "company_tier"
96
- * - Expected: enterprise ~10% fewer stockout alerts per user than
97
- * mid_market or small_business
98
- *
99
- * Report 2: Stockout-to-Order Ratio
100
- * - Report type: Insights (formula)
101
- * - A = "stockout alert" total, B = "purchase order created" total
102
- * - Formula: A / B
103
- * - Breakdown: user property "company_tier"
104
- * - Expected: enterprise ratio ~0.9x vs small_business baseline
105
- *
106
- * Report 3: Stockout-to-Inventory-Check Ratio (NORMALIZED — recommended)
107
- * - Report type: Insights (formula)
108
- * - A = "stockout alert" total, B = "inventory checked" total
109
- * - Formula: A / B
110
- * - Breakdown: user property "company_tier"
111
- * - Expected: enterprise ratio ~ 0.9x SMB ratio (10% reduction visible)
112
- * - WHY THIS METRIC: per-user counts are dominated by persona event multipliers
113
- * (enterprise has 5x baseline activity); ratio normalizes that out.
114
- *
115
- * REAL-WORLD ANALOGUE: Enterprise operations have dedicated
116
- * procurement teams and predictive analytics reducing stockouts.
117
- *
118
- * -------------------------------------------------------------------
119
- * 4. INTEGRATION COMPLETION DRIVES RETENTION (everything hook)
120
- * -------------------------------------------------------------------
121
- *
122
- * PATTERN: Users with 3+ "integration connected" events get cloned
123
- * "report generated" events. Connected integrations produce richer
124
- * reporting and deeper platform engagement.
125
- *
126
- * HOW TO FIND IT IN MIXPANEL:
127
- *
128
- * Report 1: Report Volume by Integration Count
129
- * - Report type: Insights
130
- * - Event: "report generated"
131
- * - Measure: Total per user
132
- * - Breakdown: user property "integration_count" (or filter by
133
- * users who did "integration connected" 3+ times)
134
- * - Expected: Users with 3+ integrations ~2x report events
135
- *
136
- * REAL-WORLD ANALOGUE: Businesses that integrate their ERP, shipping,
137
- * and accounting systems generate more automated reports, increasing
138
- * stickiness and reducing churn.
139
- *
140
- * -------------------------------------------------------------------
141
- * 5. ALERT FATIGUE (everything hook)
142
- * -------------------------------------------------------------------
143
- *
144
- * PATTERN: Users with >30 "stockout alert" events get increasing
145
- * response_time_hours on their later alerts. The 20th+ alert has
146
- * response_time scaled up by 1.5-3x based on position.
147
- *
148
- * HOW TO FIND IT IN MIXPANEL:
149
- *
150
- * Report 1: Alert Response Time Over Time
151
- * - Report type: Insights
152
- * - Event: "stockout alert"
153
- * - Measure: Average of "response_time_hours"
154
- * - Line chart by week
155
- * - Filter: users with high alert volume
156
- * - Expected: Response time trends upward over time for heavy-alert users
157
- * (early alerts ~4h, later alerts ~8-12h)
158
- *
159
- * REAL-WORLD ANALOGUE: Alert fatigue is a real operational problem --
160
- * too many alerts desensitize warehouse managers, slowing response.
161
- *
162
- * -------------------------------------------------------------------
163
- * 6. TRIAL CHURN (everything hook)
164
- * -------------------------------------------------------------------
165
- *
166
- * PATTERN: Users with <10 total events lose 50% of their events after
167
- * day 7 of the dataset. Simulates trial users who briefly explore then
168
- * abandon the platform.
169
- *
170
- * HOW TO FIND IT IN MIXPANEL:
171
- *
172
- * Report 1: Retention by Event Volume
173
- * - Report type: Retention
174
- * - Starting event: "account created"
175
- * - Return event: Any event
176
- * - Breakdown: user property "company_tier"
177
- * - Expected: trial users show sharp drop after week 1 (~50% drop)
178
- *
179
- * Report 2: Event Volume Distribution
180
- * - Report type: Insights
181
- * - Event: All events
182
- * - Measure: Total per user
183
- * - Breakdown: user property "company_tier"
184
- * - Expected: trial users cluster at <5 events
185
- *
186
- * REAL-WORLD ANALOGUE: SaaS trial users who don't activate within
187
- * the first week rarely convert to paying customers.
188
- *
189
- * -------------------------------------------------------------------
190
- * 7. ENTERPRISE PROFILES (user hook)
191
- * -------------------------------------------------------------------
192
- *
193
- * PATTERN: Users with company_tier "enterprise" get warehouse_count
194
- * boosted to 5-15 and employee_count to 200-2000. Enterprise ops
195
- * manage significantly more infrastructure.
196
- *
197
- * HOW TO FIND IT IN MIXPANEL:
198
- *
199
- * Report 1: Warehouse Count by Tier
200
- * - Report type: Insights
201
- * - Measure: Profiles -> Average of "warehouse_count"
202
- * - Breakdown: "company_tier"
203
- * - Expected: enterprise ~10 warehouses vs small_business ~2
204
- *
205
- * Report 2: Employee Count Distribution
206
- * - Report type: Insights
207
- * - Measure: Profiles -> Average of "employee_count"
208
- * - Breakdown: "company_tier"
209
- * - Expected: enterprise ~1100 vs small_business ~50
210
- *
211
- * REAL-WORLD ANALOGUE: Enterprise customers operate multi-warehouse
212
- * networks with large logistics teams, driving higher ACV.
213
- *
214
- * -------------------------------------------------------------------
215
- * 8. SMALL-BUSINESS CONVERSION DROP (everything hook)
216
- * -------------------------------------------------------------------
217
- *
218
- * PATTERN: Small-business users lose ~35% of "alert configured"
219
- * events (last step of the Integration Setup funnel). This is
220
- * implemented via event filtering in the everything hook rather
221
- * than conversionRate modification in funnel-pre, so the effect
222
- * is not diluted by organic (non-funnel) events.
223
- *
224
- * HOW TO FIND IT IN MIXPANEL:
225
- *
226
- * Report 1: Integration Funnel by Tier
227
- * - Report type: Funnels
228
- * - Steps: "integration connected" -> "report generated" -> "alert configured"
229
- * - Breakdown: user property "company_tier"
230
- * - Expected: small_business ~20% vs enterprise/mid_market ~30% conversion
231
- *
232
- * REAL-WORLD ANALOGUE: Small businesses lack dedicated IT teams,
233
- * leading to incomplete integration setup and lower alert adoption.
234
- *
235
- * -------------------------------------------------------------------
236
- * 9. INVENTORY-CHECK MAGIC NUMBER (everything hook)
237
- * -------------------------------------------------------------------
238
- *
239
- * PATTERN: Users with 5-15 "inventory checked" events sit in the
240
- * "engaged-but-focused" sweet spot — every "purchase order created"
241
- * event gets quantity boosted ~25%. Users with 16 or more inventory
242
- * checks are over-engaged (paralysis); ~60% of their "purchase order
243
- * created" events are dropped. No flag is stamped — discoverable only
244
- * by binning users on inventory-check COUNT and comparing PO totals.
245
- *
246
- * HOW TO FIND IT IN MIXPANEL:
247
- *
248
- * Report 1: PO Quantity by Inventory-Check Bucket
249
- * - Report type: Insights (with cohort)
250
- * - Cohort A: users with 5-15 "inventory checked" events
251
- * - Cohort B: users with 0-4 "inventory checked" events
252
- * - Event: "purchase order created"
253
- * - Measure: Average of "quantity"
254
- * - Compare cohort A vs cohort B
255
- * - Expected: cohort A ~ 1.25x higher quantity than B
256
- *
257
- * Report 2: POs per User by Browse Intensity
258
- * - Report type: Insights (with cohort)
259
- * - Cohort C: users with >= 16 "inventory checked" events
260
- * - Cohort A: users with 5-15 "inventory checked" events
261
- * - Event: "purchase order created"
262
- * - Measure: Total events per user
263
- * - Compare cohort C vs cohort A
264
- * - Expected: cohort C has ~ 60% fewer POs per user
265
- *
266
- * REAL-WORLD ANALOGUE: A focused operations team that monitors
267
- * stock just enough places larger, more confident orders; an
268
- * obsessive checker is paralysed and orders less.
269
- *
270
- * -------------------------------------------------------------------
271
- * 10. ONBOARDING TIME-TO-CONVERT (funnel-post hook)
272
- * -------------------------------------------------------------------
273
- *
274
- * PATTERN: Enterprise-tier users complete the Onboarding funnel
275
- * 1.4x faster (time gaps scaled by 0.71). Small-business and trial
276
- * users complete it 1.3x slower (gaps scaled by 1.3). The hook
277
- * iterates over the funnel-post event array, compresses or stretches
278
- * the inter-step time gaps based on the user's company_tier from
279
- * meta.profile, then rewrites each event's timestamp.
280
- *
281
- * HOW TO FIND IT IN MIXPANEL:
282
- *
283
- * Report 1: Onboarding TTC by Company Tier
284
- * - Report type: Funnels
285
- * - Steps: "account created" -> "inventory checked" -> "integration connected" -> "report generated"
286
- * - Breakdown: user property "company_tier"
287
- * - Metric: Median time to convert
288
- * - Expected: enterprise median TTC ~ 0.71x of small_business/trial TTC
289
- * (e.g., enterprise ~ 36h vs small_business ~ 66h)
290
- *
291
- * NOTE: This effect is visible ONLY in Mixpanel funnel median TTC.
292
- * Cross-event MIN->MIN SQL queries on raw events do NOT show this
293
- * because funnel-post mutates timestamps after event generation but
294
- * before storage.
295
- *
296
- * REAL-WORLD ANALOGUE: Enterprise customers have dedicated IT teams
297
- * and onboarding specialists who move through setup, integration,
298
- * and first reporting much faster than small businesses configuring
299
- * the platform themselves.
300
- *
301
- * ===================================================================
302
- * EXPECTED METRICS SUMMARY
303
- * ===================================================================
304
- *
305
- * Hook | Metric | Baseline | Effect | Ratio
306
- * ----------------------------|---------------------|----------|---------|------
307
- * Month-End Reporting | report_pages | 20 | 40 | 2x
308
- * Rush Order Premium | unit_cost | $50 | $75 | 1.5x
309
- * Reorder Accuracy by Tier | stockout alerts/user| 5 | 4.5 | 0.9x
310
- * Integration Retention | reports/user | 3 | 6 | 2x
311
- * Alert Fatigue | response_time_hours | 4h | 8-12h | 2-3x
312
- * Trial Churn | events after wk 1 | 5 | 2.5 | 0.5x
313
- * Enterprise Profiles | warehouse_count | 3 | 10 | 3.3x
314
- * Small-Biz Conversion Drop | funnel conversion | 30% | 20% | 0.65x
315
- * Inventory-Check Magic Num | sweet PO quantity | 1x | 1.25x | 1.25x
316
- * Inventory-Check Magic Num | over POs/user | 1x | 0.4x | -60%
317
- * Onboarding TTC | funnel median TTC | 1x | 0.71x | 1.4x faster (enterprise)
318
- */
319
-
320
- // ── SCALE ──
321
- const SEED = "dm4-logistics";
322
- const NUM_USERS = 10_000;
323
- const DATASET_START = "2026-01-01T00:00:00Z";
324
- const DATASET_END = "2026-05-01T23:59:59Z";
325
- const EVENTS_PER_DAY = 1.2;
326
- const token = process.env.MP_TOKEN || "your-mixpanel-token";
327
-
328
- const chance = u.initChance(SEED);
329
-
330
- // ── KNOBS (tweak these to reshape stories) ──
331
- const MONTH_END_DAY_THRESHOLD = 28;
332
- const MONTH_END_PAGES_MULT = 2.5;
333
-
334
- const RUSH_ORDER_COST_MULT = 1.5;
335
-
336
- const ENTERPRISE_STOCKOUT_DROP_LIKELIHOOD = 10;
337
-
338
- const INTEGRATION_THRESHOLD = 3;
339
- const INTEGRATION_REPORT_CLONE_LIKELIHOOD = 65;
340
-
341
- const ALERT_FATIGUE_THRESHOLD = 30;
342
- const ALERT_FATIGUE_START_IDX = 20;
343
- const ALERT_FATIGUE_BASE_MULT = 1.5;
344
- const ALERT_FATIGUE_RAMP_MULT = 1.5;
345
-
346
- const TRIAL_CHURN_EVENT_THRESHOLD = 10;
347
- const TRIAL_CHURN_CUTOFF_DAYS = 7;
348
- const TRIAL_CHURN_DROP_LIKELIHOOD = 50;
349
-
350
- const SMB_ALERT_DROP_LIKELIHOOD = 35;
351
-
352
- const INVENTORY_SWEET_MIN = 5;
353
- const INVENTORY_SWEET_MAX = 15;
354
- const INVENTORY_OVER_THRESHOLD = 16;
355
- const INVENTORY_PO_QUANTITY_BOOST = 1.4;
356
- const INVENTORY_OVER_PO_DROP_LIKELIHOOD = 60;
357
-
358
- const TTC_ENTERPRISE_FACTOR = 0.71;
359
- const TTC_SMB_FACTOR = 1.3;
360
-
361
- // ── DATA ARRAYS ──
362
- const warehouseIds = v.range(1, 80).map(() => `WH_${v.uid(6)}`);
363
- const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
364
-
365
- // ── HELPER FUNCTIONS ──
366
- function handleUserHooks(record) {
367
- // H7: ENTERPRISE PROFILES — large warehouse_count + employee_count by tier.
368
- if (record.company_tier === "enterprise") {
369
- record.warehouse_count = chance.integer({ min: 5, max: 15 });
370
- record.employee_count = chance.integer({ min: 200, max: 2000 });
371
- } else if (record.company_tier === "mid_market") {
372
- record.warehouse_count = chance.integer({ min: 2, max: 6 });
373
- record.employee_count = chance.integer({ min: 20, max: 200 });
374
- } else if (record.company_tier === "small_business") {
375
- record.warehouse_count = chance.integer({ min: 1, max: 3 });
376
- record.employee_count = chance.integer({ min: 5, max: 80 });
377
- } else if (record.company_tier === "trial") {
378
- record.warehouse_count = 1;
379
- record.employee_count = chance.integer({ min: 1, max: 10 });
380
- }
381
- return record;
382
- }
383
-
384
- function handleEventHooks(record) {
385
- // H2: RUSH ORDER PREMIUM — urgent purchase orders get 1.5x unit_cost.
386
- if (record.event === "purchase order created" && record.priority === "urgent") {
387
- record.unit_cost = Math.floor((record.unit_cost || 50) * RUSH_ORDER_COST_MULT);
388
- }
389
- return record;
390
- }
391
-
392
- function handleFunnelPostHooks(record, meta) {
393
- // H10: ONBOARDING TIME-TO-CONVERT — enterprise 1.4x faster (0.71);
394
- // small_business + trial 1.3x slower (1.3).
395
- const segment = meta?.profile?.company_tier;
396
- if (Array.isArray(record) && record.length > 1) {
397
- const factor = (
398
- segment === "enterprise" ? TTC_ENTERPRISE_FACTOR :
399
- segment === "small_business" || segment === "trial" ? TTC_SMB_FACTOR :
400
- 1.0
401
- );
402
- if (factor !== 1.0) {
403
- for (let i = 1; i < record.length; i++) {
404
- const prev = dayjs(record[i - 1].time);
405
- const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
406
- record[i].time = prev.add(newGap, "milliseconds").toISOString();
407
- }
408
- }
409
- }
410
- return record;
411
- }
412
-
413
- function handleEverythingHooks(record, meta) {
414
- if (!record.length) return record;
415
- const profile = meta.profile;
416
-
417
- // ── SUPER-PROP STAMPING ──
418
- // Stamp superProps from profile so they are consistent per-user.
419
- if (profile) {
420
- const plat = profile.Platform;
421
- const plan = profile.subscription_plan;
422
- record.forEach(e => {
423
- if (plat) e.Platform = plat;
424
- if (plan) e.subscription_plan = plan;
425
- });
426
- }
427
-
428
- // H1: MONTH-END REPORTING SURGE — reports on calendar days 28-31 get
429
- // 2.5x report_pages. Runs in everything (post-sessionization) so the
430
- // day-of-month tag matches the final timestamp.
431
- for (const e of record) {
432
- if (e.event === "report generated") {
433
- const dayOfMonth = new Date(e.time).getUTCDate();
434
- if (dayOfMonth >= MONTH_END_DAY_THRESHOLD) {
435
- e.report_pages = Math.floor((e.report_pages || 20) * MONTH_END_PAGES_MULT);
436
- }
437
- }
438
- }
439
-
440
- // H8: SMALL-BUSINESS CONVERSION DROP — small_business users lose
441
- // ~35% of "alert configured" events (last step of Integration Setup
442
- // funnel), simulating lower conversion without dedicated IT.
443
- if (profile && profile.company_tier === "small_business") {
444
- record = record.filter(e => {
445
- if (e.event === "alert configured" && chance.bool({ likelihood: SMB_ALERT_DROP_LIKELIHOOD })) {
446
- return false;
447
- }
448
- return true;
449
- });
450
- }
451
-
452
- // H3: REORDER ACCURACY BY TIER — enterprise users get 10% of
453
- // stockout alerts removed (better forecasting + safety stock).
454
- if (profile && profile.company_tier === "enterprise") {
455
- for (let i = record.length - 1; i >= 0; i--) {
456
- if (record[i].event === "stockout alert" && chance.bool({ likelihood: ENTERPRISE_STOCKOUT_DROP_LIKELIHOOD })) {
457
- record.splice(i, 1);
458
- }
459
- }
460
- }
461
-
462
- // H4: INTEGRATION COMPLETION DRIVES RETENTION — users with 3+
463
- // "integration connected" events get cloned "report generated" events.
464
- const integrationCount = record.filter(e => e.event === "integration connected").length;
465
- if (integrationCount >= INTEGRATION_THRESHOLD) {
466
- const templateReport = record.find(e => e.event === "report generated");
467
- if (templateReport) {
468
- const integrationEvents = record.filter(e => e.event === "integration connected");
469
- integrationEvents.forEach(ie => {
470
- if (chance.bool({ likelihood: INTEGRATION_REPORT_CLONE_LIKELIHOOD })) {
471
- record.push({
472
- ...templateReport,
473
- time: dayjs(ie.time).add(chance.integer({ min: 1, max: 5 }), "days").toISOString(),
474
- user_id: ie.user_id,
475
- report_type: "integration_summary",
476
- report_pages: chance.integer({ min: 5, max: 25 }),
477
- });
478
- }
479
- });
480
- }
481
- }
482
-
483
- // H5: ALERT FATIGUE — users with >30 stockout alerts get increasing
484
- // response_time_hours starting at the 20th alert.
485
- const alertEvents = record.filter(e => e.event === "stockout alert");
486
- if (alertEvents.length > ALERT_FATIGUE_THRESHOLD) {
487
- alertEvents.forEach((alert, idx) => {
488
- if (idx >= ALERT_FATIGUE_START_IDX) {
489
- const fatigueMultiplier = ALERT_FATIGUE_BASE_MULT + ((idx - ALERT_FATIGUE_START_IDX) / alertEvents.length) * ALERT_FATIGUE_RAMP_MULT;
490
- alert.response_time_hours = Math.floor((alert.response_time_hours || 4) * fatigueMultiplier);
491
- }
492
- });
493
- }
494
-
495
- // H6: TRIAL CHURN — users with <10 total events lose 50% after day 7.
496
- if (record.length < TRIAL_CHURN_EVENT_THRESHOLD) {
497
- const userFirstEvent = record[0];
498
- if (userFirstEvent) {
499
- const firstTime = dayjs(userFirstEvent.time);
500
- const cutoff = firstTime.add(TRIAL_CHURN_CUTOFF_DAYS, "days");
501
- for (let i = record.length - 1; i >= 0; i--) {
502
- if (dayjs(record[i].time).isAfter(cutoff) && chance.bool({ likelihood: TRIAL_CHURN_DROP_LIKELIHOOD })) {
503
- record.splice(i, 1);
504
- }
505
- }
506
- }
507
- }
508
-
509
- // H9: INVENTORY-CHECK MAGIC NUMBER (no flags) — sweet 5-15 inventory
510
- // checks → +25% PO quantity (1.4x to overcome dilution); over 16+ →
511
- // drop 60% of PO created events.
512
- const invCheckCount = record.filter(e => e.event === "inventory checked").length;
513
- if (invCheckCount >= INVENTORY_SWEET_MIN && invCheckCount <= INVENTORY_SWEET_MAX) {
514
- record.forEach(e => {
515
- if (e.event === "purchase order created" && typeof e.quantity === "number") {
516
- e.quantity = Math.round(e.quantity * INVENTORY_PO_QUANTITY_BOOST);
517
- }
518
- });
519
- } else if (invCheckCount >= INVENTORY_OVER_THRESHOLD) {
520
- for (let i = record.length - 1; i >= 0; i--) {
521
- if (record[i].event === "purchase order created" && chance.bool({ likelihood: INVENTORY_OVER_PO_DROP_LIKELIHOOD })) {
522
- record.splice(i, 1);
523
- }
524
- }
525
- }
526
-
527
- return record;
528
- }
529
-
530
- // ── CONFIG ──
531
- /** @type {Config} */
532
- const config = {
533
- version: 2,
534
- seed: SEED,
535
- datasetStart: DATASET_START,
536
- datasetEnd: DATASET_END,
537
- avgEventsPerUserPerDay: EVENTS_PER_DAY,
538
- numUsers: NUM_USERS,
539
- format: "json",
540
- gzip: true,
541
- credentials: {
542
- token,
543
- },
544
- switches: {
545
- hasSessionIds: true,
546
- alsoInferFunnels: false,
547
- hasLocation: true,
548
- hasAndroidDevices: false,
549
- hasIOSDevices: false,
550
- hasDesktopDevices: true,
551
- hasBrowser: true,
552
- hasCampaigns: false,
553
- isAnonymous: false,
554
- hasAdSpend: false,
555
- hasAvatar: true,
556
- },
557
- identity: {
558
- avgDevicePerUser: 2,
559
- },
560
- concurrency: 1,
561
- writeToDisk: false,
562
- scdProps: {
563
- company_tier: {
564
- values: ["trial", "starter", "professional", "enterprise"],
565
- frequency: "month",
566
- timing: "fuzzy",
567
- max: 6
568
- }
569
- },
570
- mirrorProps: {},
571
- lookupTables: [],
572
-
573
- // -- Events (17) --------------------------------------------------
574
- events: [
575
- {
576
- event: "account created",
577
- weight: 1,
578
- isFirstEvent: true,
579
- isAuthEvent: true,
580
- properties: {
581
- referral_source: ["organic", "partner_referral", "google_search", "trade_show", "linkedin"],
582
- },
583
- },
584
- {
585
- event: "inventory checked",
586
- weight: 8,
587
- isStrictEvent: false,
588
- properties: {
589
- warehouse_id: chance.pickone.bind(chance, warehouseIds),
590
- sku_category: ["electronics", "apparel", "food_beverage", "pharma", "raw_materials", "packaging", "automotive_parts"],
591
- stock_level: u.weighNumRange(0, 5000, 0.4, 500),
592
- reorder_point: u.weighNumRange(10, 500, 0.5, 100),
593
- reorder_method: ["manual"],
594
- },
595
- },
596
- {
597
- event: "stockout alert",
598
- weight: 5,
599
- isStrictEvent: false,
600
- properties: {
601
- warehouse_id: chance.pickone.bind(chance, warehouseIds),
602
- sku_category: ["electronics", "apparel", "food_beverage", "pharma", "raw_materials", "packaging"],
603
- items_affected: u.weighNumRange(1, 50, 0.4, 5),
604
- severity: ["low", "medium", "medium", "high", "critical"],
605
- response_time_hours: u.weighNumRange(0.5, 48, 0.3, 4),
606
- },
607
- },
608
- {
609
- event: "purchase order created",
610
- weight: 5,
611
- isStrictEvent: false,
612
- properties: {
613
- supplier_id: chance.pickone.bind(chance, supplierIds),
614
- sku_category: ["electronics", "apparel", "food_beverage", "pharma", "raw_materials", "packaging", "automotive_parts"],
615
- quantity: u.weighNumRange(10, 5000, 0.3, 200),
616
- unit_cost: u.weighNumRange(5, 200, 0.4, 50),
617
- priority: ["standard", "standard", "standard", "expedited", "urgent"],
618
- estimated_delivery_days: u.weighNumRange(1, 30, 0.4, 7),
619
- },
620
- },
621
- {
622
- event: "order received",
623
- weight: 4,
624
- properties: {
625
- warehouse_id: chance.pickone.bind(chance, warehouseIds),
626
- supplier_id: chance.pickone.bind(chance, supplierIds),
627
- quantity_received: u.weighNumRange(10, 5000, 0.3, 200),
628
- quantity_damaged: u.weighNumRange(0, 20, 0.2),
629
- receiving_time_hours: u.weighNumRange(0.5, 8, 0.5, 2),
630
- },
631
- },
632
- {
633
- event: "shipment tracked",
634
- weight: 6,
635
- properties: {
636
- carrier: ["fedex", "ups", "dhl", "usps", "freight_line", "regional_courier"],
637
- shipment_status: ["in_transit", "in_transit", "in_transit", "delivered", "delayed", "exception"],
638
- origin_warehouse: chance.pickone.bind(chance, warehouseIds),
639
- estimated_days: u.weighNumRange(1, 14, 0.4, 3),
640
- weight_kg: u.weighNumRange(1, 2000, 0.3, 50),
641
- },
642
- },
643
- {
644
- event: "supplier contacted",
645
- weight: 3,
646
- properties: {
647
- supplier_id: chance.pickone.bind(chance, supplierIds),
648
- contact_reason: ["price_negotiation", "order_status", "quality_issue", "new_product", "contract_renewal", "returns"],
649
- contact_method: ["email", "email", "phone", "portal"],
650
- supplier_access: ["email_only"],
651
- },
652
- },
653
- {
654
- event: "integration connected",
655
- weight: 2,
656
- isStrictEvent: false,
657
- properties: {
658
- integration_type: ["erp", "accounting", "shipping", "ecommerce", "crm", "bi_tool"],
659
- integration_name: ["SAP", "QuickBooks", "ShipStation", "Shopify", "Salesforce", "NetSuite", "Xero"],
660
- setup_time_minutes: u.weighNumRange(5, 120, 0.4, 30),
661
- },
662
- },
663
- {
664
- event: "report generated",
665
- weight: 4,
666
- isStrictEvent: false,
667
- properties: {
668
- report_type: ["inventory_summary", "order_history", "supplier_performance", "cost_analysis", "forecast", "compliance"],
669
- report_pages: u.weighNumRange(1, 50, 0.4, 20),
670
- export_format: ["pdf", "csv", "xlsx"],
671
- time_range_days: [7, 14, 30, 30, 90],
672
- },
673
- },
674
- {
675
- event: "alert configured",
676
- weight: 2,
677
- isStrictEvent: false,
678
- properties: {
679
- alert_type: ["low_stock", "delivery_delay", "price_change", "quality_threshold", "expiry_warning"],
680
- threshold_value: u.weighNumRange(1, 500, 0.4, 50),
681
- notification_channel: ["email", "email", "sms", "push", "slack"],
682
- },
683
- },
684
- {
685
- event: "warehouse transfer",
686
- weight: 3,
687
- properties: {
688
- from_warehouse: chance.pickone.bind(chance, warehouseIds),
689
- to_warehouse: chance.pickone.bind(chance, warehouseIds),
690
- sku_category: ["electronics", "apparel", "food_beverage", "pharma", "raw_materials"],
691
- transfer_quantity: u.weighNumRange(10, 1000, 0.4, 100),
692
- transfer_reason: ["rebalance", "demand_shift", "consolidation", "overflow", "seasonal"],
693
- },
694
- },
695
- {
696
- event: "quality inspection",
697
- weight: 2,
698
- properties: {
699
- warehouse_id: chance.pickone.bind(chance, warehouseIds),
700
- inspection_result: ["pass", "pass", "pass", "pass", "minor_issue", "major_issue", "fail"],
701
- items_inspected: u.weighNumRange(10, 500, 0.4, 50),
702
- defect_rate_pct: u.weighNumRange(0, 15, 0.3, 2),
703
- },
704
- },
705
- {
706
- event: "invoice processed",
707
- weight: 3,
708
- properties: {
709
- supplier_id: chance.pickone.bind(chance, supplierIds),
710
- invoice_amount: u.weighNumRange(100, 50000, 0.3, 2500),
711
- payment_terms: ["net_30", "net_30", "net_60", "net_15", "on_receipt"],
712
- payment_status: ["paid", "paid", "paid", "pending", "overdue"],
713
- },
714
- },
715
- {
716
- event: "notification received",
717
- weight: 6,
718
- properties: {
719
- notification_type: ["low_stock_alert", "low_stock_alert", "delivery_update", "order_confirmation", "invoice_due", "system_update"],
720
- channel: ["email", "email", "push", "sms"],
721
- opened: [true, true, true, false],
722
- },
723
- },
724
- {
725
- event: "support ticket",
726
- weight: 1,
727
- properties: {
728
- category: ["billing", "technical", "integration", "data_import", "feature_request", "training"],
729
- priority: ["low", "low", "medium", "medium", "high"],
730
- resolution_hours: u.weighNumRange(1, 72, 0.4, 12),
731
- },
732
- },
733
- {
734
- event: "app session",
735
- weight: 7,
736
- properties: {
737
- session_duration_sec: u.weighNumRange(30, 3600, 0.4, 300),
738
- pages_viewed: u.weighNumRange(1, 20, 0.5, 5),
739
- },
740
- },
741
- {
742
- event: "account deactivated",
743
- weight: 1,
744
- isChurnEvent: true,
745
- returnLikelihood: 0.10,
746
- isStrictEvent: true,
747
- properties: {
748
- reason: ["too_expensive", "switched_competitor", "business_closed", "missing_features", "poor_support"],
749
- },
750
- },
751
- ],
752
-
753
- // -- Funnels (5) --------------------------------------------------
754
- funnels: [
755
- {
756
- name: "Onboarding",
757
- sequence: ["account created", "inventory checked", "integration connected", "report generated"],
758
- conversionRate: 40,
759
- order: "sequential",
760
- isFirstFunnel: true,
761
- timeToConvert: 72,
762
- weight: 3,
763
- },
764
- {
765
- name: "Order Fulfillment",
766
- sequence: ["inventory checked", "purchase order created", "order received", "shipment tracked"],
767
- conversionRate: 35,
768
- order: "sequential",
769
- timeToConvert: 168,
770
- weight: 5,
771
- reentry: true,
772
- },
773
- {
774
- name: "Supplier Management",
775
- sequence: ["supplier contacted", "purchase order created", "invoice processed"],
776
- conversionRate: 45,
777
- order: "sequential",
778
- timeToConvert: 336,
779
- weight: 3,
780
- },
781
- {
782
- name: "Integration Setup",
783
- sequence: ["integration connected", "report generated", "alert configured"],
784
- conversionRate: 30,
785
- order: "sequential",
786
- timeToConvert: 48,
787
- weight: 2,
788
- },
789
- {
790
- name: "Alert Response",
791
- sequence: ["stockout alert", "purchase order created", "order received"],
792
- conversionRate: 50,
793
- order: "sequential",
794
- timeToConvert: 120,
795
- weight: 3,
796
- },
797
- ],
798
-
799
- // -- SuperProps ----------------------------------------------------
800
- superProps: {
801
- Platform: ["web", "web", "desktop_app"],
802
- subscription_plan: ["free_trial", "free_trial", "starter", "starter", "professional", "enterprise"],
803
- },
804
-
805
- // -- UserProps -----------------------------------------------------
806
- userProps: {
807
- company_tier: ["small_business"],
808
- warehouse_count: u.weighNumRange(1, 10, 0.4, 2),
809
- employee_count: u.weighNumRange(1, 500, 0.3, 25),
810
- industry: ["retail", "manufacturing", "food_beverage", "pharma", "electronics"],
811
- integration_count: [0],
812
- Platform: ["web", "web", "desktop_app"],
813
- subscription_plan: ["free_trial", "free_trial", "starter", "starter", "professional", "enterprise"],
814
- },
815
-
816
- // -- Phase 2: Personas --------------------------------------------
817
- personas: [
818
- {
819
- name: "enterprise_ops",
820
- weight: 15,
821
- eventMultiplier: 5.0,
822
- conversionModifier: 1.5,
823
- churnRate: 0.01,
824
- properties: {
825
- company_tier: "enterprise",
826
- segment: "enterprise_ops",
827
- },
828
- },
829
- {
830
- name: "mid_market",
831
- weight: 35,
832
- eventMultiplier: 1.5,
833
- conversionModifier: 1.0,
834
- churnRate: 0.05,
835
- properties: {
836
- company_tier: "mid_market",
837
- segment: "mid_market",
838
- },
839
- },
840
- {
841
- name: "small_business",
842
- weight: 40,
843
- eventMultiplier: 0.8,
844
- conversionModifier: 0.7,
845
- churnRate: 0.10,
846
- properties: {
847
- company_tier: "small_business",
848
- segment: "small_business",
849
- },
850
- },
851
- {
852
- name: "trial_explorer",
853
- weight: 10,
854
- eventMultiplier: 0.4,
855
- conversionModifier: 0.3,
856
- churnRate: 0.4,
857
- properties: {
858
- company_tier: "trial",
859
- segment: "trial_explorer",
860
- },
861
- activeWindow: { maxDays: 14 },
862
- },
863
- ],
864
-
865
- // -- Phase 2: World Events ----------------------------------------
866
- worldEvents: [
867
- {
868
- name: "supply_chain_disruption",
869
- type: "outage",
870
- startDay: 35,
871
- duration: 5,
872
- volumeMultiplier: 3.0,
873
- affectsEvents: ["stockout alert"],
874
- injectProps: { disruption: "supply_chain_disruption" },
875
- aftermath: { duration: 5, volumeMultiplier: 1.5 },
876
- },
877
- {
878
- name: "holiday_surge",
879
- type: "campaign",
880
- startDay: 70,
881
- duration: 7,
882
- volumeMultiplier: 2.0,
883
- affectsEvents: ["purchase order created", "order received", "shipment tracked"],
884
- injectProps: { surge: "holiday_prep" },
885
- },
886
- ],
887
-
888
- // -- Phase 2: Subscription ----------------------------------------
889
- subscription: {
890
- plans: [
891
- { name: "free_trial", price: 0, default: true, trialDays: 30 },
892
- { name: "starter", price: 49 },
893
- { name: "professional", price: 199 },
894
- { name: "enterprise", price: 599 },
895
- ],
896
- lifecycle: {
897
- trialToPayRate: 0.25,
898
- upgradeRate: 0.06,
899
- downgradeRate: 0.03,
900
- churnRate: 0.05,
901
- winBackRate: 0.08,
902
- winBackDelay: 30,
903
- paymentFailureRate: 0.02,
904
- },
905
- },
906
-
907
- // -- Phase 2: Geo -------------------------------------------------
908
- geo: {
909
- sticky: true,
910
- regions: [
911
- {
912
- name: "us",
913
- countries: ["US"],
914
- weight: 45,
915
- timezoneOffset: -5,
916
- properties: { currency: "USD", locale: "en-US" },
917
- },
918
- {
919
- name: "eu",
920
- countries: ["GB", "DE", "NL"],
921
- weight: 30,
922
- timezoneOffset: 1,
923
- properties: { currency: "EUR", locale: "en-EU", vat_tracking: true },
924
- },
925
- {
926
- name: "apac",
927
- countries: ["SG", "JP", "AU"],
928
- weight: 25,
929
- timezoneOffset: 8,
930
- properties: { currency: "SGD", locale: "en-APAC" },
931
- },
932
- ],
933
- },
934
-
935
- // -- Phase 2: Features --------------------------------------------
936
- features: [
937
- {
938
- name: "predictive_reorder",
939
- launchDay: 40,
940
- adoptionCurve: "fast",
941
- property: "reorder_method",
942
- values: ["manual", "predictive"],
943
- defaultBefore: "manual",
944
- affectsEvents: ["inventory checked"],
945
- },
946
- {
947
- name: "supplier_portal",
948
- launchDay: 65,
949
- adoptionCurve: { k: 0.08, midpoint: 25 },
950
- property: "supplier_access",
951
- values: ["email_only", "portal"],
952
- defaultBefore: "email_only",
953
- affectsEvents: ["supplier contacted"],
954
- },
955
- ],
956
-
957
- // -- Phase 2: Anomalies -------------------------------------------
958
- anomalies: [
959
- {
960
- type: "extreme_value",
961
- event: "stockout alert",
962
- property: "items_affected",
963
- frequency: 0.005,
964
- multiplier: 30,
965
- tag: "mass_stockout",
966
- },
967
- {
968
- type: "coordinated",
969
- event: "purchase order created",
970
- day: 72,
971
- window: 0.08,
972
- count: 150,
973
- tag: "holiday_prep",
974
- },
975
- ],
976
-
977
- hook(record, type, meta) {
978
- if (type === "user") return handleUserHooks(record);
979
- if (type === "event") return handleEventHooks(record);
980
- if (type === "funnel-post") return handleFunnelPostHooks(record, meta);
981
- if (type === "everything") return handleEverythingHooks(record, meta);
982
- return record;
983
- },
984
- };
985
-
986
- export default config;