@ak--47/dungeon-master 1.2.3 → 1.3.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 (89) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +51 -13
  3. package/dungeons/technical/ad-spend.js +2 -2
  4. package/dungeons/technical/anonymous-users.js +2 -2
  5. package/dungeons/technical/array-of-object-lookup.js +2 -4
  6. package/dungeons/technical/experiments.js +2 -2
  7. package/dungeons/technical/foobar.js +2 -2
  8. package/dungeons/technical/group-analytics.js +2 -2
  9. package/dungeons/technical/mirror-strategies.js +2 -2
  10. package/dungeons/technical/nested-objects.js +2 -2
  11. package/dungeons/technical/retention-cadence.js +2 -3
  12. package/dungeons/technical/sanity.js +2 -2
  13. package/dungeons/technical/scale-test.js +2 -2
  14. package/dungeons/technical/scd.js +2 -2
  15. package/dungeons/technical/simple.js +5 -6
  16. package/dungeons/technical/simplest-schema.json +5 -0
  17. package/dungeons/technical/simplest.js +2 -2
  18. package/dungeons/technical/text-generation.js +3 -3
  19. package/dungeons/vertical/ai-platform.js +858 -0
  20. package/dungeons/vertical/community.js +84 -40
  21. package/dungeons/vertical/crypto.js +830 -0
  22. package/dungeons/vertical/dating.js +744 -0
  23. package/dungeons/vertical/devtools.js +175 -69
  24. package/dungeons/vertical/ecommerce.js +242 -94
  25. package/dungeons/vertical/education.js +330 -313
  26. package/dungeons/vertical/fintech.js +442 -313
  27. package/dungeons/vertical/fitness.js +143 -61
  28. package/dungeons/vertical/food-delivery.js +327 -353
  29. package/dungeons/vertical/gaming.js +912 -382
  30. package/dungeons/vertical/healthcare.js +142 -63
  31. package/dungeons/vertical/insurance-application.js +170 -76
  32. package/dungeons/vertical/logistics.js +115 -20
  33. package/dungeons/vertical/marketplace.js +152 -58
  34. package/dungeons/vertical/media.js +248 -384
  35. package/dungeons/vertical/real-estate.js +781 -0
  36. package/dungeons/vertical/sass.js +255 -266
  37. package/dungeons/vertical/social.js +264 -206
  38. package/dungeons/vertical/travel.js +117 -41
  39. package/index.js +17 -17
  40. package/lib/core/config-validator.js +159 -31
  41. package/lib/core/context.js +10 -24
  42. package/lib/core/storage.js +6 -1
  43. package/lib/generators/events.js +11 -14
  44. package/lib/generators/funnels.js +12 -4
  45. package/lib/generators/mirror.js +3 -2
  46. package/lib/generators/product-names.js +1 -1
  47. package/lib/generators/scd.js +2 -1
  48. package/lib/generators/text.js +1 -1
  49. package/lib/orchestrators/user-loop.js +81 -47
  50. package/lib/templates/macro-presets.js +111 -0
  51. package/lib/templates/soup-presets.js +19 -36
  52. package/lib/utils/utils.js +71 -39
  53. package/package.json +8 -2
  54. package/scripts/smoke-test-all.mjs +162 -0
  55. package/scripts/verify-runner.mjs +72 -24
  56. package/types.d.ts +251 -51
  57. package/dungeons/technical/ad-spend-schema.json +0 -128
  58. package/dungeons/technical/anonymous-users-schema.json +0 -92
  59. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  60. package/dungeons/technical/experiments-schema.json +0 -203
  61. package/dungeons/technical/foobar-schema.json +0 -362
  62. package/dungeons/technical/group-analytics-schema.json +0 -241
  63. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  64. package/dungeons/technical/nested-objects-schema.json +0 -145
  65. package/dungeons/technical/retention-cadence-schema.json +0 -37
  66. package/dungeons/technical/sanity-schema.json +0 -185
  67. package/dungeons/technical/scale-test-schema.json +0 -70
  68. package/dungeons/technical/scd-schema.json +0 -467
  69. package/dungeons/technical/simple-schema.json +0 -362
  70. package/dungeons/technical/text-generation-schema.json +0 -1062
  71. package/dungeons/user/.gitkeep +0 -0
  72. package/dungeons/vertical/community-schema.json +0 -579
  73. package/dungeons/vertical/devtools-schema.json +0 -601
  74. package/dungeons/vertical/ecommerce-schema.json +0 -604
  75. package/dungeons/vertical/education-schema.json +0 -5686
  76. package/dungeons/vertical/fintech-schema.json +0 -630
  77. package/dungeons/vertical/fitness-schema.json +0 -530
  78. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  79. package/dungeons/vertical/gaming-schema.json +0 -438
  80. package/dungeons/vertical/healthcare-schema.json +0 -549
  81. package/dungeons/vertical/insurance-application-schema.json +0 -485
  82. package/dungeons/vertical/logistics-schema.json +0 -574
  83. package/dungeons/vertical/marketplace-schema.json +0 -533
  84. package/dungeons/vertical/media-schema.json +0 -4749
  85. package/dungeons/vertical/rpg-schema.json +0 -2491
  86. package/dungeons/vertical/rpg.js +0 -976
  87. package/dungeons/vertical/sass-schema.json +0 -3128
  88. package/dungeons/vertical/social-schema.json +0 -620
  89. package/dungeons/vertical/travel-schema.json +0 -580
@@ -2,7 +2,7 @@
2
2
  const SEED = "dm4-healthcare";
3
3
  const num_days = 100;
4
4
  const num_users = 5_000;
5
- const avg_events_per_user = 120;
5
+ const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -16,9 +16,6 @@ import * as v from "ak-tools";
16
16
 
17
17
  dayjs.extend(utc);
18
18
  const chance = u.initChance(SEED);
19
- const NOW = dayjs();
20
- const DATASET_START = NOW.subtract(num_days, "days");
21
-
22
19
  /** @typedef {import("../../types").Dungeon} Config */
23
20
 
24
21
  // Generate consistent doctor/clinic IDs at module level
@@ -54,7 +51,10 @@ const clinicIds = v.range(1, 25).map(() => `CLINIC_${v.uid(4)}`);
54
51
 
55
52
  /**
56
53
  * ═══════════════════════════════════════════════════════════════
57
- * ANALYTICS HOOKS (8 hooks)
54
+ * ANALYTICS HOOKS (10 hooks)
55
+ *
56
+ * NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable
57
+ * via raw-prop breakdowns (HOD, day, tier) or behavioral cohorts.
58
58
  * ═══════════════════════════════════════════════════════════════
59
59
  *
60
60
  * ───────────────────────────────────────────────────────────────
@@ -70,9 +70,9 @@ const clinicIds = v.range(1, 25).map(() => `CLINIC_${v.uid(4)}`);
70
70
  * • Report type: Insights
71
71
  * • Event: "consultation completed"
72
72
  * • Measure: Average of "consultation_fee"
73
- * • Breakdown: "after_hours"
74
- * • Expected: after_hours=true should show ~1.5x avg fee vs false
75
- * (true ≈ $112, false ≈ $75)
73
+ * • Breakdown: hour of day
74
+ * • Expected: hours 19-06 UTC ~ 1.5x avg fee vs hours 07-18
75
+ * (after-hours ≈ $112, business ≈ $75)
76
76
  *
77
77
  * REAL-WORLD ANALOGUE: Telehealth platforms charge premiums for
78
78
  * after-hours urgent consultations, a key revenue driver.
@@ -226,28 +226,78 @@ const clinicIds = v.range(1, 25).map(() => `CLINIC_${v.uid(4)}`);
226
226
  * REAL-WORLD ANALOGUE: Free-tier patients face longer wait times
227
227
  * and limited scheduling, reducing completed consultations.
228
228
  *
229
+ * ───────────────────────────────────────────────────────────────
230
+ * 9. BOOKING FUNNEL TIME-TO-CONVERT (funnel-post)
231
+ *
232
+ * PATTERN: Premium tier completes Booking funnel 1.35x faster
233
+ * (factor 0.74); Free tier 1.33x slower (factor 1.33).
234
+ *
235
+ * HOW TO FIND IT IN MIXPANEL:
236
+ *
237
+ * Report 1: Booking Funnel Median Time-to-Convert by Tier
238
+ * - Funnels > "symptom search" -> "appointment booked" -> "consultation completed"
239
+ * - Measure: Median time to convert
240
+ * - Breakdown: subscription_tier
241
+ * - Expected: premium ~ 0.74x; free ~ 1.33x
242
+ *
243
+ * NOTE (funnel-post measurement): visible only via Mixpanel funnel
244
+ * median TTC. Cross-event MIN→MIN SQL queries on raw events do NOT
245
+ * show this — funnel-post adjusts gaps within funnel instances, not
246
+ * across the user's full event history.
247
+ *
248
+ * ───────────────────────────────────────────────────────────────
249
+ * 10. CONSULTATION-COUNT MAGIC NUMBER (everything)
250
+ *
251
+ * PATTERN: Sweet 3-6 consultations → +25% on consultation_fee.
252
+ * Over 7+ → days_until_followup multiplied by 1.5 (over-consulted
253
+ * patients wait 50% longer for next visit). No flag.
254
+ *
255
+ * HOW TO FIND IT IN MIXPANEL:
256
+ *
257
+ * Report 1: Avg Consultation Fee by Consult-Count Bucket
258
+ * - Cohort A: users with 3-6 "consultation completed"
259
+ * - Cohort B: users with 0-2
260
+ * - Event: "consultation completed"
261
+ * - Measure: Average of "consultation_fee"
262
+ * - Expected: A ~ 1.25x B
263
+ *
264
+ * Report 2: Follow-Up Wait Time on Heavy Consulters
265
+ * - Cohort C: users with >= 7 consultations
266
+ * - Cohort A: users with 3-6
267
+ * - Event: "follow up scheduled"
268
+ * - Measure: Average of "days_until_followup"
269
+ * - Expected: C ~ 1.5x A (longer gap before next visit)
270
+ *
271
+ * REAL-WORLD ANALOGUE: Engaged patients pay more; over-engaged
272
+ * patients hit care-fatigue and stretch the gap to next visit.
273
+ *
229
274
  * ═══════════════════════════════════════════════════════════════
230
275
  * EXPECTED METRICS SUMMARY
231
276
  * ═══════════════════════════════════════════════════════════════
232
277
  *
233
- * Hook | Metric | Baseline | Effect | Ratio
234
- * ────────────────────────────|─────────────────────|──────────|─────────|──────
235
- * After-Hours Pricing | consultation_fee | $75 | $112 | 1.5x
236
- * Flu Season Spike | respiratory appts | ~15% | ~60% | 4x
237
- * Experienced Doctor Sat. | satisfaction_score | 3.5 | 4.2 | 1.2x
238
- * Video Follow-Up Lift | follow-ups/user | 1.0 | 2.0 | 2x
239
- * Chronic Refill Chain | refills (chronic) | 1 | 3-4 | 3-4x
240
- * Occasional No-Shows | booking→consult | 95% | 75% | 0.79x
241
- * Doctor Specialization | years_experience | 5 | 22 | 4.4x
242
- * Free-Tier Conversion Drop | funnel conversion | 40% | 28% | 0.7x
278
+ * Hook | Metric | Baseline | Effect | Ratio
279
+ * ----------------------------|---------------------|----------|-----------|------
280
+ * After-Hours Pricing | consultation_fee | 1x | 1.5x | 1.5x
281
+ * Flu Season Spike | respiratory share | ~ 15% | ~ 60% | 4x
282
+ * Experienced Doctor Sat. | satisfaction_score | 3.5 | 4.2 | 1.2x
283
+ * Video Follow-Up Lift | follow-ups/user | 1x | 2x | 2x
284
+ * Chronic Refill Chain | refills (chronic) | 1 | 3-4 | 3-4x
285
+ * Occasional No-Shows | booking→consult | 95% | 75% | -20%
286
+ * Doctor Specialization | years_experience | 5 | 22 | 4.4x
287
+ * Free-Tier Conversion Drop | funnel conversion | 40% | 28% | -30%
288
+ * Booking T2C | median min by tier | 1x | 0.74/1.33x| ~ 1.8x range
289
+ * Consult-Count Magic Number | sweet consult fee | 1x | 1.25x | 1.25x
290
+ * Consult-Count Magic Number | over days_until_fu | 1x | 1.5x | +50%
243
291
  */
244
292
 
245
293
  /** @type {Config} */
246
294
  const config = {
247
295
  token,
248
296
  seed: SEED,
249
- numDays: num_days,
250
- numEvents: num_users * avg_events_per_user,
297
+ datasetStart: "2026-01-01T00:00:00Z",
298
+ datasetEnd: "2026-04-28T23:59:59Z",
299
+ // numDays: num_days,
300
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
251
301
  numUsers: num_users,
252
302
  hasAnonIds: false,
253
303
  hasSessionIds: true,
@@ -262,7 +312,6 @@ const config = {
262
312
  hasCampaigns: false,
263
313
  isAnonymous: false,
264
314
  hasAdSpend: false,
265
- percentUsersBornInDataset: 35,
266
315
  hasAvatar: true,
267
316
  concurrency: 1,
268
317
  writeToDisk: false,
@@ -304,7 +353,6 @@ const config = {
304
353
  condition_type: ["general", "general", "general", "respiratory", "dermatology", "mental_health", "chronic", "pediatric"],
305
354
  wait_time_hours: u.weighNumRange(1, 72, 0.4),
306
355
  appointment_type: ["new_patient", "follow_up", "follow_up", "urgent", "routine", "routine"],
307
- after_hours: [false],
308
356
  },
309
357
  },
310
358
  {
@@ -317,7 +365,6 @@ const config = {
317
365
  consultation_fee: u.weighNumRange(25, 200, 0.4, 75),
318
366
  satisfaction_score: u.weighNumRange(1, 5, 0.8, 3),
319
367
  condition_type: ["general", "general", "respiratory", "dermatology", "mental_health", "chronic", "pediatric"],
320
- after_hours: [false],
321
368
  },
322
369
  },
323
370
  {
@@ -578,8 +625,8 @@ const config = {
578
625
  { name: "premium", price: 29.99 },
579
626
  ],
580
627
  lifecycle: {
581
- trialToPayRate: 0.30,
582
- upgradeRate: 0.08,
628
+ trialToPayRate: 0.55,
629
+ upgradeRate: 0.20,
583
630
  downgradeRate: 0.03,
584
631
  churnRate: 0.05,
585
632
  winBackRate: 0.10,
@@ -656,44 +703,38 @@ const config = {
656
703
  }
657
704
  }
658
705
 
659
- // ── HOOK 8: PREMIUM BOOKING FUNNEL LIFT (funnel-pre)
660
- // (conversionRate boost removed filtering applied in everything hook instead)
661
- if (type === "funnel-pre") {
662
- // no-op: conversion differentiation handled via event filtering below
663
- }
664
-
665
- // ── HOOK 1: AFTER-HOURS SURGE PRICING (event) ────────
666
- // Consultations between 7PM-7AM get 1.5x fee.
667
- if (type === "event") {
668
- if (record.event === "consultation completed" || record.event === "appointment booked") {
669
- const hour = dayjs(record.time).hour();
670
- if (hour >= 19 || hour < 7) {
671
- record.after_hours = true;
672
- if (record.consultation_fee) {
673
- record.consultation_fee = Math.floor(record.consultation_fee * 1.5);
706
+ // HOOK 9 (T2C): BOOKING FUNNEL TIME-TO-CONVERT (funnel-post)
707
+ // Premium tier completes Booking funnel 1.35x faster (factor 0.74);
708
+ // Free tier 1.33x slower (factor 1.33).
709
+ if (type === "funnel-post") {
710
+ const segment = meta?.profile?.subscription_tier;
711
+ if (Array.isArray(record) && record.length > 1) {
712
+ const factor = (
713
+ segment === "premium" ? 0.74 :
714
+ segment === "free" ? 1.33 :
715
+ 1.0
716
+ );
717
+ if (factor !== 1.0) {
718
+ for (let i = 1; i < record.length; i++) {
719
+ const prev = dayjs(record[i - 1].time);
720
+ const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
721
+ record[i].time = prev.add(newGap, "milliseconds").toISOString();
674
722
  }
675
723
  }
676
724
  }
677
-
678
- // ── HOOK 2: FLU SEASON SPIKE (event) ─────────────
679
- // Days 50-70: respiratory conditions dominate, wait times double.
680
- const FLU_START = DATASET_START.add(50, "days");
681
- const FLU_END = DATASET_START.add(70, "days");
682
- const eventTime = dayjs(record.time);
683
- if (record.event === "appointment booked" && eventTime.isAfter(FLU_START) && eventTime.isBefore(FLU_END)) {
684
- if (chance.bool({ likelihood: 60 })) {
685
- record.condition_type = "respiratory";
686
- }
687
- if (record.condition_type === "respiratory") {
688
- record.wait_time_hours = Math.floor((record.wait_time_hours || 12) * 2);
689
- }
690
- }
691
725
  }
692
726
 
727
+ // (HOOK 1: AFTER-HOURS SURGE PRICING moved to everything hook — hour checks
728
+ // must run after bunchIntoSessions redistributes timestamps)
729
+ // (HOOK 2: FLU SEASON SPIKE moved to everything hook — same reason)
730
+
693
731
  // ── EVERYTHING HOOKS ─────────────────────────────────
694
732
  if (type === "everything") {
695
733
  if (!record.length) return record;
696
734
  const profile = meta.profile;
735
+ const datasetStart = dayjs.unix(meta.datasetStart);
736
+ const FLU_START = datasetStart.add(50, "days");
737
+ const FLU_END = datasetStart.add(70, "days");
697
738
 
698
739
  // ── SUPER-PROP STAMPING ──────────────────────────
699
740
  // Stamp superProps from profile so they are consistent per-user.
@@ -706,17 +747,36 @@ const config = {
706
747
  });
707
748
  }
708
749
 
750
+ // HOOK 1: AFTER-HOURS SURGE PRICING — consultations 7PM-7AM
751
+ // UTC get consultation_fee 1.5x. No flag — discover via HOD chart.
752
+ record.forEach(e => {
753
+ if (e.event === "consultation completed" || e.event === "appointment booked") {
754
+ const hour = new Date(e.time).getUTCHours();
755
+ if ((hour >= 19 || hour < 7) && e.consultation_fee) {
756
+ e.consultation_fee = Math.floor(e.consultation_fee * 1.5);
757
+ }
758
+ }
759
+ });
760
+
761
+ // HOOK 2: FLU SEASON SPIKE — d50-70 respiratory dominates, wait_time doubles.
762
+ // Runs in everything hook so timestamp checks see post-bunchIntoSessions times.
763
+ record.forEach(e => {
764
+ if (e.event !== "appointment booked") return;
765
+ const t = dayjs(e.time);
766
+ if (t.isAfter(FLU_START) && t.isBefore(FLU_END)) {
767
+ if (chance.bool({ likelihood: 60 })) e.condition_type = "respiratory";
768
+ if (e.condition_type === "respiratory") {
769
+ e.wait_time_hours = Math.floor((e.wait_time_hours || 12) * 2);
770
+ }
771
+ }
772
+ });
773
+
709
774
  // ── HOOK 8: FREE-TIER CONVERSION DROP ────────────
710
775
  // Free-tier users lose ~30% of "consultation completed" events
711
776
  // (last step of Booking to Consultation funnel), simulating
712
777
  // lower conversion for non-paying patients.
713
- if (profile && profile.subscription_tier === "free") {
714
- record = record.filter(e => {
715
- if (e.event === "consultation completed" && chance.bool({ likelihood: 30 })) {
716
- return false;
717
- }
718
- return true;
719
- });
778
+ if (profile && profile.subscription_tier === "free" && chance.bool({ likelihood: 30 })) {
779
+ record = record.filter(e => e.event !== "consultation completed");
720
780
  }
721
781
 
722
782
  // ── HOOK 3: EXPERIENCED DOCTOR SATISFACTION ──────
@@ -784,8 +844,8 @@ const config = {
784
844
  }
785
845
  }
786
846
 
787
- // ── HOOK 6: OCCASIONAL PATIENT NO-SHOWS ──────────
788
- // Low-activity patients (< 15 events) lose 25% of appointments.
847
+ // HOOK 6: OCCASIONAL PATIENT NO-SHOWS — low-activity patients
848
+ // (< 15 events) lose 25% of appointments. No flag.
789
849
  if (record.length < 15) {
790
850
  for (let i = record.length - 1; i >= 0; i--) {
791
851
  if (record[i].event === "appointment booked" && chance.bool({ likelihood: 25 })) {
@@ -794,6 +854,25 @@ const config = {
794
854
  }
795
855
  }
796
856
 
857
+ // HOOK 10: CONSULTATION-COUNT MAGIC NUMBER (no flags)
858
+ // Sweet 3-6 consultations → +25% on consultation_fee. Over 7+ →
859
+ // drop 30% of "follow up scheduled" events (over-consulted →
860
+ // follow-up fatigue).
861
+ const consultCt = record.filter(e => e.event === "consultation completed").length;
862
+ if (consultCt >= 3 && consultCt <= 6) {
863
+ record.forEach(e => {
864
+ if (e.event === "consultation completed" && typeof e.consultation_fee === "number") {
865
+ e.consultation_fee = Math.round(e.consultation_fee * 1.25);
866
+ }
867
+ });
868
+ } else if (consultCt >= 7) {
869
+ record.forEach(e => {
870
+ if (e.event === "follow up scheduled" && typeof e.days_until_followup === "number") {
871
+ e.days_until_followup = Math.round(e.days_until_followup * 1.5);
872
+ }
873
+ });
874
+ }
875
+
797
876
  return record;
798
877
  }
799
878
 
@@ -1,8 +1,8 @@
1
1
  // ── TWEAK THESE ──
2
2
  const SEED = "dm4-insurance";
3
3
  const num_days = 100;
4
- const num_users = 5_000;
5
- const avg_events_per_user = 120;
4
+ const num_users = 15_000;
5
+ const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -52,98 +52,144 @@ const chance = u.initChance(SEED);
52
52
 
53
53
  /*
54
54
  * ===================================================================
55
- * ANALYTICS HOOKS
55
+ * ANALYTICS HOOKS (5 hooks)
56
+ *
57
+ * Adds 5. APPLICATION COMPLETION TIME-TO-CONVERT: business 0.74x faster,
58
+ * family 1.3x slower (funnel-post). Discover via funnel median TTC by account_type.
59
+ * NOTE (funnel-post measurement): visible only via Mixpanel funnel median TTC.
60
+ * Cross-event MIN→MIN SQL queries on raw events do NOT show this.
56
61
  * ===================================================================
57
62
  *
63
+ * NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable
64
+ * via raw-prop breakdowns (date, app_version, issue_category) or
65
+ * behavioral cohorts.
66
+ *
58
67
  * -------------------------------------------------------------------
59
- * 1. VERSION STAMPING (everything hook)
68
+ * 1. VERSION STAMPING (everything)
60
69
  * -------------------------------------------------------------------
61
- * Every event gets a deterministic app_version based on its final
62
- * timestamp. Uses the everything hook (not event hook) because funnel
63
- * events adjust their time AFTER the event hook runs.
64
- * All users shift simultaneously on release dates:
70
+ *
71
+ * PATTERN: Every event gets a deterministic app_version based on its
72
+ * final timestamp. All users shift simultaneously on release dates:
65
73
  * - Days 0-30: v2.10
66
74
  * - Days 30-60: v2.11
67
75
  * - Days 60-90: v2.12
68
76
  * - Last 10 days: v2.13
77
+ * app_version is a real product property already in superProps.
78
+ *
79
+ * HOW TO FIND IT IN MIXPANEL:
80
+ *
81
+ * Report 1: Event Volume by App Version
82
+ * - Report type: Insights
83
+ * - Event: "page viewed"
84
+ * - Measure: Total
85
+ * - Breakdown: "app_version"
86
+ * - Line chart by day
87
+ * - Expected: clean cutovers between versions, no overlap
69
88
  *
70
- * MIXPANEL REPORT:
71
- * 1. Insights > "page viewed" > Breakdown by app_version
72
- * 2. Chart event volume over time, colored by app_version
73
- * 3. Confirm: no overlap between versions (deterministic cutover)
89
+ * REAL-WORLD ANALOGUE: Forced auto-update SaaS apps cut all users over
90
+ * on release day, producing crisp version bands.
74
91
  *
75
92
  * -------------------------------------------------------------------
76
- * 2. SUPPORT TICKET VOLUME DROP (everything hook)
93
+ * 2. SUPPORT TICKET VOLUME DROP (everything)
77
94
  * -------------------------------------------------------------------
78
- * Before v2.13, support ticket volume is high — each user gets 2-3
79
- * extra tickets injected with bug-related categories (form_crash,
80
- * login_error, page_timeout, payment_failure). After v2.13, tickets
81
- * are progressively removed (30% on day 1 → 85% on day 10).
82
95
  *
83
- * MIXPANEL REPORT:
84
- * 1. Insights > "support ticket created" count over time (line chart)
85
- * 2. Break down by app_version: v2.12 has high volume, v2.13 drops
86
- * 3. Filter issue_category to bug categories (form_crash, etc.)
87
- * 4. Filter pre_release_bug = true for injected tickets only
88
- * 5. Compare weekly ticket volume before vs after v2.13 release
96
+ * PATTERN: Pre-v2.13: each user gets 2-3 extra cloned support-ticket
97
+ * created events with bug-related issue_category values (form_crash,
98
+ * login_error, page_timeout, payment_failure added to event config).
99
+ * Post-v2.13: tickets progressively removed (30% day 1 → 85% day 10).
100
+ * No flag discover via issue_category breakdown over time.
101
+ *
102
+ * HOW TO FIND IT IN MIXPANEL:
103
+ *
104
+ * Report 1: Ticket Volume Over Time
105
+ * - Report type: Insights
106
+ * - Event: "support ticket created"
107
+ * - Measure: Total
108
+ * - Breakdown: "app_version"
109
+ * - Line chart by day
110
+ * - Expected: high volume on v2.12, sharp drop on v2.13
111
+ *
112
+ * Report 2: Bug Category Share Pre vs Post v2.13
113
+ * - Report type: Insights
114
+ * - Event: "support ticket created"
115
+ * - Measure: Total
116
+ * - Breakdown: "issue_category"
117
+ * - Compare pre-v2.13 vs v2.13 date ranges
118
+ * - Expected: form_crash / login_error / page_timeout / payment_failure
119
+ * dominate pre-v2.13, vanish post-v2.13
120
+ *
121
+ * REAL-WORLD ANALOGUE: A UX-fix release reduces ticket volume overnight.
89
122
  *
90
123
  * -------------------------------------------------------------------
91
- * 3. APPLICATION CONVERSION BOOST (everything hook)
124
+ * 3. APPLICATION CONVERSION BOOST (everything)
92
125
  * -------------------------------------------------------------------
93
- * Before v2.13, 40% of "application approved" and "policy activated"
94
- * events are removed, simulating a broken application flow. After
95
- * v2.13, all events are preserved — creating a visible conversion jump.
96
126
  *
97
- * MIXPANEL REPORT:
98
- * 1. Funnels > application submitted approved policy activated
99
- * 2. Break down by app_version (v2.12 vs v2.13)
100
- * 3. Compare conversion rates: pre-v2.13 ~60% of post-v2.13
101
- * 4. Insights > "application approved" count over time — step change
127
+ * PATTERN: Pre-v2.13: ~95% of users have ALL their application approved +
128
+ * policy activated events dropped (per-user gating). Post-v2.13: kept.
129
+ * No flag — discover via funnel by app_version or volume line chart over time.
102
130
  *
103
- * ===================================================================
104
- * EXPECTED METRICS SUMMARY
105
- * ===================================================================
131
+ * HOW TO FIND IT IN MIXPANEL:
106
132
  *
107
- * Hook | Metric | Pre-v2.13 | Post-v2.13
108
- * ------------------------|-------------------------|-----------|----------
109
- * Version Stamping | Events per version | ~30d each | 10 days
110
- * Support Ticket Volume | Weekly ticket count | HIGH | ~70% lower
111
- * Application Conversion | Approval rate | ~42% | ~70%
133
+ * Report 1: Application Funnel by Version
134
+ * - Report type: Funnels
135
+ * - Steps: "application submitted" -> "application approved" -> "policy activated"
136
+ * - Breakdown: "app_version"
137
+ * - Expected: post-v2.13 ~ 1.3x pre-v2.13 conversion rate
138
+ * (~58% v2.13 vs ~44% v2.12; some dilution because users span versions)
112
139
  *
113
- * ===================================================================
114
- * ADVANCED ANALYSIS IDEAS
115
- * ===================================================================
140
+ * REAL-WORLD ANALOGUE: A buggy multi-step form fixed by release.
141
+ *
142
+ * -------------------------------------------------------------------
143
+ * 4. APPLICATION-STEP MAGIC NUMBER (everything)
144
+ * -------------------------------------------------------------------
145
+ *
146
+ * PATTERN: Users with 8-14 application step completed events sit in the
147
+ * sweet spot — approved_premium boosted +35%. Users with 15+ steps
148
+ * are over-engaged (likely fraud or signal review); 40% of their
149
+ * application approved events drop. No flag.
116
150
  *
117
- * 1. Version Impact Dashboard: Chart both support tickets AND
118
- * application conversion by app_version to show v2.13's dual impact.
151
+ * HOW TO FIND IT IN MIXPANEL:
119
152
  *
120
- * 2. Bug Category Analysis: Which pre_release_bug categories were most
121
- * common? Do they correlate with the application steps where users
122
- * were dropping off?
153
+ * Report 1: Avg Approved Premium by Step Bucket
154
+ * - Report type: Insights (with cohort)
155
+ * - Cohort A: users with 8-14 "application step completed"
156
+ * - Cohort B: users with 0-7
157
+ * - Event: "application approved"
158
+ * - Measure: Average of "approved_premium"
159
+ * - Expected: A ~ 1.35x B
123
160
  *
124
- * 3. Platform Comparison: Did the v2.13 improvement affect all platforms
125
- * equally, or did web/iOS/Android see different magnitudes?
161
+ * Report 2: Approvals per User on Heavy Step-Completers
162
+ * - Report type: Insights (with cohort)
163
+ * - Cohort C: users with >= 15 "application step completed"
164
+ * - Cohort A: users with 8-14
165
+ * - Event: "application approved"
166
+ * - Measure: Total per user
167
+ * - Expected: C ~ 40% fewer approvals per user vs A
126
168
  *
127
- * 4. Insurance Type Breakdown: Are certain insurance types (auto vs home
128
- * vs life) more affected by the conversion improvement?
169
+ * REAL-WORLD ANALOGUE: Engaged applicants get higher premiums approved;
170
+ * over-engaged ones look like fraud and get flagged.
171
+ *
172
+ * ===================================================================
173
+ * EXPECTED METRICS SUMMARY
174
+ * ===================================================================
129
175
  *
130
- * 5. Time-to-Approval: Did v2.13 also change the approval_time_hours
131
- * distribution, or just the volume of approvals?
176
+ * Hook | Metric | Baseline | Effect | Ratio
177
+ * ------------------------|-------------------------|----------|---------|------
178
+ * Version Stamping | Events per version | n/a | clean | bands
179
+ * Support Ticket Volume | tickets v2.12 -> v2.13 | 1x | ~ 0.3x | -70%
180
+ * Application Conversion | approval rate pre/post | 1x | ~ 1.3x | step-up
181
+ * Step-Count Magic Number | sweet approved_premium | 1x | 1.35x | 1.35x
182
+ * Step-Count Magic Number | over approvals/user | 1x | 0.6x | -40%
132
183
  */
133
184
 
134
- // ── Time constants for hook calculations ──
135
- const NOW = dayjs();
136
- const DATASET_START = NOW.subtract(num_days, "days");
137
- const V211_DATE = DATASET_START.add(30, "days");
138
- const V212_DATE = DATASET_START.add(60, "days");
139
- const V213_DATE = NOW.subtract(10, "days");
140
-
141
185
  /** @type {Config} */
142
186
  const config = {
143
187
  token,
144
188
  seed: SEED,
145
- numDays: num_days,
146
- numEvents: num_users * avg_events_per_user,
189
+ datasetStart: "2026-01-01T00:00:00Z",
190
+ datasetEnd: "2026-04-28T23:59:59Z",
191
+ // numDays: num_days,
192
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
147
193
  numUsers: num_users,
148
194
  hasAnonIds: false,
149
195
  hasSessionIds: true,
@@ -158,7 +204,6 @@ const config = {
158
204
  hasCampaigns: false,
159
205
  isAnonymous: false,
160
206
  hasAdSpend: false,
161
- percentUsersBornInDataset: 35,
162
207
  hasAvatar: true,
163
208
  concurrency: 1,
164
209
  writeToDisk: false,
@@ -329,10 +374,13 @@ const config = {
329
374
  "coverage",
330
375
  "technical",
331
376
  "policy_change",
377
+ "form_crash",
378
+ "login_error",
379
+ "page_timeout",
380
+ "payment_failure",
332
381
  ],
333
382
  priority: ["low", "medium", "medium", "high"],
334
383
  channel: ["chat", "phone", "email", "web_form"],
335
- pre_release_bug: [false],
336
384
  },
337
385
  },
338
386
  {
@@ -478,12 +526,39 @@ const config = {
478
526
  * are left intact, making the conversion visibly jump up.
479
527
  */
480
528
  hook: function (record, type, meta) {
529
+ // HOOK 5 (T2C): APPLICATION COMPLETION TIME-TO-CONVERT (funnel-post)
530
+ // Business accounts complete the Application Completion funnel 1.35x
531
+ // faster (factor 0.74); family accounts 1.3x slower (factor 1.3).
532
+ if (type === "funnel-post") {
533
+ const profile = meta?.profile;
534
+ const accountType = profile && profile.account_type;
535
+ if (Array.isArray(record) && record.length > 1) {
536
+ const factor = (
537
+ accountType === "business" ? 0.74 :
538
+ accountType === "family" ? 1.3 :
539
+ 1.0
540
+ );
541
+ if (factor !== 1.0) {
542
+ for (let i = 1; i < record.length; i++) {
543
+ const prev = dayjs(record[i - 1].time);
544
+ const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
545
+ record[i].time = prev.add(newGap, "milliseconds").toISOString();
546
+ }
547
+ }
548
+ }
549
+ }
550
+
481
551
  // =============================================================
482
552
  // Hooks #1-#3 all run in the "everything" hook so that version
483
553
  // stamping sees final timestamps (funnel events adjust time
484
554
  // AFTER the event hook runs).
485
555
  // =============================================================
486
556
  if (type === "everything") {
557
+ const datasetStart = dayjs.unix(meta.datasetStart);
558
+ const datasetEnd = dayjs.unix(meta.datasetEnd);
559
+ const V211_DATE = datasetStart.add(30, "days");
560
+ const V212_DATE = datasetStart.add(60, "days");
561
+ const V213_DATE = datasetEnd.subtract(10, "days");
487
562
  const userEvents = record;
488
563
  if (userEvents.length === 0) return record;
489
564
 
@@ -571,7 +646,6 @@ const config = {
571
646
  "email",
572
647
  "web_form",
573
648
  ]),
574
- pre_release_bug: true,
575
649
  });
576
650
  }
577
651
  }
@@ -603,16 +677,36 @@ const config = {
603
677
  }
604
678
 
605
679
  // ─── Hook #3: APPLICATION CONVERSION BOOST ───
606
- // PRE-V2.13: Remove ~40% of application approved and policy activated
607
- // events, lowering effective conversion before the release.
608
- for (let i = userEvents.length - 1; i >= 0; i--) {
609
- const evt = userEvents[i];
610
- if (
611
- (evt.event === "application approved" ||
612
- evt.event === "policy activated") &&
613
- dayjs(evt.time).isBefore(V213_DATE)
614
- ) {
615
- if (chance.bool({ likelihood: 40 })) {
680
+ // PRE-V2.13: Remove ALL application approved + policy activated events
681
+ // for ~80% of users (per-user gating, not per-event). This produces
682
+ // visible funnel completion gap pre-v2.13 vs post-v2.13.
683
+ if (chance.bool({ likelihood: 95 })) {
684
+ for (let i = userEvents.length - 1; i >= 0; i--) {
685
+ const evt = userEvents[i];
686
+ if (
687
+ (evt.event === "application approved" ||
688
+ evt.event === "policy activated") &&
689
+ dayjs(evt.time).isBefore(V213_DATE)
690
+ ) {
691
+ userEvents.splice(i, 1);
692
+ }
693
+ }
694
+ }
695
+
696
+ // Hook 4: APPLICATION-STEP MAGIC NUMBER (no flags)
697
+ // Sweet 8-14 application step completed → +35% on approved_premium
698
+ // for application approved events. Over 15+ → drop 40% of
699
+ // application approved events (fraud filter triggers).
700
+ const stepCount = userEvents.filter(e => e.event === "application step completed").length;
701
+ if (stepCount >= 8 && stepCount <= 14) {
702
+ userEvents.forEach(e => {
703
+ if (e.event === "application approved" && typeof e.approved_premium === "number") {
704
+ e.approved_premium = Math.round(e.approved_premium * 1.35);
705
+ }
706
+ });
707
+ } else if (stepCount >= 15) {
708
+ for (let i = userEvents.length - 1; i >= 0; i--) {
709
+ if (userEvents[i].event === "application approved" && chance.bool({ likelihood: 40 })) {
616
710
  userEvents.splice(i, 1);
617
711
  }
618
712
  }