@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,1019 +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: TradeNest
13
- * APP: Two-sided marketplace connecting sellers who list products with
14
- * buyers who search, purchase, and review. Sellers list items;
15
- * buyers browse, message, negotiate, and buy. Revenue from
16
- * marketplace fees and listing fees.
17
- * SCALE: 10,000 users, ~1.4M events, 121 days (2026-01-01 → 2026-05-01)
18
- * CORE LOOP: search → view → add to cart → purchase → review
19
- * (seller side: create listing → receive offers → accept → ship)
20
- *
21
- * EVENTS (18):
22
- * item searched (8) > app session (8) > item viewed (7) > notification received (6)
23
- * > add to cart (5) > message sent (5) > listing created (4) > purchase completed (3)
24
- * > listing updated (3) > offer received (3) > shipping updated (3) > offer accepted (2)
25
- * > review submitted (2) > seller rated (2) > profile updated (2) > account created (1)
26
- * > refund requested (1) > account deactivated (1)
27
- *
28
- * FUNNELS (5):
29
- * - Buyer Onboarding: account created → item searched → item viewed → add to cart (40%)
30
- * - Browse to Purchase: item searched → item viewed → add to cart → purchase completed (30%)
31
- * - Seller Listing Flow: listing created → listing updated → offer received → offer accepted (25%)
32
- * - Offer Negotiation: offer received → message sent → offer accepted (35%)
33
- * - Review After Purchase: purchase completed → shipping updated → review submitted (20%)
34
- *
35
- * USER PROPS: user_type, segment, seller_rating, total_transactions, response_time_hours,
36
- * store_name, Platform, category
37
- * SUPER PROPS: Platform, category
38
- * SCD PROPS: seller_tier (new/verified/power/featured, monthly fuzzy, max 8)
39
- * GROUPS: none
40
- */
41
-
42
- // ── HOOK STORIES ──
43
- /*
44
- * NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable via
45
- * raw-prop breakdowns (segment, day, category) or behavioral cohorts.
46
- *
47
- * ───────────────────────────────────────────────────────────────
48
- * 1. FEE CHANGE IMPACT (everything hook)
49
- * ───────────────────────────────────────────────────────────────
50
- *
51
- * PATTERN: After day 45 (permanent marketplace fee increase), all
52
- * "listing created" events get listing_fee multiplied by 1.3x.
53
- * Simulates the revenue impact of a platform fee adjustment. No flag —
54
- * discover via line chart of avg listing_fee over time.
55
- *
56
- * HOW TO FIND IT IN MIXPANEL:
57
- *
58
- * Report 1: Listing Fee Over Time
59
- * • Report type: Insights
60
- * • Event: "listing created"
61
- * • Measure: Average of "listing_fee"
62
- * • Line chart by week
63
- * • Expected: Clear step-up around day 45 from ~$15 avg to ~$20 avg
64
- *
65
- * REAL-WORLD ANALOGUE: Marketplace platforms periodically adjust
66
- * commission/listing fees, impacting seller economics and behavior.
67
- *
68
- * ───────────────────────────────────────────────────────────────
69
- * 2. WEEKEND SHOPPING SURGE (event hook)
70
- * ───────────────────────────────────────────────────────────────
71
- *
72
- * PATTERN: "purchase completed" events on Saturday or Sunday get
73
- * total_amount multiplied by 1.2x. Weekend shoppers spend more
74
- * per transaction due to leisure browsing.
75
- *
76
- * HOW TO FIND IT IN MIXPANEL:
77
- *
78
- * Report 1: Weekend vs Weekday Average Spend
79
- * • Report type: Insights
80
- * • Event: "purchase completed"
81
- * • Measure: Average of "total_amount"
82
- * • Breakdown: Day of week
83
- * • Expected: Sat/Sun avg ≈ $72 vs Mon-Fri avg ≈ $60
84
- *
85
- * REAL-WORLD ANALOGUE: E-commerce platforms see higher average
86
- * order values on weekends when buyers browse leisurely.
87
- *
88
- * ───────────────────────────────────────────────────────────────
89
- * 3. SELLER SUCCESS → BUYER TRUST (everything hook)
90
- * ───────────────────────────────────────────────────────────────
91
- *
92
- * PATTERN: Users with user_type "seller" and segment "power_seller"
93
- * (from meta.profile) get their "purchase completed" events cloned
94
- * 2x. Power sellers attract more transactions due to trust/reputation.
95
- *
96
- * HOW TO FIND IT IN MIXPANEL:
97
- *
98
- * Report 1: Purchases per User by Segment
99
- * • Report type: Insights
100
- * • Event: "purchase completed"
101
- * • Measure: Total per user
102
- * • Breakdown: user property "segment"
103
- * • Expected: power_seller ≈ 2x purchases vs casual_seller
104
- *
105
- * REAL-WORLD ANALOGUE: High-rated sellers with established reputations
106
- * receive disproportionately more sales on platforms like eBay/Etsy.
107
- *
108
- * ───────────────────────────────────────────────────────────────
109
- * 4. SEARCH-TO-PURCHASE BY CATEGORY (everything hook)
110
- * ───────────────────────────────────────────────────────────────
111
- *
112
- * PATTERN: Users whose events include "item searched" with category
113
- * "electronics" get cloned "purchase completed" events (electronics
114
- * buyers have higher conversion). Creates category-specific lift.
115
- *
116
- * HOW TO FIND IT IN MIXPANEL:
117
- *
118
- * Report 1: Purchase Volume by Category
119
- * • Report type: Insights
120
- * • Event: "purchase completed"
121
- * • Measure: Total
122
- * • Breakdown: "category" (superProp)
123
- * • Expected: electronics ≈ 1.5-2x more purchases than clothing/home
124
- *
125
- * REAL-WORLD ANALOGUE: Electronics shoppers have higher purchase
126
- * intent — they research, decide, and buy with less browsing.
127
- *
128
- * ───────────────────────────────────────────────────────────────
129
- * 5. RESPONSE TIME → CONVERSION (everything hook)
130
- * ───────────────────────────────────────────────────────────────
131
- *
132
- * PATTERN: Users whose average response_time_hours on "message sent"
133
- * events is <= 4 hours get additional "offer accepted" events cloned
134
- * (2x existing + 60% of offer_received). Slow responders (> 4h avg)
135
- * lose 60% of offer_accepted events. Fast responders close more deals.
136
- *
137
- * HOW TO FIND IT IN MIXPANEL:
138
- *
139
- * Report 1: Offer Accepted Count vs Response Time
140
- * • Report type: Insights
141
- * • Event: "offer accepted"
142
- * • Measure: Total per user
143
- * • Breakdown: user property "response_time_hours"
144
- * • Expected: Users with < 2hr avg response ≈ 2x offer accepts
145
- *
146
- * REAL-WORLD ANALOGUE: Sellers who respond quickly to inquiries
147
- * close significantly more deals on marketplace platforms.
148
- *
149
- * ───────────────────────────────────────────────────────────────
150
- * 6. NEW SELLER CHURN (everything hook)
151
- * ───────────────────────────────────────────────────────────────
152
- *
153
- * PATTERN: Users with segment "new_seller" and fewer than 10 total
154
- * events lose 40% of events after day 14. Simulates new sellers
155
- * who try the platform and abandon it quickly.
156
- *
157
- * HOW TO FIND IT IN MIXPANEL:
158
- *
159
- * Report 1: New Seller Retention
160
- * • Report type: Insights
161
- * • Event: All events
162
- * • Measure: Total per user
163
- * • Filter: segment = "new_seller"
164
- * • Line chart by week
165
- * • Expected: Sharp drop-off around week 2-3
166
- *
167
- * Report 2: Active Users by Segment
168
- * • Report type: Insights
169
- * • Event: Any active event
170
- * • Measure: Uniques
171
- * • Breakdown: user property "segment"
172
- * • Expected: new_seller retention ~60% vs power_seller ~99%
173
- *
174
- * REAL-WORLD ANALOGUE: Most new sellers on marketplace platforms
175
- * churn within the first month if they don't get early traction.
176
- *
177
- * ───────────────────────────────────────────────────────────────
178
- * 7. POWER SELLER PROFILES (user hook)
179
- * ───────────────────────────────────────────────────────────────
180
- *
181
- * PATTERN: Users with segment "power_seller" get total_transactions
182
- * set to 100-500, seller_rating to 4.5-5.0, and store_name to a
183
- * realistic business name. Enriches profiles for realistic cohort analysis.
184
- *
185
- * HOW TO FIND IT IN MIXPANEL:
186
- *
187
- * Report 1: Transaction Count Distribution by Segment
188
- * • Report type: Insights
189
- * • Measure: Profiles → Breakdown by "total_transactions" (histogram)
190
- * • Filter: segment = "power_seller"
191
- * • Expected: power_seller total_transactions 100-500, others ≈ 0
192
- *
193
- * Report 2: Seller Rating by Segment
194
- * • Report type: Insights
195
- * • Measure: Profiles → Average of "seller_rating"
196
- * • Breakdown: "segment"
197
- * • Expected: power_seller ≈ 4.7 avg, casual_seller ≈ 3.5
198
- *
199
- * REAL-WORLD ANALOGUE: Top sellers have established storefronts,
200
- * high transaction volumes, and near-perfect ratings.
201
- *
202
- * ───────────────────────────────────────────────────────────────
203
- * 8. FREQUENT BUYER FUNNEL LIFT (everything hook)
204
- * ───────────────────────────────────────────────────────────────
205
- *
206
- * PATTERN: Non-frequent-buyer users lose ~25% of "purchase completed"
207
- * events (last step of Browse to Purchase funnel), simulating lower
208
- * conversion. Frequent buyers retain all purchase events.
209
- *
210
- * HOW TO FIND IT IN MIXPANEL:
211
- *
212
- * Report 1: Browse-to-Purchase Conversion by Segment
213
- * • Report type: Funnels
214
- * • Steps: "item searched" → "item viewed" → "add to cart" → "purchase completed"
215
- * • Breakdown: user property "segment"
216
- * • Expected: frequent_buyer ≈ 39% vs window_shopper ≈ 20%
217
- *
218
- * REAL-WORLD ANALOGUE: Returning buyers have established trust
219
- * and familiarity with the platform, converting at higher rates.
220
- *
221
- * ───────────────────────────────────────────────────────────────
222
- * 9. BROWSE TO PURCHASE TIME-TO-CONVERT (funnel-post)
223
- * ───────────────────────────────────────────────────────────────
224
- *
225
- * PATTERN: Power_seller and frequent_buyer users complete the
226
- * Browse to Purchase funnel 1.4x faster (factor 0.71); window_shopper
227
- * 1.4x slower (factor 1.4).
228
- *
229
- * HOW TO FIND IT IN MIXPANEL:
230
- *
231
- * Report 1: Browse to Purchase Median Time-to-Convert by Segment
232
- * - Funnels > "item searched" -> "item viewed" -> "add to cart" -> "purchase completed"
233
- * - Measure: Median time to convert
234
- * - Breakdown: segment
235
- * - Expected: power/frequent ~ 0.71x; window ~ 1.4x
236
- *
237
- * NOTE (funnel-post measurement): visible only via Mixpanel funnel
238
- * median TTC. Cross-event MIN→MIN SQL queries on raw events do NOT
239
- * show this — funnel-post adjusts gaps within funnel instances, not
240
- * across the user's full event history.
241
- *
242
- * ───────────────────────────────────────────────────────────────
243
- * 10. MESSAGE-COUNT MAGIC NUMBER (in-funnel, everything)
244
- * ───────────────────────────────────────────────────────────────
245
- *
246
- * PATTERN: Sweet 2-5 message-sent events between item-viewed and
247
- * offer-received → +35% on offer_amount of offer-received events.
248
- * Over 6+ → drop 25% of purchase-completed events (haggling deadlock).
249
- * No flag.
250
- *
251
- * HOW TO FIND IT IN MIXPANEL:
252
- *
253
- * Report 1: Avg Offer Amount by Message Bucket
254
- * - Cohort A: users with 2-5 messages between item-viewed and offer-received
255
- * - Cohort B: users with 0-1
256
- * - Event: "offer received"
257
- * - Measure: Average of "offer_amount"
258
- * - Expected: A ~ 1.35x B
259
- *
260
- * Report 2: Purchase Rate on Heavy Messengers
261
- * - Cohort C: users with >= 6 messages
262
- * - Cohort A: users with 2-5
263
- * - Event: "purchase completed"
264
- * - Measure: Total per user
265
- * - Expected: C ~ 25% fewer purchases per user
266
- *
267
- * REAL-WORLD ANALOGUE: A few quick clarifying messages close deals;
268
- * extended haggling kills conversion.
269
- *
270
- * ═══════════════════════════════════════════════════════════════
271
- * EXPECTED METRICS SUMMARY
272
- * ═══════════════════════════════════════════════════════════════
273
- *
274
- * Hook | Metric | Baseline | Effect | Ratio
275
- * ----------------------------|---------------------|----------|-----------|------
276
- * Fee Change Impact | listing_fee post-D45| 1x | 1.3x | 1.3x
277
- * Weekend Shopping Surge | total_amount Sat/Sun| 1x | 1.2x | 1.2x
278
- * Seller Success → Trust | purchases/user | 1x | 2x | 2x
279
- * Electronics Category Lift | electronics purch. | 1x | 1.5x | 1.5x
280
- * Response Time → Conversion | offer_accepted/user | 1x | 2x | 2x
281
- * New Seller Churn | events post-D14 | 1x | 0.6x | -40%
282
- * Power Seller Profiles | total_transactions | 0 | 100-500 | --
283
- * Frequent Buyer Funnel | funnel conversion | 30% | 39% | 1.3x
284
- * Browse to Purchase T2C | median min by segment| 1x | 0.71/1.4x | ~ 2x range
285
- * Message Magic Number | sweet offer_amount | 1x | 1.35x | 1.35x
286
- * Message Magic Number | over purchases/user | 1x | 0.75x | -25%
287
- */
288
-
289
- // ── SCALE ──
290
- const SEED = "dm4-marketplace";
291
- const NUM_USERS = 10_000;
292
- const DATASET_START = "2026-01-01T00:00:00Z";
293
- const DATASET_END = "2026-05-01T23:59:59Z";
294
- const EVENTS_PER_DAY = 1.2;
295
- const token = process.env.MP_TOKEN || "your-mixpanel-token";
296
-
297
- const chance = u.initChance(SEED);
298
-
299
- // ── KNOBS (tweak these to reshape stories) ──
300
- const FEE_CHANGE_DAY = 45;
301
- const FEE_CHANGE_MULT = 1.3;
302
-
303
- const WEEKEND_SPEND_MULT = 1.2;
304
-
305
- const POWER_SELLER_CLONE_LIKELIHOOD = 65;
306
-
307
- const ELECTRONICS_CLONE_LIKELIHOOD = 40;
308
- const ELECTRONICS_MAX_CLONES = 3;
309
-
310
- const FAST_RESPONDER_HASH_MOD = 5;
311
- const FAST_RESPONDER_HASH_THRESHOLD = 2;
312
- const FAST_OFFER_CLONE_COUNT = 2;
313
- const FAST_OFFER_FROM_RECEIVED_LIKELIHOOD = 60;
314
- const SLOW_OFFER_DROP_LIKELIHOOD = 60;
315
-
316
- const NEW_SELLER_CUTOFF_DAYS = 14;
317
- const NEW_SELLER_DROP_LIKELIHOOD = 50;
318
-
319
- const FREQUENT_BUYER_DROP_LIKELIHOOD = 25;
320
-
321
- const TTC_FAST_FACTOR = 0.71;
322
- const TTC_SLOW_FACTOR = 1.4;
323
-
324
- const MSG_SWEET_MIN = 2;
325
- const MSG_SWEET_MAX = 5;
326
- const MSG_OVER_THRESHOLD = 6;
327
- const MSG_OFFER_BOOST = 1.35;
328
- const MSG_PURCHASE_DROP_LIKELIHOOD = 25;
329
-
330
- // ── DATA ARRAYS ──
331
- // Generate consistent seller store and listing IDs at module level
332
- const storeIds = v.range(1, 150).map(() => `STORE_${v.uid(6)}`);
333
- const listingIds = v.range(1, 500).map(() => `LST_${v.uid(8)}`);
334
-
335
- // ── HELPER FUNCTIONS ──
336
- function handleUserHooks(record) {
337
- // H7: Power seller profiles — enrich segment-tagged profiles
338
- if (record.segment === "power_seller") {
339
- record.total_transactions = chance.integer({ min: 100, max: 500 });
340
- record.seller_rating = chance.floating({ min: 4.5, max: 5.0, fixed: 1 });
341
- record.store_name = chance.pickone([
342
- "TechVault Pro", "StyleHaven", "GearFactory", "PrimeFinds",
343
- "TopShelf Goods", "EliteDeals", "QualityFirst Store", "BestBuy Resale",
344
- ]);
345
- } else if (record.segment === "casual_seller") {
346
- record.total_transactions = chance.integer({ min: 5, max: 50 });
347
- record.seller_rating = chance.floating({ min: 3.0, max: 4.5, fixed: 1 });
348
- record.store_name = chance.pickone([
349
- "My Garage Sale", "Closet Cleanout", "Random Finds", "Weekend Seller",
350
- ]);
351
- } else if (record.segment === "new_seller") {
352
- record.total_transactions = chance.integer({ min: 0, max: 3 });
353
- record.seller_rating = 0;
354
- record.store_name = "New Store";
355
- }
356
- return record;
357
- }
358
-
359
- function handleFunnelPostHooks(record, meta) {
360
- // H9: Browse-to-purchase TTC scaled by segment
361
- const segment = meta?.profile?.segment;
362
- if (Array.isArray(record) && record.length > 1) {
363
- const factor = (
364
- segment === "power_seller" || segment === "frequent_buyer" ? TTC_FAST_FACTOR :
365
- segment === "window_shopper" ? TTC_SLOW_FACTOR :
366
- 1.0
367
- );
368
- if (factor !== 1.0) {
369
- for (let i = 1; i < record.length; i++) {
370
- const prev = dayjs(record[i - 1].time);
371
- const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
372
- record[i].time = prev.add(newGap, "milliseconds").toISOString();
373
- }
374
- }
375
- }
376
- return record;
377
- }
378
-
379
- function handleEverythingHooks(record, meta) {
380
- const datasetStart = dayjs.unix(meta.datasetStart);
381
- const FEE_CHANGE_CUTOFF = datasetStart.add(FEE_CHANGE_DAY, "days");
382
- let events = record;
383
- if (!events.length) return record;
384
-
385
- const profile = meta.profile;
386
-
387
- // Stamp superProps from profile so they are consistent per user.
388
- if (profile) {
389
- events.forEach(e => {
390
- if (profile.Platform) e.Platform = profile.Platform;
391
- if (profile.category) e.category = profile.category;
392
- });
393
- }
394
-
395
- // H1: Fee change impact — listings after d45 get listing_fee 1.3x.
396
- // In everything hook so timestamp comparison sees post-bunchIntoSessions times.
397
- events.forEach(e => {
398
- if (e.event === "listing created" && dayjs(e.time).isAfter(FEE_CHANGE_CUTOFF)) {
399
- e.listing_fee = Math.floor((e.listing_fee || 15) * FEE_CHANGE_MULT);
400
- }
401
- });
402
-
403
- // H8: Frequent buyer conversion filter — non-frequent-buyer users
404
- // drop ~25% of "purchase completed" events.
405
- if (profile && profile.segment !== "frequent_buyer") {
406
- record = record.filter(e => {
407
- if (e.event === "purchase completed" && chance.bool({ likelihood: FREQUENT_BUYER_DROP_LIKELIHOOD })) return false;
408
- return true;
409
- });
410
- events = record;
411
- }
412
-
413
- // H3: Seller success → buyer trust — power sellers get 2x purchase events cloned.
414
- if (profile && profile.segment === "power_seller") {
415
- const purchases = events.filter(e => e.event === "purchase completed");
416
- const templatePurchase = purchases[0];
417
- if (templatePurchase && purchases.length > 0) {
418
- purchases.forEach(p => {
419
- if (chance.bool({ likelihood: POWER_SELLER_CLONE_LIKELIHOOD })) {
420
- events.push({
421
- ...templatePurchase,
422
- time: dayjs(p.time).add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
423
- user_id: p.user_id,
424
- total_amount: chance.integer({ min: 15, max: 400 }),
425
- item_count: chance.integer({ min: 1, max: 4 }),
426
- });
427
- }
428
- });
429
- }
430
- }
431
-
432
- // H4: Search-to-purchase by category — electronics gets cloned purchase events.
433
- const hasElectronicsSearch = events.some(e =>
434
- e.event === "item searched" && e.category === "electronics"
435
- );
436
- if (hasElectronicsSearch) {
437
- const templatePurchase = events.find(e => e.event === "purchase completed");
438
- if (templatePurchase) {
439
- const electronicsSearches = events.filter(e =>
440
- e.event === "item searched" && e.category === "electronics"
441
- );
442
- electronicsSearches.slice(0, ELECTRONICS_MAX_CLONES).forEach(search => {
443
- if (chance.bool({ likelihood: ELECTRONICS_CLONE_LIKELIHOOD })) {
444
- events.push({
445
- ...templatePurchase,
446
- time: dayjs(search.time).add(chance.integer({ min: 1, max: 12 }), "hours").toISOString(),
447
- user_id: search.user_id,
448
- total_amount: chance.integer({ min: 50, max: 300 }),
449
- category: "electronics",
450
- });
451
- }
452
- });
453
- }
454
- }
455
-
456
- // H5: Response time → conversion — deterministic fast/slow cohorts via user hash.
457
- // Fast (~40%): set response_time_hours 1-4, clone offer_accepted.
458
- // Slow (~60%): set 8-36, drop 60% of offer_accepted events.
459
- const msgEvents = events.filter(e => e.event === "message sent");
460
- if (msgEvents.length > 0) {
461
- const uid = msgEvents[0].user_id || "";
462
- const isFast = (uid.charCodeAt(0) + uid.charCodeAt(uid.length - 1)) % FAST_RESPONDER_HASH_MOD < FAST_RESPONDER_HASH_THRESHOLD;
463
- msgEvents.forEach(m => {
464
- m.response_time_hours = isFast
465
- ? chance.floating({ min: 0.5, max: 4, fixed: 1 })
466
- : chance.floating({ min: 8, max: 36, fixed: 1 });
467
- });
468
- const templateOffer = events.find(e => e.event === "offer accepted");
469
- if (isFast && templateOffer) {
470
- // Fast responders: clone existing offer_accepted 2x each
471
- const existingAccepts = events.filter(e => e.event === "offer accepted");
472
- existingAccepts.forEach(accept => {
473
- for (let c = 0; c < FAST_OFFER_CLONE_COUNT; c++) {
474
- events.push({
475
- ...accept,
476
- time: dayjs(accept.time).add(chance.integer({ min: 1, max: 8 }), "hours").toISOString(),
477
- user_id: accept.user_id,
478
- response_time_hours: chance.floating({ min: 0.1, max: 2, fixed: 1 }),
479
- });
480
- }
481
- });
482
- // Also clone from offer_received → offer_accepted
483
- const offers = events.filter(e => e.event === "offer received");
484
- offers.forEach(offer => {
485
- if (chance.bool({ likelihood: FAST_OFFER_FROM_RECEIVED_LIKELIHOOD })) {
486
- events.push({
487
- ...templateOffer,
488
- time: dayjs(offer.time).add(chance.integer({ min: 1, max: 6 }), "hours").toISOString(),
489
- user_id: offer.user_id,
490
- response_time_hours: chance.floating({ min: 0.1, max: 3, fixed: 1 }),
491
- });
492
- }
493
- });
494
- } else if (!isFast) {
495
- // Slow responders: drop 60% of offer_accepted events
496
- for (let i = events.length - 1; i >= 0; i--) {
497
- if (events[i].event === "offer accepted" && chance.bool({ likelihood: SLOW_OFFER_DROP_LIKELIHOOD })) {
498
- events.splice(i, 1);
499
- }
500
- }
501
- }
502
- }
503
-
504
- // H6: New seller churn — drop 50% of events after user's first 14 days.
505
- if (profile && profile.segment === "new_seller") {
506
- const firstEventTime = events.length > 0 ? dayjs(events[0].time) : null;
507
- if (firstEventTime) {
508
- const userCutoff = firstEventTime.add(NEW_SELLER_CUTOFF_DAYS, "days");
509
- for (let i = events.length - 1; i >= 0; i--) {
510
- if (dayjs(events[i].time).isAfter(userCutoff) && chance.bool({ likelihood: NEW_SELLER_DROP_LIKELIHOOD })) {
511
- events.splice(i, 1);
512
- }
513
- }
514
- }
515
- }
516
-
517
- // H2: Weekend shopping surge — Sat/Sun purchases get total_amount 1.2x.
518
- events.forEach(e => {
519
- if (e.event === "purchase completed") {
520
- const dow = new Date(e.time).getUTCDay();
521
- if (dow === 0 || dow === 6) {
522
- e.total_amount = Math.floor((e.total_amount || 60) * WEEKEND_SPEND_MULT);
523
- }
524
- }
525
- });
526
-
527
- // H10: Message-count magic number (in-funnel, no flags)
528
- // Sweet 2-5 messages between item-viewed and offer-received →
529
- // +35% on offer-received offer_amount. Over 6+ → drop 25% of purchases.
530
- const itemViewed = events.find(e => e.event === "item viewed");
531
- const offerReceived = events.find(e => e.event === "offer received");
532
- if (itemViewed && offerReceived) {
533
- const aTime = dayjs(itemViewed.time);
534
- const bTime = dayjs(offerReceived.time);
535
- const msgBetween = events.filter(e =>
536
- e.event === "message sent" &&
537
- dayjs(e.time).isAfter(aTime) &&
538
- dayjs(e.time).isBefore(bTime)
539
- ).length;
540
- if (msgBetween >= MSG_SWEET_MIN && msgBetween <= MSG_SWEET_MAX) {
541
- events.forEach(e => {
542
- if (e.event === "offer received" && typeof e.offer_amount === "number") {
543
- e.offer_amount = Math.round(e.offer_amount * MSG_OFFER_BOOST);
544
- }
545
- });
546
- } else if (msgBetween >= MSG_OVER_THRESHOLD) {
547
- for (let i = events.length - 1; i >= 0; i--) {
548
- if (events[i].event === "purchase completed" && chance.bool({ likelihood: MSG_PURCHASE_DROP_LIKELIHOOD })) {
549
- events.splice(i, 1);
550
- }
551
- }
552
- }
553
- }
554
-
555
- return record;
556
- }
557
-
558
- // ── CONFIG ──
559
- /** @type {Config} */
560
- const config = {
561
- version: 2,
562
- seed: SEED,
563
- datasetStart: DATASET_START,
564
- datasetEnd: DATASET_END,
565
- avgEventsPerUserPerDay: EVENTS_PER_DAY,
566
- numUsers: NUM_USERS,
567
- format: "json",
568
- gzip: true,
569
- credentials: {
570
- token,
571
- },
572
- switches: {
573
- hasSessionIds: true,
574
- alsoInferFunnels: false,
575
- hasLocation: true,
576
- hasAndroidDevices: true,
577
- hasIOSDevices: true,
578
- hasDesktopDevices: true,
579
- hasBrowser: false,
580
- hasCampaigns: false,
581
- isAnonymous: false,
582
- hasAdSpend: false,
583
- hasAvatar: true,
584
- },
585
- identity: {
586
- avgDevicePerUser: 2,
587
- },
588
- concurrency: 1,
589
- writeToDisk: false,
590
- scdProps: {
591
- seller_tier: {
592
- values: ["new", "verified", "power", "featured"],
593
- frequency: "month",
594
- timing: "fuzzy",
595
- max: 8
596
- }
597
- },
598
- mirrorProps: {},
599
- lookupTables: [],
600
-
601
- events: [
602
- {
603
- event: "account created",
604
- weight: 1,
605
- isFirstEvent: true,
606
- isAuthEvent: true,
607
- properties: {
608
- signup_source: ["organic", "google_shopping", "tiktok", "seller_referral", "email_campaign", "word_of_mouth"],
609
- },
610
- },
611
- {
612
- event: "item searched",
613
- weight: 8,
614
- isStrictEvent: false,
615
- properties: {
616
- search_query: ["laptop", "vintage jacket", "sneakers", "headphones", "phone case", "gaming console", "watch", "sunglasses", "backpack", "camera"],
617
- results_count: u.weighNumRange(0, 50, 0.5),
618
- sort_by: ["relevance", "relevance", "price_low", "price_high", "newest", "best_selling"],
619
- },
620
- },
621
- {
622
- event: "item viewed",
623
- weight: 7,
624
- isStrictEvent: false,
625
- properties: {
626
- listing_id: chance.pickone.bind(chance, listingIds),
627
- item_price: u.weighNumRange(5, 500, 0.3, 45),
628
- condition: ["new", "new", "like_new", "good", "fair"],
629
- seller_rating: u.weighNumRange(1, 5, 0.7, 4),
630
- photos_count: u.weighNumRange(1, 10, 0.5, 3),
631
- },
632
- },
633
- {
634
- event: "add to cart",
635
- weight: 5,
636
- properties: {
637
- listing_id: chance.pickone.bind(chance, listingIds),
638
- item_price: u.weighNumRange(5, 500, 0.3, 45),
639
- quantity: u.weighNumRange(1, 5, 0.2),
640
- },
641
- },
642
- {
643
- event: "purchase completed",
644
- weight: 3,
645
- isStrictEvent: false,
646
- properties: {
647
- listing_id: chance.pickone.bind(chance, listingIds),
648
- total_amount: u.weighNumRange(10, 500, 0.3, 60),
649
- item_count: u.weighNumRange(1, 5, 0.3),
650
- payment_method: ["credit_card", "credit_card", "paypal", "apple_pay", "debit"],
651
- shipping_method: ["standard", "standard", "express", "pickup"],
652
- },
653
- },
654
- {
655
- event: "listing created",
656
- weight: 4,
657
- isStrictEvent: false,
658
- properties: {
659
- listing_id: chance.pickone.bind(chance, listingIds),
660
- store_id: chance.pickone.bind(chance, storeIds),
661
- asking_price: u.weighNumRange(5, 500, 0.3, 50),
662
- condition: ["new", "new", "like_new", "good", "fair"],
663
- listing_fee: u.weighNumRange(5, 30, 0.5, 15),
664
- },
665
- },
666
- {
667
- event: "listing updated",
668
- weight: 3,
669
- properties: {
670
- listing_id: chance.pickone.bind(chance, listingIds),
671
- field_updated: ["price", "price", "description", "photos", "condition", "shipping"],
672
- price_change_pct: u.weighNumRange(-30, 30, 0.5),
673
- },
674
- },
675
- {
676
- event: "offer received",
677
- weight: 3,
678
- isStrictEvent: false,
679
- properties: {
680
- listing_id: chance.pickone.bind(chance, listingIds),
681
- offer_amount: u.weighNumRange(5, 400, 0.3, 40),
682
- offer_pct_of_asking: u.weighNumRange(50, 100, 0.5, 80),
683
- },
684
- },
685
- {
686
- event: "offer accepted",
687
- weight: 2,
688
- isStrictEvent: false,
689
- properties: {
690
- listing_id: chance.pickone.bind(chance, listingIds),
691
- final_price: u.weighNumRange(10, 500, 0.3, 55),
692
- negotiation_rounds: u.weighNumRange(1, 5, 0.3),
693
- response_time_hours: u.weighNumRange(0.1, 48, 0.3, 4),
694
- },
695
- },
696
- {
697
- event: "review submitted",
698
- weight: 2,
699
- properties: {
700
- listing_id: chance.pickone.bind(chance, listingIds),
701
- rating: u.weighNumRange(1, 5, 0.7, 4),
702
- review_length: u.weighNumRange(10, 300, 0.5, 80),
703
- is_verified_purchase: [true, true, true, false],
704
- },
705
- },
706
- {
707
- event: "seller rated",
708
- weight: 2,
709
- properties: {
710
- store_id: chance.pickone.bind(chance, storeIds),
711
- seller_rating: u.weighNumRange(1, 5, 0.7, 4),
712
- would_buy_again: [true, true, true, true, false],
713
- },
714
- },
715
- {
716
- event: "message sent",
717
- weight: 5,
718
- isStrictEvent: false,
719
- properties: {
720
- message_type: ["inquiry", "inquiry", "negotiation", "shipping_question", "complaint", "thank_you"],
721
- recipient_type: ["seller", "seller", "buyer", "support"],
722
- response_time_hours: u.weighNumRange(0.1, 48, 0.3, 6),
723
- },
724
- },
725
- {
726
- event: "shipping updated",
727
- weight: 3,
728
- properties: {
729
- status: ["label_created", "in_transit", "in_transit", "out_for_delivery", "delivered", "delivered"],
730
- carrier: ["usps", "ups", "fedex", "dhl"],
731
- tracking_viewed: [true, true, false],
732
- },
733
- },
734
- {
735
- event: "refund requested",
736
- weight: 1,
737
- properties: {
738
- reason: ["not_as_described", "damaged", "wrong_item", "changed_mind", "late_delivery"],
739
- refund_amount: u.weighNumRange(5, 300, 0.4, 40),
740
- resolution_status: ["pending", "approved", "approved", "denied"],
741
- },
742
- },
743
- {
744
- event: "notification received",
745
- weight: 6,
746
- properties: {
747
- notification_type: ["price_drop", "price_drop", "new_offer", "shipping_update", "message_received", "sale_reminder"],
748
- channel: ["push", "push", "email", "sms"],
749
- opened: [true, true, true, false],
750
- },
751
- },
752
- {
753
- event: "profile updated",
754
- weight: 2,
755
- properties: {
756
- field_updated: ["address", "payment_method", "profile_photo", "bio", "phone", "store_description"],
757
- },
758
- },
759
- {
760
- event: "app session",
761
- weight: 8,
762
- properties: {
763
- session_duration_sec: u.weighNumRange(10, 1800, 0.4, 120),
764
- pages_viewed: u.weighNumRange(1, 20, 0.5, 4),
765
- },
766
- },
767
- {
768
- event: "account deactivated",
769
- weight: 1,
770
- isChurnEvent: true,
771
- returnLikelihood: 0.15,
772
- isStrictEvent: true,
773
- properties: {
774
- reason: ["low_sales", "high_fees", "found_alternative", "bad_experience", "no_longer_needed"],
775
- },
776
- },
777
- ],
778
-
779
- funnels: [
780
- {
781
- name: "Buyer Onboarding",
782
- sequence: ["account created", "item searched", "item viewed", "add to cart"],
783
- conversionRate: 40,
784
- order: "sequential",
785
- isFirstFunnel: true,
786
- timeToConvert: 72,
787
- weight: 3,
788
- },
789
- {
790
- name: "Browse to Purchase",
791
- sequence: ["item searched", "item viewed", "add to cart", "purchase completed"],
792
- conversionRate: 30,
793
- order: "sequential",
794
- timeToConvert: 48,
795
- weight: 5,
796
- },
797
- {
798
- name: "Seller Listing Flow",
799
- sequence: ["listing created", "listing updated", "offer received", "offer accepted"],
800
- conversionRate: 25,
801
- order: "sequential",
802
- timeToConvert: 168,
803
- weight: 3,
804
- },
805
- {
806
- name: "Offer Negotiation",
807
- sequence: ["offer received", "message sent", "offer accepted"],
808
- conversionRate: 35,
809
- order: "sequential",
810
- timeToConvert: 72,
811
- weight: 2,
812
- },
813
- {
814
- name: "Review After Purchase",
815
- sequence: ["purchase completed", "shipping updated", "review submitted"],
816
- conversionRate: 20,
817
- order: "sequential",
818
- timeToConvert: 336,
819
- weight: 2,
820
- },
821
- ],
822
-
823
- superProps: {
824
- Platform: ["ios", "android", "web"],
825
- category: ["electronics", "clothing", "home_garden", "collectibles", "sports", "toys", "automotive"],
826
- },
827
-
828
- userProps: {
829
- user_type: ["buyer"],
830
- segment: ["window_shopper"],
831
- seller_rating: u.weighNumRange(0, 5, 0.5),
832
- total_transactions: [0],
833
- response_time_hours: u.weighNumRange(0, 48, 0.3),
834
- store_name: ["none"],
835
- Platform: ["ios", "android", "web"],
836
- category: ["electronics", "clothing", "home_garden", "collectibles", "sports", "toys", "automotive"],
837
- },
838
-
839
- personas: [
840
- {
841
- name: "power_seller",
842
- weight: 8,
843
- eventMultiplier: 5.0,
844
- conversionModifier: 1.8,
845
- churnRate: 0.01,
846
- properties: {
847
- user_type: "seller",
848
- segment: "power_seller",
849
- },
850
- },
851
- {
852
- name: "casual_seller",
853
- weight: 20,
854
- eventMultiplier: 1.5,
855
- conversionModifier: 1.0,
856
- churnRate: 0.08,
857
- properties: {
858
- user_type: "seller",
859
- segment: "casual_seller",
860
- },
861
- },
862
- {
863
- name: "frequent_buyer",
864
- weight: 25,
865
- eventMultiplier: 2.0,
866
- conversionModifier: 1.3,
867
- churnRate: 0.03,
868
- properties: {
869
- user_type: "buyer",
870
- segment: "frequent_buyer",
871
- },
872
- },
873
- {
874
- name: "window_shopper",
875
- weight: 35,
876
- eventMultiplier: 0.5,
877
- conversionModifier: 0.4,
878
- churnRate: 0.15,
879
- properties: {
880
- user_type: "buyer",
881
- segment: "window_shopper",
882
- },
883
- },
884
- {
885
- name: "new_seller",
886
- weight: 12,
887
- eventMultiplier: 0.8,
888
- conversionModifier: 0.5,
889
- churnRate: 0.3,
890
- properties: {
891
- user_type: "seller",
892
- segment: "new_seller",
893
- },
894
- activeWindow: { maxDays: 28 },
895
- },
896
- ],
897
-
898
- worldEvents: [
899
- {
900
- name: "marketplace_fee_change",
901
- type: "product_launch",
902
- startDay: 45,
903
- duration: null,
904
- injectProps: { fee_change: "increased" },
905
- affectsEvents: ["listing created"],
906
- },
907
- {
908
- name: "viral_tiktok_moment",
909
- type: "campaign",
910
- startDay: 55,
911
- duration: 3,
912
- volumeMultiplier: 3.0,
913
- affectsEvents: ["item searched", "item viewed", "purchase completed"],
914
- },
915
- ],
916
-
917
- engagementDecay: {
918
- model: "exponential",
919
- halfLife: 75,
920
- floor: 0.1,
921
- },
922
-
923
- attribution: {
924
- model: "last_touch",
925
- window: 7,
926
- campaigns: [
927
- {
928
- name: "google_shopping",
929
- source: "google",
930
- medium: "cpc",
931
- activeDays: [0, 100],
932
- dailyBudget: [200, 800],
933
- acquisitionRate: 0.03,
934
- userPersonaBias: { frequent_buyer: 0.5 },
935
- },
936
- {
937
- name: "tiktok",
938
- source: "tiktok",
939
- medium: "social",
940
- activeDays: [10, 90],
941
- dailyBudget: [100, 400],
942
- acquisitionRate: 0.02,
943
- userPersonaBias: { window_shopper: 0.5 },
944
- },
945
- {
946
- name: "seller_referral",
947
- source: "referral",
948
- medium: "referral",
949
- activeDays: [0, 100],
950
- dailyBudget: [50, 200],
951
- acquisitionRate: 0.02,
952
- userPersonaBias: { power_seller: 0.4 },
953
- },
954
- ],
955
- organicRate: 0.30,
956
- },
957
-
958
- geo: {
959
- sticky: true,
960
- regions: [
961
- {
962
- name: "us",
963
- countries: ["US"],
964
- weight: 50,
965
- timezoneOffset: -5,
966
- properties: { currency: "USD", locale: "en-US" },
967
- },
968
- {
969
- name: "uk",
970
- countries: ["GB"],
971
- weight: 20,
972
- timezoneOffset: 0,
973
- properties: { currency: "GBP", locale: "en-GB" },
974
- },
975
- {
976
- name: "australia",
977
- countries: ["AU"],
978
- weight: 15,
979
- timezoneOffset: 10,
980
- properties: { currency: "AUD", locale: "en-AU" },
981
- },
982
- {
983
- name: "canada",
984
- countries: ["CA"],
985
- weight: 15,
986
- timezoneOffset: -5,
987
- properties: { currency: "CAD", locale: "en-CA" },
988
- },
989
- ],
990
- },
991
-
992
- anomalies: [
993
- {
994
- type: "extreme_value",
995
- event: "purchase completed",
996
- property: "total_amount",
997
- frequency: 0.002,
998
- multiplier: 30,
999
- tag: "whale_purchase",
1000
- },
1001
- {
1002
- type: "coordinated",
1003
- event: "account created",
1004
- day: 68,
1005
- window: 0.02,
1006
- count: 200,
1007
- tag: "viral_signup",
1008
- },
1009
- ],
1010
-
1011
- hook(record, type, meta) {
1012
- if (type === "user") return handleUserHooks(record);
1013
- if (type === "funnel-post") return handleFunnelPostHooks(record, meta);
1014
- if (type === "everything") return handleEverythingHooks(record, meta);
1015
- return record;
1016
- },
1017
- };
1018
-
1019
- export default config;