@ak--47/dungeon-master 1.5.0 → 1.5.2
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.
- package/.claude/skills/create-dungeon/SKILL.md +139 -46
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +31 -6
- package/.claude/skills/verify-dungeon/references/sql-recipes.md +44 -25
- package/.claude/skills/write-hooks/SKILL.md +31 -3
- package/CHANGELOG.md +85 -0
- package/HOOKS.md +13 -0
- package/dungeons/technical/ad-spend.js +41 -49
- package/dungeons/technical/anonymous-users.js +38 -36
- package/dungeons/technical/array-of-object-lookup.js +136 -153
- package/dungeons/technical/datagen-v15-verify.js +24 -11
- package/dungeons/technical/experiments.js +42 -40
- package/dungeons/technical/foobar.js +114 -118
- package/dungeons/technical/group-analytics.js +42 -40
- package/dungeons/technical/hook-helpers-verify.js +69 -50
- package/dungeons/technical/identity-model-verify.js +22 -12
- package/dungeons/technical/mirror-strategies.js +37 -39
- package/dungeons/technical/nested-objects.js +119 -118
- package/dungeons/technical/pattern-aggregate-by-bin.js +21 -8
- package/dungeons/technical/pattern-attributed-by-source.js +23 -9
- package/dungeons/technical/pattern-frequency-by-frequency.js +21 -8
- package/dungeons/technical/pattern-funnel-frequency.js +30 -15
- package/dungeons/technical/pattern-ttc-by-segment.js +21 -8
- package/dungeons/technical/retention-cadence.js +115 -112
- package/dungeons/technical/sanity.js +86 -80
- package/dungeons/technical/scale-test.js +34 -38
- package/dungeons/technical/scd.js +111 -128
- package/dungeons/technical/simple.js +134 -141
- package/dungeons/technical/simplest.js +54 -62
- package/dungeons/technical/text-generation.js +110 -146
- package/dungeons/vertical/ai-platform.js +296 -333
- package/dungeons/vertical/community.js +284 -255
- package/dungeons/vertical/crypto.js +395 -391
- package/dungeons/vertical/dating.js +411 -378
- package/dungeons/vertical/devtools.js +336 -298
- package/dungeons/vertical/ecommerce.js +316 -394
- package/dungeons/vertical/education.js +369 -325
- package/dungeons/vertical/fintech.js +358 -325
- package/dungeons/vertical/fitness.js +335 -291
- package/dungeons/vertical/food-delivery.js +343 -307
- package/dungeons/vertical/gaming.js +480 -444
- package/dungeons/vertical/healthcare.js +306 -262
- package/dungeons/vertical/insurance-application.js +427 -409
- package/dungeons/vertical/logistics.js +271 -252
- package/dungeons/vertical/marketplace.js +333 -323
- package/dungeons/vertical/media.js +382 -335
- package/dungeons/vertical/real-estate.js +395 -346
- package/dungeons/vertical/sass.js +319 -333
- package/dungeons/vertical/social.js +368 -316
- package/dungeons/vertical/travel.js +297 -295
- package/index.js +46 -4
- package/lib/core/config-validator.js +126 -28
- package/lib/generators/funnels.js +4 -1
- package/lib/orchestrators/mixpanel-sender.js +7 -0
- package/lib/orchestrators/user-loop.js +132 -31
- package/lib/templates/defaults.js +59 -59
- package/lib/templates/macro-presets.js +14 -2
- package/lib/utils/dataset-context.js +103 -0
- package/lib/utils/retention-curve.js +140 -0
- package/lib/utils/utils.js +149 -38
- package/lib/verify/counting.js +40 -0
- package/lib/verify/emulate-breakdown.js +20 -1
- package/lib/verify/index.js +1 -0
- package/lib/verify/schema-validator.js +3 -1
- package/package.json +11 -2
- package/scripts/run-dungeon.mjs +12 -1
- package/types.d.ts +117 -1
|
@@ -1,49 +1,47 @@
|
|
|
1
|
-
// ──
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const num_users = 500;
|
|
5
|
-
const avg_events_per_user_per_day = 2;
|
|
6
|
-
let token = "";
|
|
7
|
-
|
|
8
|
-
// ── env overrides ──
|
|
9
|
-
if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
|
|
1
|
+
// ── IMPORTS ──
|
|
2
|
+
import { weighNumRange } from "../../lib/utils/utils.js";
|
|
3
|
+
/** @typedef {import("../../types").Dungeon} Config */
|
|
10
4
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - 500 users, 30K events, 30 days
|
|
19
|
-
* - 5 simple events, no funnels, no hooks
|
|
5
|
+
// ── OVERVIEW ──
|
|
6
|
+
/*
|
|
7
|
+
* NAME: mirror-strategies
|
|
8
|
+
* PURPOSE: exercises all 4 mirrorProps strategies (create, update, fill, delete)
|
|
9
|
+
* SCALE: 500 users, ~30K events, 30 days
|
|
10
|
+
* EVENTS (5): page view, checkout, profile update, subscription change, login
|
|
11
|
+
* FUNNELS (0): none
|
|
20
12
|
*/
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
// ── SCALE ──
|
|
15
|
+
const SEED = "mirror-strategies";
|
|
16
|
+
const NUM_DAYS = 30;
|
|
17
|
+
const NUM_USERS = 500;
|
|
18
|
+
const EVENTS_PER_DAY = 2;
|
|
19
|
+
const token = process.env.MP_TOKEN || "";
|
|
25
20
|
|
|
26
|
-
|
|
27
|
-
/** @type {
|
|
21
|
+
// ── CONFIG ──
|
|
22
|
+
/** @type {Config} */
|
|
28
23
|
const config = {
|
|
29
|
-
token,
|
|
30
24
|
seed: SEED,
|
|
31
|
-
numDays:
|
|
32
|
-
avgEventsPerUserPerDay:
|
|
33
|
-
numUsers:
|
|
25
|
+
numDays: NUM_DAYS,
|
|
26
|
+
avgEventsPerUserPerDay: EVENTS_PER_DAY,
|
|
27
|
+
numUsers: NUM_USERS,
|
|
34
28
|
format: "json",
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
29
|
+
credentials: {
|
|
30
|
+
token,
|
|
31
|
+
region: "US",
|
|
32
|
+
},
|
|
33
|
+
switches: {
|
|
34
|
+
hasSessionIds: false,
|
|
35
|
+
hasAdSpend: false,
|
|
36
|
+
hasLocation: false,
|
|
37
|
+
hasAndroidDevices: false,
|
|
38
|
+
hasIOSDevices: false,
|
|
39
|
+
hasDesktopDevices: false,
|
|
40
|
+
hasBrowser: false,
|
|
41
|
+
hasCampaigns: false,
|
|
42
|
+
isAnonymous: false,
|
|
43
|
+
alsoInferFunnels: false,
|
|
44
|
+
},
|
|
47
45
|
concurrency: 1,
|
|
48
46
|
writeToDisk: false,
|
|
49
47
|
|
|
@@ -1,35 +1,31 @@
|
|
|
1
|
-
// ──
|
|
2
|
-
|
|
3
|
-
const num_days = 30;
|
|
4
|
-
const num_users = 500;
|
|
5
|
-
const avg_events_per_user_per_day = 2;
|
|
6
|
-
let 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 {
|
|
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
|
-
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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.
|
|
9
|
-
* with
|
|
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
|
-
|
|
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,
|