@ak--47/dungeon-master 1.1.0 → 1.2.1

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 (72) hide show
  1. package/dungeons/technical/ad-spend-schema.json +2 -2
  2. package/dungeons/technical/ad-spend.js +16 -5
  3. package/dungeons/technical/anonymous-users-schema.json +4 -3
  4. package/dungeons/technical/anonymous-users.js +16 -6
  5. package/dungeons/technical/array-of-object-lookup-schema.json +1 -1
  6. package/dungeons/technical/array-of-object-lookup.js +22 -6
  7. package/dungeons/technical/experiments-schema.json +25 -19
  8. package/dungeons/technical/experiments.js +24 -6
  9. package/dungeons/technical/foobar.js +29 -13
  10. package/dungeons/technical/group-analytics-schema.json +92 -0
  11. package/dungeons/technical/group-analytics.js +16 -6
  12. package/dungeons/technical/mirror-strategies-schema.json +4 -4
  13. package/dungeons/technical/mirror-strategies.js +16 -6
  14. package/dungeons/technical/nested-objects.js +16 -5
  15. package/dungeons/technical/retention-cadence.js +14 -9
  16. package/dungeons/technical/sanity-schema.json +22 -22
  17. package/dungeons/technical/sanity.js +26 -6
  18. package/dungeons/technical/scale-test.js +16 -5
  19. package/dungeons/technical/scd-schema.json +230 -33
  20. package/dungeons/technical/scd.js +25 -5
  21. package/dungeons/technical/simple-schema.json +132 -48
  22. package/dungeons/technical/simple.js +21 -6
  23. package/dungeons/technical/simplest-schema.json +168 -79
  24. package/dungeons/technical/simplest.js +24 -7
  25. package/dungeons/technical/text-generation-schema.json +7 -4
  26. package/dungeons/technical/text-generation.js +23 -8
  27. package/dungeons/user/.gitkeep +0 -0
  28. package/dungeons/user/shalini.js +847 -0
  29. package/dungeons/vertical/community-schema.json +71 -49
  30. package/dungeons/vertical/community.js +57 -30
  31. package/dungeons/vertical/devtools-schema.json +69 -48
  32. package/dungeons/vertical/devtools.js +58 -31
  33. package/dungeons/vertical/ecommerce-schema.json +126 -108
  34. package/dungeons/vertical/ecommerce.js +32 -8
  35. package/dungeons/vertical/education-schema.json +5261 -272
  36. package/dungeons/vertical/education.js +45 -22
  37. package/dungeons/vertical/fintech-schema.json +165 -98
  38. package/dungeons/vertical/fintech.js +43 -11
  39. package/dungeons/vertical/fitness-schema.json +69 -47
  40. package/dungeons/vertical/fitness.js +56 -24
  41. package/dungeons/vertical/food-delivery-schema.json +36237 -187
  42. package/dungeons/vertical/food-delivery.js +67 -15
  43. package/dungeons/vertical/gaming-schema.json +104 -60
  44. package/dungeons/vertical/gaming.js +61 -16
  45. package/dungeons/vertical/healthcare-schema.json +70 -48
  46. package/dungeons/vertical/healthcare.js +77 -42
  47. package/dungeons/vertical/insurance-application-schema.json +66 -47
  48. package/dungeons/vertical/insurance-application.js +34 -10
  49. package/dungeons/vertical/logistics-schema.json +60 -38
  50. package/dungeons/vertical/logistics.js +77 -43
  51. package/dungeons/vertical/marketplace-schema.json +65 -43
  52. package/dungeons/vertical/marketplace.js +52 -21
  53. package/dungeons/vertical/media-schema.json +4085 -123
  54. package/dungeons/vertical/media.js +65 -18
  55. package/dungeons/vertical/rpg-schema.json +1984 -147
  56. package/dungeons/vertical/rpg.js +47 -11
  57. package/dungeons/vertical/sass-schema.json +2622 -147
  58. package/dungeons/vertical/sass.js +40 -10
  59. package/dungeons/vertical/social-schema.json +146 -78
  60. package/dungeons/vertical/social.js +39 -10
  61. package/dungeons/vertical/travel-schema.json +57 -35
  62. package/dungeons/vertical/travel.js +58 -21
  63. package/index.js +4 -1
  64. package/lib/core/config-validator.js +13 -2
  65. package/lib/core/context.js +9 -0
  66. package/lib/core/storage.js +2 -0
  67. package/lib/generators/events.js +3 -5
  68. package/lib/orchestrators/user-loop.js +24 -1
  69. package/lib/utils/utils.js +129 -9
  70. package/package.json +1 -1
  71. package/scripts/extract-dungeon-schema.mjs +64 -18
  72. package/types.d.ts +8 -3
@@ -1,16 +1,23 @@
1
+ // ── TWEAK THESE ──
2
+ const SEED = "dm4-logistics";
3
+ const num_days = 100;
4
+ const num_users = 5_000;
5
+ const avg_events_per_user = 120;
6
+ let token = "your-mixpanel-token";
7
+
8
+ // ── env overrides ──
9
+ if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
10
+
1
11
  import dayjs from "dayjs";
2
12
  import utc from "dayjs/plugin/utc.js";
3
13
  import "dotenv/config";
4
14
  import * as u from "../../lib/utils/utils.js";
5
15
  import * as v from "ak-tools";
6
16
 
7
- const SEED = "dm4-logistics";
8
17
  dayjs.extend(utc);
9
18
  const chance = u.initChance(SEED);
10
- const num_users = 5_000;
11
- const days = 100;
12
19
  const NOW = dayjs();
13
- const DATASET_START = NOW.subtract(days, "days");
20
+ const DATASET_START = NOW.subtract(num_days, "days");
14
21
 
15
22
  /** @typedef {import("../../types").Dungeon} Config */
16
23
 
@@ -216,12 +223,14 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
216
223
  * networks with large logistics teams, driving higher ACV.
217
224
  *
218
225
  * -------------------------------------------------------------------
219
- * 8. ENTERPRISE INTEGRATION FUNNEL LIFT (funnel-pre hook)
226
+ * 8. SMALL-BUSINESS CONVERSION DROP (everything hook)
220
227
  * -------------------------------------------------------------------
221
228
  *
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.
229
+ * PATTERN: Small-business users lose ~35% of "alert configured"
230
+ * events (last step of the Integration Setup funnel). This is
231
+ * implemented via event filtering in the everything hook rather
232
+ * than conversionRate modification in funnel-pre, so the effect
233
+ * is not diluted by organic (non-funnel) events.
225
234
  *
226
235
  * HOW TO FIND IT IN MIXPANEL:
227
236
  *
@@ -229,10 +238,10 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
229
238
  * - Report type: Funnels
230
239
  * - Steps: "integration connected" -> "report generated" -> "alert configured"
231
240
  * - Breakdown: user property "company_tier"
232
- * - Expected: enterprise ~60% vs small_business ~30% conversion
241
+ * - Expected: small_business ~20% vs enterprise/mid_market ~30% conversion
233
242
  *
234
- * REAL-WORLD ANALOGUE: Enterprise IT teams have dedicated resources
235
- * for integrations, leading to faster and more complete setup.
243
+ * REAL-WORLD ANALOGUE: Small businesses lack dedicated IT teams,
244
+ * leading to incomplete integration setup and lower alert adoption.
236
245
  *
237
246
  * ===================================================================
238
247
  * EXPECTED METRICS SUMMARY
@@ -247,15 +256,15 @@ const supplierIds = v.range(1, 150).map(() => `SUP_${v.uid(6)}`);
247
256
  * Alert Fatigue | response_time_hours | 4h | 8-12h | 2-3x
248
257
  * Trial Churn | events after wk 1 | 5 | 2.5 | 0.5x
249
258
  * Enterprise Profiles | warehouse_count | 3 | 10 | 3.3x
250
- * Enterprise Integration Lift | funnel conversion | 30% | 45% | 1.5x
259
+ * Small-Biz Conversion Drop | funnel conversion | 30% | 20% | 0.65x
251
260
  */
252
261
 
253
262
  /** @type {Config} */
254
263
  const config = {
255
- token: "",
264
+ token,
256
265
  seed: SEED,
257
- numDays: days,
258
- numEvents: num_users * 120,
266
+ numDays: num_days,
267
+ numEvents: num_users * avg_events_per_user,
259
268
  numUsers: num_users,
260
269
  hasAnonIds: false,
261
270
  hasSessionIds: true,
@@ -272,10 +281,16 @@ const config = {
272
281
  hasAdSpend: false,
273
282
  percentUsersBornInDataset: 35,
274
283
  hasAvatar: true,
275
- batchSize: 2_500_000,
276
284
  concurrency: 1,
277
285
  writeToDisk: false,
278
- scdProps: {},
286
+ scdProps: {
287
+ company_tier: {
288
+ values: ["trial", "starter", "professional", "enterprise"],
289
+ frequency: "month",
290
+ timing: "fuzzy",
291
+ max: 6
292
+ }
293
+ },
279
294
  mirrorProps: {},
280
295
  lookupTables: [],
281
296
 
@@ -499,7 +514,7 @@ const config = {
499
514
 
500
515
  // -- SuperProps ----------------------------------------------------
501
516
  superProps: {
502
- platform: ["web", "web", "desktop_app"],
517
+ Platform: ["web", "web", "desktop_app"],
503
518
  subscription_plan: ["free_trial", "free_trial", "starter", "starter", "professional", "enterprise"],
504
519
  },
505
520
 
@@ -510,6 +525,8 @@ const config = {
510
525
  employee_count: u.weighNumRange(1, 500, 0.3, 25),
511
526
  industry: ["retail", "manufacturing", "food_beverage", "pharma", "electronics"],
512
527
  integration_count: [0],
528
+ Platform: ["web", "web", "desktop_app"],
529
+ subscription_plan: ["free_trial", "free_trial", "starter", "starter", "professional", "enterprise"],
513
530
  },
514
531
 
515
532
  // -- Phase 2: Personas --------------------------------------------
@@ -691,16 +708,9 @@ const config = {
691
708
  }
692
709
 
693
710
  // -- HOOK 8: ENTERPRISE INTEGRATION FUNNEL LIFT (funnel-pre) --
694
- // Enterprise subscribers convert 1.5x through integration funnel.
711
+ // (conversionRate boost removed filtering applied in everything hook instead)
695
712
  if (type === "funnel-pre") {
696
- if (meta && meta.profile) {
697
- const tier = meta.profile.company_tier;
698
- if (tier === "enterprise") {
699
- record.conversionRate = Math.min(record.conversionRate * 1.5, 95);
700
- } else if (tier === "mid_market") {
701
- record.conversionRate = Math.min(record.conversionRate * 1.2, 85);
702
- }
703
- }
713
+ // no-op: conversion differentiation handled via event filtering below
704
714
  }
705
715
 
706
716
  // -- HOOK 1: MONTH-END REPORTING SURGE (event) ----------------
@@ -722,29 +732,53 @@ const config = {
722
732
 
723
733
  // -- EVERYTHING HOOKS -----------------------------------------
724
734
  if (type === "everything") {
725
- const events = record;
726
- if (!events.length) return record;
735
+ if (!record.length) return record;
736
+ const profile = meta.profile;
737
+
738
+ // -- SUPER-PROP STAMPING ----------------------------------
739
+ // Stamp superProps from profile so they are consistent per-user.
740
+ if (profile) {
741
+ const plat = profile.Platform;
742
+ const plan = profile.subscription_plan;
743
+ record.forEach(e => {
744
+ if (plat) e.Platform = plat;
745
+ if (plan) e.subscription_plan = plan;
746
+ });
747
+ }
748
+
749
+ // -- HOOK 8: SMALL-BUSINESS CONVERSION DROP ---------------
750
+ // Small-business users lose ~35% of "alert configured" events
751
+ // (last step of Integration Setup funnel), simulating lower
752
+ // conversion for smaller teams without dedicated IT resources.
753
+ if (profile && profile.company_tier === "small_business") {
754
+ record = record.filter(e => {
755
+ if (e.event === "alert configured" && chance.bool({ likelihood: 35 })) {
756
+ return false;
757
+ }
758
+ return true;
759
+ });
760
+ }
727
761
 
728
762
  // -- HOOK 3: REORDER ACCURACY BY TIER ---------------------
729
763
  // Enterprise users get 10% of stockout alerts removed.
730
- if (meta.profile && meta.profile.company_tier === "enterprise") {
731
- for (let i = events.length - 1; i >= 0; i--) {
732
- if (events[i].event === "stockout alert" && chance.bool({ likelihood: 10 })) {
733
- events.splice(i, 1);
764
+ if (profile && profile.company_tier === "enterprise") {
765
+ for (let i = record.length - 1; i >= 0; i--) {
766
+ if (record[i].event === "stockout alert" && chance.bool({ likelihood: 10 })) {
767
+ record.splice(i, 1);
734
768
  }
735
769
  }
736
770
  }
737
771
 
738
772
  // -- HOOK 4: INTEGRATION COMPLETION DRIVES RETENTION ------
739
773
  // Users with 3+ integration events get cloned report events.
740
- const integrationCount = events.filter(e => e.event === "integration connected").length;
774
+ const integrationCount = record.filter(e => e.event === "integration connected").length;
741
775
  if (integrationCount >= 3) {
742
- const templateReport = events.find(e => e.event === "report generated");
776
+ const templateReport = record.find(e => e.event === "report generated");
743
777
  if (templateReport) {
744
- const integrationEvents = events.filter(e => e.event === "integration connected");
778
+ const integrationEvents = record.filter(e => e.event === "integration connected");
745
779
  integrationEvents.forEach(ie => {
746
780
  if (chance.bool({ likelihood: 65 })) {
747
- events.push({
781
+ record.push({
748
782
  ...templateReport,
749
783
  time: dayjs(ie.time).add(chance.integer({ min: 1, max: 5 }), "days").toISOString(),
750
784
  user_id: ie.user_id,
@@ -758,7 +792,7 @@ const config = {
758
792
 
759
793
  // -- HOOK 5: ALERT FATIGUE --------------------------------
760
794
  // Users with >30 stockout alerts get increasing response times.
761
- const alertEvents = events.filter(e => e.event === "stockout alert");
795
+ const alertEvents = record.filter(e => e.event === "stockout alert");
762
796
  if (alertEvents.length > 30) {
763
797
  alertEvents.forEach((alert, idx) => {
764
798
  if (idx >= 20) {
@@ -770,14 +804,14 @@ const config = {
770
804
 
771
805
  // -- HOOK 6: TRIAL CHURN ----------------------------------
772
806
  // Users with <10 events lose 50% after day 7.
773
- if (events.length < 10) {
774
- const userFirstEvent = events[0];
807
+ if (record.length < 10) {
808
+ const userFirstEvent = record[0];
775
809
  if (userFirstEvent) {
776
810
  const firstTime = dayjs(userFirstEvent.time);
777
811
  const cutoff = firstTime.add(7, "days");
778
- for (let i = events.length - 1; i >= 0; i--) {
779
- if (dayjs(events[i].time).isAfter(cutoff) && chance.bool({ likelihood: 50 })) {
780
- events.splice(i, 1);
812
+ for (let i = record.length - 1; i >= 0; i--) {
813
+ if (dayjs(record[i].time).isAfter(cutoff) && chance.bool({ likelihood: 50 })) {
814
+ record.splice(i, 1);
781
815
  }
782
816
  }
783
817
  }
@@ -399,6 +399,9 @@
399
399
  },
400
400
  {
401
401
  "event": "account deactivated",
402
+ "isChurnEvent": true,
403
+ "returnLikelihood": 0.15,
404
+ "isStrictEvent": true,
402
405
  "properties": {
403
406
  "reason": [
404
407
  "low_sales",
@@ -410,48 +413,6 @@
410
413
  }
411
414
  }
412
415
  ],
413
- "superProps": {
414
- "platform": [
415
- "ios",
416
- "android",
417
- "web"
418
- ],
419
- "category": [
420
- "electronics",
421
- "clothing",
422
- "home_garden",
423
- "collectibles",
424
- "sports",
425
- "toys",
426
- "automotive"
427
- ]
428
- },
429
- "userProps": {
430
- "user_type": [
431
- "buyer"
432
- ],
433
- "segment": [
434
- "window_shopper"
435
- ],
436
- "seller_rating": {
437
- "$range": [
438
- 1,
439
- 4
440
- ]
441
- },
442
- "total_transactions": [
443
- 0
444
- ],
445
- "response_time_hours": {
446
- "$range": [
447
- 7,
448
- 38
449
- ]
450
- },
451
- "store_name": [
452
- "none"
453
- ]
454
- },
455
416
  "funnels": [
456
417
  {
457
418
  "sequence": [
@@ -460,6 +421,7 @@
460
421
  "item viewed",
461
422
  "add to cart"
462
423
  ],
424
+ "name": "Buyer Onboarding",
463
425
  "isFirstFunnel": true,
464
426
  "conversionRate": 40,
465
427
  "timeToConvert": 72,
@@ -472,6 +434,7 @@
472
434
  "add to cart",
473
435
  "purchase completed"
474
436
  ],
437
+ "name": "Browse to Purchase",
475
438
  "conversionRate": 30,
476
439
  "timeToConvert": 48,
477
440
  "weight": 5
@@ -483,6 +446,7 @@
483
446
  "offer received",
484
447
  "offer accepted"
485
448
  ],
449
+ "name": "Seller Listing Flow",
486
450
  "conversionRate": 25,
487
451
  "timeToConvert": 168,
488
452
  "weight": 3
@@ -493,6 +457,7 @@
493
457
  "message sent",
494
458
  "offer accepted"
495
459
  ],
460
+ "name": "Offer Negotiation",
496
461
  "conversionRate": 35,
497
462
  "timeToConvert": 72,
498
463
  "weight": 2
@@ -503,9 +468,66 @@
503
468
  "shipping updated",
504
469
  "review submitted"
505
470
  ],
471
+ "name": "Review After Purchase",
506
472
  "conversionRate": 20,
507
473
  "timeToConvert": 336,
508
474
  "weight": 2
509
475
  }
510
- ]
476
+ ],
477
+ "superProps": {
478
+ "Platform": [
479
+ "ios",
480
+ "android",
481
+ "web"
482
+ ],
483
+ "category": [
484
+ "electronics",
485
+ "clothing",
486
+ "home_garden",
487
+ "collectibles",
488
+ "sports",
489
+ "toys",
490
+ "automotive"
491
+ ]
492
+ },
493
+ "userProps": {
494
+ "user_type": [
495
+ "buyer"
496
+ ],
497
+ "segment": [
498
+ "window_shopper"
499
+ ],
500
+ "seller_rating": {
501
+ "$range": [
502
+ 1,
503
+ 4
504
+ ]
505
+ },
506
+ "total_transactions": [
507
+ 0
508
+ ],
509
+ "response_time_hours": {
510
+ "$range": [
511
+ 7,
512
+ 38
513
+ ]
514
+ },
515
+ "store_name": [
516
+ "none"
517
+ ]
518
+ },
519
+ "scdProps": {
520
+ "seller_tier": {
521
+ "type": "user",
522
+ "frequency": "month",
523
+ "timing": "fuzzy",
524
+ "max": 8,
525
+ "values": [
526
+ "new",
527
+ "verified",
528
+ "power",
529
+ "featured"
530
+ ]
531
+ }
532
+ }
511
533
  }
@@ -1,16 +1,23 @@
1
+ // ── TWEAK THESE ──
2
+ const SEED = "dm4-marketplace";
3
+ const num_days = 100;
4
+ const num_users = 5_000;
5
+ const avg_events_per_user = 120;
6
+ let token = "your-mixpanel-token";
7
+
8
+ // ── env overrides ──
9
+ if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
10
+
1
11
  import dayjs from "dayjs";
2
12
  import utc from "dayjs/plugin/utc.js";
3
13
  import "dotenv/config";
4
14
  import * as u from "../../lib/utils/utils.js";
5
15
  import * as v from "ak-tools";
6
16
 
7
- const SEED = "dm4-marketplace";
8
17
  dayjs.extend(utc);
9
18
  const chance = u.initChance(SEED);
10
- const num_users = 5_000;
11
- const days = 100;
12
19
  const NOW = dayjs();
13
- const DATASET_START = NOW.subtract(days, "days");
20
+ const DATASET_START = NOW.subtract(num_days, "days");
14
21
 
15
22
  /** @typedef {import("../../types").Dungeon} Config */
16
23
 
@@ -213,12 +220,12 @@ const listingIds = v.range(1, 500).map(() => `LST_${v.uid(8)}`);
213
220
  * high transaction volumes, and near-perfect ratings.
214
221
  *
215
222
  * ───────────────────────────────────────────────────────────────
216
- * 8. FREQUENT BUYER FUNNEL LIFT (funnel-pre hook)
223
+ * 8. FREQUENT BUYER FUNNEL LIFT (everything hook)
217
224
  * ───────────────────────────────────────────────────────────────
218
225
  *
219
- * PATTERN: Users with segment "frequent_buyer" get 1.3x conversion
220
- * rate on the browse-to-purchase funnel. Repeat buyers know what
221
- * they want and convert faster.
226
+ * PATTERN: Non-frequent-buyer users lose ~25% of "purchase completed"
227
+ * events (last step of Browse to Purchase funnel), simulating lower
228
+ * conversion. Frequent buyers retain all purchase events.
222
229
  *
223
230
  * HOW TO FIND IT IN MIXPANEL:
224
231
  *
@@ -249,10 +256,10 @@ const listingIds = v.range(1, 500).map(() => `LST_${v.uid(8)}`);
249
256
 
250
257
  /** @type {Config} */
251
258
  const config = {
252
- token: "",
259
+ token,
253
260
  seed: SEED,
254
- numDays: days,
255
- numEvents: num_users * 120,
261
+ numDays: num_days,
262
+ numEvents: num_users * avg_events_per_user,
256
263
  numUsers: num_users,
257
264
  hasAnonIds: false,
258
265
  hasSessionIds: true,
@@ -269,10 +276,16 @@ const config = {
269
276
  hasAdSpend: false,
270
277
  percentUsersBornInDataset: 35,
271
278
  hasAvatar: true,
272
- batchSize: 2_500_000,
273
279
  concurrency: 1,
274
280
  writeToDisk: false,
275
- scdProps: {},
281
+ scdProps: {
282
+ seller_tier: {
283
+ values: ["new", "verified", "power", "featured"],
284
+ frequency: "month",
285
+ timing: "fuzzy",
286
+ max: 8
287
+ }
288
+ },
276
289
  mirrorProps: {},
277
290
  lookupTables: [],
278
291
 
@@ -496,7 +509,7 @@ const config = {
496
509
 
497
510
  // ── SuperProps ──────────────────────────────────────────
498
511
  superProps: {
499
- platform: ["ios", "android", "web"],
512
+ Platform: ["ios", "android", "web"],
500
513
  category: ["electronics", "clothing", "home_garden", "collectibles", "sports", "toys", "automotive"],
501
514
  },
502
515
 
@@ -508,6 +521,8 @@ const config = {
508
521
  total_transactions: [0],
509
522
  response_time_hours: u.weighNumRange(0, 48, 0.3),
510
523
  store_name: ["none"],
524
+ Platform: ["ios", "android", "web"],
525
+ category: ["electronics", "clothing", "home_garden", "collectibles", "sports", "toys", "automotive"],
511
526
  },
512
527
 
513
528
  // ── Personas ──────────────────────────────────
@@ -715,13 +730,9 @@ const config = {
715
730
  }
716
731
 
717
732
  // ── HOOK 8: FREQUENT BUYER FUNNEL LIFT (funnel-pre) ──
718
- // Frequent buyers convert 1.3x better through browse-to-purchase.
733
+ // (conversionRate filtering moved to everything hook)
719
734
  if (type === "funnel-pre") {
720
- if (meta && meta.profile) {
721
- if (meta.profile.segment === "frequent_buyer") {
722
- record.conversionRate = Math.min(record.conversionRate * 1.3, 90);
723
- }
724
- }
735
+ // no-op: conversion filtering handled via event dropping in everything hook
725
736
  }
726
737
 
727
738
  // ── HOOK 1: FEE CHANGE IMPACT (event) ────────────────
@@ -749,11 +760,31 @@ const config = {
749
760
 
750
761
  // ── EVERYTHING HOOKS ─────────────────────────────────
751
762
  if (type === "everything") {
752
- const events = record;
763
+ let events = record;
753
764
  if (!events.length) return record;
754
765
 
755
766
  const profile = meta.profile;
756
767
 
768
+ // ── SUPERPROP STAMPING ──────────────────────────
769
+ // Stamp superProps from profile so they are consistent per user.
770
+ if (profile) {
771
+ events.forEach(e => {
772
+ if (profile.Platform) e.Platform = profile.Platform;
773
+ if (profile.category) e.category = profile.category;
774
+ });
775
+ }
776
+
777
+ // ── HOOK 8: FREQUENT BUYER CONVERSION FILTER ────
778
+ // Non-frequent-buyer users drop ~25% of "purchase completed"
779
+ // (last step of Browse to Purchase funnel) to simulate lower conversion.
780
+ if (profile && profile.segment !== "frequent_buyer") {
781
+ record = record.filter(e => {
782
+ if (e.event === "purchase completed" && chance.bool({ likelihood: 25 })) return false;
783
+ return true;
784
+ });
785
+ events = record;
786
+ }
787
+
757
788
  // ── HOOK 3: SELLER SUCCESS → BUYER TRUST ─────────
758
789
  // Power sellers get 2x purchase events (cloned from existing).
759
790
  if (profile && profile.segment === "power_seller") {