@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
@@ -15,9 +15,6 @@ import * as u from "../../lib/utils/utils.js";
15
15
 
16
16
  dayjs.extend(utc);
17
17
  const chance = u.initChance(SEED);
18
- const NOW = dayjs();
19
- const DATASET_START = NOW.subtract(num_days, "days");
20
-
21
18
  /** @typedef {import("../../types").Dungeon} Config */
22
19
 
23
20
  /**
@@ -50,29 +47,28 @@ const DATASET_START = NOW.subtract(num_days, "days");
50
47
 
51
48
  /**
52
49
  * ═══════════════════════════════════════════════════════════════
53
- * ANALYTICS HOOKS (8 hooks)
50
+ * ANALYTICS HOOKS (10 hooks)
54
51
  * ═══════════════════════════════════════════════════════════════
55
52
  *
53
+ * NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable
54
+ * via raw-prop breakdowns (HOD, day, segment) or behavioral cohorts.
55
+ *
56
56
  * ───────────────────────────────────────────────────────────────
57
- * 1. MORNING WORKOUT BOOST (event hook)
57
+ * 1. MORNING WORKOUT BOOST (everything)
58
58
  * ───────────────────────────────────────────────────────────────
59
59
  *
60
- * PATTERN: Workouts completed between 5AM-9AM get 1.3x
61
- * calories_burned. Simulates the metabolic boost from fasted
62
- * morning exercise.
60
+ * PATTERN: Workouts in 5-9 UTC get calories_burned 1.3x. Mutates
61
+ * raw prop. No flag.
63
62
  *
64
63
  * HOW TO FIND IT IN MIXPANEL:
65
64
  *
66
- * Report 1: Morning vs Non-Morning Calorie Burn
67
- * Report type: Insights
68
- * Event: "workout completed"
69
- * Measure: Average of "calories_burned"
70
- * Breakdown: "is_morning_workout"
71
- * • Expected: is_morning_workout=true should show ~1.3x avg
72
- * calories vs false (true ≈ 390, false ≈ 300)
65
+ * Report 1: Avg Calories Burned by Hour of Day
66
+ * - Event: "workout completed"
67
+ * - Measure: Average of "calories_burned"
68
+ * - Breakdown: hour of day
69
+ * - Expected: 5-9 hours show ~ 1.3x baseline
73
70
  *
74
- * REAL-WORLD ANALOGUE: Fitness apps promote morning workouts
75
- * as more effective for fat loss, driving engagement.
71
+ * REAL-WORLD ANALOGUE: Morning workouts get a metabolic boost.
76
72
  *
77
73
  * ───────────────────────────────────────────────────────────────
78
74
  * 2. POST-LAUNCH AI COACHING LIFT (event hook)
@@ -244,27 +240,79 @@ const DATASET_START = NOW.subtract(num_days, "days");
244
240
  * REAL-WORLD ANALOGUE: Annual gym memberships have higher
245
241
  * utilization — sunk cost + commitment drives consistency.
246
242
  *
243
+ * ───────────────────────────────────────────────────────────────
244
+ * 9. WORKOUT LOOP TIME-TO-CONVERT (funnel-post)
245
+ * ───────────────────────────────────────────────────────────────
246
+ *
247
+ * PATTERN: Annual + family subscribers complete the Workout Loop
248
+ * funnel 1.3x faster (factor 0.77); Free users 1.25x slower (factor 1.25).
249
+ *
250
+ * HOW TO FIND IT IN MIXPANEL:
251
+ *
252
+ * Report 1: Workout Loop Median Time-to-Convert by Subscription
253
+ * - Funnels > "workout planned" -> "workout completed" -> "progress checked"
254
+ * - Measure: Median time to convert
255
+ * - Breakdown: subscription_plan
256
+ * - Expected: annual ~ 0.77x; free ~ 1.25x
257
+ *
258
+ * NOTE (funnel-post measurement): visible only via Mixpanel funnel
259
+ * median TTC. Cross-event MIN→MIN SQL queries on raw events do NOT
260
+ * show this — funnel-post adjusts gaps within funnel instances, not
261
+ * across the user's full event history.
262
+ *
263
+ * ───────────────────────────────────────────────────────────────
264
+ * 10. WORKOUT-COUNT MAGIC NUMBER (everything)
265
+ * ───────────────────────────────────────────────────────────────
266
+ *
267
+ * PATTERN: Sweet 12-20 workouts/user → +35% on workout
268
+ * duration_minutes (peak progression). Over 21+ → drop 30% of
269
+ * post-day-30 events (overtraining churn). No flag.
270
+ *
271
+ * HOW TO FIND IT IN MIXPANEL:
272
+ *
273
+ * Report 1: Avg Workout Duration by Workout-Count Bucket
274
+ * - Cohort A: users with 12-20 "workout completed"
275
+ * - Cohort B: users with 0-11
276
+ * - Event: "workout completed"
277
+ * - Measure: Average of "duration_minutes"
278
+ * - Expected: A ~ 1.35x B
279
+ *
280
+ * Report 2: D30+ Activity on Heavy Workout Cohort
281
+ * - Cohort C: users with >= 21 "workout completed"
282
+ * - Cohort A: users with 12-20
283
+ * - Event: any event
284
+ * - Measure: Total per user, post-day-30
285
+ * - Expected: C ~ 30% fewer post-day-30 events per user
286
+ *
287
+ * REAL-WORLD ANALOGUE: Sweet-spot training drives progression;
288
+ * over-training causes injury and burnout.
289
+ *
247
290
  * ═══════════════════════════════════════════════════════════════
248
291
  * EXPECTED METRICS SUMMARY
249
292
  * ═══════════════════════════════════════════════════════════════
250
293
  *
251
- * Hook | Metric | Baseline | Effect | Ratio
252
- * ────────────────────────────|─────────────────────|──────────|─────────|──────
253
- * Morning Workout Boost | calories_burned | 300 | 390 | 1.3x
254
- * AI Coaching Lift | duration_minutes | 40 | 48 | 1.2x
255
- * Streak Retention | achievements/user | 1 | 2-3 | 2-3x
256
- * Social Challenge Completion | challenges/user | 1 | 1.5 | 1.5x
257
- * Resolver Churn Cliff | events after day 14 | 100% | 30% | 0.3x
258
- * Coach Session Quality | satisfaction_score | 3.0 | 4.3 | 1.4x
259
- * Coach Profile Enrichment | total_workouts | 0 | 350 | N/A
260
- * Annual Funnel Lift | funnel conversion | 45% | 63% | 1.4x
294
+ * Hook | Metric | Baseline | Effect | Ratio
295
+ * ----------------------------|---------------------|----------|-----------|------
296
+ * Morning Workout Boost | calories_burned 5-9 | 1x | 1.3x | 1.3x
297
+ * AI Coaching Lift | duration_minutes | 1x | 1.2x | 1.2x
298
+ * Streak Retention | achievements/user | 1x | 2-3x | 2-3x
299
+ * Social Challenge Completion | challenges/user | 1x | 1.5x | 1.5x
300
+ * Resolver Churn Cliff | events after day 14 | 1x | 0.3x | -70%
301
+ * Coach Session Quality | satisfaction_score | 3.0 | 4.3 | 1.4x
302
+ * Coach Profile Enrichment | total_workouts | 0 | 350 | n/a
303
+ * Annual Funnel Lift | funnel conversion | 45% | 63% | 1.4x
304
+ * Workout Loop T2C | median min by tier | 1x | 0.77/1.25x| ~ 1.6x range
305
+ * Workout Magic Number | sweet duration_min | 1x | 1.35x | 1.35x
306
+ * Workout Magic Number | over D30+ activity | 1x | 0.7x | -30%
261
307
  */
262
308
 
263
309
  /** @type {Config} */
264
310
  const config = {
265
311
  token,
266
312
  seed: SEED,
267
- numDays: num_days,
313
+ datasetStart: "2026-01-01T00:00:00Z",
314
+ datasetEnd: "2026-04-28T23:59:59Z",
315
+ // numDays: num_days,
268
316
  avgEventsPerUserPerDay: avg_events_per_user_per_day,
269
317
  numUsers: num_users,
270
318
  hasAnonIds: false,
@@ -311,7 +359,6 @@ const config = {
311
359
  duration_minutes: u.weighNumRange(10, 90, 0.5, 40),
312
360
  calories_burned: u.weighNumRange(50, 800, 0.4, 300),
313
361
  heart_rate_avg: u.weighNumRange(80, 185, 0.5, 130),
314
- is_morning_workout: [false],
315
362
  satisfaction_score: u.weighNumRange(1, 5, 0.7, 3),
316
363
  coaching_mode: ["self_guided"],
317
364
  challenge_mode: ["solo"],
@@ -591,8 +638,8 @@ const config = {
591
638
  { name: "family", price: 149.99 },
592
639
  ],
593
640
  lifecycle: {
594
- trialToPayRate: 0.30,
595
- upgradeRate: 0.08,
641
+ trialToPayRate: 0.65,
642
+ upgradeRate: 0.65,
596
643
  downgradeRate: 0.03,
597
644
  churnRate: 0.05,
598
645
  winBackRate: 0.10,
@@ -677,8 +724,8 @@ const config = {
677
724
 
678
725
  // ── Hook Function ──────────────────────────────────────
679
726
  hook: function (record, type, meta) {
680
- // ── HOOK 7: COACH PROFILE ENRICHMENT (user) ──────────
681
- // Coach segment gets boosted total_workouts and streak_days.
727
+ // HOOK 7: COACH PROFILE ENRICHMENT (user) — coach segment users
728
+ // get high total_workouts + streak_days.
682
729
  if (type === "user") {
683
730
  if (record.segment === "coach") {
684
731
  record.total_workouts = chance.integer({ min: 200, max: 500 });
@@ -686,29 +733,35 @@ const config = {
686
733
  }
687
734
  }
688
735
 
689
- // ── HOOK 8: ANNUAL SUBSCRIBER WORKOUT FUNNEL LIFT (funnel-pre)
690
- // (conversionRate filtering moved to everything hook)
691
- if (type === "funnel-pre") {
692
- // no-op: conversion filtering handled via event dropping in everything hook
693
- }
694
-
695
- // ── HOOK 1: MORNING WORKOUT BOOST (event) ────────────
696
- // Workouts between 5AM-9AM get 1.3x calories_burned.
697
- if (type === "event") {
698
- if (record.event === "workout completed") {
699
- const hour = dayjs(record.time).hour();
700
- if (hour >= 5 && hour < 9) {
701
- record.is_morning_workout = true;
702
- if (record.calories_burned) {
703
- record.calories_burned = Math.floor(record.calories_burned * 1.3);
736
+ // HOOK 9 (T2C): WORKOUT LOOP TIME-TO-CONVERT (funnel-post)
737
+ // Annual subscribers complete the Workout Loop funnel 1.3x faster
738
+ // (factor 0.77); Free users 1.25x slower (factor 1.25).
739
+ if (type === "funnel-post") {
740
+ const segment = meta?.profile?.subscription_plan;
741
+ if (Array.isArray(record) && record.length > 1) {
742
+ const factor = (
743
+ segment === "annual" || segment === "family" ? 0.77 :
744
+ segment === "free" ? 1.25 :
745
+ 1.0
746
+ );
747
+ if (factor !== 1.0) {
748
+ for (let i = 1; i < record.length; i++) {
749
+ const prev = dayjs(record[i - 1].time);
750
+ const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
751
+ record[i].time = prev.add(newGap, "milliseconds").toISOString();
704
752
  }
705
753
  }
706
754
  }
755
+ }
707
756
 
757
+ // (HOOK 1: MORNING WORKOUT BOOST moved to everything hook — hour checks
758
+ // must run after bunchIntoSessions redistributes timestamps)
759
+ if (type === "event") {
760
+ const datasetStart = dayjs.unix(meta.datasetStart);
708
761
  // ── HOOK 2: POST-LAUNCH AI COACHING LIFT (event) ────
709
762
  // After day 35, ai_assisted workouts get 1.2x duration.
710
763
  if (record.event === "workout completed" || record.event === "workout planned") {
711
- const AI_LAUNCH = DATASET_START.add(35, "days");
764
+ const AI_LAUNCH = datasetStart.add(35, "days");
712
765
  const eventTime = dayjs(record.time);
713
766
  if (eventTime.isAfter(AI_LAUNCH) && record.coaching_mode === "ai_assisted") {
714
767
  if (record.duration_minutes) {
@@ -723,6 +776,7 @@ const config = {
723
776
 
724
777
  // ── EVERYTHING HOOKS ─────────────────────────────────
725
778
  if (type === "everything") {
779
+ const datasetStart = dayjs.unix(meta.datasetStart);
726
780
  let events = record;
727
781
  if (!events.length) return record;
728
782
 
@@ -736,16 +790,26 @@ const config = {
736
790
  });
737
791
  }
738
792
 
793
+ // HOOK 1: MORNING WORKOUT BOOST — 5AM-9AM UTC workouts get
794
+ // calories_burned 1.3x. No flag — analyst breaks down by HOD.
795
+ events.forEach(e => {
796
+ if (e.event === "workout completed") {
797
+ const hour = new Date(e.time).getUTCHours();
798
+ if (hour >= 5 && hour < 9 && e.calories_burned) {
799
+ e.calories_burned = Math.floor(e.calories_burned * 1.3);
800
+ }
801
+ }
802
+ });
803
+
739
804
  // ── HOOK 8: ANNUAL SUBSCRIBER CONVERSION FILTER ─
740
805
  // Free/monthly-tier users drop ~30% of "progress checked"
741
806
  // (last step of Workout Loop funnel) to simulate lower conversion.
807
+ // NOTE: subscription field is named `subscription_plan` (set by the
808
+ // subscription feature in user-loop.js), NOT `subscription_tier`.
742
809
  if (meta && meta.profile) {
743
- const tier = meta.profile.subscription_tier;
744
- if (tier !== "annual" && tier !== "family") {
745
- record = record.filter(e => {
746
- if (e.event === "progress checked" && chance.bool({ likelihood: 30 })) return false;
747
- return true;
748
- });
810
+ const plan = meta.profile.subscription_plan;
811
+ if (plan !== "annual" && plan !== "family" && chance.bool({ likelihood: 30 })) {
812
+ record = record.filter(e => e.event !== "progress checked");
749
813
  events = record;
750
814
  }
751
815
  }
@@ -803,7 +867,7 @@ const config = {
803
867
  // ── HOOK 5: RESOLVER CHURN CLIFF ─────────────────
804
868
  // Resolver segment users with <8 events lose 70% after day 14.
805
869
  if (meta && meta.profile && meta.profile.segment === "resolver" && events.length < 8) {
806
- const CHURN_CLIFF = DATASET_START.add(14, "days");
870
+ const CHURN_CLIFF = datasetStart.add(14, "days");
807
871
  for (let i = events.length - 1; i >= 0; i--) {
808
872
  const eventTime = dayjs(events[i].time);
809
873
  if (eventTime.isAfter(CHURN_CLIFF) && chance.bool({ likelihood: 70 })) {
@@ -812,8 +876,7 @@ const config = {
812
876
  }
813
877
  }
814
878
 
815
- // ── HOOK 6: COACH SESSION QUALITY ────────────────
816
- // Users with coach_session events get higher satisfaction.
879
+ // HOOK 6: COACH SESSION QUALITY — coach-session satisfaction 4-5.
817
880
  const hasCoachSessions = events.some(e => e.event === "coach session");
818
881
  if (hasCoachSessions) {
819
882
  events.forEach(e => {
@@ -823,6 +886,26 @@ const config = {
823
886
  });
824
887
  }
825
888
 
889
+ // HOOK 10: WORKOUT-COUNT MAGIC NUMBER (no flags)
890
+ // Sweet 12-20 workouts → +35% on workout duration_minutes (peak
891
+ // progression). Over 21+ → drop 30% of post-day-30 events
892
+ // (overtraining → churn).
893
+ const workoutCount = events.filter(e => e.event === "workout completed").length;
894
+ if (workoutCount >= 12 && workoutCount <= 20) {
895
+ events.forEach(e => {
896
+ if (e.event === "workout completed" && typeof e.duration_minutes === "number") {
897
+ e.duration_minutes = Math.round(e.duration_minutes * 1.35);
898
+ }
899
+ });
900
+ } else if (workoutCount >= 21) {
901
+ const day30 = datasetStart.add(30, "days");
902
+ for (let i = events.length - 1; i >= 0; i--) {
903
+ if (dayjs(events[i].time).isAfter(day30) && chance.bool({ likelihood: 30 })) {
904
+ events.splice(i, 1);
905
+ }
906
+ }
907
+ }
908
+
826
909
  return record;
827
910
  }
828
911