@ak--47/dungeon-master 1.3.0 → 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 (74) hide show
  1. package/dungeons/technical/array-of-object-lookup.js +0 -2
  2. package/dungeons/technical/simple.js +3 -4
  3. package/dungeons/technical/simplest-schema.json +5 -0
  4. package/dungeons/technical/text-generation.js +1 -1
  5. package/dungeons/vertical/ai-platform.js +192 -133
  6. package/dungeons/vertical/community.js +64 -34
  7. package/dungeons/vertical/crypto.js +333 -224
  8. package/dungeons/vertical/dating.js +228 -282
  9. package/dungeons/vertical/devtools.js +164 -62
  10. package/dungeons/vertical/ecommerce.js +240 -91
  11. package/dungeons/vertical/education.js +328 -310
  12. package/dungeons/vertical/fintech.js +437 -311
  13. package/dungeons/vertical/fitness.js +141 -58
  14. package/dungeons/vertical/food-delivery.js +298 -318
  15. package/dungeons/vertical/gaming.js +378 -231
  16. package/dungeons/vertical/healthcare.js +140 -60
  17. package/dungeons/vertical/insurance-application.js +168 -73
  18. package/dungeons/vertical/logistics.js +95 -6
  19. package/dungeons/vertical/marketplace.js +137 -51
  20. package/dungeons/vertical/media.js +241 -386
  21. package/dungeons/vertical/real-estate.js +322 -315
  22. package/dungeons/vertical/sass.js +253 -263
  23. package/dungeons/vertical/social.js +262 -206
  24. package/dungeons/vertical/travel.js +59 -16
  25. package/index.js +17 -17
  26. package/lib/core/config-validator.js +93 -21
  27. package/lib/core/context.js +10 -24
  28. package/lib/core/storage.js +6 -1
  29. package/lib/generators/events.js +11 -14
  30. package/lib/generators/funnels.js +12 -4
  31. package/lib/generators/mirror.js +3 -2
  32. package/lib/generators/product-names.js +1 -1
  33. package/lib/generators/scd.js +2 -1
  34. package/lib/generators/text.js +1 -1
  35. package/lib/orchestrators/user-loop.js +47 -47
  36. package/lib/utils/utils.js +71 -39
  37. package/package.json +1 -1
  38. package/scripts/smoke-test-all.mjs +162 -0
  39. package/scripts/verify-runner.mjs +72 -24
  40. package/types.d.ts +38 -15
  41. package/dungeons/technical/ad-spend-schema.json +0 -128
  42. package/dungeons/technical/anonymous-users-schema.json +0 -92
  43. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  44. package/dungeons/technical/experiments-schema.json +0 -203
  45. package/dungeons/technical/foobar-schema.json +0 -362
  46. package/dungeons/technical/group-analytics-schema.json +0 -241
  47. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  48. package/dungeons/technical/nested-objects-schema.json +0 -145
  49. package/dungeons/technical/retention-cadence-schema.json +0 -37
  50. package/dungeons/technical/sanity-schema.json +0 -185
  51. package/dungeons/technical/scale-test-schema.json +0 -70
  52. package/dungeons/technical/scd-schema.json +0 -467
  53. package/dungeons/technical/simple-schema.json +0 -362
  54. package/dungeons/technical/text-generation-schema.json +0 -1062
  55. package/dungeons/vertical/ai-platform-schema.json +0 -617
  56. package/dungeons/vertical/community-schema.json +0 -579
  57. package/dungeons/vertical/crypto-schema.json +0 -546
  58. package/dungeons/vertical/dating-schema.json +0 -401
  59. package/dungeons/vertical/devtools-schema.json +0 -601
  60. package/dungeons/vertical/ecommerce-schema.json +0 -604
  61. package/dungeons/vertical/education-schema.json +0 -5686
  62. package/dungeons/vertical/fintech-schema.json +0 -630
  63. package/dungeons/vertical/fitness-schema.json +0 -530
  64. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  65. package/dungeons/vertical/gaming-schema.json +0 -2703
  66. package/dungeons/vertical/healthcare-schema.json +0 -549
  67. package/dungeons/vertical/insurance-application-schema.json +0 -485
  68. package/dungeons/vertical/logistics-schema.json +0 -574
  69. package/dungeons/vertical/marketplace-schema.json +0 -533
  70. package/dungeons/vertical/media-schema.json +0 -4749
  71. package/dungeons/vertical/real-estate-schema.json +0 -527
  72. package/dungeons/vertical/sass-schema.json +0 -3128
  73. package/dungeons/vertical/social-schema.json +0 -620
  74. package/dungeons/vertical/travel-schema.json +0 -580
@@ -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,27 +226,77 @@ 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,
297
+ datasetStart: "2026-01-01T00:00:00Z",
298
+ datasetEnd: "2026-04-28T23:59:59Z",
299
+ // numDays: num_days,
250
300
  avgEventsPerUserPerDay: avg_events_per_user_per_day,
251
301
  numUsers: num_users,
252
302
  hasAnonIds: false,
@@ -303,7 +353,6 @@ const config = {
303
353
  condition_type: ["general", "general", "general", "respiratory", "dermatology", "mental_health", "chronic", "pediatric"],
304
354
  wait_time_hours: u.weighNumRange(1, 72, 0.4),
305
355
  appointment_type: ["new_patient", "follow_up", "follow_up", "urgent", "routine", "routine"],
306
- after_hours: [false],
307
356
  },
308
357
  },
309
358
  {
@@ -316,7 +365,6 @@ const config = {
316
365
  consultation_fee: u.weighNumRange(25, 200, 0.4, 75),
317
366
  satisfaction_score: u.weighNumRange(1, 5, 0.8, 3),
318
367
  condition_type: ["general", "general", "respiratory", "dermatology", "mental_health", "chronic", "pediatric"],
319
- after_hours: [false],
320
368
  },
321
369
  },
322
370
  {
@@ -577,8 +625,8 @@ const config = {
577
625
  { name: "premium", price: 29.99 },
578
626
  ],
579
627
  lifecycle: {
580
- trialToPayRate: 0.30,
581
- upgradeRate: 0.08,
628
+ trialToPayRate: 0.55,
629
+ upgradeRate: 0.20,
582
630
  downgradeRate: 0.03,
583
631
  churnRate: 0.05,
584
632
  winBackRate: 0.10,
@@ -655,44 +703,38 @@ const config = {
655
703
  }
656
704
  }
657
705
 
658
- // ── HOOK 8: PREMIUM BOOKING FUNNEL LIFT (funnel-pre)
659
- // (conversionRate boost removed filtering applied in everything hook instead)
660
- if (type === "funnel-pre") {
661
- // no-op: conversion differentiation handled via event filtering below
662
- }
663
-
664
- // ── HOOK 1: AFTER-HOURS SURGE PRICING (event) ────────
665
- // Consultations between 7PM-7AM get 1.5x fee.
666
- if (type === "event") {
667
- if (record.event === "consultation completed" || record.event === "appointment booked") {
668
- const hour = dayjs(record.time).hour();
669
- if (hour >= 19 || hour < 7) {
670
- record.after_hours = true;
671
- if (record.consultation_fee) {
672
- 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();
673
722
  }
674
723
  }
675
724
  }
676
-
677
- // ── HOOK 2: FLU SEASON SPIKE (event) ─────────────
678
- // Days 50-70: respiratory conditions dominate, wait times double.
679
- const FLU_START = DATASET_START.add(50, "days");
680
- const FLU_END = DATASET_START.add(70, "days");
681
- const eventTime = dayjs(record.time);
682
- if (record.event === "appointment booked" && eventTime.isAfter(FLU_START) && eventTime.isBefore(FLU_END)) {
683
- if (chance.bool({ likelihood: 60 })) {
684
- record.condition_type = "respiratory";
685
- }
686
- if (record.condition_type === "respiratory") {
687
- record.wait_time_hours = Math.floor((record.wait_time_hours || 12) * 2);
688
- }
689
- }
690
725
  }
691
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
+
692
731
  // ── EVERYTHING HOOKS ─────────────────────────────────
693
732
  if (type === "everything") {
694
733
  if (!record.length) return record;
695
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");
696
738
 
697
739
  // ── SUPER-PROP STAMPING ──────────────────────────
698
740
  // Stamp superProps from profile so they are consistent per-user.
@@ -705,17 +747,36 @@ const config = {
705
747
  });
706
748
  }
707
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
+
708
774
  // ── HOOK 8: FREE-TIER CONVERSION DROP ────────────
709
775
  // Free-tier users lose ~30% of "consultation completed" events
710
776
  // (last step of Booking to Consultation funnel), simulating
711
777
  // lower conversion for non-paying patients.
712
- if (profile && profile.subscription_tier === "free") {
713
- record = record.filter(e => {
714
- if (e.event === "consultation completed" && chance.bool({ likelihood: 30 })) {
715
- return false;
716
- }
717
- return true;
718
- });
778
+ if (profile && profile.subscription_tier === "free" && chance.bool({ likelihood: 30 })) {
779
+ record = record.filter(e => e.event !== "consultation completed");
719
780
  }
720
781
 
721
782
  // ── HOOK 3: EXPERIENCED DOCTOR SATISFACTION ──────
@@ -783,8 +844,8 @@ const config = {
783
844
  }
784
845
  }
785
846
 
786
- // ── HOOK 6: OCCASIONAL PATIENT NO-SHOWS ──────────
787
- // 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.
788
849
  if (record.length < 15) {
789
850
  for (let i = record.length - 1; i >= 0; i--) {
790
851
  if (record[i].event === "appointment booked" && chance.bool({ likelihood: 25 })) {
@@ -793,6 +854,25 @@ const config = {
793
854
  }
794
855
  }
795
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
+
796
876
  return record;
797
877
  }
798
878
 
@@ -1,7 +1,7 @@
1
1
  // ── TWEAK THESE ──
2
2
  const SEED = "dm4-insurance";
3
3
  const num_days = 100;
4
- const num_users = 5_000;
4
+ const num_users = 15_000;
5
5
  const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
@@ -52,97 +52,143 @@ 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,
189
+ datasetStart: "2026-01-01T00:00:00Z",
190
+ datasetEnd: "2026-04-28T23:59:59Z",
191
+ // numDays: num_days,
146
192
  avgEventsPerUserPerDay: avg_events_per_user_per_day,
147
193
  numUsers: num_users,
148
194
  hasAnonIds: false,
@@ -328,10 +374,13 @@ const config = {
328
374
  "coverage",
329
375
  "technical",
330
376
  "policy_change",
377
+ "form_crash",
378
+ "login_error",
379
+ "page_timeout",
380
+ "payment_failure",
331
381
  ],
332
382
  priority: ["low", "medium", "medium", "high"],
333
383
  channel: ["chat", "phone", "email", "web_form"],
334
- pre_release_bug: [false],
335
384
  },
336
385
  },
337
386
  {
@@ -477,12 +526,39 @@ const config = {
477
526
  * are left intact, making the conversion visibly jump up.
478
527
  */
479
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
+
480
551
  // =============================================================
481
552
  // Hooks #1-#3 all run in the "everything" hook so that version
482
553
  // stamping sees final timestamps (funnel events adjust time
483
554
  // AFTER the event hook runs).
484
555
  // =============================================================
485
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");
486
562
  const userEvents = record;
487
563
  if (userEvents.length === 0) return record;
488
564
 
@@ -570,7 +646,6 @@ const config = {
570
646
  "email",
571
647
  "web_form",
572
648
  ]),
573
- pre_release_bug: true,
574
649
  });
575
650
  }
576
651
  }
@@ -602,16 +677,36 @@ const config = {
602
677
  }
603
678
 
604
679
  // ─── Hook #3: APPLICATION CONVERSION BOOST ───
605
- // PRE-V2.13: Remove ~40% of application approved and policy activated
606
- // events, lowering effective conversion before the release.
607
- for (let i = userEvents.length - 1; i >= 0; i--) {
608
- const evt = userEvents[i];
609
- if (
610
- (evt.event === "application approved" ||
611
- evt.event === "policy activated") &&
612
- dayjs(evt.time).isBefore(V213_DATE)
613
- ) {
614
- 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 })) {
615
710
  userEvents.splice(i, 1);
616
711
  }
617
712
  }