@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,799 @@
|
|
|
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-logistics";
|
|
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 warehouse/supplier IDs at module level
|
|
18
|
+
const warehouseIds = v.range(1, 80).map(() => `WH_${v.uid(6)}`);
|
|
19
|
+
const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* ===================================================================
|
|
23
|
+
* DATASET OVERVIEW
|
|
24
|
+
* ===================================================================
|
|
25
|
+
*
|
|
26
|
+
* SupplyStack -- a B2B warehouse/inventory management SaaS for
|
|
27
|
+
* businesses to track stock levels, purchase orders, suppliers,
|
|
28
|
+
* shipments, and quality inspections across multiple warehouses.
|
|
29
|
+
*
|
|
30
|
+
* - 5,000 users over 100 days, ~600K events
|
|
31
|
+
* - Multi-tier system: enterprise (15%), mid-market (35%), small business (40%), trial (10%)
|
|
32
|
+
* - Core loop: sign up -> check inventory -> create PO -> receive order -> track shipment
|
|
33
|
+
* - Revenue: free_trial / starter ($49) / professional ($199) / enterprise ($599)
|
|
34
|
+
*
|
|
35
|
+
* Advanced Features:
|
|
36
|
+
* - Personas: 4 archetypes (enterprise_ops, mid_market, small_business, trial_explorer)
|
|
37
|
+
* - World Events: supply_chain_disruption (day 35), holiday_surge (day 70)
|
|
38
|
+
* - Subscription: 4-tier revenue lifecycle with 30-day trial
|
|
39
|
+
* - Geo: US/EU/APAC with currency and regional properties
|
|
40
|
+
* - Features: predictive_reorder (day 40) and supplier_portal (day 65)
|
|
41
|
+
* - Anomalies: extreme stockout spikes, coordinated holiday prep burst
|
|
42
|
+
*
|
|
43
|
+
* Key entities:
|
|
44
|
+
* - warehouse_id: specific warehouse location
|
|
45
|
+
* - supplier_id: vendor supplying goods
|
|
46
|
+
* - sku_category: product category being managed
|
|
47
|
+
* - priority: urgency level for orders and alerts
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* ===================================================================
|
|
52
|
+
* ANALYTICS HOOKS (8 hooks)
|
|
53
|
+
* ===================================================================
|
|
54
|
+
*
|
|
55
|
+
* -------------------------------------------------------------------
|
|
56
|
+
* 1. MONTH-END REPORTING SURGE (event hook)
|
|
57
|
+
* -------------------------------------------------------------------
|
|
58
|
+
*
|
|
59
|
+
* PATTERN: Reports generated on calendar days 28-31 have 2x the
|
|
60
|
+
* report_pages. Simulates end-of-month compliance and audit reporting.
|
|
61
|
+
*
|
|
62
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
63
|
+
*
|
|
64
|
+
* Report 1: Report Volume by Day of Month
|
|
65
|
+
* - Report type: Insights
|
|
66
|
+
* - Event: "report generated"
|
|
67
|
+
* - Measure: Average of "report_pages"
|
|
68
|
+
* - Breakdown: Day of month (custom formula or daily chart)
|
|
69
|
+
* - Expected: Days 28-31 should show ~2x avg report_pages vs mid-month
|
|
70
|
+
* (month-end ~40 pages, mid-month ~20 pages)
|
|
71
|
+
*
|
|
72
|
+
* REAL-WORLD ANALOGUE: Warehouse managers generate larger, more
|
|
73
|
+
* comprehensive reports at month-end for audits and compliance.
|
|
74
|
+
*
|
|
75
|
+
* -------------------------------------------------------------------
|
|
76
|
+
* 2. RUSH ORDER PREMIUM (event hook)
|
|
77
|
+
* -------------------------------------------------------------------
|
|
78
|
+
*
|
|
79
|
+
* PATTERN: Purchase orders with priority "urgent" get 1.5x unit_cost.
|
|
80
|
+
* Simulates expedited shipping and rush fulfillment surcharges.
|
|
81
|
+
*
|
|
82
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
83
|
+
*
|
|
84
|
+
* Report 1: Unit Cost by Priority
|
|
85
|
+
* - Report type: Insights
|
|
86
|
+
* - Event: "purchase order created"
|
|
87
|
+
* - Measure: Average of "unit_cost"
|
|
88
|
+
* - Breakdown: "priority"
|
|
89
|
+
* - Expected: urgent ~$75 vs standard ~$50 (1.5x ratio)
|
|
90
|
+
*
|
|
91
|
+
* REAL-WORLD ANALOGUE: Rush orders from suppliers incur premium
|
|
92
|
+
* pricing for expedited manufacturing and express shipping.
|
|
93
|
+
*
|
|
94
|
+
* -------------------------------------------------------------------
|
|
95
|
+
* 3. REORDER ACCURACY BY TIER (everything hook)
|
|
96
|
+
* -------------------------------------------------------------------
|
|
97
|
+
*
|
|
98
|
+
* PATTERN: Enterprise users have 0.9x stockout rate -- they get 10%
|
|
99
|
+
* of their "stockout alert" events removed. Better forecasting and
|
|
100
|
+
* larger safety stock reduces out-of-stock incidents.
|
|
101
|
+
*
|
|
102
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
103
|
+
*
|
|
104
|
+
* Report 1: Stockout Rate by Company Tier
|
|
105
|
+
* - Report type: Insights
|
|
106
|
+
* - Event: "stockout alert"
|
|
107
|
+
* - Measure: Total per user
|
|
108
|
+
* - Breakdown: user property "company_tier"
|
|
109
|
+
* - Expected: enterprise ~10% fewer stockout alerts per user than
|
|
110
|
+
* mid_market or small_business
|
|
111
|
+
*
|
|
112
|
+
* Report 2: Stockout-to-Order Ratio
|
|
113
|
+
* - Report type: Insights (formula)
|
|
114
|
+
* - A = "stockout alert" total, B = "purchase order created" total
|
|
115
|
+
* - Formula: A / B
|
|
116
|
+
* - Breakdown: user property "company_tier"
|
|
117
|
+
* - Expected: enterprise ratio ~0.9x vs small_business baseline
|
|
118
|
+
*
|
|
119
|
+
* REAL-WORLD ANALOGUE: Enterprise operations have dedicated
|
|
120
|
+
* procurement teams and predictive analytics reducing stockouts.
|
|
121
|
+
*
|
|
122
|
+
* -------------------------------------------------------------------
|
|
123
|
+
* 4. INTEGRATION COMPLETION DRIVES RETENTION (everything hook)
|
|
124
|
+
* -------------------------------------------------------------------
|
|
125
|
+
*
|
|
126
|
+
* PATTERN: Users with 3+ "integration connected" events get cloned
|
|
127
|
+
* "report generated" events. Connected integrations produce richer
|
|
128
|
+
* reporting and deeper platform engagement.
|
|
129
|
+
*
|
|
130
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
131
|
+
*
|
|
132
|
+
* Report 1: Report Volume by Integration Count
|
|
133
|
+
* - Report type: Insights
|
|
134
|
+
* - Event: "report generated"
|
|
135
|
+
* - Measure: Total per user
|
|
136
|
+
* - Breakdown: user property "integration_count" (or filter by
|
|
137
|
+
* users who did "integration connected" 3+ times)
|
|
138
|
+
* - Expected: Users with 3+ integrations ~2x report events
|
|
139
|
+
*
|
|
140
|
+
* REAL-WORLD ANALOGUE: Businesses that integrate their ERP, shipping,
|
|
141
|
+
* and accounting systems generate more automated reports, increasing
|
|
142
|
+
* stickiness and reducing churn.
|
|
143
|
+
*
|
|
144
|
+
* -------------------------------------------------------------------
|
|
145
|
+
* 5. ALERT FATIGUE (everything hook)
|
|
146
|
+
* -------------------------------------------------------------------
|
|
147
|
+
*
|
|
148
|
+
* PATTERN: Users with >30 "stockout alert" events get increasing
|
|
149
|
+
* response_time_hours on their later alerts. The 20th+ alert has
|
|
150
|
+
* response_time scaled up by 1.5-3x based on position.
|
|
151
|
+
*
|
|
152
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
153
|
+
*
|
|
154
|
+
* Report 1: Alert Response Time Over Time
|
|
155
|
+
* - Report type: Insights
|
|
156
|
+
* - Event: "stockout alert"
|
|
157
|
+
* - Measure: Average of "response_time_hours"
|
|
158
|
+
* - Line chart by week
|
|
159
|
+
* - Filter: users with high alert volume
|
|
160
|
+
* - Expected: Response time trends upward over time for heavy-alert users
|
|
161
|
+
* (early alerts ~4h, later alerts ~8-12h)
|
|
162
|
+
*
|
|
163
|
+
* REAL-WORLD ANALOGUE: Alert fatigue is a real operational problem --
|
|
164
|
+
* too many alerts desensitize warehouse managers, slowing response.
|
|
165
|
+
*
|
|
166
|
+
* -------------------------------------------------------------------
|
|
167
|
+
* 6. TRIAL CHURN (everything hook)
|
|
168
|
+
* -------------------------------------------------------------------
|
|
169
|
+
*
|
|
170
|
+
* PATTERN: Users with <10 total events lose 50% of their events after
|
|
171
|
+
* day 7 of the dataset. Simulates trial users who briefly explore then
|
|
172
|
+
* abandon the platform.
|
|
173
|
+
*
|
|
174
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
175
|
+
*
|
|
176
|
+
* Report 1: Retention by Event Volume
|
|
177
|
+
* - Report type: Retention
|
|
178
|
+
* - Starting event: "account created"
|
|
179
|
+
* - Return event: Any event
|
|
180
|
+
* - Breakdown: user property "company_tier"
|
|
181
|
+
* - Expected: trial users show sharp drop after week 1 (~50% drop)
|
|
182
|
+
*
|
|
183
|
+
* Report 2: Event Volume Distribution
|
|
184
|
+
* - Report type: Insights
|
|
185
|
+
* - Event: All events
|
|
186
|
+
* - Measure: Total per user
|
|
187
|
+
* - Breakdown: user property "company_tier"
|
|
188
|
+
* - Expected: trial users cluster at <5 events
|
|
189
|
+
*
|
|
190
|
+
* REAL-WORLD ANALOGUE: SaaS trial users who don't activate within
|
|
191
|
+
* the first week rarely convert to paying customers.
|
|
192
|
+
*
|
|
193
|
+
* -------------------------------------------------------------------
|
|
194
|
+
* 7. ENTERPRISE PROFILES (user hook)
|
|
195
|
+
* -------------------------------------------------------------------
|
|
196
|
+
*
|
|
197
|
+
* PATTERN: Users with company_tier "enterprise" get warehouse_count
|
|
198
|
+
* boosted to 5-15 and employee_count to 200-2000. Enterprise ops
|
|
199
|
+
* manage significantly more infrastructure.
|
|
200
|
+
*
|
|
201
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
202
|
+
*
|
|
203
|
+
* Report 1: Warehouse Count by Tier
|
|
204
|
+
* - Report type: Insights
|
|
205
|
+
* - Measure: Profiles -> Average of "warehouse_count"
|
|
206
|
+
* - Breakdown: "company_tier"
|
|
207
|
+
* - Expected: enterprise ~10 warehouses vs small_business ~2
|
|
208
|
+
*
|
|
209
|
+
* Report 2: Employee Count Distribution
|
|
210
|
+
* - Report type: Insights
|
|
211
|
+
* - Measure: Profiles -> Average of "employee_count"
|
|
212
|
+
* - Breakdown: "company_tier"
|
|
213
|
+
* - Expected: enterprise ~1100 vs small_business ~50
|
|
214
|
+
*
|
|
215
|
+
* REAL-WORLD ANALOGUE: Enterprise customers operate multi-warehouse
|
|
216
|
+
* networks with large logistics teams, driving higher ACV.
|
|
217
|
+
*
|
|
218
|
+
* -------------------------------------------------------------------
|
|
219
|
+
* 8. ENTERPRISE INTEGRATION FUNNEL LIFT (funnel-pre hook)
|
|
220
|
+
* -------------------------------------------------------------------
|
|
221
|
+
*
|
|
222
|
+
* PATTERN: Users with company_tier "enterprise" convert 1.5x better
|
|
223
|
+
* through the Integration Setup funnel. Enterprises have dedicated
|
|
224
|
+
* IT teams and structured onboarding processes.
|
|
225
|
+
*
|
|
226
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
227
|
+
*
|
|
228
|
+
* Report 1: Integration Funnel by Tier
|
|
229
|
+
* - Report type: Funnels
|
|
230
|
+
* - Steps: "integration connected" -> "report generated" -> "alert configured"
|
|
231
|
+
* - Breakdown: user property "company_tier"
|
|
232
|
+
* - Expected: enterprise ~60% vs small_business ~30% conversion
|
|
233
|
+
*
|
|
234
|
+
* REAL-WORLD ANALOGUE: Enterprise IT teams have dedicated resources
|
|
235
|
+
* for integrations, leading to faster and more complete setup.
|
|
236
|
+
*
|
|
237
|
+
* ===================================================================
|
|
238
|
+
* EXPECTED METRICS SUMMARY
|
|
239
|
+
* ===================================================================
|
|
240
|
+
*
|
|
241
|
+
* Hook | Metric | Baseline | Effect | Ratio
|
|
242
|
+
* ----------------------------|---------------------|----------|---------|------
|
|
243
|
+
* Month-End Reporting | report_pages | 20 | 40 | 2x
|
|
244
|
+
* Rush Order Premium | unit_cost | $50 | $75 | 1.5x
|
|
245
|
+
* Reorder Accuracy by Tier | stockout alerts/user| 5 | 4.5 | 0.9x
|
|
246
|
+
* Integration Retention | reports/user | 3 | 6 | 2x
|
|
247
|
+
* Alert Fatigue | response_time_hours | 4h | 8-12h | 2-3x
|
|
248
|
+
* Trial Churn | events after wk 1 | 5 | 2.5 | 0.5x
|
|
249
|
+
* Enterprise Profiles | warehouse_count | 3 | 10 | 3.3x
|
|
250
|
+
* Enterprise Integration Lift | funnel conversion | 30% | 45% | 1.5x
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
/** @type {Config} */
|
|
254
|
+
const config = {
|
|
255
|
+
token: "",
|
|
256
|
+
seed: SEED,
|
|
257
|
+
numDays: days,
|
|
258
|
+
numEvents: num_users * 120,
|
|
259
|
+
numUsers: num_users,
|
|
260
|
+
hasAnonIds: false,
|
|
261
|
+
hasSessionIds: true,
|
|
262
|
+
format: "json",
|
|
263
|
+
gzip: true,
|
|
264
|
+
alsoInferFunnels: false,
|
|
265
|
+
hasLocation: true,
|
|
266
|
+
hasAndroidDevices: false,
|
|
267
|
+
hasIOSDevices: false,
|
|
268
|
+
hasDesktopDevices: true,
|
|
269
|
+
hasBrowser: true,
|
|
270
|
+
hasCampaigns: false,
|
|
271
|
+
isAnonymous: false,
|
|
272
|
+
hasAdSpend: false,
|
|
273
|
+
percentUsersBornInDataset: 35,
|
|
274
|
+
hasAvatar: true,
|
|
275
|
+
concurrency: 1,
|
|
276
|
+
writeToDisk: false,
|
|
277
|
+
scdProps: {
|
|
278
|
+
company_tier: {
|
|
279
|
+
values: ["trial", "starter", "professional", "enterprise"],
|
|
280
|
+
frequency: "month",
|
|
281
|
+
timing: "fuzzy",
|
|
282
|
+
max: 6
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
mirrorProps: {},
|
|
286
|
+
lookupTables: [],
|
|
287
|
+
|
|
288
|
+
// -- Events (17) --------------------------------------------------
|
|
289
|
+
events: [
|
|
290
|
+
{
|
|
291
|
+
event: "account created",
|
|
292
|
+
weight: 1,
|
|
293
|
+
isFirstEvent: true,
|
|
294
|
+
properties: {
|
|
295
|
+
referral_source: ["organic", "partner_referral", "google_search", "trade_show", "linkedin"],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
event: "inventory checked",
|
|
300
|
+
weight: 8,
|
|
301
|
+
properties: {
|
|
302
|
+
warehouse_id: chance.pickone.bind(chance, warehouseIds),
|
|
303
|
+
sku_category: ["electronics", "apparel", "food_beverage", "pharma", "raw_materials", "packaging", "automotive_parts"],
|
|
304
|
+
stock_level: u.weighNumRange(0, 5000, 0.4, 500),
|
|
305
|
+
reorder_point: u.weighNumRange(10, 500, 0.5, 100),
|
|
306
|
+
reorder_method: ["manual"],
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
event: "stockout alert",
|
|
311
|
+
weight: 5,
|
|
312
|
+
properties: {
|
|
313
|
+
warehouse_id: chance.pickone.bind(chance, warehouseIds),
|
|
314
|
+
sku_category: ["electronics", "apparel", "food_beverage", "pharma", "raw_materials", "packaging"],
|
|
315
|
+
items_affected: u.weighNumRange(1, 50, 0.4, 5),
|
|
316
|
+
severity: ["low", "medium", "medium", "high", "critical"],
|
|
317
|
+
response_time_hours: u.weighNumRange(0.5, 48, 0.3, 4),
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
event: "purchase order created",
|
|
322
|
+
weight: 5,
|
|
323
|
+
properties: {
|
|
324
|
+
supplier_id: chance.pickone.bind(chance, supplierIds),
|
|
325
|
+
sku_category: ["electronics", "apparel", "food_beverage", "pharma", "raw_materials", "packaging", "automotive_parts"],
|
|
326
|
+
quantity: u.weighNumRange(10, 5000, 0.3, 200),
|
|
327
|
+
unit_cost: u.weighNumRange(5, 200, 0.4, 50),
|
|
328
|
+
priority: ["standard", "standard", "standard", "expedited", "urgent"],
|
|
329
|
+
estimated_delivery_days: u.weighNumRange(1, 30, 0.4, 7),
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
event: "order received",
|
|
334
|
+
weight: 4,
|
|
335
|
+
properties: {
|
|
336
|
+
warehouse_id: chance.pickone.bind(chance, warehouseIds),
|
|
337
|
+
supplier_id: chance.pickone.bind(chance, supplierIds),
|
|
338
|
+
quantity_received: u.weighNumRange(10, 5000, 0.3, 200),
|
|
339
|
+
quantity_damaged: u.weighNumRange(0, 20, 0.2),
|
|
340
|
+
receiving_time_hours: u.weighNumRange(0.5, 8, 0.5, 2),
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
event: "shipment tracked",
|
|
345
|
+
weight: 6,
|
|
346
|
+
properties: {
|
|
347
|
+
carrier: ["fedex", "ups", "dhl", "usps", "freight_line", "regional_courier"],
|
|
348
|
+
shipment_status: ["in_transit", "in_transit", "in_transit", "delivered", "delayed", "exception"],
|
|
349
|
+
origin_warehouse: chance.pickone.bind(chance, warehouseIds),
|
|
350
|
+
estimated_days: u.weighNumRange(1, 14, 0.4, 3),
|
|
351
|
+
weight_kg: u.weighNumRange(1, 2000, 0.3, 50),
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
event: "supplier contacted",
|
|
356
|
+
weight: 3,
|
|
357
|
+
properties: {
|
|
358
|
+
supplier_id: chance.pickone.bind(chance, supplierIds),
|
|
359
|
+
contact_reason: ["price_negotiation", "order_status", "quality_issue", "new_product", "contract_renewal", "returns"],
|
|
360
|
+
contact_method: ["email", "email", "phone", "portal"],
|
|
361
|
+
supplier_access: ["email_only"],
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
event: "integration connected",
|
|
366
|
+
weight: 2,
|
|
367
|
+
properties: {
|
|
368
|
+
integration_type: ["erp", "accounting", "shipping", "ecommerce", "crm", "bi_tool"],
|
|
369
|
+
integration_name: ["SAP", "QuickBooks", "ShipStation", "Shopify", "Salesforce", "NetSuite", "Xero"],
|
|
370
|
+
setup_time_minutes: u.weighNumRange(5, 120, 0.4, 30),
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
event: "report generated",
|
|
375
|
+
weight: 4,
|
|
376
|
+
properties: {
|
|
377
|
+
report_type: ["inventory_summary", "order_history", "supplier_performance", "cost_analysis", "forecast", "compliance"],
|
|
378
|
+
report_pages: u.weighNumRange(1, 50, 0.4, 20),
|
|
379
|
+
export_format: ["pdf", "csv", "xlsx"],
|
|
380
|
+
time_range_days: [7, 14, 30, 30, 90],
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
event: "alert configured",
|
|
385
|
+
weight: 2,
|
|
386
|
+
properties: {
|
|
387
|
+
alert_type: ["low_stock", "delivery_delay", "price_change", "quality_threshold", "expiry_warning"],
|
|
388
|
+
threshold_value: u.weighNumRange(1, 500, 0.4, 50),
|
|
389
|
+
notification_channel: ["email", "email", "sms", "push", "slack"],
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
event: "warehouse transfer",
|
|
394
|
+
weight: 3,
|
|
395
|
+
properties: {
|
|
396
|
+
from_warehouse: chance.pickone.bind(chance, warehouseIds),
|
|
397
|
+
to_warehouse: chance.pickone.bind(chance, warehouseIds),
|
|
398
|
+
sku_category: ["electronics", "apparel", "food_beverage", "pharma", "raw_materials"],
|
|
399
|
+
transfer_quantity: u.weighNumRange(10, 1000, 0.4, 100),
|
|
400
|
+
transfer_reason: ["rebalance", "demand_shift", "consolidation", "overflow", "seasonal"],
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
event: "quality inspection",
|
|
405
|
+
weight: 2,
|
|
406
|
+
properties: {
|
|
407
|
+
warehouse_id: chance.pickone.bind(chance, warehouseIds),
|
|
408
|
+
inspection_result: ["pass", "pass", "pass", "pass", "minor_issue", "major_issue", "fail"],
|
|
409
|
+
items_inspected: u.weighNumRange(10, 500, 0.4, 50),
|
|
410
|
+
defect_rate_pct: u.weighNumRange(0, 15, 0.3, 2),
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
event: "invoice processed",
|
|
415
|
+
weight: 3,
|
|
416
|
+
properties: {
|
|
417
|
+
supplier_id: chance.pickone.bind(chance, supplierIds),
|
|
418
|
+
invoice_amount: u.weighNumRange(100, 50000, 0.3, 2500),
|
|
419
|
+
payment_terms: ["net_30", "net_30", "net_60", "net_15", "on_receipt"],
|
|
420
|
+
payment_status: ["paid", "paid", "paid", "pending", "overdue"],
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
event: "notification received",
|
|
425
|
+
weight: 6,
|
|
426
|
+
properties: {
|
|
427
|
+
notification_type: ["low_stock_alert", "low_stock_alert", "delivery_update", "order_confirmation", "invoice_due", "system_update"],
|
|
428
|
+
channel: ["email", "email", "push", "sms"],
|
|
429
|
+
opened: [true, true, true, false],
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
event: "support ticket",
|
|
434
|
+
weight: 1,
|
|
435
|
+
properties: {
|
|
436
|
+
category: ["billing", "technical", "integration", "data_import", "feature_request", "training"],
|
|
437
|
+
priority: ["low", "low", "medium", "medium", "high"],
|
|
438
|
+
resolution_hours: u.weighNumRange(1, 72, 0.4, 12),
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
event: "app session",
|
|
443
|
+
weight: 7,
|
|
444
|
+
properties: {
|
|
445
|
+
session_duration_sec: u.weighNumRange(30, 3600, 0.4, 300),
|
|
446
|
+
pages_viewed: u.weighNumRange(1, 20, 0.5, 5),
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
event: "account deactivated",
|
|
451
|
+
weight: 1,
|
|
452
|
+
isChurnEvent: true,
|
|
453
|
+
returnLikelihood: 0.10,
|
|
454
|
+
isStrictEvent: true,
|
|
455
|
+
properties: {
|
|
456
|
+
reason: ["too_expensive", "switched_competitor", "business_closed", "missing_features", "poor_support"],
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
],
|
|
460
|
+
|
|
461
|
+
// -- Funnels (5) --------------------------------------------------
|
|
462
|
+
funnels: [
|
|
463
|
+
{
|
|
464
|
+
name: "Onboarding",
|
|
465
|
+
sequence: ["account created", "inventory checked", "integration connected", "report generated"],
|
|
466
|
+
conversionRate: 40,
|
|
467
|
+
order: "sequential",
|
|
468
|
+
isFirstFunnel: true,
|
|
469
|
+
timeToConvert: 72,
|
|
470
|
+
weight: 3,
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "Order Fulfillment",
|
|
474
|
+
sequence: ["inventory checked", "purchase order created", "order received", "shipment tracked"],
|
|
475
|
+
conversionRate: 35,
|
|
476
|
+
order: "sequential",
|
|
477
|
+
timeToConvert: 168,
|
|
478
|
+
weight: 5,
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
name: "Supplier Management",
|
|
482
|
+
sequence: ["supplier contacted", "purchase order created", "invoice processed"],
|
|
483
|
+
conversionRate: 45,
|
|
484
|
+
order: "sequential",
|
|
485
|
+
timeToConvert: 336,
|
|
486
|
+
weight: 3,
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: "Integration Setup",
|
|
490
|
+
sequence: ["integration connected", "report generated", "alert configured"],
|
|
491
|
+
conversionRate: 30,
|
|
492
|
+
order: "sequential",
|
|
493
|
+
timeToConvert: 48,
|
|
494
|
+
weight: 2,
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
name: "Alert Response",
|
|
498
|
+
sequence: ["stockout alert", "purchase order created", "order received"],
|
|
499
|
+
conversionRate: 50,
|
|
500
|
+
order: "sequential",
|
|
501
|
+
timeToConvert: 120,
|
|
502
|
+
weight: 3,
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
|
|
506
|
+
// -- SuperProps ----------------------------------------------------
|
|
507
|
+
superProps: {
|
|
508
|
+
platform: ["web", "web", "desktop_app"],
|
|
509
|
+
subscription_plan: ["free_trial", "free_trial", "starter", "starter", "professional", "enterprise"],
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
// -- UserProps -----------------------------------------------------
|
|
513
|
+
userProps: {
|
|
514
|
+
company_tier: ["small_business"],
|
|
515
|
+
warehouse_count: u.weighNumRange(1, 10, 0.4, 2),
|
|
516
|
+
employee_count: u.weighNumRange(1, 500, 0.3, 25),
|
|
517
|
+
industry: ["retail", "manufacturing", "food_beverage", "pharma", "electronics"],
|
|
518
|
+
integration_count: [0],
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
// -- Phase 2: Personas --------------------------------------------
|
|
522
|
+
personas: [
|
|
523
|
+
{
|
|
524
|
+
name: "enterprise_ops",
|
|
525
|
+
weight: 15,
|
|
526
|
+
eventMultiplier: 5.0,
|
|
527
|
+
conversionModifier: 1.5,
|
|
528
|
+
churnRate: 0.01,
|
|
529
|
+
properties: {
|
|
530
|
+
company_tier: "enterprise",
|
|
531
|
+
segment: "enterprise_ops",
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: "mid_market",
|
|
536
|
+
weight: 35,
|
|
537
|
+
eventMultiplier: 1.5,
|
|
538
|
+
conversionModifier: 1.0,
|
|
539
|
+
churnRate: 0.05,
|
|
540
|
+
properties: {
|
|
541
|
+
company_tier: "mid_market",
|
|
542
|
+
segment: "mid_market",
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "small_business",
|
|
547
|
+
weight: 40,
|
|
548
|
+
eventMultiplier: 0.8,
|
|
549
|
+
conversionModifier: 0.7,
|
|
550
|
+
churnRate: 0.10,
|
|
551
|
+
properties: {
|
|
552
|
+
company_tier: "small_business",
|
|
553
|
+
segment: "small_business",
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: "trial_explorer",
|
|
558
|
+
weight: 10,
|
|
559
|
+
eventMultiplier: 0.4,
|
|
560
|
+
conversionModifier: 0.3,
|
|
561
|
+
churnRate: 0.4,
|
|
562
|
+
properties: {
|
|
563
|
+
company_tier: "trial",
|
|
564
|
+
segment: "trial_explorer",
|
|
565
|
+
},
|
|
566
|
+
activeWindow: { maxDays: 14 },
|
|
567
|
+
},
|
|
568
|
+
],
|
|
569
|
+
|
|
570
|
+
// -- Phase 2: World Events ----------------------------------------
|
|
571
|
+
worldEvents: [
|
|
572
|
+
{
|
|
573
|
+
name: "supply_chain_disruption",
|
|
574
|
+
type: "outage",
|
|
575
|
+
startDay: 35,
|
|
576
|
+
duration: 5,
|
|
577
|
+
volumeMultiplier: 3.0,
|
|
578
|
+
affectsEvents: ["stockout alert"],
|
|
579
|
+
injectProps: { disruption: "supply_chain_disruption" },
|
|
580
|
+
aftermath: { duration: 5, volumeMultiplier: 1.5 },
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
name: "holiday_surge",
|
|
584
|
+
type: "campaign",
|
|
585
|
+
startDay: 70,
|
|
586
|
+
duration: 7,
|
|
587
|
+
volumeMultiplier: 2.0,
|
|
588
|
+
affectsEvents: ["purchase order created", "order received", "shipment tracked"],
|
|
589
|
+
injectProps: { surge: "holiday_prep" },
|
|
590
|
+
},
|
|
591
|
+
],
|
|
592
|
+
|
|
593
|
+
// -- Phase 2: Subscription ----------------------------------------
|
|
594
|
+
subscription: {
|
|
595
|
+
plans: [
|
|
596
|
+
{ name: "free_trial", price: 0, default: true, trialDays: 30 },
|
|
597
|
+
{ name: "starter", price: 49 },
|
|
598
|
+
{ name: "professional", price: 199 },
|
|
599
|
+
{ name: "enterprise", price: 599 },
|
|
600
|
+
],
|
|
601
|
+
lifecycle: {
|
|
602
|
+
trialToPayRate: 0.25,
|
|
603
|
+
upgradeRate: 0.06,
|
|
604
|
+
downgradeRate: 0.03,
|
|
605
|
+
churnRate: 0.05,
|
|
606
|
+
winBackRate: 0.08,
|
|
607
|
+
winBackDelay: 30,
|
|
608
|
+
paymentFailureRate: 0.02,
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
// -- Phase 2: Geo -------------------------------------------------
|
|
613
|
+
geo: {
|
|
614
|
+
sticky: true,
|
|
615
|
+
regions: [
|
|
616
|
+
{
|
|
617
|
+
name: "us",
|
|
618
|
+
countries: ["US"],
|
|
619
|
+
weight: 45,
|
|
620
|
+
timezoneOffset: -5,
|
|
621
|
+
properties: { currency: "USD", locale: "en-US" },
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: "eu",
|
|
625
|
+
countries: ["GB", "DE", "NL"],
|
|
626
|
+
weight: 30,
|
|
627
|
+
timezoneOffset: 1,
|
|
628
|
+
properties: { currency: "EUR", locale: "en-EU", vat_tracking: true },
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
name: "apac",
|
|
632
|
+
countries: ["SG", "JP", "AU"],
|
|
633
|
+
weight: 25,
|
|
634
|
+
timezoneOffset: 8,
|
|
635
|
+
properties: { currency: "SGD", locale: "en-APAC" },
|
|
636
|
+
},
|
|
637
|
+
],
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
// -- Phase 2: Features --------------------------------------------
|
|
641
|
+
features: [
|
|
642
|
+
{
|
|
643
|
+
name: "predictive_reorder",
|
|
644
|
+
launchDay: 40,
|
|
645
|
+
adoptionCurve: "fast",
|
|
646
|
+
property: "reorder_method",
|
|
647
|
+
values: ["manual", "predictive"],
|
|
648
|
+
defaultBefore: "manual",
|
|
649
|
+
affectsEvents: ["inventory checked"],
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
name: "supplier_portal",
|
|
653
|
+
launchDay: 65,
|
|
654
|
+
adoptionCurve: { k: 0.08, midpoint: 25 },
|
|
655
|
+
property: "supplier_access",
|
|
656
|
+
values: ["email_only", "portal"],
|
|
657
|
+
defaultBefore: "email_only",
|
|
658
|
+
affectsEvents: ["supplier contacted"],
|
|
659
|
+
},
|
|
660
|
+
],
|
|
661
|
+
|
|
662
|
+
// -- Phase 2: Anomalies -------------------------------------------
|
|
663
|
+
anomalies: [
|
|
664
|
+
{
|
|
665
|
+
type: "extreme_value",
|
|
666
|
+
event: "stockout alert",
|
|
667
|
+
property: "items_affected",
|
|
668
|
+
frequency: 0.005,
|
|
669
|
+
multiplier: 30,
|
|
670
|
+
tag: "mass_stockout",
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
type: "coordinated",
|
|
674
|
+
event: "purchase order created",
|
|
675
|
+
day: 72,
|
|
676
|
+
window: 0.08,
|
|
677
|
+
count: 150,
|
|
678
|
+
tag: "holiday_prep",
|
|
679
|
+
},
|
|
680
|
+
],
|
|
681
|
+
|
|
682
|
+
// -- Hook Function ------------------------------------------------
|
|
683
|
+
hook: function (record, type, meta) {
|
|
684
|
+
// -- HOOK 7: ENTERPRISE PROFILES (user) -----------------------
|
|
685
|
+
// Enterprise ops get large warehouse_count and employee_count.
|
|
686
|
+
if (type === "user") {
|
|
687
|
+
if (record.company_tier === "enterprise") {
|
|
688
|
+
record.warehouse_count = chance.integer({ min: 5, max: 15 });
|
|
689
|
+
record.employee_count = chance.integer({ min: 200, max: 2000 });
|
|
690
|
+
} else if (record.company_tier === "mid_market") {
|
|
691
|
+
record.warehouse_count = chance.integer({ min: 2, max: 6 });
|
|
692
|
+
record.employee_count = chance.integer({ min: 20, max: 200 });
|
|
693
|
+
} else if (record.company_tier === "trial") {
|
|
694
|
+
record.warehouse_count = 1;
|
|
695
|
+
record.employee_count = chance.integer({ min: 1, max: 10 });
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// -- HOOK 8: ENTERPRISE INTEGRATION FUNNEL LIFT (funnel-pre) --
|
|
700
|
+
// Enterprise subscribers convert 1.5x through integration funnel.
|
|
701
|
+
if (type === "funnel-pre") {
|
|
702
|
+
if (meta && meta.profile) {
|
|
703
|
+
const tier = meta.profile.company_tier;
|
|
704
|
+
if (tier === "enterprise") {
|
|
705
|
+
record.conversionRate = Math.min(record.conversionRate * 1.5, 95);
|
|
706
|
+
} else if (tier === "mid_market") {
|
|
707
|
+
record.conversionRate = Math.min(record.conversionRate * 1.2, 85);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// -- HOOK 1: MONTH-END REPORTING SURGE (event) ----------------
|
|
713
|
+
// Reports on days 28-31 get 2x report_pages.
|
|
714
|
+
if (type === "event") {
|
|
715
|
+
if (record.event === "report generated") {
|
|
716
|
+
const dayOfMonth = dayjs(record.time).date();
|
|
717
|
+
if (dayOfMonth >= 28) {
|
|
718
|
+
record.report_pages = Math.floor((record.report_pages || 20) * 2);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// -- HOOK 2: RUSH ORDER PREMIUM (event) -------------------
|
|
723
|
+
// Urgent purchase orders get 1.5x unit_cost.
|
|
724
|
+
if (record.event === "purchase order created" && record.priority === "urgent") {
|
|
725
|
+
record.unit_cost = Math.floor((record.unit_cost || 50) * 1.5);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// -- EVERYTHING HOOKS -----------------------------------------
|
|
730
|
+
if (type === "everything") {
|
|
731
|
+
const events = record;
|
|
732
|
+
if (!events.length) return record;
|
|
733
|
+
|
|
734
|
+
// -- HOOK 3: REORDER ACCURACY BY TIER ---------------------
|
|
735
|
+
// Enterprise users get 10% of stockout alerts removed.
|
|
736
|
+
if (meta.profile && meta.profile.company_tier === "enterprise") {
|
|
737
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
738
|
+
if (events[i].event === "stockout alert" && chance.bool({ likelihood: 10 })) {
|
|
739
|
+
events.splice(i, 1);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// -- HOOK 4: INTEGRATION COMPLETION DRIVES RETENTION ------
|
|
745
|
+
// Users with 3+ integration events get cloned report events.
|
|
746
|
+
const integrationCount = events.filter(e => e.event === "integration connected").length;
|
|
747
|
+
if (integrationCount >= 3) {
|
|
748
|
+
const templateReport = events.find(e => e.event === "report generated");
|
|
749
|
+
if (templateReport) {
|
|
750
|
+
const integrationEvents = events.filter(e => e.event === "integration connected");
|
|
751
|
+
integrationEvents.forEach(ie => {
|
|
752
|
+
if (chance.bool({ likelihood: 65 })) {
|
|
753
|
+
events.push({
|
|
754
|
+
...templateReport,
|
|
755
|
+
time: dayjs(ie.time).add(chance.integer({ min: 1, max: 5 }), "days").toISOString(),
|
|
756
|
+
user_id: ie.user_id,
|
|
757
|
+
report_type: "integration_summary",
|
|
758
|
+
report_pages: chance.integer({ min: 5, max: 25 }),
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// -- HOOK 5: ALERT FATIGUE --------------------------------
|
|
766
|
+
// Users with >30 stockout alerts get increasing response times.
|
|
767
|
+
const alertEvents = events.filter(e => e.event === "stockout alert");
|
|
768
|
+
if (alertEvents.length > 30) {
|
|
769
|
+
alertEvents.forEach((alert, idx) => {
|
|
770
|
+
if (idx >= 20) {
|
|
771
|
+
const fatigueMultiplier = 1.5 + ((idx - 20) / alertEvents.length) * 1.5;
|
|
772
|
+
alert.response_time_hours = Math.floor((alert.response_time_hours || 4) * fatigueMultiplier);
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// -- HOOK 6: TRIAL CHURN ----------------------------------
|
|
778
|
+
// Users with <10 events lose 50% after day 7.
|
|
779
|
+
if (events.length < 10) {
|
|
780
|
+
const userFirstEvent = events[0];
|
|
781
|
+
if (userFirstEvent) {
|
|
782
|
+
const firstTime = dayjs(userFirstEvent.time);
|
|
783
|
+
const cutoff = firstTime.add(7, "days");
|
|
784
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
785
|
+
if (dayjs(events[i].time).isAfter(cutoff) && chance.bool({ likelihood: 50 })) {
|
|
786
|
+
events.splice(i, 1);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return record;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return record;
|
|
796
|
+
},
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
export default config;
|