@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
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Smoke-test runner — runs every dungeon at tiny scale in parallel to verify it
4
+ * loads, generates events, and writes output without crashing.
5
+ *
6
+ * Default scale: 100 users, 1000 events per dungeon. NOT for verification —
7
+ * use scripts/verify-runner.mjs at full fidelity for that.
8
+ *
9
+ * Usage:
10
+ * node scripts/smoke-test-all.mjs
11
+ * node scripts/smoke-test-all.mjs --dir dungeons/vertical # default
12
+ * node scripts/smoke-test-all.mjs --dir dungeons/technical
13
+ * node scripts/smoke-test-all.mjs --dir dungeons # both subdirs
14
+ * node scripts/smoke-test-all.mjs --concurrency 4 # default: cpu count
15
+ * node scripts/smoke-test-all.mjs --users 500 --events 5000 # override scale
16
+ *
17
+ * Output: per-dungeon PASS/FAIL line + a final summary table. Spawns each
18
+ * dungeon as a child node process so a crash in one doesn't abort the run.
19
+ */
20
+ import { spawn } from 'child_process';
21
+ import { readdirSync, statSync, rmSync, existsSync, mkdirSync } from 'fs';
22
+ import path from 'path';
23
+ import os from 'os';
24
+
25
+ const args = process.argv.slice(2);
26
+ function arg(name, fallback) {
27
+ const i = args.indexOf(`--${name}`);
28
+ return i === -1 ? fallback : args[i + 1];
29
+ }
30
+
31
+ const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
32
+ const dirArg = arg('dir', 'dungeons/vertical');
33
+ const numUsers = parseInt(arg('users', '100'), 10);
34
+ const numEvents = parseInt(arg('events', '1000'), 10);
35
+ const concurrency = parseInt(arg('concurrency', String(Math.max(2, os.cpus().length))), 10);
36
+ const keep = args.includes('--keep');
37
+
38
+ function findDungeons(dir) {
39
+ const abs = path.resolve(ROOT, dir);
40
+ if (!existsSync(abs)) return [];
41
+ const stat = statSync(abs);
42
+ if (stat.isFile()) return [abs];
43
+ const out = [];
44
+ for (const entry of readdirSync(abs)) {
45
+ const full = path.join(abs, entry);
46
+ const s = statSync(full);
47
+ if (s.isDirectory()) out.push(...findDungeons(full));
48
+ else if (entry.endsWith('.js')) out.push(full);
49
+ }
50
+ return out;
51
+ }
52
+
53
+ const dungeons = findDungeons(dirArg).sort();
54
+ if (dungeons.length === 0) {
55
+ console.error(`No dungeons found under ${dirArg}`);
56
+ process.exit(1);
57
+ }
58
+
59
+ const dataDir = path.join(ROOT, 'data');
60
+ mkdirSync(dataDir, { recursive: true });
61
+
62
+ const RUNNER = `
63
+ import generate from '${path.join(ROOT, 'index.js')}';
64
+ // With node -e, process.argv = [nodePath, ...userArgs] — no script slot.
65
+ const [dungeonPath, name, numUsers, numEvents] = process.argv.slice(1);
66
+ const r = await generate(dungeonPath, {
67
+ numUsers: parseInt(numUsers, 10),
68
+ numEvents: parseInt(numEvents, 10),
69
+ avgEventsPerUserPerDay: undefined, // force numEvents path
70
+ writeToDisk: true,
71
+ name,
72
+ format: 'json',
73
+ verbose: false,
74
+ concurrency: 1,
75
+ token: '',
76
+ serviceAccount: 'fake', serviceSecret: 'fake', projectId: '1',
77
+ });
78
+ console.log(JSON.stringify({ eventCount: r.eventCount, userCount: r.userCount }));
79
+ `;
80
+
81
+ function runOne(dungeonPath) {
82
+ const base = path.basename(dungeonPath, '.js');
83
+ const namePrefix = `smoke-${base}`;
84
+ return new Promise(resolve => {
85
+ const start = Date.now();
86
+ const child = spawn(
87
+ process.execPath,
88
+ ['--input-type=module', '-e', RUNNER, dungeonPath, namePrefix, String(numUsers), String(numEvents)],
89
+ { cwd: ROOT, stdio: ['ignore', 'pipe', 'pipe'] }
90
+ );
91
+ let out = '';
92
+ let err = '';
93
+ child.stdout.on('data', d => { out += d.toString(); });
94
+ child.stderr.on('data', d => { err += d.toString(); });
95
+ child.on('close', code => {
96
+ const ms = Date.now() - start;
97
+ let result;
98
+ try {
99
+ const lastLine = out.trim().split('\n').filter(Boolean).pop() || '{}';
100
+ result = JSON.parse(lastLine);
101
+ } catch {
102
+ result = {};
103
+ }
104
+ const ok = code === 0 && result.eventCount > 0;
105
+ if (!keep) {
106
+ try {
107
+ for (const f of readdirSync(dataDir)) {
108
+ if (f.startsWith(`${namePrefix}-`)) rmSync(path.join(dataDir, f));
109
+ }
110
+ } catch {}
111
+ }
112
+ resolve({
113
+ dungeon: path.relative(ROOT, dungeonPath),
114
+ ok,
115
+ code,
116
+ ms,
117
+ eventCount: result.eventCount || 0,
118
+ userCount: result.userCount || 0,
119
+ err: ok ? '' : (err.trim().split('\n').slice(-3).join(' | ') || `exit ${code}`),
120
+ });
121
+ });
122
+ });
123
+ }
124
+
125
+ console.log(`Smoke test: ${dungeons.length} dungeons, ${numUsers} users / ${numEvents} events each, concurrency=${concurrency}`);
126
+ const queue = [...dungeons];
127
+ const results = [];
128
+ const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
129
+ while (queue.length) {
130
+ const d = queue.shift();
131
+ const res = await runOne(d);
132
+ const tag = res.ok ? 'PASS' : 'FAIL';
133
+ console.log(` [${tag}] ${path.basename(res.dungeon)} — ${res.eventCount} events, ${res.userCount} users, ${(res.ms / 1000).toFixed(2)}s${res.ok ? '' : ` — ${res.err}`}`);
134
+ results.push(res);
135
+ }
136
+ });
137
+ await Promise.all(workers);
138
+
139
+ results.sort((a, b) => a.dungeon.localeCompare(b.dungeon));
140
+ const fails = results.filter(r => !r.ok);
141
+
142
+ console.log('\n┌─────────────────────────────────────────────────┬───────┬────────┬───────┬──────┐');
143
+ console.log('│ Dungeon │ State │ Events │ Users │ ms │');
144
+ console.log('├─────────────────────────────────────────────────┼───────┼────────┼───────┼──────┤');
145
+ for (const r of results) {
146
+ const name = r.dungeon.padEnd(47).slice(0, 47);
147
+ const state = (r.ok ? 'PASS' : 'FAIL').padEnd(5);
148
+ const ev = String(r.eventCount).padStart(6);
149
+ const u = String(r.userCount).padStart(5);
150
+ const ms = String(r.ms).padStart(4);
151
+ console.log(`│ ${name} │ ${state} │ ${ev} │ ${u} │ ${ms} │`);
152
+ }
153
+ console.log('└─────────────────────────────────────────────────┴───────┴────────┴───────┴──────┘');
154
+
155
+ console.log(`\n${results.length - fails.length}/${results.length} passed`);
156
+ if (fails.length > 0) {
157
+ console.log('\nFailures:');
158
+ for (const f of fails) {
159
+ console.log(` ✗ ${f.dungeon}: ${f.err}`);
160
+ }
161
+ process.exit(1);
162
+ }
@@ -1,42 +1,90 @@
1
1
  /**
2
- * Verify Runner — runs a dungeon at constrained params for hook verification.
2
+ * Verify Runner — runs a dungeon for hook verification.
3
3
  *
4
- * Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name]
4
+ * Default mode: FULL FIDELITY — runs the dungeon with its own configured
5
+ * numUsers / avgEventsPerUserPerDay / numDays. This is the only way to
6
+ * verify the trends a benchmark consumer will actually see.
5
7
  *
6
- * Example: node scripts/verify-runner.mjs dungeons/streaming.js verify-streaming
8
+ * --small mode: ~1K users with avgEventsPerUserPerDay scaled proportionally
9
+ * so total events stay near 100K. Use only for fast smoke checks; do NOT
10
+ * ship verification verdicts based on --small runs.
11
+ *
12
+ * Usage:
13
+ * node scripts/verify-runner.mjs <dungeon-path> [run-name] [--small]
14
+ *
15
+ * Examples:
16
+ * node scripts/verify-runner.mjs dungeons/vertical/gaming.js verify-gaming
17
+ * node scripts/verify-runner.mjs dungeons/vertical/gaming.js verify-gaming --small
7
18
  */
8
19
  import generate from '../index.js';
9
20
  import path from 'path';
10
21
 
11
- const dungeonPath = process.argv[2];
22
+ const args = process.argv.slice(2);
23
+ const flags = new Set(args.filter(a => a.startsWith('--')));
24
+ const positional = args.filter(a => !a.startsWith('--'));
25
+
26
+ const dungeonPath = positional[0];
12
27
  if (!dungeonPath) {
13
- console.error('Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name]');
28
+ console.error('Usage: node scripts/verify-runner.mjs <dungeon-path> [run-name] [--small]');
14
29
  process.exit(1);
15
30
  }
16
31
 
17
- const runName = process.argv[3] || 'verify-hooks';
32
+ const runName = positional[1] || 'verify-hooks';
33
+ const isSmall = flags.has('--small');
18
34
  const absolutePath = path.isAbsolute(dungeonPath)
19
- ? dungeonPath
20
- : path.resolve(process.cwd(), dungeonPath);
35
+ ? dungeonPath
36
+ : path.resolve(process.cwd(), dungeonPath);
21
37
 
22
38
  const { default: config } = await import(absolutePath);
23
39
 
24
- const results = await generate({
25
- ...config,
26
- token: "",
27
- numUsers: 1000,
28
- numEvents: 100_000,
29
- format: "json",
30
- gzip: false,
31
- writeToDisk: true,
32
- name: runName,
33
- concurrency: 1,
34
- verbose: false,
35
- });
40
+ let override;
41
+
42
+ if (isSmall) {
43
+ // SMALL mode: 1K users, scaled per-day rate so total events ≈ 100K.
44
+ // Preserves the dungeon's per-user-per-day shape while shrinking the dataset.
45
+ // Use only for quick smoke checks — verdicts must come from full fidelity.
46
+ const numUsers = 1000;
47
+ const numDays = config.numDays || 100;
48
+ const targetTotal = 100_000;
49
+ const scaledRate = targetTotal / (numUsers * numDays);
50
+ override = {
51
+ ...config,
52
+ token: '',
53
+ numUsers,
54
+ numDays,
55
+ avgEventsPerUserPerDay: scaledRate,
56
+ numEvents: undefined,
57
+ format: 'json',
58
+ gzip: false,
59
+ writeToDisk: true,
60
+ name: runName,
61
+ concurrency: 1,
62
+ verbose: false,
63
+ };
64
+ } else {
65
+ // FULL FIDELITY: use the dungeon's own scale settings as-shipped.
66
+ // Full-scale runs can take minutes for 50K-user dungeons; that's expected.
67
+ override = {
68
+ ...config,
69
+ token: '',
70
+ format: 'json',
71
+ gzip: false,
72
+ writeToDisk: true,
73
+ name: runName,
74
+ concurrency: config.concurrency || 1,
75
+ verbose: false,
76
+ };
77
+ }
78
+
79
+ const t0 = Date.now();
80
+ const results = await generate(override);
81
+ const wallMs = Date.now() - t0;
36
82
 
37
83
  console.log(JSON.stringify({
38
- eventCount: results.eventCount,
39
- userCount: results.userCount,
40
- files: results.files,
41
- duration: results.time?.human || results.time?.delta + 'ms'
84
+ mode: isSmall ? 'small' : 'full',
85
+ eventCount: results.eventCount,
86
+ userCount: results.userCount,
87
+ files: results.files,
88
+ duration: results.time?.human || `${wallMs}ms`,
89
+ wallMs,
42
90
  }));
package/types.d.ts CHANGED
@@ -21,11 +21,22 @@ export interface Dungeon {
21
21
  token?: string;
22
22
  /** RNG seed for reproducible output. Same seed + concurrency=1 = identical data. */
23
23
  seed?: string;
24
- /** Number of days the dataset spans (from "now" looking backward). Default: 30 */
24
+ /** Number of days the dataset spans. Used as fallback when datasetStart/datasetEnd are NOT both set — window becomes (today_start - numDays, today_start). Default: 30. When datasetStart/datasetEnd ARE both set, numDays is recomputed from the window and any user-supplied value is ignored (with a warning). */
25
25
  numDays?: number;
26
- /** Explicit start of dataset window (unix seconds). Alternative to numDays. */
26
+ /**
27
+ * Explicit start of the dataset window. Pin BOTH `datasetStart` and `datasetEnd` for
28
+ * bit-exact deterministic runs. Accepts ISO string ("2026-01-01T00:00:00Z"), unix
29
+ * seconds (1735689600), unix milliseconds (1735689600000), or anything `dayjs()`
30
+ * can parse. Setting only one of datasetStart/datasetEnd throws.
31
+ */
32
+ datasetStart?: string | number;
33
+ /**
34
+ * Explicit end of the dataset window. See `datasetStart` — both must be set together.
35
+ */
36
+ datasetEnd?: string | number;
37
+ /** @deprecated Legacy alias internally aliased to datasetStart on validated config. Prefer `datasetStart`. */
27
38
  epochStart?: number;
28
- /** Explicit end of dataset window (unix seconds). Defaults to FIXED_NOW. */
39
+ /** @deprecated Legacy alias internally aliased to datasetEnd on validated config. Prefer `datasetEnd`. */
29
40
  epochEnd?: number;
30
41
  /** Target total number of events to generate across all users. Fallback when avgEventsPerUserPerDay is not set; otherwise derived from rate × numUsers × numDays. */
31
42
  numEvents?: number;
@@ -279,8 +290,20 @@ export type hookTypes =
279
290
  */
280
291
  export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
281
292
 
293
+ /**
294
+ * Time-window anchors present on every hook's `meta`. Use these to derive relative
295
+ * dates inside hooks (e.g. `dayjs.unix(meta.datasetStart).add(45, 'days')`). NEVER
296
+ * read wall-clock `dayjs()` inside a hook — it makes hooks non-deterministic.
297
+ */
298
+ export interface HookMetaTimeAnchors {
299
+ /** Start of the dataset window (unix seconds). Same value the engine uses to bound event generation. */
300
+ datasetStart: number;
301
+ /** End of the dataset window (unix seconds). Same value the engine uses to bound event generation. */
302
+ datasetEnd: number;
303
+ }
304
+
282
305
  /** Meta passed to the "event" hook. */
283
- export interface HookMetaEvent {
306
+ export interface HookMetaEvent extends HookMetaTimeAnchors {
284
307
  /** The user this event belongs to (only `distinct_id` is guaranteed). */
285
308
  user: { distinct_id: string };
286
309
  /** The fully-resolved dungeon config. */
@@ -288,7 +311,7 @@ export interface HookMetaEvent {
288
311
  }
289
312
 
290
313
  /** Meta passed to the "user" hook (fires when a user profile is created). */
291
- export interface HookMetaUser {
314
+ export interface HookMetaUser extends HookMetaTimeAnchors {
292
315
  /** The user object being constructed (mutate in place). */
293
316
  user: UserProfile;
294
317
  /** The fully-resolved dungeon config. */
@@ -298,7 +321,7 @@ export interface HookMetaUser {
298
321
  }
299
322
 
300
323
  /** Meta passed to the "scd-pre" hook (fires per SCD prop, before insertion). */
301
- export interface HookMetaScdPre {
324
+ export interface HookMetaScdPre extends HookMetaTimeAnchors {
302
325
  /** The user profile that owns these SCD entries. */
303
326
  profile: UserProfile;
304
327
  /** The SCD prop key being generated (e.g. "plan", "tier"). */
@@ -312,7 +335,7 @@ export interface HookMetaScdPre {
312
335
  }
313
336
 
314
337
  /** Meta passed to the "funnel-pre" hook (mutate funnel before generating events). */
315
- export interface HookMetaFunnelPre {
338
+ export interface HookMetaFunnelPre extends HookMetaTimeAnchors {
316
339
  user: { distinct_id: string };
317
340
  profile: UserProfile;
318
341
  scd: Record<string, SCDSchema[]>;
@@ -323,7 +346,7 @@ export interface HookMetaFunnelPre {
323
346
  }
324
347
 
325
348
  /** Meta passed to the "funnel-post" hook (mutate generated funnel events in place). */
326
- export interface HookMetaFunnelPost {
349
+ export interface HookMetaFunnelPost extends HookMetaTimeAnchors {
327
350
  user: { distinct_id: string };
328
351
  profile: UserProfile;
329
352
  scd: Record<string, SCDSchema[]>;
@@ -332,7 +355,7 @@ export interface HookMetaFunnelPost {
332
355
  }
333
356
 
334
357
  /** Meta passed to the "everything" hook — most powerful hook (sees all events for one user). */
335
- export interface HookMetaEverything {
358
+ export interface HookMetaEverything extends HookMetaTimeAnchors {
336
359
  /** The user's profile, including merged persona/region/attribution properties. */
337
360
  profile: UserProfile;
338
361
  /** All SCD entries for this user, keyed by prop name. */
@@ -463,10 +486,14 @@ export interface Context {
463
486
  /** Pre-built UTM campaign pool (used when `hasCampaigns: true`). */
464
487
  campaigns: Record<string, ValueValid>[];
465
488
  runtime: RuntimeState;
489
+ /** End of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetEnd`, or fallback `today_start`. */
466
490
  FIXED_NOW: number;
491
+ /** Start of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetStart`, or fallback `today_start - numDays`. */
467
492
  FIXED_BEGIN?: number;
468
- TIME_SHIFT_SECONDS: number;
469
- MAX_TIME: number;
493
+ /** Alias of `FIXED_BEGIN` — surfaced on hook `meta.datasetStart`. */
494
+ DATASET_START_SECONDS: number;
495
+ /** Alias of `FIXED_NOW` — surfaced on hook `meta.datasetEnd`. */
496
+ DATASET_END_SECONDS: number;
470
497
 
471
498
  // State update methods
472
499
  incrementOperations(): void;
@@ -483,10 +510,6 @@ export interface Context {
483
510
  incrementUserCount(): void;
484
511
  incrementEventCount(): void;
485
512
  isBatchMode(): boolean;
486
-
487
- // Time helper methods
488
- getTimeShift(): number;
489
- getDaysShift(): number;
490
513
  }
491
514
 
492
515
  /**
@@ -1,128 +0,0 @@
1
- {
2
- "events": [
3
- {
4
- "event": "sign up",
5
- "isFirstEvent": true,
6
- "properties": {
7
- "method": [
8
- "email",
9
- "google",
10
- "facebook",
11
- "github"
12
- ]
13
- }
14
- },
15
- {
16
- "event": "page view",
17
- "weight": 10,
18
- "properties": {
19
- "page": [
20
- "/",
21
- "/pricing",
22
- "/features",
23
- "/docs",
24
- "/blog",
25
- "/signup"
26
- ]
27
- }
28
- },
29
- {
30
- "event": "purchase",
31
- "weight": 2,
32
- "properties": {
33
- "amount": {
34
- "$range": [
35
- 108,
36
- 392
37
- ]
38
- },
39
- "currency": [
40
- "USD",
41
- "EUR",
42
- "GBP"
43
- ],
44
- "item_count": {
45
- "$range": [
46
- 2,
47
- 7
48
- ]
49
- }
50
- }
51
- },
52
- {
53
- "event": "ad click",
54
- "weight": 6,
55
- "properties": {
56
- "partner": [
57
- "google",
58
- "facebook",
59
- "linkedin",
60
- "twitter",
61
- "bing",
62
- "tiktok"
63
- ],
64
- "ad_format": [
65
- "banner",
66
- "video",
67
- "carousel",
68
- "native",
69
- "search"
70
- ],
71
- "placement": [
72
- "feed",
73
- "sidebar",
74
- "header",
75
- "interstitial"
76
- ]
77
- }
78
- },
79
- {
80
- "event": "search",
81
- "weight": 5,
82
- "properties": {
83
- "query_length": {
84
- "$range": [
85
- 3,
86
- 28
87
- ]
88
- },
89
- "results_count": {
90
- "$range": [
91
- 21,
92
- 79
93
- ]
94
- }
95
- }
96
- },
97
- {
98
- "event": "share",
99
- "weight": 3,
100
- "properties": {
101
- "platform": [
102
- "twitter",
103
- "facebook",
104
- "linkedin",
105
- "email"
106
- ],
107
- "content_type": [
108
- "product",
109
- "page",
110
- "deal"
111
- ]
112
- }
113
- }
114
- ],
115
- "userProps": {
116
- "plan": [
117
- "free",
118
- "starter",
119
- "pro",
120
- "enterprise"
121
- ],
122
- "signup_source": [
123
- "organic",
124
- "paid",
125
- "referral"
126
- ]
127
- }
128
- }
@@ -1,92 +0,0 @@
1
- {
2
- "events": [
3
- {
4
- "event": "page view",
5
- "weight": 10,
6
- "properties": {
7
- "page": [
8
- "/",
9
- "/features",
10
- "/pricing",
11
- "/docs",
12
- "/blog"
13
- ],
14
- "referrer": [
15
- "direct",
16
- "google",
17
- "twitter",
18
- "linkedin"
19
- ]
20
- }
21
- },
22
- {
23
- "event": "signup",
24
- "isFirstEvent": true,
25
- "properties": {
26
- "method": [
27
- "email",
28
- "google",
29
- "github",
30
- "sso"
31
- ]
32
- }
33
- },
34
- {
35
- "event": "feature used",
36
- "weight": 7,
37
- "properties": {
38
- "feature": [
39
- "dashboard",
40
- "reports",
41
- "settings",
42
- "export",
43
- "import",
44
- "search"
45
- ]
46
- }
47
- },
48
- {
49
- "event": "purchase",
50
- "weight": 2,
51
- "properties": {
52
- "amount": {
53
- "$range": [
54
- 114,
55
- 397
56
- ]
57
- },
58
- "plan": [
59
- "monthly",
60
- "annual"
61
- ]
62
- }
63
- },
64
- {
65
- "event": "button click",
66
- "weight": 8,
67
- "properties": {
68
- "button": [
69
- "cta",
70
- "nav",
71
- "menu",
72
- "submit",
73
- "cancel",
74
- "back"
75
- ],
76
- "location": [
77
- "header",
78
- "body",
79
- "footer",
80
- "sidebar"
81
- ]
82
- }
83
- }
84
- ],
85
- "userProps": {
86
- "plan": [
87
- "free",
88
- "pro",
89
- "enterprise"
90
- ]
91
- }
92
- }