@ak--47/dungeon-master 1.2.3 → 1.3.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/CHANGELOG.md +42 -0
- package/README.md +51 -13
- package/dungeons/technical/ad-spend.js +2 -2
- package/dungeons/technical/anonymous-users.js +2 -2
- package/dungeons/technical/array-of-object-lookup.js +2 -2
- package/dungeons/technical/experiments.js +2 -2
- package/dungeons/technical/foobar.js +2 -2
- package/dungeons/technical/group-analytics.js +2 -2
- package/dungeons/technical/mirror-strategies.js +2 -2
- package/dungeons/technical/nested-objects.js +2 -2
- package/dungeons/technical/retention-cadence.js +2 -3
- package/dungeons/technical/sanity.js +2 -2
- package/dungeons/technical/scale-test.js +2 -2
- package/dungeons/technical/scd.js +2 -2
- package/dungeons/technical/simple.js +2 -2
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +2 -2
- package/dungeons/vertical/ai-platform-schema.json +617 -0
- package/dungeons/vertical/ai-platform.js +799 -0
- package/dungeons/vertical/community.js +40 -26
- package/dungeons/vertical/crypto-schema.json +546 -0
- package/dungeons/vertical/crypto.js +721 -0
- package/dungeons/vertical/dating-schema.json +401 -0
- package/dungeons/vertical/dating.js +798 -0
- package/dungeons/vertical/devtools.js +13 -9
- package/dungeons/vertical/ecommerce.js +2 -3
- package/dungeons/vertical/education.js +4 -5
- package/dungeons/vertical/fintech.js +32 -29
- package/dungeons/vertical/fitness.js +2 -3
- package/dungeons/vertical/food-delivery.js +37 -43
- package/dungeons/vertical/gaming-schema.json +2495 -230
- package/dungeons/vertical/gaming.js +771 -388
- package/dungeons/vertical/healthcare.js +2 -3
- package/dungeons/vertical/insurance-application.js +2 -3
- package/dungeons/vertical/logistics.js +20 -14
- package/dungeons/vertical/marketplace.js +22 -14
- package/dungeons/vertical/media.js +39 -30
- package/dungeons/vertical/real-estate-schema.json +527 -0
- package/dungeons/vertical/real-estate.js +774 -0
- package/dungeons/vertical/sass.js +2 -3
- package/dungeons/vertical/social.js +15 -13
- package/dungeons/vertical/travel.js +59 -26
- package/lib/core/config-validator.js +71 -15
- package/lib/orchestrators/user-loop.js +39 -5
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/package.json +8 -2
- package/types.d.ts +219 -42
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/rpg-schema.json +0 -2491
- package/dungeons/vertical/rpg.js +0 -976
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
// ── TWEAK THESE ──
|
|
2
|
+
const SEED = "promptforge";
|
|
3
|
+
const num_days = 120;
|
|
4
|
+
const num_users = 8_000;
|
|
5
|
+
const avg_events_per_user_per_day = 0.83;
|
|
6
|
+
let token = "your-mixpanel-token";
|
|
7
|
+
|
|
8
|
+
// ── env overrides ──
|
|
9
|
+
if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
|
|
10
|
+
|
|
11
|
+
import dayjs from "dayjs";
|
|
12
|
+
import utc from "dayjs/plugin/utc.js";
|
|
13
|
+
import "dotenv/config";
|
|
14
|
+
import * as u from "../../lib/utils/utils.js";
|
|
15
|
+
import * as v from "ak-tools";
|
|
16
|
+
|
|
17
|
+
dayjs.extend(utc);
|
|
18
|
+
const chance = u.initChance(SEED);
|
|
19
|
+
const NOW = dayjs();
|
|
20
|
+
const DATASET_START = NOW.subtract(num_days, "days");
|
|
21
|
+
|
|
22
|
+
/** @typedef {import("../../types").Dungeon} Config */
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* ===============================================================
|
|
26
|
+
* DATASET OVERVIEW
|
|
27
|
+
* ===============================================================
|
|
28
|
+
*
|
|
29
|
+
* PromptForge -- an LLM API platform (like Anthropic/OpenAI).
|
|
30
|
+
* Customers (developers and companies) send API requests for chat
|
|
31
|
+
* completions, embeddings, evaluations, and tool use. Billing is
|
|
32
|
+
* per input/output token. Key features: prompt caching, tool use,
|
|
33
|
+
* multi-turn conversations, batch API, model selection, and
|
|
34
|
+
* evaluation pipelines.
|
|
35
|
+
*
|
|
36
|
+
* - 8,000 users over 120 days, ~800K events
|
|
37
|
+
* - Three API tiers: Free, Build, Enterprise
|
|
38
|
+
* - Core loop: org created -> api key created -> api call -> iterate
|
|
39
|
+
* - Revenue: token-based billing with tier-based pricing
|
|
40
|
+
*
|
|
41
|
+
* Key entities:
|
|
42
|
+
* - model: LLM model version (sonnet-4, haiku-4, opus-4-6, opus-4-7)
|
|
43
|
+
* - api_tier: Free / Build / Enterprise (determines context window, rate limits)
|
|
44
|
+
* - tokens_used: total tokens consumed per API call (input + output)
|
|
45
|
+
* - cost_usd: dollar cost of a single API call
|
|
46
|
+
* - cache_enabled: prompt caching flag that reduces cost 70%
|
|
47
|
+
* - multi_turn: whether the call is part of a conversation
|
|
48
|
+
*
|
|
49
|
+
* ===============================================================
|
|
50
|
+
* ANALYTICS HOOKS (8 hooks)
|
|
51
|
+
* ===============================================================
|
|
52
|
+
*
|
|
53
|
+
* ---------------------------------------------------------------
|
|
54
|
+
* 1. PROMPT CACHING ADOPTION (CONVERSION — event + everything)
|
|
55
|
+
* ---------------------------------------------------------------
|
|
56
|
+
*
|
|
57
|
+
* PATTERN: Customers who enable prompt caching see 70% lower
|
|
58
|
+
* cost_per_call. Once any api call has cache_enabled=true, all
|
|
59
|
+
* subsequent calls for that user get cost_usd reduced by 70%.
|
|
60
|
+
*
|
|
61
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
62
|
+
*
|
|
63
|
+
* Report 1: Cost Per Call by Cache Status
|
|
64
|
+
* - Report type: Insights
|
|
65
|
+
* - Event: "api call"
|
|
66
|
+
* - Measure: Average of "cost_usd"
|
|
67
|
+
* - Breakdown: "cache_enabled"
|
|
68
|
+
* - Expected: cache_enabled=true ~ $0.003, false ~ $0.01 (70% cheaper)
|
|
69
|
+
*
|
|
70
|
+
* Report 2: Cache Adoption Over Time
|
|
71
|
+
* - Report type: Insights
|
|
72
|
+
* - Event: "api call"
|
|
73
|
+
* - Measure: Total
|
|
74
|
+
* - Filter: cache_enabled = true
|
|
75
|
+
* - Line chart by week
|
|
76
|
+
* - Expected: steady growth in cached calls over the dataset
|
|
77
|
+
*
|
|
78
|
+
* REAL-WORLD ANALOGUE: Prompt caching avoids re-processing long
|
|
79
|
+
* system prompts on every call, dramatically reducing cost and latency.
|
|
80
|
+
*
|
|
81
|
+
* ---------------------------------------------------------------
|
|
82
|
+
* 2. MODEL MIGRATION WAVE (TIMED RELEASE — event)
|
|
83
|
+
* ---------------------------------------------------------------
|
|
84
|
+
*
|
|
85
|
+
* PATTERN: At day 60, new model "opus-4-7" releases. After day 60,
|
|
86
|
+
* 35% of api calls from Build/Enterprise users switch model to
|
|
87
|
+
* "opus-4-7". These calls use 1.5x tokens (smarter model, longer
|
|
88
|
+
* responses).
|
|
89
|
+
*
|
|
90
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
91
|
+
*
|
|
92
|
+
* Report 1: Model Distribution Over Time
|
|
93
|
+
* - Report type: Insights
|
|
94
|
+
* - Event: "api call"
|
|
95
|
+
* - Measure: Total
|
|
96
|
+
* - Breakdown: "model"
|
|
97
|
+
* - Line chart by week
|
|
98
|
+
* - Expected: opus-4-7 appears at day 60, ramps to ~35% of paid calls
|
|
99
|
+
*
|
|
100
|
+
* Report 2: Tokens Per Model
|
|
101
|
+
* - Report type: Insights
|
|
102
|
+
* - Event: "api call"
|
|
103
|
+
* - Measure: Average of "tokens_used"
|
|
104
|
+
* - Breakdown: "model"
|
|
105
|
+
* - Expected: opus-4-7 ~ 1.5x tokens vs other models
|
|
106
|
+
*
|
|
107
|
+
* REAL-WORLD ANALOGUE: New flagship model launches cause migration
|
|
108
|
+
* waves among power users who want improved capabilities.
|
|
109
|
+
*
|
|
110
|
+
* ---------------------------------------------------------------
|
|
111
|
+
* 3. AGENTIC LOOP POWER USERS (BEHAVIORS TOGETHER — everything)
|
|
112
|
+
* ---------------------------------------------------------------
|
|
113
|
+
*
|
|
114
|
+
* PATTERN: Users who use both "tool use call" AND have multi_turn=true
|
|
115
|
+
* on any api call are agentic loop users. They get 8x tokens_used on
|
|
116
|
+
* all api calls and 3x extra api call events injected.
|
|
117
|
+
*
|
|
118
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
119
|
+
*
|
|
120
|
+
* Report 1: Token Usage — Agentic vs Standard
|
|
121
|
+
* - Report type: Insights
|
|
122
|
+
* - Event: "api call"
|
|
123
|
+
* - Measure: Average of "tokens_used"
|
|
124
|
+
* - Breakdown: "is_agentic_user"
|
|
125
|
+
* - Expected: is_agentic_user=true ~ 8x tokens (agentic ~ 40K, standard ~ 5K)
|
|
126
|
+
*
|
|
127
|
+
* Report 2: API Call Volume — Agentic vs Standard
|
|
128
|
+
* - Report type: Insights
|
|
129
|
+
* - Event: "api call"
|
|
130
|
+
* - Measure: Total per user (average)
|
|
131
|
+
* - Breakdown: "is_agentic_user"
|
|
132
|
+
* - Expected: agentic users ~ 3x more api calls
|
|
133
|
+
*
|
|
134
|
+
* REAL-WORLD ANALOGUE: Agentic workloads (coding agents, research
|
|
135
|
+
* assistants) consume dramatically more tokens via extended tool-use
|
|
136
|
+
* loops and multi-turn conversations.
|
|
137
|
+
*
|
|
138
|
+
* ---------------------------------------------------------------
|
|
139
|
+
* 4. RATE LIMIT CHURN (CHURN — everything)
|
|
140
|
+
* ---------------------------------------------------------------
|
|
141
|
+
*
|
|
142
|
+
* PATTERN: Users hitting "rate limit error" >= 5 times in first
|
|
143
|
+
* 7 days lose 60% of events after week 1. Rate-limited users
|
|
144
|
+
* churn from frustration.
|
|
145
|
+
*
|
|
146
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
147
|
+
*
|
|
148
|
+
* Report 1: Retention by Early Rate Limiting
|
|
149
|
+
* - Report type: Retention
|
|
150
|
+
* - Event A: any event
|
|
151
|
+
* - Event B: any event
|
|
152
|
+
* - Breakdown: "hit_rate_limit_early" (user property)
|
|
153
|
+
* - Expected: hit_rate_limit_early=true ~ 40% D30 retention
|
|
154
|
+
* vs ~80% for others
|
|
155
|
+
*
|
|
156
|
+
* Report 2: Event Volume Post Rate-Limit
|
|
157
|
+
* - Report type: Insights
|
|
158
|
+
* - Event: any event
|
|
159
|
+
* - Measure: Total per user (average)
|
|
160
|
+
* - Breakdown: "hit_rate_limit_early"
|
|
161
|
+
* - Expected: rate-limited users ~ 40% of normal volume
|
|
162
|
+
*
|
|
163
|
+
* REAL-WORLD ANALOGUE: Developers who get rate-limited early in
|
|
164
|
+
* their evaluation often switch to a competitor platform.
|
|
165
|
+
*
|
|
166
|
+
* ---------------------------------------------------------------
|
|
167
|
+
* 5. TIER-BASED CONTEXT WINDOW (SUBSCRIPTION TIER — everything)
|
|
168
|
+
* ---------------------------------------------------------------
|
|
169
|
+
*
|
|
170
|
+
* PATTERN: Free users have context_window=200000, Build=1000000,
|
|
171
|
+
* Enterprise=2000000. Enterprise users send 4x larger input_tokens.
|
|
172
|
+
* Context window and input tokens are scaled by tier.
|
|
173
|
+
*
|
|
174
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
175
|
+
*
|
|
176
|
+
* Report 1: Input Tokens by Tier
|
|
177
|
+
* - Report type: Insights
|
|
178
|
+
* - Event: "api call"
|
|
179
|
+
* - Measure: Average of "input_tokens"
|
|
180
|
+
* - Breakdown: "api_tier" (superProp)
|
|
181
|
+
* - Expected: Enterprise ~ 4x Free (Enterprise ~ 8K, Free ~ 2K)
|
|
182
|
+
*
|
|
183
|
+
* Report 2: Context Window by Tier
|
|
184
|
+
* - Report type: Insights
|
|
185
|
+
* - Event: "api call"
|
|
186
|
+
* - Measure: Average of "context_window"
|
|
187
|
+
* - Breakdown: "api_tier"
|
|
188
|
+
* - Expected: Free=200K, Build=1M, Enterprise=2M
|
|
189
|
+
*
|
|
190
|
+
* REAL-WORLD ANALOGUE: Enterprise customers pay for larger context
|
|
191
|
+
* windows and use them for long-document analysis and code review.
|
|
192
|
+
*
|
|
193
|
+
* ---------------------------------------------------------------
|
|
194
|
+
* 6. OUTAGE DAY (TIME-BASED — event)
|
|
195
|
+
* ---------------------------------------------------------------
|
|
196
|
+
*
|
|
197
|
+
* PATTERN: Days 40-41, is_error is set to true on 40% of api call
|
|
198
|
+
* events. error_type is set to service errors. Simulates a major
|
|
199
|
+
* platform outage.
|
|
200
|
+
*
|
|
201
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
202
|
+
*
|
|
203
|
+
* Report 1: Error Rate Over Time
|
|
204
|
+
* - Report type: Insights
|
|
205
|
+
* - Event: "api call"
|
|
206
|
+
* - Measure: Total
|
|
207
|
+
* - Filter: is_error = true
|
|
208
|
+
* - Line chart by day
|
|
209
|
+
* - Expected: massive spike on days 40-41 (8x baseline error rate)
|
|
210
|
+
*
|
|
211
|
+
* Report 2: Error Types During Outage
|
|
212
|
+
* - Report type: Insights
|
|
213
|
+
* - Event: "api call"
|
|
214
|
+
* - Filter: is_error = true
|
|
215
|
+
* - Breakdown: "error_type"
|
|
216
|
+
* - Date range: days 40-41
|
|
217
|
+
* - Expected: service_overloaded and internal_server_error dominate
|
|
218
|
+
*
|
|
219
|
+
* REAL-WORLD ANALOGUE: API platforms experience periodic outages
|
|
220
|
+
* that spike error rates across all customers.
|
|
221
|
+
*
|
|
222
|
+
* ---------------------------------------------------------------
|
|
223
|
+
* 7. BATCH API DISCOUNT (PURCHASE VALUE — everything)
|
|
224
|
+
* ---------------------------------------------------------------
|
|
225
|
+
*
|
|
226
|
+
* PATTERN: Users who submit batch jobs get 50% lower cost_per_token
|
|
227
|
+
* on api calls but use 2x tokens_used. Batch processing is cheaper
|
|
228
|
+
* per token but encourages higher volume.
|
|
229
|
+
*
|
|
230
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
231
|
+
*
|
|
232
|
+
* Report 1: Cost Per Token — Batch vs Interactive
|
|
233
|
+
* - Report type: Insights
|
|
234
|
+
* - Event: "api call"
|
|
235
|
+
* - Measure: Average of "cost_per_token"
|
|
236
|
+
* - Breakdown: "is_batch_user"
|
|
237
|
+
* - Expected: is_batch_user=true ~ 50% lower cost per token
|
|
238
|
+
*
|
|
239
|
+
* Report 2: Token Volume — Batch Users
|
|
240
|
+
* - Report type: Insights
|
|
241
|
+
* - Event: "api call"
|
|
242
|
+
* - Measure: Average of "tokens_used"
|
|
243
|
+
* - Breakdown: "is_batch_user"
|
|
244
|
+
* - Expected: batch users ~ 2x token volume
|
|
245
|
+
*
|
|
246
|
+
* REAL-WORLD ANALOGUE: Batch API pricing incentivizes high-volume
|
|
247
|
+
* workloads with discounted per-token rates.
|
|
248
|
+
*
|
|
249
|
+
* ---------------------------------------------------------------
|
|
250
|
+
* 8. EVAL-DRIVEN RETENTION (RETENTION — everything)
|
|
251
|
+
* ---------------------------------------------------------------
|
|
252
|
+
*
|
|
253
|
+
* PATTERN: Users who run "eval job" in the first 7 days have 75%
|
|
254
|
+
* D30 retention vs 25% for non-eval users. Early eval adoption
|
|
255
|
+
* indicates serious platform investment.
|
|
256
|
+
*
|
|
257
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
258
|
+
*
|
|
259
|
+
* Report 1: Retention by Early Eval Usage
|
|
260
|
+
* - Report type: Retention
|
|
261
|
+
* - Event A: any event
|
|
262
|
+
* - Event B: any event
|
|
263
|
+
* - Breakdown: "has_early_eval" (user property)
|
|
264
|
+
* - Expected: has_early_eval=true ~ 75% D30 vs 25% for false
|
|
265
|
+
*
|
|
266
|
+
* Report 2: Event Volume Over Time
|
|
267
|
+
* - Report type: Insights
|
|
268
|
+
* - Event: any event
|
|
269
|
+
* - Measure: Total per user (average)
|
|
270
|
+
* - Breakdown: "has_early_eval"
|
|
271
|
+
* - Line chart by week
|
|
272
|
+
* - Expected: early eval users sustain volume; non-eval users decay
|
|
273
|
+
*
|
|
274
|
+
* REAL-WORLD ANALOGUE: Teams that set up evaluation pipelines early
|
|
275
|
+
* are deeply invested in prompt quality and stick with the platform.
|
|
276
|
+
*
|
|
277
|
+
* ===============================================================
|
|
278
|
+
* EXPECTED METRICS SUMMARY
|
|
279
|
+
* ===============================================================
|
|
280
|
+
*
|
|
281
|
+
* Hook | Metric | Baseline | Effect | Ratio
|
|
282
|
+
* ----------------------------|----------------------|------------|--------------|------
|
|
283
|
+
* Prompt Caching Adoption | cost_usd | $0.01 | $0.003 | 0.3x
|
|
284
|
+
* Model Migration Wave | opus-4-7 share | 0% | ~35% (paid) | new
|
|
285
|
+
* Agentic Loop Power Users | tokens_used | 5K | 40K | 8x
|
|
286
|
+
* Rate Limit Churn | D30 retention | 80% | 40% | 0.5x
|
|
287
|
+
* Tier-Based Context Window | input_tokens | 2K (Free) | 8K (Ent) | 4x
|
|
288
|
+
* Outage Day | error rate | 5% | 40% | 8x
|
|
289
|
+
* Batch API Discount | cost_per_token | $0.00001 | $0.000005 | 0.5x
|
|
290
|
+
* Eval-Driven Retention | D30 retention | 25% | 75% | 3x
|
|
291
|
+
*/
|
|
292
|
+
|
|
293
|
+
/** @type {Config} */
|
|
294
|
+
const config = {
|
|
295
|
+
token,
|
|
296
|
+
seed: SEED,
|
|
297
|
+
numDays: num_days,
|
|
298
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
299
|
+
numUsers: num_users,
|
|
300
|
+
hasAnonIds: false,
|
|
301
|
+
hasSessionIds: false,
|
|
302
|
+
format: "json",
|
|
303
|
+
gzip: true,
|
|
304
|
+
alsoInferFunnels: false,
|
|
305
|
+
hasLocation: true,
|
|
306
|
+
hasAndroidDevices: false,
|
|
307
|
+
hasIOSDevices: false,
|
|
308
|
+
hasDesktopDevices: true,
|
|
309
|
+
hasBrowser: true,
|
|
310
|
+
hasCampaigns: false,
|
|
311
|
+
isAnonymous: false,
|
|
312
|
+
hasAdSpend: false,
|
|
313
|
+
hasAvatar: true,
|
|
314
|
+
concurrency: 1,
|
|
315
|
+
writeToDisk: false,
|
|
316
|
+
|
|
317
|
+
soup: "growth",
|
|
318
|
+
|
|
319
|
+
scdProps: {
|
|
320
|
+
monthly_api_usage: {
|
|
321
|
+
values: u.weighNumRange(0, 1000000, 0.3, 50),
|
|
322
|
+
frequency: "week",
|
|
323
|
+
timing: "fuzzy",
|
|
324
|
+
max: 20,
|
|
325
|
+
},
|
|
326
|
+
api_tier_history: {
|
|
327
|
+
values: ["Free", "Build", "Enterprise"],
|
|
328
|
+
frequency: "month",
|
|
329
|
+
timing: "fixed",
|
|
330
|
+
max: 6,
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
// -- Events (18) ------------------------------------------
|
|
335
|
+
events: [
|
|
336
|
+
{
|
|
337
|
+
event: "organization created",
|
|
338
|
+
weight: 1,
|
|
339
|
+
isFirstEvent: true,
|
|
340
|
+
properties: {
|
|
341
|
+
org_size: ["solo", "startup", "growth", "enterprise"],
|
|
342
|
+
referral_source: ["docs", "blog", "github", "word_of_mouth", "search", "conference"],
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
event: "api key created",
|
|
347
|
+
weight: 2,
|
|
348
|
+
properties: {
|
|
349
|
+
key_type: ["development", "production", "staging"],
|
|
350
|
+
key_scope: ["full_access", "read_only", "completions_only"],
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
event: "api key rotated",
|
|
355
|
+
weight: 1,
|
|
356
|
+
properties: {
|
|
357
|
+
rotation_reason: ["scheduled", "compromised", "policy", "manual"],
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
event: "api call",
|
|
362
|
+
weight: 10,
|
|
363
|
+
properties: {
|
|
364
|
+
model: ["sonnet-4", "sonnet-4", "sonnet-4", "haiku-4", "haiku-4", "opus-4-6"],
|
|
365
|
+
input_tokens: u.weighNumRange(50, 8000, 0.4, 2000),
|
|
366
|
+
output_tokens: u.weighNumRange(10, 4000, 0.4, 500),
|
|
367
|
+
tokens_used: u.weighNumRange(100, 12000, 0.4, 2500),
|
|
368
|
+
cost_usd: [0.001, 0.002, 0.003, 0.003, 0.005, 0.005, 0.005, 0.008, 0.008, 0.01, 0.01, 0.01, 0.01, 0.015, 0.015, 0.02, 0.025, 0.03, 0.04, 0.05],
|
|
369
|
+
cost_per_token: [0.000002, 0.000003, 0.000005, 0.000005, 0.000008, 0.000008, 0.00001, 0.00001, 0.00001, 0.000012, 0.000015, 0.00002, 0.000025, 0.00003],
|
|
370
|
+
latency_ms: u.weighNumRange(100, 15000, 0.4, 1500),
|
|
371
|
+
cache_enabled: [false],
|
|
372
|
+
is_error: [false],
|
|
373
|
+
error_type: ["none"],
|
|
374
|
+
multi_turn: [false, false, false, true],
|
|
375
|
+
context_window: [200000],
|
|
376
|
+
is_agentic_user: [false],
|
|
377
|
+
is_batch_user: [false],
|
|
378
|
+
stream: [true, true, true, false],
|
|
379
|
+
stop_reason: ["end_turn", "end_turn", "end_turn", "max_tokens", "tool_use"],
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
event: "tool use call",
|
|
384
|
+
weight: 4,
|
|
385
|
+
properties: {
|
|
386
|
+
tool_name: ["web_search", "code_interpreter", "file_reader", "calculator", "database_query", "api_connector"],
|
|
387
|
+
execution_time_ms: u.weighNumRange(50, 10000, 0.4, 800),
|
|
388
|
+
success: [true, true, true, true, false],
|
|
389
|
+
tool_input_tokens: u.weighNumRange(50, 2000, 0.4, 300),
|
|
390
|
+
tool_output_tokens: u.weighNumRange(20, 5000, 0.4, 500),
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
event: "batch job submitted",
|
|
395
|
+
weight: 2,
|
|
396
|
+
properties: {
|
|
397
|
+
batch_size: u.weighNumRange(10, 10000, 0.3, 500),
|
|
398
|
+
model: ["sonnet-4", "haiku-4", "opus-4-6"],
|
|
399
|
+
estimated_tokens: u.weighNumRange(10000, 5000000, 0.3, 500000),
|
|
400
|
+
priority: ["standard", "standard", "standard", "express"],
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
event: "batch job completed",
|
|
405
|
+
weight: 2,
|
|
406
|
+
properties: {
|
|
407
|
+
batch_size: u.weighNumRange(10, 10000, 0.3, 500),
|
|
408
|
+
processing_time_sec: u.weighNumRange(60, 7200, 0.4, 900),
|
|
409
|
+
total_tokens: u.weighNumRange(10000, 5000000, 0.3, 500000),
|
|
410
|
+
success_rate: u.weighNumRange(90, 100, 0.8, 98),
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
event: "eval job",
|
|
415
|
+
weight: 3,
|
|
416
|
+
properties: {
|
|
417
|
+
eval_type: ["accuracy", "relevance", "safety", "latency", "cost", "custom"],
|
|
418
|
+
num_test_cases: u.weighNumRange(10, 1000, 0.3, 100),
|
|
419
|
+
model: ["sonnet-4", "haiku-4", "opus-4-6"],
|
|
420
|
+
dataset_name: ["prod_prompts", "safety_suite", "regression_set", "benchmark_v2", "custom_eval"],
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
event: "eval result",
|
|
425
|
+
weight: 3,
|
|
426
|
+
properties: {
|
|
427
|
+
eval_type: ["accuracy", "relevance", "safety", "latency", "cost", "custom"],
|
|
428
|
+
score: u.weighNumRange(0, 100, 0.6, 75),
|
|
429
|
+
pass_rate: u.weighNumRange(50, 100, 0.7, 85),
|
|
430
|
+
model: ["sonnet-4", "haiku-4", "opus-4-6"],
|
|
431
|
+
regression_detected: [false, false, false, false, true],
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
event: "rate limit error",
|
|
436
|
+
weight: 3,
|
|
437
|
+
properties: {
|
|
438
|
+
error_code: [429],
|
|
439
|
+
retry_after_ms: u.weighNumRange(1000, 60000, 0.3, 5000),
|
|
440
|
+
requests_per_minute: u.weighNumRange(50, 2000, 0.4, 500),
|
|
441
|
+
tier_limit: ["Free", "Build", "Enterprise"],
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
event: "billing payment",
|
|
446
|
+
weight: 2,
|
|
447
|
+
properties: {
|
|
448
|
+
amount_usd: u.weighNumRange(5, 50000, 0.2, 500),
|
|
449
|
+
payment_method: ["credit_card", "credit_card", "credit_card", "invoice", "wire_transfer"],
|
|
450
|
+
billing_period: ["monthly", "monthly", "annual"],
|
|
451
|
+
tokens_consumed: u.weighNumRange(100000, 50000000, 0.3, 5000000),
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
event: "model selected",
|
|
456
|
+
weight: 3,
|
|
457
|
+
properties: {
|
|
458
|
+
model: ["sonnet-4", "sonnet-4", "haiku-4", "opus-4-6"],
|
|
459
|
+
is_default: [true, true, false],
|
|
460
|
+
selection_context: ["playground", "api_config", "eval_setup", "batch_config"],
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
event: "dashboard viewed",
|
|
465
|
+
weight: 5,
|
|
466
|
+
properties: {
|
|
467
|
+
dashboard_section: ["usage", "billing", "api_keys", "models", "evals", "logs"],
|
|
468
|
+
time_range: ["1h", "24h", "7d", "30d"],
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
event: "docs searched",
|
|
473
|
+
weight: 4,
|
|
474
|
+
properties: {
|
|
475
|
+
search_query_category: ["api_reference", "quickstart", "pricing", "models", "tool_use", "batch_api", "caching", "errors"],
|
|
476
|
+
results_found: u.weighNumRange(0, 50, 0.5, 8),
|
|
477
|
+
clicked_result: [true, true, true, false],
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
event: "member invited",
|
|
482
|
+
weight: 2,
|
|
483
|
+
properties: {
|
|
484
|
+
invite_role: ["admin", "developer", "developer", "billing", "viewer"],
|
|
485
|
+
invite_method: ["email", "email", "sso", "link"],
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
event: "webhook configured",
|
|
490
|
+
weight: 1,
|
|
491
|
+
properties: {
|
|
492
|
+
webhook_event: ["usage_alert", "rate_limit", "batch_complete", "eval_complete", "billing_threshold"],
|
|
493
|
+
delivery_method: ["https", "https", "slack", "email"],
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
event: "playground session",
|
|
498
|
+
weight: 4,
|
|
499
|
+
properties: {
|
|
500
|
+
model: ["sonnet-4", "sonnet-4", "haiku-4", "opus-4-6"],
|
|
501
|
+
turns: u.weighNumRange(1, 30, 0.4, 5),
|
|
502
|
+
shared: [false, false, false, true],
|
|
503
|
+
tokens_used: u.weighNumRange(100, 20000, 0.3, 3000),
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
event: "account deactivated",
|
|
508
|
+
weight: 1,
|
|
509
|
+
isChurnEvent: true,
|
|
510
|
+
returnLikelihood: 0.1,
|
|
511
|
+
isStrictEvent: true,
|
|
512
|
+
properties: {
|
|
513
|
+
reason: ["cost", "switched_provider", "project_ended", "rate_limits", "no_longer_needed", "performance"],
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
],
|
|
517
|
+
|
|
518
|
+
// -- Funnels (3) ------------------------------------------
|
|
519
|
+
funnels: [
|
|
520
|
+
{
|
|
521
|
+
name: "Onboarding",
|
|
522
|
+
sequence: ["organization created", "api key created", "api call"],
|
|
523
|
+
conversionRate: 70,
|
|
524
|
+
order: "sequential",
|
|
525
|
+
isFirstFunnel: true,
|
|
526
|
+
timeToConvert: 48,
|
|
527
|
+
weight: 3,
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: "API to Eval Pipeline",
|
|
531
|
+
sequence: ["api call", "tool use call", "eval job"],
|
|
532
|
+
conversionRate: 45,
|
|
533
|
+
order: "sequential",
|
|
534
|
+
timeToConvert: 168,
|
|
535
|
+
weight: 5,
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: "Usage to Billing",
|
|
539
|
+
sequence: ["api call", "billing payment"],
|
|
540
|
+
conversionRate: 30,
|
|
541
|
+
order: "sequential",
|
|
542
|
+
timeToConvert: 336,
|
|
543
|
+
weight: 2,
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
|
|
547
|
+
// -- SuperProps --------------------------------------------
|
|
548
|
+
superProps: {
|
|
549
|
+
api_tier: ["Free", "Free", "Build", "Build", "Enterprise"],
|
|
550
|
+
primary_use_case: ["chatbot", "code_generation", "data_extraction", "content_creation", "agents"],
|
|
551
|
+
sdk_language: ["python", "typescript", "java", "go", "curl"],
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
// -- UserProps ---------------------------------------------
|
|
555
|
+
userProps: {
|
|
556
|
+
api_tier: ["Free", "Free", "Build", "Build", "Enterprise"],
|
|
557
|
+
primary_use_case: ["chatbot", "code_generation", "data_extraction", "content_creation", "agents"],
|
|
558
|
+
sdk_language: ["python", "typescript", "java", "go", "curl"],
|
|
559
|
+
monthly_spend: u.weighNumRange(0, 50000, 0.2, 200),
|
|
560
|
+
total_api_calls: u.weighNumRange(0, 500000, 0.2, 10000),
|
|
561
|
+
preferred_model: ["sonnet-4", "sonnet-4", "haiku-4", "opus-4-6"],
|
|
562
|
+
has_eval_pipeline: [false],
|
|
563
|
+
hit_rate_limit_early: [false],
|
|
564
|
+
has_early_eval: [false],
|
|
565
|
+
},
|
|
566
|
+
|
|
567
|
+
// -- Hook Function ----------------------------------------
|
|
568
|
+
hook: function (record, type, meta) {
|
|
569
|
+
// ─────────────────────────────────────────────────────────
|
|
570
|
+
// Hook #6: OUTAGE DAY (event)
|
|
571
|
+
// Days 40-41: 40% of api calls get is_error=true with
|
|
572
|
+
// service error types
|
|
573
|
+
// ─────────────────────────────────────────────────────────
|
|
574
|
+
if (type === "event") {
|
|
575
|
+
if (record.event === "api call") {
|
|
576
|
+
const eventTime = dayjs(record.time);
|
|
577
|
+
const dayInDataset = eventTime.diff(DATASET_START, "days", true);
|
|
578
|
+
|
|
579
|
+
// Hook #6: Outage day errors
|
|
580
|
+
if (dayInDataset >= 40 && dayInDataset < 42) {
|
|
581
|
+
if (chance.bool({ likelihood: 40 })) {
|
|
582
|
+
record.is_error = true;
|
|
583
|
+
record.error_type = chance.pickone([
|
|
584
|
+
"service_overloaded",
|
|
585
|
+
"internal_server_error",
|
|
586
|
+
"gateway_timeout",
|
|
587
|
+
]);
|
|
588
|
+
record.latency_ms = Math.floor((record.latency_ms || 1500) * 3);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Hook #2: Model migration wave (event portion)
|
|
593
|
+
// After day 60, 35% of Build/Enterprise users switch to opus-4-7
|
|
594
|
+
if (dayInDataset >= 60) {
|
|
595
|
+
if (
|
|
596
|
+
(record.api_tier === "Build" || record.api_tier === "Enterprise") &&
|
|
597
|
+
chance.bool({ likelihood: 35 })
|
|
598
|
+
) {
|
|
599
|
+
record.model = "opus-4-7";
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return record;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// ─────────────────────────────────────────────────────────
|
|
608
|
+
// Hook: USER PROFILE ENRICHMENT (user)
|
|
609
|
+
// Tag user profiles for discoverability
|
|
610
|
+
// ─────────────────────────────────────────────────────────
|
|
611
|
+
if (type === "user") {
|
|
612
|
+
// Defaults for hook-driven user properties
|
|
613
|
+
record.hit_rate_limit_early = false;
|
|
614
|
+
record.has_early_eval = false;
|
|
615
|
+
record.has_eval_pipeline = false;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ─────────────────────────────────────────────────────────
|
|
619
|
+
// EVERYTHING HOOKS
|
|
620
|
+
// ─────────────────────────────────────────────────────────
|
|
621
|
+
if (type === "everything") {
|
|
622
|
+
let events = record;
|
|
623
|
+
if (!events.length) return record;
|
|
624
|
+
const profile = meta && meta.profile ? meta.profile : {};
|
|
625
|
+
|
|
626
|
+
// Stamp superProps from profile for consistency
|
|
627
|
+
events.forEach(e => {
|
|
628
|
+
if (profile.api_tier) e.api_tier = profile.api_tier;
|
|
629
|
+
if (profile.primary_use_case) e.primary_use_case = profile.primary_use_case;
|
|
630
|
+
if (profile.sdk_language) e.sdk_language = profile.sdk_language;
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// Determine first event time for relative day calculations
|
|
634
|
+
const sortedByTime = [...events].sort((a, b) => dayjs(a.time).valueOf() - dayjs(b.time).valueOf());
|
|
635
|
+
const firstEventTime = sortedByTime.length > 0 ? dayjs(sortedByTime[0].time) : DATASET_START;
|
|
636
|
+
|
|
637
|
+
// ─────────────────────────────────────────────────────
|
|
638
|
+
// Hook #5: TIER-BASED CONTEXT WINDOW (SUBSCRIPTION TIER)
|
|
639
|
+
// Scale context_window and input_tokens by tier
|
|
640
|
+
// ─────────────────────────────────────────────────────
|
|
641
|
+
const tier = profile.api_tier || "Free";
|
|
642
|
+
const contextWindow = tier === "Enterprise" ? 2000000 : tier === "Build" ? 1000000 : 200000;
|
|
643
|
+
const inputMultiplier = tier === "Enterprise" ? 4 : tier === "Build" ? 2 : 1;
|
|
644
|
+
|
|
645
|
+
events.forEach(e => {
|
|
646
|
+
if (e.event === "api call") {
|
|
647
|
+
e.context_window = contextWindow;
|
|
648
|
+
e.input_tokens = Math.floor((e.input_tokens || 2000) * inputMultiplier);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
// ─────────────────────────────────────────────────────
|
|
653
|
+
// Hook #1: PROMPT CACHING ADOPTION (CONVERSION)
|
|
654
|
+
// Users with any cache_enabled=true get 70% cost reduction
|
|
655
|
+
// on all subsequent api calls
|
|
656
|
+
// ─────────────────────────────────────────────────────
|
|
657
|
+
// ~25% of users have caching enabled on at least one event
|
|
658
|
+
const userId = events[0] && events[0].user_id;
|
|
659
|
+
const idHash = String(userId || "").split("").reduce((acc, c) => acc + c.charCodeAt(0), 0);
|
|
660
|
+
const isCacheUser = (idHash % 4) === 0;
|
|
661
|
+
|
|
662
|
+
if (isCacheUser) {
|
|
663
|
+
let cacheActivated = false;
|
|
664
|
+
// Activate caching on events after the first 20% of user events
|
|
665
|
+
const activationPoint = Math.floor(events.length * 0.2);
|
|
666
|
+
events.forEach((e, idx) => {
|
|
667
|
+
if (e.event === "api call") {
|
|
668
|
+
if (idx >= activationPoint) {
|
|
669
|
+
cacheActivated = true;
|
|
670
|
+
}
|
|
671
|
+
if (cacheActivated) {
|
|
672
|
+
e.cache_enabled = true;
|
|
673
|
+
e.cost_usd = Math.round((e.cost_usd || 0.01) * 0.3 * 10000) / 10000;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// ─────────────────────────────────────────────────────
|
|
680
|
+
// Hook #2: MODEL MIGRATION WAVE (everything portion)
|
|
681
|
+
// opus-4-7 users get 1.5x tokens_used
|
|
682
|
+
// (model assignment done in event hook above)
|
|
683
|
+
// ─────────────────────────────────────────────────────
|
|
684
|
+
events.forEach(e => {
|
|
685
|
+
if (e.event === "api call" && e.model === "opus-4-7") {
|
|
686
|
+
e.tokens_used = Math.floor((e.tokens_used || 2500) * 1.5);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
// ─────────────────────────────────────────────────────
|
|
691
|
+
// Hook #3: AGENTIC LOOP POWER USERS (BEHAVIORS TOGETHER)
|
|
692
|
+
// Users with tool use + multi_turn get 8x tokens, 3x events
|
|
693
|
+
// ─────────────────────────────────────────────────────
|
|
694
|
+
const hasToolUse = events.some(e => e.event === "tool use call");
|
|
695
|
+
const hasMultiTurn = events.some(e => e.event === "api call" && e.multi_turn === true);
|
|
696
|
+
const isAgenticUser = hasToolUse && hasMultiTurn;
|
|
697
|
+
|
|
698
|
+
if (isAgenticUser) {
|
|
699
|
+
// Mark all api calls as agentic and boost tokens
|
|
700
|
+
events.forEach(e => {
|
|
701
|
+
if (e.event === "api call") {
|
|
702
|
+
e.is_agentic_user = true;
|
|
703
|
+
e.tokens_used = Math.floor((e.tokens_used || 2500) * 8);
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// Inject 3x extra api call events by cloning existing ones
|
|
708
|
+
const apiCalls = events.filter(e => e.event === "api call");
|
|
709
|
+
const extraCount = apiCalls.length * 2; // 2 extra per existing = 3x total
|
|
710
|
+
for (let i = 0; i < extraCount; i++) {
|
|
711
|
+
const template = apiCalls[i % apiCalls.length];
|
|
712
|
+
if (template) {
|
|
713
|
+
events.push({
|
|
714
|
+
...template,
|
|
715
|
+
time: dayjs(template.time).add(chance.integer({ min: 1, max: 120 }), "minutes").toISOString(),
|
|
716
|
+
user_id: template.user_id,
|
|
717
|
+
is_agentic_user: true,
|
|
718
|
+
multi_turn: true,
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// ─────────────────────────────────────────────────────
|
|
725
|
+
// Hook #4: RATE LIMIT CHURN
|
|
726
|
+
// >=5 rate limit errors in first 7 days -> remove 60% of
|
|
727
|
+
// events after week 1
|
|
728
|
+
// ─────────────────────────────────────────────────────
|
|
729
|
+
const firstWeekEnd = firstEventTime.add(7, "days");
|
|
730
|
+
const earlyRateLimits = events.filter(e =>
|
|
731
|
+
e.event === "rate limit error" &&
|
|
732
|
+
dayjs(e.time).isBefore(firstWeekEnd)
|
|
733
|
+
).length;
|
|
734
|
+
|
|
735
|
+
if (earlyRateLimits >= 5) {
|
|
736
|
+
// Tag the user profile
|
|
737
|
+
if (profile) profile.hit_rate_limit_early = true;
|
|
738
|
+
|
|
739
|
+
// Remove 60% of events after week 1
|
|
740
|
+
events = events.filter(e => {
|
|
741
|
+
if (dayjs(e.time).isAfter(firstWeekEnd)) {
|
|
742
|
+
return chance.bool({ likelihood: 40 }); // keep 40% = remove 60%
|
|
743
|
+
}
|
|
744
|
+
return true;
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// ─────────────────────────────────────────────────────
|
|
749
|
+
// Hook #7: BATCH API DISCOUNT (PURCHASE VALUE)
|
|
750
|
+
// Batch users get 50% lower cost_per_token, 2x tokens_used
|
|
751
|
+
// ─────────────────────────────────────────────────────
|
|
752
|
+
const isBatchUser = events.some(e => e.event === "batch job submitted");
|
|
753
|
+
|
|
754
|
+
if (isBatchUser) {
|
|
755
|
+
events.forEach(e => {
|
|
756
|
+
if (e.event === "api call") {
|
|
757
|
+
e.is_batch_user = true;
|
|
758
|
+
e.cost_per_token = Math.round((e.cost_per_token || 0.00001) * 0.5 * 10000000) / 10000000;
|
|
759
|
+
e.tokens_used = Math.floor((e.tokens_used || 2500) * 2);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// ─────────────────────────────────────────────────────
|
|
765
|
+
// Hook #8: EVAL-DRIVEN RETENTION
|
|
766
|
+
// Early eval users (first 7 days) get 75% D30 retention
|
|
767
|
+
// Non-eval users get only 25% D30 retention (remove events)
|
|
768
|
+
// ─────────────────────────────────────────────────────
|
|
769
|
+
const hasEarlyEval = events.some(e =>
|
|
770
|
+
e.event === "eval job" &&
|
|
771
|
+
dayjs(e.time).isBefore(firstWeekEnd)
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
if (hasEarlyEval) {
|
|
775
|
+
// Tag user profile
|
|
776
|
+
if (profile) {
|
|
777
|
+
profile.has_early_eval = true;
|
|
778
|
+
profile.has_eval_pipeline = true;
|
|
779
|
+
}
|
|
780
|
+
// Early eval users keep all their events (high retention)
|
|
781
|
+
} else {
|
|
782
|
+
// Non-eval users: remove 75% of events after day 30
|
|
783
|
+
const day30 = firstEventTime.add(30, "days");
|
|
784
|
+
events = events.filter(e => {
|
|
785
|
+
if (dayjs(e.time).isAfter(day30)) {
|
|
786
|
+
return chance.bool({ likelihood: 25 }); // keep 25%
|
|
787
|
+
}
|
|
788
|
+
return true;
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return events;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return record;
|
|
796
|
+
},
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
export default config;
|