@ak--47/dungeon-master 1.5.0 → 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.
- package/CHANGELOG.md +43 -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,28 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// ── IMPORTS ──
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import {
|
|
4
|
+
binUsersByEventCount,
|
|
5
|
+
scalePropertyValue,
|
|
6
|
+
injectBurst,
|
|
7
|
+
cloneEvent,
|
|
8
|
+
} from '../../lib/hook-helpers/index.js';
|
|
9
|
+
/** @typedef {import("../../types").Dungeon} Config */
|
|
10
|
+
|
|
11
|
+
// ── OVERVIEW ──
|
|
12
|
+
/*
|
|
13
|
+
* NAME: hook-helpers-verify
|
|
14
|
+
* PURPOSE: Phase 3 fixture — exercises binUsersByEventCount + scalePropertyValue + injectBurst atoms
|
|
15
|
+
* SCALE: 1,000 users, ~120K events, 30 days
|
|
16
|
+
* EVENTS (2): Browse, Purchase
|
|
17
|
+
* FUNNELS (0): none
|
|
3
18
|
*
|
|
4
|
-
*
|
|
19
|
+
* Verified by DuckDB queries against the events output (see
|
|
20
|
+
* scripts/verify-runner.mjs + the Phase 3 verification gate notes in the plan).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// ── HOOK STORIES ──
|
|
24
|
+
/*
|
|
25
|
+
* Inside one `everything` hook:
|
|
5
26
|
* 1. `binUsersByEventCount` — classify users into "casual" / "engaged" / "power"
|
|
6
27
|
* based on their count of `Browse` events.
|
|
7
28
|
* 2. `scalePropertyValue` — engaged users get 2x `amount` on Purchase events;
|
|
8
29
|
* power users get 3x.
|
|
9
30
|
* 3. `injectBurst` — power users also get a burst of 5 cloned `Browse` events
|
|
10
31
|
* around the midpoint of their stream.
|
|
11
|
-
*
|
|
12
|
-
* Verified by DuckDB queries against the events output (see
|
|
13
|
-
* scripts/verify-runner.mjs + the Phase 3 verification gate notes in the plan).
|
|
14
32
|
*/
|
|
15
33
|
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
binUsersByEventCount,
|
|
19
|
-
scalePropertyValue,
|
|
20
|
-
injectBurst,
|
|
21
|
-
cloneEvent,
|
|
22
|
-
} from '../../lib/hook-helpers/index.js';
|
|
23
|
-
|
|
34
|
+
// ── SCALE ──
|
|
24
35
|
const FIXED_NOW = dayjs('2024-02-02').unix();
|
|
25
36
|
|
|
37
|
+
// ── KNOBS (tweak these to reshape stories) ──
|
|
26
38
|
const BINS = {
|
|
27
39
|
casual: [0, 5],
|
|
28
40
|
engaged: [5, 15],
|
|
@@ -32,7 +44,46 @@ const BINS = {
|
|
|
32
44
|
const SCALE_BY_BIN = { casual: 1, engaged: 2, power: 3 };
|
|
33
45
|
const BURST_COUNT_BY_BIN = { casual: 0, engaged: 0, power: 5 };
|
|
34
46
|
|
|
35
|
-
|
|
47
|
+
// ── HELPER FUNCTIONS ──
|
|
48
|
+
function handleEverythingHooks(record, meta) {
|
|
49
|
+
// 1. Classify the user via cohort atom
|
|
50
|
+
const bin = binUsersByEventCount(record, 'Browse', BINS);
|
|
51
|
+
if (!bin) return record;
|
|
52
|
+
|
|
53
|
+
// Stamp the bin onto each Purchase so verification queries can group by it
|
|
54
|
+
// without re-deriving. Non-flag-stamping: this property is config-defined when
|
|
55
|
+
// dungeons add `cohort` to Purchase.properties; here we accept the small bend
|
|
56
|
+
// for the verification fixture so DuckDB queries are clean.
|
|
57
|
+
for (const ev of record) {
|
|
58
|
+
if (ev.event === 'Purchase') ev.cohort = bin;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 2. Scale Purchase amounts via mutate atom (engaged 2x, power 3x)
|
|
62
|
+
const factor = SCALE_BY_BIN[bin] || 1;
|
|
63
|
+
if (factor !== 1) {
|
|
64
|
+
scalePropertyValue(record, e => e.event === 'Purchase', 'amount', factor);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 3. Power users get a Browse burst clustered at the dataset midpoint
|
|
68
|
+
const burstCount = BURST_COUNT_BY_BIN[bin] || 0;
|
|
69
|
+
if (burstCount > 0 && record.length) {
|
|
70
|
+
const midUnixMs = (meta.datasetStart + (meta.datasetEnd - meta.datasetStart) / 2) * 1000;
|
|
71
|
+
// Use the user's first Browse as a clone template so we honor the schema.
|
|
72
|
+
const template = record.find(e => e.event === 'Browse');
|
|
73
|
+
if (template) {
|
|
74
|
+
const tpl = cloneEvent(template, { cohort: bin });
|
|
75
|
+
injectBurst(record, tpl, burstCount, midUnixMs, 60 * 60 * 1000); // ±1h spread
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Re-sort by time after burst injection so downstream pipeline sees ordered events.
|
|
80
|
+
record.sort((a, b) => (a.time < b.time ? -1 : a.time > b.time ? 1 : 0));
|
|
81
|
+
return record;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── CONFIG ──
|
|
85
|
+
/** @type {Config} */
|
|
86
|
+
const config = {
|
|
36
87
|
name: 'hook-helpers-verify',
|
|
37
88
|
seed: 'phase3-helpers-verify',
|
|
38
89
|
datasetStart: FIXED_NOW - 30 * 86400,
|
|
@@ -40,7 +91,6 @@ export default {
|
|
|
40
91
|
numUsers: 1_000,
|
|
41
92
|
avgEventsPerUserPerDay: 4,
|
|
42
93
|
percentUsersBornInDataset: 30,
|
|
43
|
-
hasAnonIds: false,
|
|
44
94
|
format: 'json',
|
|
45
95
|
concurrency: 1,
|
|
46
96
|
writeToDisk: true,
|
|
@@ -51,39 +101,8 @@ export default {
|
|
|
51
101
|
],
|
|
52
102
|
hook: function (record, type, meta) {
|
|
53
103
|
if (type !== 'everything' || !Array.isArray(record)) return record;
|
|
54
|
-
|
|
55
|
-
// 1. Classify the user via cohort atom
|
|
56
|
-
const bin = binUsersByEventCount(record, 'Browse', BINS);
|
|
57
|
-
if (!bin) return record;
|
|
58
|
-
|
|
59
|
-
// Stamp the bin onto each Purchase so verification queries can group by it
|
|
60
|
-
// without re-deriving. Non-flag-stamping: this property is config-defined when
|
|
61
|
-
// dungeons add `cohort` to Purchase.properties; here we accept the small bend
|
|
62
|
-
// for the verification fixture so DuckDB queries are clean.
|
|
63
|
-
for (const ev of record) {
|
|
64
|
-
if (ev.event === 'Purchase') ev.cohort = bin;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 2. Scale Purchase amounts via mutate atom (engaged 2x, power 3x)
|
|
68
|
-
const factor = SCALE_BY_BIN[bin] || 1;
|
|
69
|
-
if (factor !== 1) {
|
|
70
|
-
scalePropertyValue(record, e => e.event === 'Purchase', 'amount', factor);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 3. Power users get a Browse burst clustered at the dataset midpoint
|
|
74
|
-
const burstCount = BURST_COUNT_BY_BIN[bin] || 0;
|
|
75
|
-
if (burstCount > 0 && record.length) {
|
|
76
|
-
const midUnixMs = (meta.datasetStart + (meta.datasetEnd - meta.datasetStart) / 2) * 1000;
|
|
77
|
-
// Use the user's first Browse as a clone template so we honor the schema.
|
|
78
|
-
const template = record.find(e => e.event === 'Browse');
|
|
79
|
-
if (template) {
|
|
80
|
-
const tpl = cloneEvent(template, { cohort: bin });
|
|
81
|
-
injectBurst(record, tpl, burstCount, midUnixMs, 60 * 60 * 1000); // ±1h spread
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Re-sort by time after burst injection so downstream pipeline sees ordered events.
|
|
86
|
-
record.sort((a, b) => (a.time < b.time ? -1 : a.time > b.time ? 1 : 0));
|
|
87
|
-
return record;
|
|
104
|
+
return handleEverythingHooks(record, meta);
|
|
88
105
|
},
|
|
89
106
|
};
|
|
107
|
+
|
|
108
|
+
export default config;
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
* Phase 2 identity model — verification fixture (intentionally simple).
|
|
3
|
-
*
|
|
4
|
-
* 1K born-in-dataset users with isAuthEvent on `Sign Up` (the stitch step) and a
|
|
5
|
-
* 3-step first funnel. Dataset window pinned for determinism. avgDevicePerUser:2
|
|
6
|
-
* exercises the multi-device sticky-per-session model. Used by phase 2 verification
|
|
7
|
-
* gate (DuckDB queries against the events output).
|
|
8
|
-
*/
|
|
9
|
-
|
|
1
|
+
// ── IMPORTS ──
|
|
10
2
|
import dayjs from 'dayjs';
|
|
11
3
|
|
|
4
|
+
// ── OVERVIEW ──
|
|
5
|
+
/*
|
|
6
|
+
* NAME: identity-model-verify
|
|
7
|
+
* PURPOSE: Phase 2 identity model verification fixture (intentionally simple).
|
|
8
|
+
* 1K born-in-dataset users with isAuthEvent on `Sign Up` (the stitch
|
|
9
|
+
* step) and a 3-step first funnel. avgDevicePerUser:2 exercises the
|
|
10
|
+
* multi-device sticky-per-session model. Used by the phase 2
|
|
11
|
+
* verification gate (DuckDB queries against the events output).
|
|
12
|
+
* SCALE: 1,000 users, ~90K events, 30 days
|
|
13
|
+
* EVENTS (6): Browse (5) > Save Item (2) > Land (1) > View Pricing (1) > Sign Up (1) > Onboarding (1)
|
|
14
|
+
* FUNNELS (1): Acquisition: Land → View Pricing → Sign Up → Onboarding (80%, attempts 0-2)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// ── SCALE ──
|
|
12
18
|
const FIXED_NOW = dayjs('2024-02-02').unix();
|
|
13
19
|
|
|
20
|
+
// ── CONFIG ──
|
|
14
21
|
export default {
|
|
15
22
|
name: 'identity-model-verify',
|
|
16
23
|
seed: 'phase2-identity-verify',
|
|
@@ -19,9 +26,12 @@ export default {
|
|
|
19
26
|
numUsers: 1_000,
|
|
20
27
|
avgEventsPerUserPerDay: 3,
|
|
21
28
|
percentUsersBornInDataset: 100,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
switches: {
|
|
30
|
+
hasSessionIds: true,
|
|
31
|
+
},
|
|
32
|
+
identity: {
|
|
33
|
+
avgDevicePerUser: 2,
|
|
34
|
+
},
|
|
25
35
|
format: 'json',
|
|
26
36
|
concurrency: 1,
|
|
27
37
|
writeToDisk: true,
|
|
@@ -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,
|