@ak--47/dungeon-master 1.4.5 → 1.5.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 (78) hide show
  1. package/.claude/skills/analyze-soup/SKILL.md +158 -0
  2. package/.claude/skills/create-dungeon/SKILL.md +464 -0
  3. package/.claude/skills/verify-dungeon/SKILL.md +157 -0
  4. package/.claude/skills/verify-dungeon/references/counting-semantics.md +161 -0
  5. package/.claude/skills/verify-dungeon/references/report-format.md +216 -0
  6. package/.claude/skills/verify-dungeon/references/sql-recipes.md +857 -0
  7. package/.claude/skills/write-hooks/SKILL.md +468 -0
  8. package/CHANGELOG.md +182 -0
  9. package/HOOKS.md +1256 -597
  10. package/README.md +140 -5
  11. package/dungeons/technical/ad-spend.js +41 -49
  12. package/dungeons/technical/anonymous-users.js +38 -36
  13. package/dungeons/technical/array-of-object-lookup.js +136 -153
  14. package/dungeons/technical/datagen-v15-verify.js +87 -0
  15. package/dungeons/technical/experiments.js +42 -40
  16. package/dungeons/technical/foobar.js +114 -118
  17. package/dungeons/technical/group-analytics.js +42 -40
  18. package/dungeons/technical/hook-helpers-verify.js +69 -50
  19. package/dungeons/technical/identity-model-verify.js +22 -12
  20. package/dungeons/technical/mirror-strategies.js +37 -39
  21. package/dungeons/technical/nested-objects.js +119 -118
  22. package/dungeons/technical/pattern-aggregate-by-bin.js +21 -8
  23. package/dungeons/technical/pattern-attributed-by-source.js +23 -9
  24. package/dungeons/technical/pattern-frequency-by-frequency.js +21 -8
  25. package/dungeons/technical/pattern-funnel-frequency.js +30 -15
  26. package/dungeons/technical/pattern-ttc-by-segment.js +21 -8
  27. package/dungeons/technical/retention-cadence.js +115 -112
  28. package/dungeons/technical/sanity.js +86 -80
  29. package/dungeons/technical/scale-test.js +34 -38
  30. package/dungeons/technical/scd.js +111 -128
  31. package/dungeons/technical/simple.js +134 -141
  32. package/dungeons/technical/simplest.js +111 -65
  33. package/dungeons/technical/text-generation.js +110 -146
  34. package/dungeons/vertical/ai-platform.js +300 -333
  35. package/dungeons/vertical/community.js +290 -255
  36. package/dungeons/vertical/crypto.js +400 -391
  37. package/dungeons/vertical/dating.js +421 -375
  38. package/dungeons/vertical/devtools.js +346 -298
  39. package/dungeons/vertical/ecommerce.js +322 -394
  40. package/dungeons/vertical/education.js +380 -325
  41. package/dungeons/vertical/fintech.js +371 -325
  42. package/dungeons/vertical/fitness.js +345 -291
  43. package/dungeons/vertical/food-delivery.js +352 -307
  44. package/dungeons/vertical/gaming.js +490 -444
  45. package/dungeons/vertical/healthcare.js +311 -262
  46. package/dungeons/vertical/insurance-application.js +437 -409
  47. package/dungeons/vertical/logistics.js +278 -252
  48. package/dungeons/vertical/marketplace.js +340 -323
  49. package/dungeons/vertical/media.js +390 -335
  50. package/dungeons/vertical/real-estate.js +402 -347
  51. package/dungeons/vertical/sass.js +331 -333
  52. package/dungeons/vertical/social.js +377 -316
  53. package/dungeons/vertical/travel.js +302 -295
  54. package/index.js +64 -7
  55. package/lib/core/config-validator.js +378 -17
  56. package/lib/core/dungeon-loader.js +2 -5
  57. package/lib/generators/events.js +12 -13
  58. package/lib/generators/funnels.js +76 -2
  59. package/lib/hook-helpers/index.js +1 -0
  60. package/lib/hook-helpers/inject.js +95 -0
  61. package/lib/orchestrators/mixpanel-sender.js +7 -0
  62. package/lib/orchestrators/user-loop.js +598 -48
  63. package/lib/templates/defaults.js +59 -59
  64. package/lib/templates/macro-presets.js +53 -11
  65. package/lib/utils/dataset-context.js +103 -0
  66. package/lib/utils/retention-curve.js +140 -0
  67. package/lib/utils/utils.js +157 -109
  68. package/lib/verify/counting.js +360 -0
  69. package/lib/verify/emulate-breakdown.js +531 -108
  70. package/lib/verify/funnel-engine.js +539 -0
  71. package/lib/verify/identity.js +78 -0
  72. package/lib/verify/index.js +20 -0
  73. package/lib/verify/schema-validator.js +3 -1
  74. package/lib/verify/verify-dungeon.js +58 -0
  75. package/package.json +14 -3
  76. package/scripts/run-dungeon.mjs +12 -1
  77. package/types.d.ts +353 -4
  78. package/scripts/smoke-test-all.mjs +0 -162
@@ -1,35 +1,31 @@
1
- // ── TWEAK THESE ──
2
- const SEED = "dm4-nested-objects";
3
- const num_days = 30;
4
- const num_users = 500;
5
- const avg_events_per_user_per_day = 2;
6
- let token = "your-mixpanel-token";
7
-
8
- // ── env overrides ──
9
- if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
10
-
11
- import Chance from 'chance';
12
- let chance = new Chance();
1
+ // ── IMPORTS ──
2
+ import Chance from "chance";
13
3
  import dayjs from "dayjs";
14
4
  import utc from "dayjs/plugin/utc.js";
15
5
  dayjs.extend(utc);
16
- import { uid, comma } from 'ak-tools';
17
- import { weighNumRange, date, integer, weighChoices } from "../../lib/utils/utils.js";
18
-
6
+ import { weighNumRange, integer } from "../../lib/utils/utils.js";
19
7
  /** @typedef {import("../../types").Dungeon} Config */
20
- /**
21
- * ═══════════════════════════════════════════════════════════════
22
- * TECHNICAL TEST: Nested Objects & High Cardinality
23
- * ═══════════════════════════════════════════════════════════════
24
- *
25
- * Tests Mixpanel's handling of:
26
- * - Array-of-object event properties (cart items, search results, form fields)
27
- * - Deeply nested object properties (metadata.browser.name, etc.)
28
- * - High cardinality string properties (10K product IDs, unique session IDs)
29
- *
30
- * 500 users, 30K events, 30 days. No hooks.
8
+
9
+ // ── OVERVIEW ──
10
+ /*
11
+ * NAME: nested-objects
12
+ * PURPOSE: Exercises array-of-object event properties, deeply nested object
13
+ * properties, and high-cardinality string properties.
14
+ * SCALE: 500 users, ~30K events, 30 days
15
+ * EVENTS (4): checkout, search performed, form submitted, page view
16
+ * FUNNELS (0): none
31
17
  */
32
18
 
19
+ // ── SCALE ──
20
+ const SEED = "dm4-nested-objects";
21
+ const NUM_DAYS = 30;
22
+ const NUM_USERS = 500;
23
+ const EVENTS_PER_DAY = 2;
24
+ const token = process.env.MP_TOKEN || "";
25
+
26
+ const chance = new Chance();
27
+
28
+ // ── DATA ARRAYS ──
33
29
  const productCategories = ["electronics", "books", "clothing", "home", "garden", "toys", "sports", "automotive", "beauty", "health", "grocery", "jewelry", "shoes", "tools", "office"];
34
30
  const browsers = ["Chrome", "Firefox", "Safari", "Edge", "Opera", "Brave"];
35
31
  const browserVersions = ["120.0", "121.0", "119.0", "118.0", "117.0", "116.0", "115.0"];
@@ -38,98 +34,7 @@ const osList = ["Windows 11", "macOS 14", "iOS 17", "Android 14", "Linux", "Chro
38
34
  const cities = ["New York", "London", "Tokyo", "Berlin", "Sydney", "Toronto", "Paris", "Mumbai", "Seoul", "Mexico City", "Cairo", "Lagos", "Dubai", "Bangkok", "Istanbul"];
39
35
  const countries = ["US", "UK", "JP", "DE", "AU", "CA", "FR", "IN", "KR", "MX", "EG", "NG", "AE", "TH", "TR"];
40
36
 
41
- /** @type {import('../../types').Dungeon} */
42
- const config = {
43
- token,
44
- seed: SEED,
45
- name: "nested-objects",
46
- numDays: num_days,
47
- avgEventsPerUserPerDay: avg_events_per_user_per_day,
48
- numUsers: num_users,
49
- format: 'json',
50
- region: "US",
51
- hasAnonIds: false,
52
- hasSessionIds: false,
53
- hasAdSpend: false,
54
- hasLocation: false,
55
- hasAndroidDevices: false,
56
- hasIOSDevices: false,
57
- hasDesktopDevices: true,
58
- hasBrowser: false,
59
- hasCampaigns: false,
60
- isAnonymous: false,
61
- alsoInferFunnels: false,
62
- concurrency: 1,
63
- writeToDisk: false,
64
-
65
- events: [
66
- {
67
- event: "checkout",
68
- weight: 3,
69
- properties: {
70
- // Array of objects: cart items
71
- cart: makeCartItems(),
72
- total_amount: weighNumRange(10, 2000, .25),
73
- currency: ["USD", "EUR", "GBP", "JPY", "CAD"],
74
- // High cardinality: unique session per event
75
- session_id: () => chance.guid(),
76
- // Deeply nested metadata
77
- metadata: makeMetadata(),
78
- }
79
- },
80
- {
81
- event: "search performed",
82
- weight: 5,
83
- properties: {
84
- query: ["laptop", "shoes", "headphones", "jacket", "phone case", "coffee maker", "backpack", "monitor", "keyboard", "mouse"],
85
- // Array of objects: search results
86
- results: makeSearchResults(),
87
- result_count: weighNumRange(0, 50),
88
- // High cardinality product ID
89
- top_result_id: () => `prod_${integer(1, 10000)}`,
90
- session_id: () => chance.guid(),
91
- metadata: makeMetadata(),
92
- }
93
- },
94
- {
95
- event: "form submitted",
96
- weight: 4,
97
- properties: {
98
- form_name: ["checkout", "signup", "contact", "feedback", "settings", "profile"],
99
- // Array of objects: form fields
100
- fields: makeFormFields(),
101
- submission_valid: [true, true, true, false],
102
- session_id: () => chance.guid(),
103
- metadata: makeMetadata(),
104
- }
105
- },
106
- {
107
- event: "page view",
108
- weight: 8,
109
- properties: {
110
- page: ["/", "/products", "/cart", "/search", "/account", "/help", "/about"],
111
- // High cardinality
112
- product_id: () => `prod_${integer(1, 10000)}`,
113
- session_id: () => chance.guid(),
114
- metadata: makeMetadata(),
115
- }
116
- },
117
- ],
118
- funnels: [],
119
- superProps: {},
120
- userProps: {
121
- plan: ["free", "basic", "pro", "enterprise"],
122
- },
123
- scdProps: {},
124
- mirrorProps: {},
125
- groupKeys: [],
126
- groupProps: {},
127
- lookupTables: [],
128
- hook: function (record, type, meta) {
129
- return record;
130
- }
131
- };
132
-
37
+ // ── HELPER FUNCTIONS ──
133
38
  /**
134
39
  * Generates an array-of-objects property for cart items.
135
40
  * Each cart has 1-5 items with product_id, name, price, quantity, category.
@@ -215,4 +120,100 @@ function makeMetadata() {
215
120
  };
216
121
  }
217
122
 
123
+ // ── CONFIG ──
124
+ /** @type {Config} */
125
+ const config = {
126
+ seed: SEED,
127
+ name: "nested-objects",
128
+ numDays: NUM_DAYS,
129
+ avgEventsPerUserPerDay: EVENTS_PER_DAY,
130
+ numUsers: NUM_USERS,
131
+ format: 'json',
132
+ credentials: {
133
+ token,
134
+ region: "US",
135
+ },
136
+ switches: {
137
+ hasSessionIds: false,
138
+ hasAdSpend: false,
139
+ hasLocation: false,
140
+ hasAndroidDevices: false,
141
+ hasIOSDevices: false,
142
+ hasDesktopDevices: true,
143
+ hasBrowser: false,
144
+ hasCampaigns: false,
145
+ isAnonymous: false,
146
+ alsoInferFunnels: false,
147
+ },
148
+ concurrency: 1,
149
+ writeToDisk: false,
150
+
151
+ events: [
152
+ {
153
+ event: "checkout",
154
+ weight: 3,
155
+ properties: {
156
+ // Array of objects: cart items
157
+ cart: makeCartItems(),
158
+ total_amount: weighNumRange(10, 2000, .25),
159
+ currency: ["USD", "EUR", "GBP", "JPY", "CAD"],
160
+ // High cardinality: unique session per event
161
+ session_id: () => chance.guid(),
162
+ // Deeply nested metadata
163
+ metadata: makeMetadata(),
164
+ }
165
+ },
166
+ {
167
+ event: "search performed",
168
+ weight: 5,
169
+ properties: {
170
+ query: ["laptop", "shoes", "headphones", "jacket", "phone case", "coffee maker", "backpack", "monitor", "keyboard", "mouse"],
171
+ // Array of objects: search results
172
+ results: makeSearchResults(),
173
+ result_count: weighNumRange(0, 50),
174
+ // High cardinality product ID
175
+ top_result_id: () => `prod_${integer(1, 10000)}`,
176
+ session_id: () => chance.guid(),
177
+ metadata: makeMetadata(),
178
+ }
179
+ },
180
+ {
181
+ event: "form submitted",
182
+ weight: 4,
183
+ properties: {
184
+ form_name: ["checkout", "signup", "contact", "feedback", "settings", "profile"],
185
+ // Array of objects: form fields
186
+ fields: makeFormFields(),
187
+ submission_valid: [true, true, true, false],
188
+ session_id: () => chance.guid(),
189
+ metadata: makeMetadata(),
190
+ }
191
+ },
192
+ {
193
+ event: "page view",
194
+ weight: 8,
195
+ properties: {
196
+ page: ["/", "/products", "/cart", "/search", "/account", "/help", "/about"],
197
+ // High cardinality
198
+ product_id: () => `prod_${integer(1, 10000)}`,
199
+ session_id: () => chance.guid(),
200
+ metadata: makeMetadata(),
201
+ }
202
+ },
203
+ ],
204
+ funnels: [],
205
+ superProps: {},
206
+ userProps: {
207
+ plan: ["free", "basic", "pro", "enterprise"],
208
+ },
209
+ scdProps: {},
210
+ mirrorProps: {},
211
+ groupKeys: [],
212
+ groupProps: {},
213
+ lookupTables: [],
214
+ hook: function (record, type, meta) {
215
+ return record;
216
+ }
217
+ };
218
+
218
219
  export default config;
@@ -1,15 +1,29 @@
1
- /**
2
- * Phase 4 reference dungeon — aggregate per user, by bin.
3
- *
4
- * Users in the high-Browse cohort have 4× higher Purchase amounts on average;
5
- * mid cohort 2×; low cohort 1×. Verified via the aggregatePerUser emulator.
6
- */
7
-
1
+ // ── IMPORTS ──
8
2
  import dayjs from 'dayjs';
9
3
  import { applyAggregateByBin } from '../../lib/hook-patterns/index.js';
10
4
 
5
+ // ── OVERVIEW ──
6
+ /*
7
+ * NAME: pattern-aggregate-by-bin
8
+ * PURPOSE: Phase 4 reference fixture — aggregate per user, by bin. Users in
9
+ * the high-Browse cohort have 4× higher Purchase amounts on average;
10
+ * mid 2×; low 1×. Verified via the aggregatePerUser emulator.
11
+ * SCALE: 1,000 users, ~180K events, 30 days
12
+ * EVENTS (2): Browse (6) > Purchase (2)
13
+ * FUNNELS (0): none
14
+ */
15
+
16
+ // ── HOOK STORIES ──
17
+ /*
18
+ * PATTERN: Bin users by Browse count (low 0-5, mid 5-15, high 15+);
19
+ * scale Purchase `amount` by {low:1, mid:2, high:4}. Verified via
20
+ * the aggregatePerUser emulator.
21
+ */
22
+
23
+ // ── SCALE ──
11
24
  const FIXED_NOW = dayjs('2024-02-02').unix();
12
25
 
26
+ // ── CONFIG ──
13
27
  export default {
14
28
  name: 'pattern-agg-by-bin',
15
29
  seed: 'phase4-aggbybin',
@@ -18,7 +32,6 @@ export default {
18
32
  numUsers: 1_000,
19
33
  avgEventsPerUserPerDay: 6,
20
34
  percentUsersBornInDataset: 30,
21
- hasAnonIds: false,
22
35
  format: 'json',
23
36
  concurrency: 1,
24
37
  writeToDisk: true,
@@ -1,16 +1,31 @@
1
- /**
2
- * Phase 4 reference dungeon — conversions attributed by source.
3
- *
4
- * Touch events carry one of three random sources; conversions are stamped with
5
- * the user's first-touch source weighted google:facebook:twitter = 10:5:1.
6
- * Verified via the attributedBy emulator.
7
- */
8
-
1
+ // ── IMPORTS ──
9
2
  import dayjs from 'dayjs';
10
3
  import { applyAttributedBySource } from '../../lib/hook-patterns/index.js';
11
4
 
5
+ // ── OVERVIEW ──
6
+ /*
7
+ * NAME: pattern-attributed-by-source
8
+ * PURPOSE: Phase 4 reference fixture — conversions attributed by source.
9
+ * Touch events carry one of three random sources; conversions are
10
+ * stamped with the user's first-touch source weighted
11
+ * google:facebook:twitter = 10:5:1. Verified via the attributedBy
12
+ * emulator.
13
+ * SCALE: 1,000 users, ~120K events, 30 days
14
+ * EVENTS (2): Touch (5) > Convert (2)
15
+ * FUNNELS (0): none
16
+ */
17
+
18
+ // ── HOOK STORIES ──
19
+ /*
20
+ * PATTERN: First-touch attribution — stamp each Convert event with the
21
+ * source of the user's first Touch, weighted google:facebook:twitter =
22
+ * 10:5:1. Verified via the attributedBy emulator.
23
+ */
24
+
25
+ // ── SCALE ──
12
26
  const FIXED_NOW = dayjs('2024-02-02').unix();
13
27
 
28
+ // ── CONFIG ──
14
29
  export default {
15
30
  name: 'pattern-attributed-by-source',
16
31
  seed: 'phase4-attrib',
@@ -19,7 +34,6 @@ export default {
19
34
  numUsers: 1_000,
20
35
  avgEventsPerUserPerDay: 4,
21
36
  percentUsersBornInDataset: 50,
22
- hasAnonIds: false,
23
37
  format: 'json',
24
38
  concurrency: 1,
25
39
  writeToDisk: true,
@@ -1,15 +1,29 @@
1
- /**
2
- * Phase 4 reference dungeon — frequency × frequency.
3
- *
4
- * Engages users into low / mid / high "Browse" cohorts; high-cohort users get 3×
5
- * Purchase events. Verified by the Phase 4 patterns test + verify-runner.
6
- */
7
-
1
+ // ── IMPORTS ──
8
2
  import dayjs from 'dayjs';
9
3
  import { applyFrequencyByFrequency } from '../../lib/hook-patterns/index.js';
10
4
 
5
+ // ── OVERVIEW ──
6
+ /*
7
+ * NAME: pattern-frequency-by-frequency
8
+ * PURPOSE: Phase 4 reference fixture — frequency × frequency. Engages users
9
+ * into low/mid/high "Browse" cohorts; high-cohort users get 3×
10
+ * Purchase events. Verified by the Phase 4 patterns test + verify-runner.
11
+ * SCALE: 1,000 users, ~180K events, 30 days
12
+ * EVENTS (2): Browse (6) > Purchase (2)
13
+ * FUNNELS (0): none
14
+ */
15
+
16
+ // ── HOOK STORIES ──
17
+ /*
18
+ * PATTERN: Bin users by Browse count (low 0-5, mid 5-15, high 15+);
19
+ * scale Purchase event count by {low:1, mid:2, high:3}. Verified via
20
+ * the frequencyByFrequency emulator.
21
+ */
22
+
23
+ // ── SCALE ──
11
24
  const FIXED_NOW = dayjs('2024-02-02').unix();
12
25
 
26
+ // ── CONFIG ──
13
27
  export default {
14
28
  name: 'pattern-freq-by-freq',
15
29
  seed: 'phase4-freqxfreq',
@@ -18,7 +32,6 @@ export default {
18
32
  numUsers: 1_000,
19
33
  avgEventsPerUserPerDay: 6,
20
34
  percentUsersBornInDataset: 30,
21
- hasAnonIds: false,
22
35
  format: 'json',
23
36
  concurrency: 1,
24
37
  writeToDisk: true,
@@ -1,20 +1,39 @@
1
- /**
2
- * Phase 4 reference dungeon — funnel frequency breakdown.
3
- *
4
- * Engaged-cohort users (high count of `Browse`) are 1.5× more likely to complete
5
- * the activation funnel's last step than low-cohort users. Verified via the
6
- * funnelFrequency emulator.
1
+ // ── IMPORTS ──
2
+ import dayjs from 'dayjs';
3
+ import { applyFunnelFrequencyBreakdown } from '../../lib/hook-patterns/index.js';
4
+
5
+ // ── OVERVIEW ──
6
+ /*
7
+ * NAME: pattern-funnel-frequency
8
+ * PURPOSE: Phase 4 reference fixture — funnel frequency breakdown.
9
+ * Engaged-cohort users (high count of `Browse`) are 1.5× more likely
10
+ * to complete the activation funnel's last step than low-cohort
11
+ * users. Verified via the funnelFrequency emulator.
12
+ * SCALE: 1,000 users, ~120K events, 30 days
13
+ * EVENTS (4): Browse (5) > Land (1) > Sign Up (1) > Activate (1)
14
+ * FUNNELS (1): Activation: Land → Sign Up → Activate (70%)
15
+ */
16
+
17
+ // ── HOOK STORIES ──
18
+ /*
19
+ * PATTERN: Bin users by Browse count (low 0-3, high 3+); within funnel-post,
20
+ * drop final-step `Activate` events at rates {low:0.5, high:0.95}, lifting
21
+ * the high cohort's conversion-per-step.
7
22
  *
8
- * Note: this pattern is funnel-post; it edits the funnel's own events. Combined
9
- * with the per-user funnel attempts, the breakdown should show a stronger
23
+ * Note: this pattern is funnel-post; it edits the funnel's own events.
24
+ * Combined with per-user funnel attempts, the breakdown shows a stronger
10
25
  * conversion-per-step lift in the high cohort.
26
+ *
27
+ * In funnel-post we don't have the user's full event stream — the pattern
28
+ * accepts null for `allUserEvents` and falls back to counting cohortEvent
29
+ * inside the funnel-only events. Real dungeons usually wire this via
30
+ * `everything` instead.
11
31
  */
12
32
 
13
- import dayjs from 'dayjs';
14
- import { applyFunnelFrequencyBreakdown } from '../../lib/hook-patterns/index.js';
15
-
33
+ // ── SCALE ──
16
34
  const FIXED_NOW = dayjs('2024-02-02').unix();
17
35
 
36
+ // ── CONFIG ──
18
37
  export default {
19
38
  name: 'pattern-funnel-freq',
20
39
  seed: 'phase4-funnel-freq',
@@ -23,7 +42,6 @@ export default {
23
42
  numUsers: 1_000,
24
43
  avgEventsPerUserPerDay: 4,
25
44
  percentUsersBornInDataset: 100,
26
- hasAnonIds: false,
27
45
  format: 'json',
28
46
  concurrency: 1,
29
47
  writeToDisk: true,
@@ -41,9 +59,6 @@ export default {
41
59
  hook: function (record, type, meta) {
42
60
  if (type !== 'funnel-post' || !Array.isArray(record)) return;
43
61
  if (!meta.isFirstFunnel) return;
44
- // In funnel-post we don't have the user's full event stream — pattern accepts
45
- // null for `allUserEvents` and falls back to counting cohortEvent inside the
46
- // funnel-only events. Real dungeons usually wire this via `everything` instead.
47
62
  applyFunnelFrequencyBreakdown(null, meta.profile || {}, record, {
48
63
  cohortEvent: 'Browse',
49
64
  bins: { low: [0, 3], high: [3, Infinity] },
@@ -1,15 +1,29 @@
1
- /**
2
- * Phase 4 reference dungeon — time to convert by segment.
3
- *
4
- * Trial users take 4× longer to complete the activation funnel than enterprise
5
- * users (0.5× faster). Verified via the timeToConvert emulator.
6
- */
7
-
1
+ // ── IMPORTS ──
8
2
  import dayjs from 'dayjs';
9
3
  import { applyTTCBySegment } from '../../lib/hook-patterns/index.js';
10
4
 
5
+ // ── OVERVIEW ──
6
+ /*
7
+ * NAME: pattern-ttc-by-segment
8
+ * PURPOSE: Phase 4 reference fixture — time-to-convert by segment. Trial
9
+ * users take 4× longer to complete the activation funnel; enterprise
10
+ * 0.5× faster. Verified via the timeToConvert emulator.
11
+ * SCALE: 1,000 users, ~120K events, 30 days
12
+ * EVENTS (4): Browse (5) > Land (1) > Sign Up (1) > Activate (1)
13
+ * FUNNELS (1): Activation: Land → Sign Up → Activate (100%)
14
+ */
15
+
16
+ // ── HOOK STORIES ──
17
+ /*
18
+ * PATTERN: Scale the activation funnel's time-to-convert by user tier:
19
+ * trial users 4× slower, enterprise 0.5× faster. Verified via the
20
+ * timeToConvert emulator.
21
+ */
22
+
23
+ // ── SCALE ──
11
24
  const FIXED_NOW = dayjs('2024-02-02').unix();
12
25
 
26
+ // ── CONFIG ──
13
27
  export default {
14
28
  name: 'pattern-ttc-by-segment',
15
29
  seed: 'phase4-ttc-seg',
@@ -18,7 +32,6 @@ export default {
18
32
  numUsers: 1_000,
19
33
  avgEventsPerUserPerDay: 4,
20
34
  percentUsersBornInDataset: 100,
21
- hasAnonIds: false,
22
35
  format: 'json',
23
36
  concurrency: 1,
24
37
  writeToDisk: true,