make-mp-data 3.0.3 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +46 -0
  2. package/dungeons/array-of-object-lookup-schema.json +327 -0
  3. package/dungeons/array-of-object-lookup.js +29 -9
  4. package/dungeons/capstone/capstone-ic3.js +291 -0
  5. package/dungeons/capstone/capstone-ic4.js +598 -0
  6. package/dungeons/capstone/capstone-ic5.js +668 -0
  7. package/dungeons/capstone/generate-product-lookup.js +309 -0
  8. package/dungeons/ecommerce-schema.json +462 -0
  9. package/dungeons/{copilot.js → ecommerce.js} +79 -17
  10. package/dungeons/education-schema.json +2409 -0
  11. package/dungeons/education.js +226 -462
  12. package/dungeons/fintech-schema.json +14034 -0
  13. package/dungeons/fintech.js +134 -413
  14. package/dungeons/foobar-schema.json +403 -0
  15. package/dungeons/foobar.js +27 -4
  16. package/dungeons/food-delivery-schema.json +192 -0
  17. package/dungeons/food-delivery.js +602 -0
  18. package/dungeons/food-schema.json +1152 -0
  19. package/dungeons/food.js +173 -406
  20. package/dungeons/gaming-schema.json +1270 -0
  21. package/dungeons/gaming.js +182 -42
  22. package/dungeons/insurance-application-schema.json +204 -0
  23. package/dungeons/insurance-application.js +605 -0
  24. package/dungeons/media-schema.json +906 -0
  25. package/dungeons/media.js +250 -420
  26. package/dungeons/retention-cadence-schema.json +78 -0
  27. package/dungeons/retention-cadence.js +35 -1
  28. package/dungeons/rpg-schema.json +4526 -0
  29. package/dungeons/rpg.js +171 -429
  30. package/dungeons/sanity-schema.json +255 -0
  31. package/dungeons/sanity.js +21 -10
  32. package/dungeons/sass-schema.json +1291 -0
  33. package/dungeons/sass.js +241 -368
  34. package/dungeons/scd-schema.json +919 -0
  35. package/dungeons/scd.js +41 -13
  36. package/dungeons/simple-schema.json +608 -0
  37. package/dungeons/simple.js +52 -15
  38. package/dungeons/simplest-schema.json +1418 -0
  39. package/dungeons/simplest.js +392 -0
  40. package/dungeons/social-schema.json +1118 -0
  41. package/dungeons/social.js +150 -391
  42. package/dungeons/text-generation-schema.json +3096 -0
  43. package/dungeons/text-generation.js +71 -0
  44. package/index.js +8 -6
  45. package/lib/core/config-validator.js +28 -8
  46. package/lib/core/storage.js +5 -5
  47. package/lib/generators/events.js +4 -4
  48. package/lib/orchestrators/mixpanel-sender.js +16 -13
  49. package/lib/orchestrators/user-loop.js +14 -6
  50. package/lib/templates/soup-presets.js +188 -0
  51. package/lib/utils/utils.js +52 -6
  52. package/package.json +1 -1
  53. package/types.d.ts +20 -3
  54. package/dungeons/adspend.js +0 -130
  55. package/dungeons/anon.js +0 -128
  56. package/dungeons/benchmark-heavy.js +0 -240
  57. package/dungeons/benchmark-light.js +0 -140
  58. package/dungeons/big.js +0 -226
  59. package/dungeons/business.js +0 -391
  60. package/dungeons/complex.js +0 -428
  61. package/dungeons/experiments.js +0 -137
  62. package/dungeons/funnels.js +0 -309
  63. package/dungeons/mil.js +0 -323
  64. package/dungeons/mirror.js +0 -161
  65. package/dungeons/soup-test.js +0 -52
  66. package/dungeons/streaming.js +0 -372
  67. package/dungeons/strict-event-test.js +0 -30
  68. package/dungeons/student-teacher.js +0 -438
  69. package/dungeons/too-big-events.js +0 -203
  70. package/dungeons/user-agent.js +0 -209
@@ -0,0 +1,605 @@
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
+
6
+ const SEED = "dm4-insurance";
7
+ dayjs.extend(utc);
8
+ const chance = u.initChance(SEED);
9
+ const num_users = 5_000;
10
+ const days = 100;
11
+
12
+ /** @typedef {import("../types").Dungeon} Config */
13
+
14
+ /*
15
+ * ===================================================================
16
+ * DATASET OVERVIEW
17
+ * ===================================================================
18
+ *
19
+ * SAFEHAVEN INSURANCE — Web Application Dungeon
20
+ *
21
+ * SafeHaven Insurance is a modern insurance application where users
22
+ * browse coverage options, request quotes, complete multi-step
23
+ * applications, manage policies, file claims, and make premium payments.
24
+ *
25
+ * - 5,000 users over 100 days
26
+ * - 600,000 events across 18 event types
27
+ * - 5 funnels (onboarding, application, approval, claims, renewal)
28
+ * - 5 insurance types as super property (auto, home, life, health, renters)
29
+ * - Deterministic app versioning (2.10 → 2.11 → 2.12 → 2.13)
30
+ * - Platforms: web, iOS, Android
31
+ *
32
+ * CORE LOOP:
33
+ * Users create an account, browse insurance products, and request quotes.
34
+ * They start multi-step applications (personal info, coverage selection,
35
+ * document upload) and submit for approval. Once approved, they activate
36
+ * a policy, make premium payments, and manage renewals. If something
37
+ * goes wrong, they file claims and create support tickets.
38
+ *
39
+ * KEY DATA STORY — VERSION 2.13 RELEASE:
40
+ * The app has gone through versions 2.10 → 2.11 → 2.12 → 2.13.
41
+ * Version 2.13 was released 10 days ago and fixed critical UX issues.
42
+ * Two effects are visible: support ticket volume drops immediately,
43
+ * and application funnel conversion improves significantly.
44
+ */
45
+
46
+ /*
47
+ * ===================================================================
48
+ * ANALYTICS HOOKS
49
+ * ===================================================================
50
+ *
51
+ * -------------------------------------------------------------------
52
+ * 1. VERSION STAMPING (event hook)
53
+ * -------------------------------------------------------------------
54
+ * Every event gets a deterministic app_version based on its timestamp.
55
+ * All users shift simultaneously on release dates:
56
+ * - Days 0-30: v2.10
57
+ * - Days 30-60: v2.11
58
+ * - Days 60-90: v2.12
59
+ * - Last 10 days: v2.13
60
+ *
61
+ * MIXPANEL REPORT:
62
+ * 1. Insights > "page viewed" > Breakdown by app_version
63
+ * 2. Chart event volume over time, colored by app_version
64
+ * 3. Confirm: no overlap between versions (deterministic cutover)
65
+ *
66
+ * -------------------------------------------------------------------
67
+ * 2. SUPPORT TICKET VOLUME DROP (everything hook)
68
+ * -------------------------------------------------------------------
69
+ * Before v2.13, support ticket volume is high — each user gets 2-3
70
+ * extra tickets injected with bug-related categories (form_crash,
71
+ * login_error, page_timeout, payment_failure). After v2.13, tickets
72
+ * are progressively removed (30% on day 1 → 85% on day 10).
73
+ *
74
+ * MIXPANEL REPORT:
75
+ * 1. Insights > "support ticket created" count over time (line chart)
76
+ * 2. Break down by app_version: v2.12 has high volume, v2.13 drops
77
+ * 3. Filter issue_category to bug categories (form_crash, etc.)
78
+ * 4. Filter pre_release_bug = true for injected tickets only
79
+ * 5. Compare weekly ticket volume before vs after v2.13 release
80
+ *
81
+ * -------------------------------------------------------------------
82
+ * 3. APPLICATION CONVERSION BOOST (everything hook)
83
+ * -------------------------------------------------------------------
84
+ * Before v2.13, 40% of "application approved" and "policy activated"
85
+ * events are removed, simulating a broken application flow. After
86
+ * v2.13, all events are preserved — creating a visible conversion jump.
87
+ *
88
+ * MIXPANEL REPORT:
89
+ * 1. Funnels > application submitted → approved → policy activated
90
+ * 2. Break down by app_version (v2.12 vs v2.13)
91
+ * 3. Compare conversion rates: pre-v2.13 ~60% of post-v2.13
92
+ * 4. Insights > "application approved" count over time — step change
93
+ *
94
+ * ===================================================================
95
+ * EXPECTED METRICS SUMMARY
96
+ * ===================================================================
97
+ *
98
+ * Hook | Metric | Pre-v2.13 | Post-v2.13
99
+ * ------------------------|-------------------------|-----------|----------
100
+ * Version Stamping | Events per version | ~30d each | 10 days
101
+ * Support Ticket Volume | Weekly ticket count | HIGH | ~70% lower
102
+ * Application Conversion | Approval rate | ~42% | ~70%
103
+ *
104
+ * ===================================================================
105
+ * ADVANCED ANALYSIS IDEAS
106
+ * ===================================================================
107
+ *
108
+ * 1. Version Impact Dashboard: Chart both support tickets AND
109
+ * application conversion by app_version to show v2.13's dual impact.
110
+ *
111
+ * 2. Bug Category Analysis: Which pre_release_bug categories were most
112
+ * common? Do they correlate with the application steps where users
113
+ * were dropping off?
114
+ *
115
+ * 3. Platform Comparison: Did the v2.13 improvement affect all platforms
116
+ * equally, or did web/iOS/Android see different magnitudes?
117
+ *
118
+ * 4. Insurance Type Breakdown: Are certain insurance types (auto vs home
119
+ * vs life) more affected by the conversion improvement?
120
+ *
121
+ * 5. Time-to-Approval: Did v2.13 also change the approval_time_hours
122
+ * distribution, or just the volume of approvals?
123
+ */
124
+
125
+ // ── Time constants for hook calculations ──
126
+ const NOW = dayjs();
127
+ const DATASET_START = NOW.subtract(days, "days");
128
+ const V211_DATE = DATASET_START.add(30, "days");
129
+ const V212_DATE = DATASET_START.add(60, "days");
130
+ const V213_DATE = NOW.subtract(10, "days");
131
+
132
+ /** @type {Config} */
133
+ const config = {
134
+ token: "3651ac6819e284fbf528d86036eec785",
135
+ seed: SEED,
136
+ numDays: days,
137
+ numEvents: num_users * 120,
138
+ numUsers: num_users,
139
+ hasAnonIds: false,
140
+ hasSessionIds: true,
141
+ format: "json",
142
+ gzip: true,
143
+ alsoInferFunnels: false,
144
+ hasLocation: true,
145
+ hasAndroidDevices: true,
146
+ hasIOSDevices: true,
147
+ hasDesktopDevices: true,
148
+ hasBrowser: false,
149
+ hasCampaigns: false,
150
+ isAnonymous: false,
151
+ hasAdSpend: false,
152
+ percentUsersBornInDataset: 35,
153
+ hasAvatar: true,
154
+ batchSize: 2_500_000,
155
+ concurrency: 1,
156
+ writeToDisk: false,
157
+
158
+ scdProps: {},
159
+ mirrorProps: {},
160
+ lookupTables: [],
161
+
162
+ // ── Events ──
163
+ events: [
164
+ {
165
+ event: "account created",
166
+ weight: 1,
167
+ isFirstEvent: true,
168
+ properties: {
169
+ signup_source: ["web", "mobile", "agent_referral", "partner"],
170
+ account_type: ["individual", "family", "business"],
171
+ },
172
+ },
173
+ {
174
+ event: "page viewed",
175
+ weight: 10,
176
+ properties: {
177
+ page_name: [
178
+ "home",
179
+ "quotes",
180
+ "coverage_options",
181
+ "claims",
182
+ "faq",
183
+ "profile",
184
+ "payment",
185
+ "documents",
186
+ ],
187
+ referrer: ["direct", "google", "email", "social_media"],
188
+ },
189
+ },
190
+ {
191
+ event: "quote requested",
192
+ weight: 5,
193
+ properties: {
194
+ coverage_level: ["basic", "standard", "premium"],
195
+ deductible: u.weighNumRange(250, 5000, 0.5, 1000),
196
+ },
197
+ },
198
+ {
199
+ event: "quote received",
200
+ weight: 4,
201
+ properties: {
202
+ monthly_premium: u.weighNumRange(30, 800, 0.3, 150),
203
+ coverage_amount: u.weighNumRange(10000, 500000, 0.3, 100000),
204
+ quote_comparison_count: u.weighNumRange(1, 5, 0.5, 2),
205
+ },
206
+ },
207
+ {
208
+ event: "application started",
209
+ weight: 4,
210
+ properties: {
211
+ coverage_level: ["basic", "standard", "premium"],
212
+ estimated_premium: u.weighNumRange(30, 800, 0.3, 150),
213
+ },
214
+ },
215
+ {
216
+ event: "application step completed",
217
+ weight: 6,
218
+ properties: {
219
+ step_name: [
220
+ "personal_info",
221
+ "coverage_selection",
222
+ "medical_history",
223
+ "vehicle_info",
224
+ "beneficiary",
225
+ "review",
226
+ ],
227
+ step_number: u.weighNumRange(1, 6),
228
+ time_on_step_sec: u.weighNumRange(15, 600, 0.3, 90),
229
+ },
230
+ },
231
+ {
232
+ event: "document uploaded",
233
+ weight: 3,
234
+ properties: {
235
+ document_type: [
236
+ "drivers_license",
237
+ "proof_of_address",
238
+ "vehicle_registration",
239
+ "medical_records",
240
+ "property_photos",
241
+ ],
242
+ file_size_kb: u.weighNumRange(50, 5000, 0.5, 500),
243
+ },
244
+ },
245
+ {
246
+ event: "application submitted",
247
+ weight: 3,
248
+ properties: {
249
+ documents_attached: u.weighNumRange(1, 5, 0.5, 2),
250
+ application_time_min: u.weighNumRange(5, 60, 0.3, 15),
251
+ },
252
+ },
253
+ {
254
+ event: "application approved",
255
+ weight: 2,
256
+ properties: {
257
+ approved_premium: u.weighNumRange(30, 800, 0.3, 150),
258
+ approval_time_hours: u.weighNumRange(1, 72, 0.5, 24),
259
+ },
260
+ },
261
+ {
262
+ event: "policy activated",
263
+ weight: 2,
264
+ properties: {
265
+ policy_term_months: [6, 12, 12, 12, 24],
266
+ effective_date_offset_days: u.weighNumRange(0, 30, 0.5, 7),
267
+ },
268
+ },
269
+ {
270
+ event: "claim filed",
271
+ weight: 2,
272
+ properties: {
273
+ claim_type: [
274
+ "collision",
275
+ "theft",
276
+ "water_damage",
277
+ "fire",
278
+ "medical",
279
+ "liability",
280
+ ],
281
+ estimated_amount: u.weighNumRange(100, 50000, 0.3, 3000),
282
+ },
283
+ },
284
+ {
285
+ event: "claim status checked",
286
+ weight: 4,
287
+ properties: {
288
+ claim_status: [
289
+ "submitted",
290
+ "under_review",
291
+ "approved",
292
+ "denied",
293
+ "payment_pending",
294
+ ],
295
+ days_since_filed: u.weighNumRange(1, 60, 0.5, 10),
296
+ },
297
+ },
298
+ {
299
+ event: "payment made",
300
+ weight: 5,
301
+ properties: {
302
+ payment_method: ["credit_card", "bank_transfer", "auto_pay", "check"],
303
+ amount: u.weighNumRange(30, 800, 0.3, 150),
304
+ payment_status: ["success", "success", "success", "success", "failed"],
305
+ },
306
+ },
307
+ {
308
+ event: "support ticket created",
309
+ weight: 4,
310
+ properties: {
311
+ issue_category: [
312
+ "billing",
313
+ "claims",
314
+ "coverage",
315
+ "technical",
316
+ "policy_change",
317
+ ],
318
+ priority: ["low", "medium", "medium", "high"],
319
+ channel: ["chat", "phone", "email", "web_form"],
320
+ },
321
+ },
322
+ {
323
+ event: "support ticket resolved",
324
+ weight: 3,
325
+ properties: {
326
+ resolution_type: [
327
+ "self_service",
328
+ "agent_assisted",
329
+ "escalated",
330
+ "auto_resolved",
331
+ ],
332
+ satisfaction_score: u.weighNumRange(1, 5, 1, 4),
333
+ resolution_time_hours: u.weighNumRange(0.5, 72, 0.3, 8),
334
+ },
335
+ },
336
+ {
337
+ event: "coverage reviewed",
338
+ weight: 4,
339
+ properties: {
340
+ current_premium: u.weighNumRange(30, 800, 0.3, 150),
341
+ coverage_adequate: [true, true, true, false],
342
+ },
343
+ },
344
+ {
345
+ event: "profile updated",
346
+ weight: 2,
347
+ properties: {
348
+ field_updated: [
349
+ "address",
350
+ "phone",
351
+ "email",
352
+ "beneficiary",
353
+ "payment_method",
354
+ "vehicle_info",
355
+ ],
356
+ },
357
+ },
358
+ {
359
+ event: "renewal completed",
360
+ weight: 2,
361
+ properties: {
362
+ renewal_premium: u.weighNumRange(30, 800, 0.3, 150),
363
+ premium_change_pct: u.weighNumRange(-15, 20, 1, 3),
364
+ auto_renewed: [true, true, false],
365
+ },
366
+ },
367
+ ],
368
+
369
+ // ── Funnels ──
370
+ funnels: [
371
+ {
372
+ name: "Onboarding",
373
+ sequence: ["account created", "page viewed", "quote requested"],
374
+ isFirstFunnel: true,
375
+ conversionRate: 85,
376
+ timeToConvert: 0.5,
377
+ },
378
+ {
379
+ name: "Application Completion",
380
+ sequence: [
381
+ "application started",
382
+ "application step completed",
383
+ "document uploaded",
384
+ "application submitted",
385
+ ],
386
+ conversionRate: 60,
387
+ timeToConvert: 48,
388
+ weight: 4,
389
+ order: "sequential",
390
+ },
391
+ {
392
+ name: "Application Approval",
393
+ sequence: [
394
+ "application submitted",
395
+ "application approved",
396
+ "policy activated",
397
+ ],
398
+ conversionRate: 70,
399
+ timeToConvert: 72,
400
+ weight: 3,
401
+ order: "sequential",
402
+ },
403
+ {
404
+ name: "Claims Process",
405
+ sequence: [
406
+ "claim filed",
407
+ "claim status checked",
408
+ "support ticket created",
409
+ ],
410
+ conversionRate: 50,
411
+ timeToConvert: 24,
412
+ weight: 2,
413
+ },
414
+ {
415
+ name: "Policy Renewal",
416
+ sequence: ["coverage reviewed", "payment made", "renewal completed"],
417
+ conversionRate: 65,
418
+ timeToConvert: 72,
419
+ weight: 3,
420
+ },
421
+ ],
422
+
423
+ // ── Super Props (on every event) ──
424
+ superProps: {
425
+ platform: ["web", "ios", "android"],
426
+ insurance_type: ["auto", "home", "life", "health", "renters"],
427
+ },
428
+
429
+ // ── User Props (set once per user) ──
430
+ userProps: {
431
+ age_range: ["18-25", "26-35", "36-45", "46-55", "56-65", "65+"],
432
+ risk_profile: ["low", "medium", "high"],
433
+ policy_count: u.weighNumRange(0, 5, 0.5, 1),
434
+ lifetime_premium: u.weighNumRange(0, 50000, 0.3, 5000),
435
+ preferred_contact: ["email", "phone", "app_notification"],
436
+ },
437
+
438
+ // ── Hook Function ──
439
+ /**
440
+ * ARCHITECTED ANALYTICS HOOKS
441
+ *
442
+ * This hook function creates 3 deliberate patterns in the data:
443
+ *
444
+ * 1. VERSION STAMPING (event): Every event gets a deterministic app_version
445
+ * based on its timestamp. v2.10 → v2.11 → v2.12 → v2.13.
446
+ * All users shift simultaneously on release dates.
447
+ *
448
+ * 2. SUPPORT TICKET VOLUME (everything): Pre-v2.13 period has inflated
449
+ * support ticket volume (2-3 extra tickets per user with bug-related
450
+ * categories). Post-v2.13, tickets are progressively removed — creating
451
+ * a clear volume drop that trends downward.
452
+ *
453
+ * 3. APPLICATION CONVERSION BOOST (everything): Pre-v2.13, ~40% of
454
+ * application approved and policy activated events are removed,
455
+ * lowering the effective funnel conversion rate. Post-v2.13 events
456
+ * are left intact, making the conversion visibly jump up.
457
+ */
458
+ hook: function (record, type, meta) {
459
+ // =============================================================
460
+ // Hook #1: VERSION STAMPING (event)
461
+ // Deterministic app_version on every event based on timestamp.
462
+ // v2.10 (days 0-30) → v2.11 (30-60) → v2.12 (60-90) → v2.13 (last 10 days)
463
+ // =============================================================
464
+ if (type === "event") {
465
+ const eventTime = dayjs(record.time);
466
+
467
+ if (eventTime.isBefore(V211_DATE)) {
468
+ record.app_version = "2.10";
469
+ } else if (eventTime.isBefore(V212_DATE)) {
470
+ record.app_version = "2.11";
471
+ } else if (eventTime.isBefore(V213_DATE)) {
472
+ record.app_version = "2.12";
473
+ } else {
474
+ record.app_version = "2.13";
475
+ }
476
+ }
477
+
478
+ // =============================================================
479
+ // Hook #2 & #3: SUPPORT TICKET VOLUME + APPLICATION CONVERSION
480
+ // (everything)
481
+ // =============================================================
482
+ if (type === "everything") {
483
+ const userEvents = record;
484
+ if (userEvents.length === 0) return record;
485
+
486
+ // Find a user_id from any existing event
487
+ const userId =
488
+ userEvents.find((e) => e.user_id)?.user_id ||
489
+ userEvents[0]?.device_id;
490
+
491
+ // ─── Hook #2: SUPPORT TICKET VOLUME ───
492
+ // PRE-V2.13: Inject 2-3 extra support tickets with bug-related categories
493
+ const preV213Tickets = userEvents.filter(
494
+ (e) =>
495
+ e.event === "support ticket created" &&
496
+ dayjs(e.time).isBefore(V213_DATE)
497
+ );
498
+
499
+ if (preV213Tickets.length > 0) {
500
+ const extraCount = chance.integer({ min: 2, max: 3 });
501
+ const bugCategories = [
502
+ "form_crash",
503
+ "login_error",
504
+ "page_timeout",
505
+ "payment_failure",
506
+ ];
507
+
508
+ for (let i = 0; i < extraCount; i++) {
509
+ // Pick a random pre-v2.13 ticket to base timing on
510
+ const sourceTicket = chance.pickone(preV213Tickets);
511
+ const sourceTime = dayjs(sourceTicket.time);
512
+ // Offset by a few hours to days
513
+ const offsetHours = chance.integer({ min: 1, max: 72 });
514
+ let newTime = sourceTime.add(offsetHours, "hours");
515
+ // Ensure it stays before v2.13
516
+ if (newTime.isAfter(V213_DATE)) {
517
+ newTime = V213_DATE.subtract(
518
+ chance.integer({ min: 1, max: 48 }),
519
+ "hours"
520
+ );
521
+ }
522
+
523
+ // Compute app_version for injected event
524
+ let injectedVersion;
525
+ if (newTime.isBefore(V211_DATE)) {
526
+ injectedVersion = "2.10";
527
+ } else if (newTime.isBefore(V212_DATE)) {
528
+ injectedVersion = "2.11";
529
+ } else {
530
+ injectedVersion = "2.12"; // always pre-v2.13
531
+ }
532
+
533
+ userEvents.push({
534
+ event: "support ticket created",
535
+ time: newTime.toISOString(),
536
+ user_id: userId,
537
+ app_version: injectedVersion,
538
+ issue_category: chance.pickone(bugCategories),
539
+ priority: chance.pickone(["medium", "high", "high"]),
540
+ channel: chance.pickone([
541
+ "chat",
542
+ "phone",
543
+ "email",
544
+ "web_form",
545
+ ]),
546
+ pre_release_bug: true,
547
+ });
548
+ }
549
+ }
550
+
551
+ // POST-V2.13: Remove support tickets with increasing probability
552
+ // Day 1 after release: ~30% removal
553
+ // Day 5: ~60% removal
554
+ // Day 10: ~85% removal
555
+ for (let i = userEvents.length - 1; i >= 0; i--) {
556
+ const evt = userEvents[i];
557
+ if (evt.event === "support ticket created") {
558
+ const evtTime = dayjs(evt.time);
559
+ if (evtTime.isAfter(V213_DATE)) {
560
+ const daysSinceRelease = evtTime.diff(
561
+ V213_DATE,
562
+ "days",
563
+ true
564
+ );
565
+ // Linear ramp: 30% base + 5.5% per day → ~85% at day 10
566
+ const removalLikelihood = Math.min(
567
+ 85,
568
+ 30 + daysSinceRelease * 5.5
569
+ );
570
+ if (chance.bool({ likelihood: removalLikelihood })) {
571
+ userEvents.splice(i, 1);
572
+ }
573
+ }
574
+ }
575
+ }
576
+
577
+ // ─── Hook #3: APPLICATION CONVERSION BOOST ───
578
+ // PRE-V2.13: Remove ~40% of application approved and policy activated
579
+ // events, lowering effective conversion before the release.
580
+ for (let i = userEvents.length - 1; i >= 0; i--) {
581
+ const evt = userEvents[i];
582
+ if (
583
+ (evt.event === "application approved" ||
584
+ evt.event === "policy activated") &&
585
+ dayjs(evt.time).isBefore(V213_DATE)
586
+ ) {
587
+ if (chance.bool({ likelihood: 40 })) {
588
+ userEvents.splice(i, 1);
589
+ }
590
+ }
591
+ }
592
+
593
+ // Sort events by time after injection/removal
594
+ userEvents.sort(
595
+ (a, b) => new Date(a.time) - new Date(b.time)
596
+ );
597
+
598
+ return record;
599
+ }
600
+
601
+ return record;
602
+ },
603
+ };
604
+
605
+ export default config;