@ak--47/dungeon-master 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/dungeons/technical/ad-spend-schema.json +128 -0
- package/dungeons/technical/ad-spend.js +116 -0
- package/dungeons/technical/anonymous-users-schema.json +92 -0
- package/dungeons/technical/anonymous-users.js +102 -0
- package/dungeons/technical/array-of-object-lookup-schema.json +191 -0
- package/dungeons/{array-of-object-lookup.js → technical/array-of-object-lookup.js} +12 -5
- package/dungeons/technical/experiments-schema.json +203 -0
- package/dungeons/technical/experiments.js +168 -0
- package/dungeons/technical/foobar-schema.json +362 -0
- package/dungeons/{foobar.js → technical/foobar.js} +3 -3
- package/dungeons/technical/group-analytics-schema.json +241 -0
- package/dungeons/technical/group-analytics.js +151 -0
- package/dungeons/technical/mirror-strategies-schema.json +84 -0
- package/dungeons/technical/mirror-strategies.js +126 -0
- package/dungeons/technical/nested-objects-schema.json +145 -0
- package/dungeons/technical/nested-objects.js +207 -0
- package/dungeons/technical/retention-cadence-schema.json +37 -0
- package/dungeons/{retention-cadence.js → technical/retention-cadence.js} +2 -3
- package/dungeons/technical/sanity-schema.json +185 -0
- package/dungeons/{sanity.js → technical/sanity.js} +4 -2
- package/dungeons/technical/scale-test-schema.json +70 -0
- package/dungeons/technical/scale-test.js +90 -0
- package/dungeons/technical/scd-schema.json +467 -0
- package/dungeons/{scd.js → technical/scd.js} +20 -10
- package/dungeons/technical/simple-schema.json +362 -0
- package/dungeons/{simple.js → technical/simple.js} +7 -7
- package/dungeons/technical/simplest-schema.json +724 -0
- package/dungeons/{simplest.js → technical/simplest.js} +7 -7
- package/dungeons/technical/text-generation-schema.json +1062 -0
- package/dungeons/{text-generation.js → technical/text-generation.js} +24 -11
- package/dungeons/vertical/community-schema.json +579 -0
- package/dungeons/vertical/community.js +794 -0
- package/dungeons/vertical/devtools-schema.json +601 -0
- package/dungeons/vertical/devtools.js +843 -0
- package/dungeons/vertical/ecommerce-schema.json +604 -0
- package/dungeons/{ecommerce.js → vertical/ecommerce.js} +43 -5
- package/dungeons/vertical/education-schema.json +5686 -0
- package/dungeons/{education.js → vertical/education.js} +70 -39
- package/dungeons/vertical/fintech-schema.json +630 -0
- package/dungeons/{fintech.js → vertical/fintech.js} +107 -56
- package/dungeons/vertical/fitness-schema.json +530 -0
- package/dungeons/vertical/fitness.js +808 -0
- package/dungeons/vertical/food-delivery-schema.json +36728 -0
- package/dungeons/{food.js → vertical/food-delivery.js} +67 -26
- package/dungeons/vertical/gaming-schema.json +438 -0
- package/dungeons/{gaming.js → vertical/gaming.js} +29 -16
- package/dungeons/vertical/healthcare-schema.json +549 -0
- package/dungeons/vertical/healthcare.js +775 -0
- package/dungeons/vertical/insurance-application-schema.json +485 -0
- package/dungeons/{insurance-application.js → vertical/insurance-application.js} +41 -31
- package/dungeons/vertical/logistics-schema.json +574 -0
- package/dungeons/vertical/logistics.js +799 -0
- package/dungeons/vertical/marketplace-schema.json +533 -0
- package/dungeons/vertical/marketplace.js +850 -0
- package/dungeons/vertical/media-schema.json +4749 -0
- package/dungeons/{media.js → vertical/media.js} +103 -19
- package/dungeons/vertical/rpg-schema.json +2491 -0
- package/dungeons/{rpg.js → vertical/rpg.js} +87 -60
- package/dungeons/vertical/sass-schema.json +3128 -0
- package/dungeons/{sass.js → vertical/sass.js} +65 -26
- package/dungeons/vertical/social-schema.json +620 -0
- package/dungeons/{social.js → vertical/social.js} +76 -17
- package/dungeons/vertical/travel-schema.json +580 -0
- package/dungeons/vertical/travel.js +742 -0
- package/index.js +20 -16
- package/lib/core/config-validator.js +286 -1
- package/lib/core/context.js +16 -5
- package/lib/core/storage.js +2 -0
- package/lib/generators/adspend.js +3 -3
- package/lib/generators/events.js +141 -14
- package/lib/generators/funnels.js +67 -7
- package/lib/generators/text.js +2 -2
- package/lib/orchestrators/user-loop.js +444 -22
- package/lib/templates/soup-presets.js +2 -2
- package/lib/utils/utils.js +4 -5
- package/package.json +4 -3
- package/scripts/dungeon-to-json.mjs +163 -0
- package/scripts/extract-dungeon-schema.mjs +343 -0
- package/scripts/json-to-dungeon.mjs +200 -0
- package/scripts/run-dungeon.mjs +76 -0
- package/scripts/run-many.mjs +118 -0
- package/scripts/verify-runner.mjs +42 -0
- package/types.d.ts +316 -1
- package/dungeons/array-of-object-lookup-schema.json +0 -327
- package/dungeons/ecommerce-schema.json +0 -462
- package/dungeons/education-schema.json +0 -2409
- package/dungeons/fintech-schema.json +0 -14034
- package/dungeons/foobar-schema.json +0 -403
- package/dungeons/food-delivery-schema.json +0 -192
- package/dungeons/food-delivery.js +0 -602
- package/dungeons/food-schema.json +0 -1152
- package/dungeons/gaming-schema.json +0 -1270
- package/dungeons/insurance-application-schema.json +0 -204
- package/dungeons/media-schema.json +0 -906
- package/dungeons/retention-cadence-schema.json +0 -78
- package/dungeons/rpg-schema.json +0 -4526
- package/dungeons/sanity-schema.json +0 -255
- package/dungeons/sass-schema.json +0 -1291
- package/dungeons/scd-schema.json +0 -919
- package/dungeons/simple-schema.json +0 -608
- package/dungeons/simplest-schema.json +0 -1418
- package/dungeons/social-schema.json +0 -1118
- package/dungeons/text-generation-schema.json +0 -3096
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import utc from "dayjs/plugin/utc.js";
|
|
3
|
+
import "dotenv/config";
|
|
4
|
+
import * as u from "../../lib/utils/utils.js";
|
|
5
|
+
import * as v from "ak-tools";
|
|
6
|
+
|
|
7
|
+
const SEED = "dm4-marketplace";
|
|
8
|
+
dayjs.extend(utc);
|
|
9
|
+
const chance = u.initChance(SEED);
|
|
10
|
+
const num_users = 5_000;
|
|
11
|
+
const days = 100;
|
|
12
|
+
const NOW = dayjs();
|
|
13
|
+
const DATASET_START = NOW.subtract(days, "days");
|
|
14
|
+
|
|
15
|
+
/** @typedef {import("../../types").Dungeon} Config */
|
|
16
|
+
|
|
17
|
+
// Generate consistent seller store and listing IDs at module level
|
|
18
|
+
const storeIds = v.range(1, 150).map(() => `STORE_${v.uid(6)}`);
|
|
19
|
+
const listingIds = v.range(1, 500).map(() => `LST_${v.uid(8)}`);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ═══════════════════════════════════════════════════════════════
|
|
23
|
+
* DATASET OVERVIEW
|
|
24
|
+
* ═══════════════════════════════════════════════════════════════
|
|
25
|
+
*
|
|
26
|
+
* TradeNest — a two-sided marketplace connecting sellers who list
|
|
27
|
+
* products with buyers who search, purchase, and review items.
|
|
28
|
+
*
|
|
29
|
+
* - 5,000 users over 100 days, ~600K events
|
|
30
|
+
* - Two-sided: sellers (power, casual, new) and buyers (frequent, window_shopper)
|
|
31
|
+
* - Core loop: search → view → add to cart → purchase → review
|
|
32
|
+
* - Seller loop: create listing → receive offers → accept → ship
|
|
33
|
+
* - Revenue: marketplace fees on transactions, seller listing fees
|
|
34
|
+
*
|
|
35
|
+
* Advanced Features:
|
|
36
|
+
* - Personas: 5 archetypes spanning both buyer and seller roles
|
|
37
|
+
* - World Events: marketplace fee change (day 45, permanent), viral TikTok moment (day 55, 3 days)
|
|
38
|
+
* - Engagement Decay: exponential with 75-day half-life, 0.1 floor
|
|
39
|
+
* - Attribution: Google Shopping, TikTok, Seller Referral, organic
|
|
40
|
+
* - Geo: US/UK/Australia/Canada with local currencies
|
|
41
|
+
* - Anomalies: whale purchases, viral signup burst
|
|
42
|
+
*
|
|
43
|
+
* Key entities:
|
|
44
|
+
* - store_id: unique seller storefront
|
|
45
|
+
* - listing_id: individual product listing
|
|
46
|
+
* - category: product vertical (electronics, clothing, etc.)
|
|
47
|
+
* - user_type: buyer vs seller role
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* ═══════════════════════════════════════════════════════════════
|
|
52
|
+
* ANALYTICS HOOKS (8 hooks)
|
|
53
|
+
* ═══════════════════════════════════════════════════════════════
|
|
54
|
+
*
|
|
55
|
+
* ───────────────────────────────────────────────────────────────
|
|
56
|
+
* 1. FEE CHANGE IMPACT (event hook)
|
|
57
|
+
* ───────────────────────────────────────────────────────────────
|
|
58
|
+
*
|
|
59
|
+
* PATTERN: After day 45 (permanent marketplace fee increase), all
|
|
60
|
+
* "listing created" events get listing_fee multiplied by 1.3x.
|
|
61
|
+
* Simulates the revenue impact of a platform fee adjustment.
|
|
62
|
+
*
|
|
63
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
64
|
+
*
|
|
65
|
+
* Report 1: Listing Fee Before vs After Fee Change
|
|
66
|
+
* • Report type: Insights
|
|
67
|
+
* • Event: "listing created"
|
|
68
|
+
* • Measure: Average of "listing_fee"
|
|
69
|
+
* • Line chart by week
|
|
70
|
+
* • Expected: Clear step-up around day 45 from ~$15 avg to ~$20 avg
|
|
71
|
+
*
|
|
72
|
+
* Report 2: Fee Change Flag Distribution
|
|
73
|
+
* • Report type: Insights
|
|
74
|
+
* • Event: "listing created"
|
|
75
|
+
* • Measure: Total
|
|
76
|
+
* • Breakdown: "fee_change"
|
|
77
|
+
* • Expected: "increased" appears only after day 45
|
|
78
|
+
*
|
|
79
|
+
* REAL-WORLD ANALOGUE: Marketplace platforms periodically adjust
|
|
80
|
+
* commission/listing fees, impacting seller economics and behavior.
|
|
81
|
+
*
|
|
82
|
+
* ───────────────────────────────────────────────────────────────
|
|
83
|
+
* 2. WEEKEND SHOPPING SURGE (event hook)
|
|
84
|
+
* ───────────────────────────────────────────────────────────────
|
|
85
|
+
*
|
|
86
|
+
* PATTERN: "purchase completed" events on Saturday or Sunday get
|
|
87
|
+
* total_amount multiplied by 1.2x. Weekend shoppers spend more
|
|
88
|
+
* per transaction due to leisure browsing.
|
|
89
|
+
*
|
|
90
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
91
|
+
*
|
|
92
|
+
* Report 1: Weekend vs Weekday Average Spend
|
|
93
|
+
* • Report type: Insights
|
|
94
|
+
* • Event: "purchase completed"
|
|
95
|
+
* • Measure: Average of "total_amount"
|
|
96
|
+
* • Breakdown: Day of week
|
|
97
|
+
* • Expected: Sat/Sun avg ≈ $72 vs Mon-Fri avg ≈ $60
|
|
98
|
+
*
|
|
99
|
+
* REAL-WORLD ANALOGUE: E-commerce platforms see higher average
|
|
100
|
+
* order values on weekends when buyers browse leisurely.
|
|
101
|
+
*
|
|
102
|
+
* ───────────────────────────────────────────────────────────────
|
|
103
|
+
* 3. SELLER SUCCESS → BUYER TRUST (everything hook)
|
|
104
|
+
* ───────────────────────────────────────────────────────────────
|
|
105
|
+
*
|
|
106
|
+
* PATTERN: Users with user_type "seller" and segment "power_seller"
|
|
107
|
+
* (from meta.profile) get their "purchase completed" events cloned
|
|
108
|
+
* 2x. Power sellers attract more transactions due to trust/reputation.
|
|
109
|
+
*
|
|
110
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
111
|
+
*
|
|
112
|
+
* Report 1: Purchases per User by Segment
|
|
113
|
+
* • Report type: Insights
|
|
114
|
+
* • Event: "purchase completed"
|
|
115
|
+
* • Measure: Total per user
|
|
116
|
+
* • Breakdown: user property "segment"
|
|
117
|
+
* • Expected: power_seller ≈ 2x purchases vs casual_seller
|
|
118
|
+
*
|
|
119
|
+
* REAL-WORLD ANALOGUE: High-rated sellers with established reputations
|
|
120
|
+
* receive disproportionately more sales on platforms like eBay/Etsy.
|
|
121
|
+
*
|
|
122
|
+
* ───────────────────────────────────────────────────────────────
|
|
123
|
+
* 4. SEARCH-TO-PURCHASE BY CATEGORY (everything hook)
|
|
124
|
+
* ───────────────────────────────────────────────────────────────
|
|
125
|
+
*
|
|
126
|
+
* PATTERN: Users whose events include "item searched" with category
|
|
127
|
+
* "electronics" get cloned "purchase completed" events (electronics
|
|
128
|
+
* buyers have higher conversion). Creates category-specific lift.
|
|
129
|
+
*
|
|
130
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
131
|
+
*
|
|
132
|
+
* Report 1: Purchase Volume by Category
|
|
133
|
+
* • Report type: Insights
|
|
134
|
+
* • Event: "purchase completed"
|
|
135
|
+
* • Measure: Total
|
|
136
|
+
* • Breakdown: "category" (superProp)
|
|
137
|
+
* • Expected: electronics ≈ 1.5-2x more purchases than clothing/home
|
|
138
|
+
*
|
|
139
|
+
* REAL-WORLD ANALOGUE: Electronics shoppers have higher purchase
|
|
140
|
+
* intent — they research, decide, and buy with less browsing.
|
|
141
|
+
*
|
|
142
|
+
* ───────────────────────────────────────────────────────────────
|
|
143
|
+
* 5. RESPONSE TIME → CONVERSION (everything hook)
|
|
144
|
+
* ───────────────────────────────────────────────────────────────
|
|
145
|
+
*
|
|
146
|
+
* PATTERN: Users whose average response_time_hours on "message sent"
|
|
147
|
+
* events is < 2 hours get additional "offer accepted" events cloned.
|
|
148
|
+
* Fast responders close more deals.
|
|
149
|
+
*
|
|
150
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
151
|
+
*
|
|
152
|
+
* Report 1: Offer Accepted Count vs Response Time
|
|
153
|
+
* • Report type: Insights
|
|
154
|
+
* • Event: "offer accepted"
|
|
155
|
+
* • Measure: Total per user
|
|
156
|
+
* • Breakdown: user property "response_time_hours"
|
|
157
|
+
* • Expected: Users with < 2hr avg response ≈ 2x offer accepts
|
|
158
|
+
*
|
|
159
|
+
* REAL-WORLD ANALOGUE: Sellers who respond quickly to inquiries
|
|
160
|
+
* close significantly more deals on marketplace platforms.
|
|
161
|
+
*
|
|
162
|
+
* ───────────────────────────────────────────────────────────────
|
|
163
|
+
* 6. NEW SELLER CHURN (everything hook)
|
|
164
|
+
* ───────────────────────────────────────────────────────────────
|
|
165
|
+
*
|
|
166
|
+
* PATTERN: Users with segment "new_seller" and fewer than 10 total
|
|
167
|
+
* events lose 40% of events after day 14. Simulates new sellers
|
|
168
|
+
* who try the platform and abandon it quickly.
|
|
169
|
+
*
|
|
170
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
171
|
+
*
|
|
172
|
+
* Report 1: New Seller Retention
|
|
173
|
+
* • Report type: Insights
|
|
174
|
+
* • Event: All events
|
|
175
|
+
* • Measure: Total per user
|
|
176
|
+
* • Filter: segment = "new_seller"
|
|
177
|
+
* • Line chart by week
|
|
178
|
+
* • Expected: Sharp drop-off around week 2-3
|
|
179
|
+
*
|
|
180
|
+
* Report 2: Active Users by Segment
|
|
181
|
+
* • Report type: Insights
|
|
182
|
+
* • Event: Any active event
|
|
183
|
+
* • Measure: Uniques
|
|
184
|
+
* • Breakdown: user property "segment"
|
|
185
|
+
* • Expected: new_seller retention ~60% vs power_seller ~99%
|
|
186
|
+
*
|
|
187
|
+
* REAL-WORLD ANALOGUE: Most new sellers on marketplace platforms
|
|
188
|
+
* churn within the first month if they don't get early traction.
|
|
189
|
+
*
|
|
190
|
+
* ───────────────────────────────────────────────────────────────
|
|
191
|
+
* 7. POWER SELLER PROFILES (user hook)
|
|
192
|
+
* ───────────────────────────────────────────────────────────────
|
|
193
|
+
*
|
|
194
|
+
* PATTERN: Users with segment "power_seller" get total_transactions
|
|
195
|
+
* set to 100-500, seller_rating to 4.5-5.0, and store_name to a
|
|
196
|
+
* realistic business name. Enriches profiles for realistic cohort analysis.
|
|
197
|
+
*
|
|
198
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
199
|
+
*
|
|
200
|
+
* Report 1: Transaction Count Distribution by Segment
|
|
201
|
+
* • Report type: Insights
|
|
202
|
+
* • Measure: Profiles → Breakdown by "total_transactions" (histogram)
|
|
203
|
+
* • Filter: segment = "power_seller"
|
|
204
|
+
* • Expected: power_seller total_transactions 100-500, others ≈ 0
|
|
205
|
+
*
|
|
206
|
+
* Report 2: Seller Rating by Segment
|
|
207
|
+
* • Report type: Insights
|
|
208
|
+
* • Measure: Profiles → Average of "seller_rating"
|
|
209
|
+
* • Breakdown: "segment"
|
|
210
|
+
* • Expected: power_seller ≈ 4.7 avg, casual_seller ≈ 3.5
|
|
211
|
+
*
|
|
212
|
+
* REAL-WORLD ANALOGUE: Top sellers have established storefronts,
|
|
213
|
+
* high transaction volumes, and near-perfect ratings.
|
|
214
|
+
*
|
|
215
|
+
* ───────────────────────────────────────────────────────────────
|
|
216
|
+
* 8. FREQUENT BUYER FUNNEL LIFT (funnel-pre hook)
|
|
217
|
+
* ───────────────────────────────────────────────────────────────
|
|
218
|
+
*
|
|
219
|
+
* PATTERN: Users with segment "frequent_buyer" get 1.3x conversion
|
|
220
|
+
* rate on the browse-to-purchase funnel. Repeat buyers know what
|
|
221
|
+
* they want and convert faster.
|
|
222
|
+
*
|
|
223
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
224
|
+
*
|
|
225
|
+
* Report 1: Browse-to-Purchase Conversion by Segment
|
|
226
|
+
* • Report type: Funnels
|
|
227
|
+
* • Steps: "item searched" → "item viewed" → "add to cart" → "purchase completed"
|
|
228
|
+
* • Breakdown: user property "segment"
|
|
229
|
+
* • Expected: frequent_buyer ≈ 39% vs window_shopper ≈ 20%
|
|
230
|
+
*
|
|
231
|
+
* REAL-WORLD ANALOGUE: Returning buyers have established trust
|
|
232
|
+
* and familiarity with the platform, converting at higher rates.
|
|
233
|
+
*
|
|
234
|
+
* ═══════════════════════════════════════════════════════════════
|
|
235
|
+
* EXPECTED METRICS SUMMARY
|
|
236
|
+
* ═══════════════════════════════════════════════════════════════
|
|
237
|
+
*
|
|
238
|
+
* Hook | Metric | Baseline | Effect | Ratio
|
|
239
|
+
* ────────────────────────────|─────────────────────|──────────|─────────|──────
|
|
240
|
+
* Fee Change Impact | listing_fee | $15 | $20 | 1.3x
|
|
241
|
+
* Weekend Shopping Surge | total_amount | $60 | $72 | 1.2x
|
|
242
|
+
* Seller Success → Trust | purchases/user | 3 | 6 | 2x
|
|
243
|
+
* Electronics Category Lift | electronics purch. | baseline | 1.5x | 1.5x
|
|
244
|
+
* Response Time → Conversion | offer_accepted/user | 1 | 2 | 2x
|
|
245
|
+
* New Seller Churn | events post-day-14 | 100% | 60% | 0.6x
|
|
246
|
+
* Power Seller Profiles | total_transactions | 0 | 100-500 | --
|
|
247
|
+
* Frequent Buyer Funnel | funnel conversion | 30% | 39% | 1.3x
|
|
248
|
+
*/
|
|
249
|
+
|
|
250
|
+
/** @type {Config} */
|
|
251
|
+
const config = {
|
|
252
|
+
token: "",
|
|
253
|
+
seed: SEED,
|
|
254
|
+
numDays: days,
|
|
255
|
+
numEvents: num_users * 120,
|
|
256
|
+
numUsers: num_users,
|
|
257
|
+
hasAnonIds: false,
|
|
258
|
+
hasSessionIds: true,
|
|
259
|
+
format: "json",
|
|
260
|
+
gzip: true,
|
|
261
|
+
alsoInferFunnels: false,
|
|
262
|
+
hasLocation: true,
|
|
263
|
+
hasAndroidDevices: true,
|
|
264
|
+
hasIOSDevices: true,
|
|
265
|
+
hasDesktopDevices: true,
|
|
266
|
+
hasBrowser: false,
|
|
267
|
+
hasCampaigns: false,
|
|
268
|
+
isAnonymous: false,
|
|
269
|
+
hasAdSpend: false,
|
|
270
|
+
percentUsersBornInDataset: 35,
|
|
271
|
+
hasAvatar: true,
|
|
272
|
+
concurrency: 1,
|
|
273
|
+
writeToDisk: false,
|
|
274
|
+
scdProps: {
|
|
275
|
+
seller_tier: {
|
|
276
|
+
values: ["new", "verified", "power", "featured"],
|
|
277
|
+
frequency: "month",
|
|
278
|
+
timing: "fuzzy",
|
|
279
|
+
max: 8
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
mirrorProps: {},
|
|
283
|
+
lookupTables: [],
|
|
284
|
+
|
|
285
|
+
// ── Events (18) ──────────────────────────────────────────
|
|
286
|
+
events: [
|
|
287
|
+
{
|
|
288
|
+
event: "account created",
|
|
289
|
+
weight: 1,
|
|
290
|
+
isFirstEvent: true,
|
|
291
|
+
properties: {
|
|
292
|
+
signup_source: ["organic", "google_shopping", "tiktok", "seller_referral", "email_campaign", "word_of_mouth"],
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
event: "item searched",
|
|
297
|
+
weight: 8,
|
|
298
|
+
properties: {
|
|
299
|
+
search_query: ["laptop", "vintage jacket", "sneakers", "headphones", "phone case", "gaming console", "watch", "sunglasses", "backpack", "camera"],
|
|
300
|
+
results_count: u.weighNumRange(0, 50, 0.5),
|
|
301
|
+
sort_by: ["relevance", "relevance", "price_low", "price_high", "newest", "best_selling"],
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
event: "item viewed",
|
|
306
|
+
weight: 7,
|
|
307
|
+
properties: {
|
|
308
|
+
listing_id: chance.pickone.bind(chance, listingIds),
|
|
309
|
+
item_price: u.weighNumRange(5, 500, 0.3, 45),
|
|
310
|
+
condition: ["new", "new", "like_new", "good", "fair"],
|
|
311
|
+
seller_rating: u.weighNumRange(1, 5, 0.7, 4),
|
|
312
|
+
photos_count: u.weighNumRange(1, 10, 0.5, 3),
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
event: "add to cart",
|
|
317
|
+
weight: 5,
|
|
318
|
+
properties: {
|
|
319
|
+
listing_id: chance.pickone.bind(chance, listingIds),
|
|
320
|
+
item_price: u.weighNumRange(5, 500, 0.3, 45),
|
|
321
|
+
quantity: u.weighNumRange(1, 5, 0.2),
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
event: "purchase completed",
|
|
326
|
+
weight: 3,
|
|
327
|
+
properties: {
|
|
328
|
+
listing_id: chance.pickone.bind(chance, listingIds),
|
|
329
|
+
total_amount: u.weighNumRange(10, 500, 0.3, 60),
|
|
330
|
+
item_count: u.weighNumRange(1, 5, 0.3),
|
|
331
|
+
payment_method: ["credit_card", "credit_card", "paypal", "apple_pay", "debit"],
|
|
332
|
+
shipping_method: ["standard", "standard", "express", "pickup"],
|
|
333
|
+
is_whale_purchase: [false],
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
event: "listing created",
|
|
338
|
+
weight: 4,
|
|
339
|
+
properties: {
|
|
340
|
+
listing_id: chance.pickone.bind(chance, listingIds),
|
|
341
|
+
store_id: chance.pickone.bind(chance, storeIds),
|
|
342
|
+
asking_price: u.weighNumRange(5, 500, 0.3, 50),
|
|
343
|
+
condition: ["new", "new", "like_new", "good", "fair"],
|
|
344
|
+
listing_fee: u.weighNumRange(5, 30, 0.5, 15),
|
|
345
|
+
fee_change: ["none"],
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
event: "listing updated",
|
|
350
|
+
weight: 3,
|
|
351
|
+
properties: {
|
|
352
|
+
listing_id: chance.pickone.bind(chance, listingIds),
|
|
353
|
+
field_updated: ["price", "price", "description", "photos", "condition", "shipping"],
|
|
354
|
+
price_change_pct: u.weighNumRange(-30, 30, 0.5),
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
event: "offer received",
|
|
359
|
+
weight: 3,
|
|
360
|
+
properties: {
|
|
361
|
+
listing_id: chance.pickone.bind(chance, listingIds),
|
|
362
|
+
offer_amount: u.weighNumRange(5, 400, 0.3, 40),
|
|
363
|
+
offer_pct_of_asking: u.weighNumRange(50, 100, 0.5, 80),
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
event: "offer accepted",
|
|
368
|
+
weight: 2,
|
|
369
|
+
properties: {
|
|
370
|
+
listing_id: chance.pickone.bind(chance, listingIds),
|
|
371
|
+
final_price: u.weighNumRange(10, 500, 0.3, 55),
|
|
372
|
+
negotiation_rounds: u.weighNumRange(1, 5, 0.3),
|
|
373
|
+
response_time_hours: u.weighNumRange(0.1, 48, 0.3, 4),
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
event: "review submitted",
|
|
378
|
+
weight: 2,
|
|
379
|
+
properties: {
|
|
380
|
+
listing_id: chance.pickone.bind(chance, listingIds),
|
|
381
|
+
rating: u.weighNumRange(1, 5, 0.7, 4),
|
|
382
|
+
review_length: u.weighNumRange(10, 300, 0.5, 80),
|
|
383
|
+
is_verified_purchase: [true, true, true, false],
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
event: "seller rated",
|
|
388
|
+
weight: 2,
|
|
389
|
+
properties: {
|
|
390
|
+
store_id: chance.pickone.bind(chance, storeIds),
|
|
391
|
+
seller_rating: u.weighNumRange(1, 5, 0.7, 4),
|
|
392
|
+
would_buy_again: [true, true, true, true, false],
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
event: "message sent",
|
|
397
|
+
weight: 5,
|
|
398
|
+
properties: {
|
|
399
|
+
message_type: ["inquiry", "inquiry", "negotiation", "shipping_question", "complaint", "thank_you"],
|
|
400
|
+
recipient_type: ["seller", "seller", "buyer", "support"],
|
|
401
|
+
response_time_hours: u.weighNumRange(0.1, 48, 0.3, 6),
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
event: "shipping updated",
|
|
406
|
+
weight: 3,
|
|
407
|
+
properties: {
|
|
408
|
+
status: ["label_created", "in_transit", "in_transit", "out_for_delivery", "delivered", "delivered"],
|
|
409
|
+
carrier: ["usps", "ups", "fedex", "dhl"],
|
|
410
|
+
tracking_viewed: [true, true, false],
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
event: "refund requested",
|
|
415
|
+
weight: 1,
|
|
416
|
+
properties: {
|
|
417
|
+
reason: ["not_as_described", "damaged", "wrong_item", "changed_mind", "late_delivery"],
|
|
418
|
+
refund_amount: u.weighNumRange(5, 300, 0.4, 40),
|
|
419
|
+
resolution_status: ["pending", "approved", "approved", "denied"],
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
event: "notification received",
|
|
424
|
+
weight: 6,
|
|
425
|
+
properties: {
|
|
426
|
+
notification_type: ["price_drop", "price_drop", "new_offer", "shipping_update", "message_received", "sale_reminder"],
|
|
427
|
+
channel: ["push", "push", "email", "sms"],
|
|
428
|
+
opened: [true, true, true, false],
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
event: "profile updated",
|
|
433
|
+
weight: 2,
|
|
434
|
+
properties: {
|
|
435
|
+
field_updated: ["address", "payment_method", "profile_photo", "bio", "phone", "store_description"],
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
event: "app session",
|
|
440
|
+
weight: 8,
|
|
441
|
+
properties: {
|
|
442
|
+
session_duration_sec: u.weighNumRange(10, 1800, 0.4, 120),
|
|
443
|
+
pages_viewed: u.weighNumRange(1, 20, 0.5, 4),
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
event: "account deactivated",
|
|
448
|
+
weight: 1,
|
|
449
|
+
isChurnEvent: true,
|
|
450
|
+
returnLikelihood: 0.15,
|
|
451
|
+
isStrictEvent: true,
|
|
452
|
+
properties: {
|
|
453
|
+
reason: ["low_sales", "high_fees", "found_alternative", "bad_experience", "no_longer_needed"],
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
|
|
458
|
+
// ── Funnels (5) ──────────────────────────────────────────
|
|
459
|
+
funnels: [
|
|
460
|
+
{
|
|
461
|
+
name: "Buyer Onboarding",
|
|
462
|
+
sequence: ["account created", "item searched", "item viewed", "add to cart"],
|
|
463
|
+
conversionRate: 40,
|
|
464
|
+
order: "sequential",
|
|
465
|
+
isFirstFunnel: true,
|
|
466
|
+
timeToConvert: 72,
|
|
467
|
+
weight: 3,
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: "Browse to Purchase",
|
|
471
|
+
sequence: ["item searched", "item viewed", "add to cart", "purchase completed"],
|
|
472
|
+
conversionRate: 30,
|
|
473
|
+
order: "sequential",
|
|
474
|
+
timeToConvert: 48,
|
|
475
|
+
weight: 5,
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
name: "Seller Listing Flow",
|
|
479
|
+
sequence: ["listing created", "listing updated", "offer received", "offer accepted"],
|
|
480
|
+
conversionRate: 25,
|
|
481
|
+
order: "sequential",
|
|
482
|
+
timeToConvert: 168,
|
|
483
|
+
weight: 3,
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: "Offer Negotiation",
|
|
487
|
+
sequence: ["offer received", "message sent", "offer accepted"],
|
|
488
|
+
conversionRate: 35,
|
|
489
|
+
order: "sequential",
|
|
490
|
+
timeToConvert: 72,
|
|
491
|
+
weight: 2,
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
name: "Review After Purchase",
|
|
495
|
+
sequence: ["purchase completed", "shipping updated", "review submitted"],
|
|
496
|
+
conversionRate: 20,
|
|
497
|
+
order: "sequential",
|
|
498
|
+
timeToConvert: 336,
|
|
499
|
+
weight: 2,
|
|
500
|
+
},
|
|
501
|
+
],
|
|
502
|
+
|
|
503
|
+
// ── SuperProps ──────────────────────────────────────────
|
|
504
|
+
superProps: {
|
|
505
|
+
platform: ["ios", "android", "web"],
|
|
506
|
+
category: ["electronics", "clothing", "home_garden", "collectibles", "sports", "toys", "automotive"],
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
// ── UserProps ──────────────────────────────────────────
|
|
510
|
+
userProps: {
|
|
511
|
+
user_type: ["buyer"],
|
|
512
|
+
segment: ["window_shopper"],
|
|
513
|
+
seller_rating: u.weighNumRange(0, 5, 0.5),
|
|
514
|
+
total_transactions: [0],
|
|
515
|
+
response_time_hours: u.weighNumRange(0, 48, 0.3),
|
|
516
|
+
store_name: ["none"],
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
// ── Personas ──────────────────────────────────
|
|
520
|
+
personas: [
|
|
521
|
+
{
|
|
522
|
+
name: "power_seller",
|
|
523
|
+
weight: 8,
|
|
524
|
+
eventMultiplier: 5.0,
|
|
525
|
+
conversionModifier: 1.8,
|
|
526
|
+
churnRate: 0.01,
|
|
527
|
+
properties: {
|
|
528
|
+
user_type: "seller",
|
|
529
|
+
segment: "power_seller",
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
name: "casual_seller",
|
|
534
|
+
weight: 20,
|
|
535
|
+
eventMultiplier: 1.5,
|
|
536
|
+
conversionModifier: 1.0,
|
|
537
|
+
churnRate: 0.08,
|
|
538
|
+
properties: {
|
|
539
|
+
user_type: "seller",
|
|
540
|
+
segment: "casual_seller",
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: "frequent_buyer",
|
|
545
|
+
weight: 25,
|
|
546
|
+
eventMultiplier: 2.0,
|
|
547
|
+
conversionModifier: 1.3,
|
|
548
|
+
churnRate: 0.03,
|
|
549
|
+
properties: {
|
|
550
|
+
user_type: "buyer",
|
|
551
|
+
segment: "frequent_buyer",
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: "window_shopper",
|
|
556
|
+
weight: 35,
|
|
557
|
+
eventMultiplier: 0.5,
|
|
558
|
+
conversionModifier: 0.4,
|
|
559
|
+
churnRate: 0.15,
|
|
560
|
+
properties: {
|
|
561
|
+
user_type: "buyer",
|
|
562
|
+
segment: "window_shopper",
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: "new_seller",
|
|
567
|
+
weight: 12,
|
|
568
|
+
eventMultiplier: 0.8,
|
|
569
|
+
conversionModifier: 0.5,
|
|
570
|
+
churnRate: 0.3,
|
|
571
|
+
properties: {
|
|
572
|
+
user_type: "seller",
|
|
573
|
+
segment: "new_seller",
|
|
574
|
+
},
|
|
575
|
+
activeWindow: { maxDays: 28 },
|
|
576
|
+
},
|
|
577
|
+
],
|
|
578
|
+
|
|
579
|
+
// ── World Events ──────────────────────────────
|
|
580
|
+
worldEvents: [
|
|
581
|
+
{
|
|
582
|
+
name: "marketplace_fee_change",
|
|
583
|
+
type: "product_launch",
|
|
584
|
+
startDay: 45,
|
|
585
|
+
duration: null,
|
|
586
|
+
injectProps: { fee_change: "increased" },
|
|
587
|
+
affectsEvents: ["listing created"],
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
name: "viral_tiktok_moment",
|
|
591
|
+
type: "campaign",
|
|
592
|
+
startDay: 55,
|
|
593
|
+
duration: 3,
|
|
594
|
+
volumeMultiplier: 3.0,
|
|
595
|
+
affectsEvents: ["item searched", "item viewed", "purchase completed"],
|
|
596
|
+
},
|
|
597
|
+
],
|
|
598
|
+
|
|
599
|
+
// ── Engagement Decay ──────────────────────────
|
|
600
|
+
engagementDecay: {
|
|
601
|
+
model: "exponential",
|
|
602
|
+
halfLife: 75,
|
|
603
|
+
floor: 0.1,
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
// ── Attribution ──────────────────────────────
|
|
607
|
+
attribution: {
|
|
608
|
+
model: "last_touch",
|
|
609
|
+
window: 7,
|
|
610
|
+
campaigns: [
|
|
611
|
+
{
|
|
612
|
+
name: "google_shopping",
|
|
613
|
+
source: "google",
|
|
614
|
+
medium: "cpc",
|
|
615
|
+
activeDays: [0, 100],
|
|
616
|
+
dailyBudget: [200, 800],
|
|
617
|
+
acquisitionRate: 0.03,
|
|
618
|
+
userPersonaBias: { frequent_buyer: 0.5 },
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
name: "tiktok",
|
|
622
|
+
source: "tiktok",
|
|
623
|
+
medium: "social",
|
|
624
|
+
activeDays: [10, 90],
|
|
625
|
+
dailyBudget: [100, 400],
|
|
626
|
+
acquisitionRate: 0.02,
|
|
627
|
+
userPersonaBias: { window_shopper: 0.5 },
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
name: "seller_referral",
|
|
631
|
+
source: "referral",
|
|
632
|
+
medium: "referral",
|
|
633
|
+
activeDays: [0, 100],
|
|
634
|
+
dailyBudget: [50, 200],
|
|
635
|
+
acquisitionRate: 0.02,
|
|
636
|
+
userPersonaBias: { power_seller: 0.4 },
|
|
637
|
+
},
|
|
638
|
+
],
|
|
639
|
+
organicRate: 0.30,
|
|
640
|
+
},
|
|
641
|
+
|
|
642
|
+
// ── Geo ──────────────────────────────────────
|
|
643
|
+
geo: {
|
|
644
|
+
sticky: true,
|
|
645
|
+
regions: [
|
|
646
|
+
{
|
|
647
|
+
name: "us",
|
|
648
|
+
countries: ["US"],
|
|
649
|
+
weight: 50,
|
|
650
|
+
timezoneOffset: -5,
|
|
651
|
+
properties: { currency: "USD", locale: "en-US" },
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: "uk",
|
|
655
|
+
countries: ["GB"],
|
|
656
|
+
weight: 20,
|
|
657
|
+
timezoneOffset: 0,
|
|
658
|
+
properties: { currency: "GBP", locale: "en-GB" },
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
name: "australia",
|
|
662
|
+
countries: ["AU"],
|
|
663
|
+
weight: 15,
|
|
664
|
+
timezoneOffset: 10,
|
|
665
|
+
properties: { currency: "AUD", locale: "en-AU" },
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
name: "canada",
|
|
669
|
+
countries: ["CA"],
|
|
670
|
+
weight: 15,
|
|
671
|
+
timezoneOffset: -5,
|
|
672
|
+
properties: { currency: "CAD", locale: "en-CA" },
|
|
673
|
+
},
|
|
674
|
+
],
|
|
675
|
+
},
|
|
676
|
+
|
|
677
|
+
// ── Anomalies ──────────────────────────────────
|
|
678
|
+
anomalies: [
|
|
679
|
+
{
|
|
680
|
+
type: "extreme_value",
|
|
681
|
+
event: "purchase completed",
|
|
682
|
+
property: "total_amount",
|
|
683
|
+
frequency: 0.002,
|
|
684
|
+
multiplier: 30,
|
|
685
|
+
tag: "whale_purchase",
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
type: "coordinated",
|
|
689
|
+
event: "account created",
|
|
690
|
+
day: 68,
|
|
691
|
+
window: 0.02,
|
|
692
|
+
count: 200,
|
|
693
|
+
tag: "viral_signup",
|
|
694
|
+
},
|
|
695
|
+
],
|
|
696
|
+
|
|
697
|
+
// ── Hook Function ──────────────────────────────────────
|
|
698
|
+
hook: function (record, type, meta) {
|
|
699
|
+
// ── HOOK 7: POWER SELLER PROFILES (user) ─────────────
|
|
700
|
+
// Power sellers get high total_transactions, top seller_rating,
|
|
701
|
+
// and a realistic store name. Casual sellers get mid-range stats.
|
|
702
|
+
if (type === "user") {
|
|
703
|
+
if (record.segment === "power_seller") {
|
|
704
|
+
record.total_transactions = chance.integer({ min: 100, max: 500 });
|
|
705
|
+
record.seller_rating = chance.floating({ min: 4.5, max: 5.0, fixed: 1 });
|
|
706
|
+
record.store_name = chance.pickone([
|
|
707
|
+
"TechVault Pro", "StyleHaven", "GearFactory", "PrimeFinds",
|
|
708
|
+
"TopShelf Goods", "EliteDeals", "QualityFirst Store", "BestBuy Resale",
|
|
709
|
+
]);
|
|
710
|
+
} else if (record.segment === "casual_seller") {
|
|
711
|
+
record.total_transactions = chance.integer({ min: 5, max: 50 });
|
|
712
|
+
record.seller_rating = chance.floating({ min: 3.0, max: 4.5, fixed: 1 });
|
|
713
|
+
record.store_name = chance.pickone([
|
|
714
|
+
"My Garage Sale", "Closet Cleanout", "Random Finds", "Weekend Seller",
|
|
715
|
+
]);
|
|
716
|
+
} else if (record.segment === "new_seller") {
|
|
717
|
+
record.total_transactions = chance.integer({ min: 0, max: 3 });
|
|
718
|
+
record.seller_rating = 0;
|
|
719
|
+
record.store_name = "New Store";
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// ── HOOK 8: FREQUENT BUYER FUNNEL LIFT (funnel-pre) ──
|
|
724
|
+
// Frequent buyers convert 1.3x better through browse-to-purchase.
|
|
725
|
+
if (type === "funnel-pre") {
|
|
726
|
+
if (meta && meta.profile) {
|
|
727
|
+
if (meta.profile.segment === "frequent_buyer") {
|
|
728
|
+
record.conversionRate = Math.min(record.conversionRate * 1.3, 90);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// ── HOOK 1: FEE CHANGE IMPACT (event) ────────────────
|
|
734
|
+
// Listings after day 45 get 1.3x higher listing_fee.
|
|
735
|
+
if (type === "event") {
|
|
736
|
+
const FEE_CHANGE_DAY = DATASET_START.add(45, "days");
|
|
737
|
+
if (record.event === "listing created") {
|
|
738
|
+
const eventTime = dayjs(record.time);
|
|
739
|
+
if (eventTime.isAfter(FEE_CHANGE_DAY)) {
|
|
740
|
+
record.listing_fee = Math.floor((record.listing_fee || 15) * 1.3);
|
|
741
|
+
record.fee_change = "increased";
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// ── HOOK 2: WEEKEND SHOPPING SURGE (event) ───────
|
|
746
|
+
// Purchases on Sat/Sun get 1.2x total_amount.
|
|
747
|
+
if (record.event === "purchase completed") {
|
|
748
|
+
const dayOfWeek = dayjs(record.time).day();
|
|
749
|
+
// Saturday=6, Sunday=0
|
|
750
|
+
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
|
751
|
+
record.total_amount = Math.floor((record.total_amount || 60) * 1.2);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// ── EVERYTHING HOOKS ─────────────────────────────────
|
|
757
|
+
if (type === "everything") {
|
|
758
|
+
const events = record;
|
|
759
|
+
if (!events.length) return record;
|
|
760
|
+
|
|
761
|
+
const profile = meta.profile;
|
|
762
|
+
|
|
763
|
+
// ── HOOK 3: SELLER SUCCESS → BUYER TRUST ─────────
|
|
764
|
+
// Power sellers get 2x purchase events (cloned from existing).
|
|
765
|
+
if (profile && profile.segment === "power_seller") {
|
|
766
|
+
const purchases = events.filter(e => e.event === "purchase completed");
|
|
767
|
+
const templatePurchase = purchases[0];
|
|
768
|
+
if (templatePurchase && purchases.length > 0) {
|
|
769
|
+
purchases.forEach(p => {
|
|
770
|
+
if (chance.bool({ likelihood: 65 })) {
|
|
771
|
+
events.push({
|
|
772
|
+
...templatePurchase,
|
|
773
|
+
time: dayjs(p.time).add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
|
|
774
|
+
user_id: p.user_id,
|
|
775
|
+
total_amount: chance.integer({ min: 15, max: 400 }),
|
|
776
|
+
item_count: chance.integer({ min: 1, max: 4 }),
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// ── HOOK 4: SEARCH-TO-PURCHASE BY CATEGORY ───────
|
|
784
|
+
// Electronics category gets cloned purchase events (higher conversion).
|
|
785
|
+
const hasElectronicsSearch = events.some(e =>
|
|
786
|
+
e.event === "item searched" && e.category === "electronics"
|
|
787
|
+
);
|
|
788
|
+
if (hasElectronicsSearch) {
|
|
789
|
+
const templatePurchase = events.find(e => e.event === "purchase completed");
|
|
790
|
+
if (templatePurchase) {
|
|
791
|
+
const electronicsSearches = events.filter(e =>
|
|
792
|
+
e.event === "item searched" && e.category === "electronics"
|
|
793
|
+
);
|
|
794
|
+
electronicsSearches.slice(0, 3).forEach(search => {
|
|
795
|
+
if (chance.bool({ likelihood: 40 })) {
|
|
796
|
+
events.push({
|
|
797
|
+
...templatePurchase,
|
|
798
|
+
time: dayjs(search.time).add(chance.integer({ min: 1, max: 12 }), "hours").toISOString(),
|
|
799
|
+
user_id: search.user_id,
|
|
800
|
+
total_amount: chance.integer({ min: 50, max: 300 }),
|
|
801
|
+
category: "electronics",
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// ── HOOK 5: RESPONSE TIME → CONVERSION ───────────
|
|
809
|
+
// Sellers with avg response_time < 2 hours get more offer_accepted.
|
|
810
|
+
const messages = events.filter(e => e.event === "message sent" && e.response_time_hours);
|
|
811
|
+
if (messages.length > 0) {
|
|
812
|
+
const avgResponseTime = messages.reduce((sum, m) => sum + m.response_time_hours, 0) / messages.length;
|
|
813
|
+
if (avgResponseTime < 2) {
|
|
814
|
+
const templateOffer = events.find(e => e.event === "offer accepted");
|
|
815
|
+
if (templateOffer) {
|
|
816
|
+
const offers = events.filter(e => e.event === "offer received");
|
|
817
|
+
offers.forEach(offer => {
|
|
818
|
+
if (chance.bool({ likelihood: 50 })) {
|
|
819
|
+
events.push({
|
|
820
|
+
...templateOffer,
|
|
821
|
+
time: dayjs(offer.time).add(chance.integer({ min: 1, max: 4 }), "hours").toISOString(),
|
|
822
|
+
user_id: offer.user_id,
|
|
823
|
+
response_time_hours: chance.floating({ min: 0.1, max: 2, fixed: 1 }),
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// ── HOOK 6: NEW SELLER CHURN ─────────────────────
|
|
832
|
+
// New sellers with <10 events lose 40% of events after day 14.
|
|
833
|
+
if (profile && profile.segment === "new_seller" && events.length < 10) {
|
|
834
|
+
const DAY_14 = DATASET_START.add(14, "days");
|
|
835
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
836
|
+
const eventTime = dayjs(events[i].time);
|
|
837
|
+
if (eventTime.isAfter(DAY_14) && chance.bool({ likelihood: 40 })) {
|
|
838
|
+
events.splice(i, 1);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
return record;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return record;
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
export default config;
|