@ak--47/dungeon-master 1.3.0 → 1.4.0
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 +58 -0
- package/dungeons/technical/array-of-object-lookup.js +0 -2
- package/dungeons/technical/hook-helpers-verify.js +89 -0
- package/dungeons/technical/identity-model-verify.js +47 -0
- package/dungeons/technical/pattern-aggregate-by-bin.js +41 -0
- package/dungeons/technical/pattern-attributed-by-source.js +42 -0
- package/dungeons/technical/pattern-frequency-by-frequency.js +40 -0
- package/dungeons/technical/pattern-funnel-frequency.js +54 -0
- package/dungeons/technical/pattern-ttc-by-segment.js +45 -0
- package/dungeons/technical/simple.js +3 -4
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/text-generation.js +1 -1
- package/dungeons/vertical/ai-platform.js +216 -164
- package/dungeons/vertical/community.js +74 -41
- package/dungeons/vertical/crypto.js +343 -233
- package/dungeons/vertical/dating.js +246 -292
- package/dungeons/vertical/devtools.js +179 -70
- package/dungeons/vertical/ecommerce.js +275 -122
- package/dungeons/vertical/education.js +345 -312
- package/dungeons/vertical/fintech.js +445 -314
- package/dungeons/vertical/fitness.js +199 -165
- package/dungeons/vertical/food-delivery.js +309 -330
- package/dungeons/vertical/gaming.js +387 -241
- package/dungeons/vertical/healthcare.js +149 -66
- package/dungeons/vertical/insurance-application.js +173 -75
- package/dungeons/vertical/logistics.js +107 -12
- package/dungeons/vertical/marketplace.js +171 -76
- package/dungeons/vertical/media.js +259 -402
- package/dungeons/vertical/real-estate.js +336 -318
- package/dungeons/vertical/sass.js +294 -288
- package/dungeons/vertical/social.js +289 -234
- package/dungeons/vertical/travel.js +66 -20
- package/index.js +17 -17
- package/lib/core/config-validator.js +226 -175
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +57 -104
- package/lib/generators/funnels.js +212 -93
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/hook-helpers/_internal.js +23 -0
- package/lib/hook-helpers/cohort.js +124 -0
- package/lib/hook-helpers/identity.js +56 -0
- package/lib/hook-helpers/index.js +44 -0
- package/lib/hook-helpers/inject.js +99 -0
- package/lib/hook-helpers/mutate.js +151 -0
- package/lib/hook-helpers/timing.js +99 -0
- package/lib/hook-patterns/aggregate-per-user-by-bin.js +38 -0
- package/lib/hook-patterns/attributed-by-source.js +72 -0
- package/lib/hook-patterns/frequency-by-frequency.js +46 -0
- package/lib/hook-patterns/funnel-frequency-breakdown.js +73 -0
- package/lib/hook-patterns/index.js +14 -0
- package/lib/hook-patterns/time-to-convert-by-segment.js +41 -0
- package/lib/orchestrators/user-loop.js +143 -293
- package/lib/utils/utils.js +97 -52
- package/lib/verify/emulate-breakdown.js +281 -0
- package/lib/verify/index.js +12 -0
- package/lib/verify/verify-dungeon.js +61 -0
- package/package.json +6 -4
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +434 -225
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/vertical/ai-platform-schema.json +0 -617
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/crypto-schema.json +0 -546
- package/dungeons/vertical/dating-schema.json +0 -401
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -2703
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/real-estate-schema.json +0 -527
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
package/types.d.ts
CHANGED
|
@@ -17,15 +17,40 @@ export type ValueValid = Primitives | ValueValid[] | (() => ValueValid);
|
|
|
17
17
|
*/
|
|
18
18
|
export interface Dungeon {
|
|
19
19
|
// ── Core Parameters ──
|
|
20
|
+
/** Optional dungeon version. Not used by the engine — serves as metadata for tracking revisions when configs are saved/shared. */
|
|
21
|
+
version?: string | number;
|
|
20
22
|
/** Mixpanel project token. If provided, data will be imported to Mixpanel after generation. */
|
|
21
23
|
token?: string;
|
|
22
24
|
/** RNG seed for reproducible output. Same seed + concurrency=1 = identical data. */
|
|
23
25
|
seed?: string;
|
|
24
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Number of days the dataset spans. Default: 30.
|
|
28
|
+
*
|
|
29
|
+
* Three resolution modes:
|
|
30
|
+
* 1. **`numDays` alone (no datasetStart/End):** Window = `[today - numDays, today]`.
|
|
31
|
+
* Simplest API for ad-hoc dungeons. NOT deterministic across runs (today changes).
|
|
32
|
+
* 2. **`datasetStart` + `datasetEnd` (no numDays):** Window pinned exactly. `numDays`
|
|
33
|
+
* derived automatically. Fully deterministic — use for vertical/production dungeons.
|
|
34
|
+
* 3. **All three set:** `datasetStart`/`datasetEnd` win. `numDays` is recomputed from
|
|
35
|
+
* the window; user-supplied value is ignored (with a warning).
|
|
36
|
+
*
|
|
37
|
+
* Setting only one of `datasetStart`/`datasetEnd` throws.
|
|
38
|
+
*/
|
|
25
39
|
numDays?: number;
|
|
26
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Explicit start of the dataset window. Pin BOTH `datasetStart` and `datasetEnd` for
|
|
42
|
+
* bit-exact deterministic runs. Accepts ISO string ("2026-01-01T00:00:00Z"), unix
|
|
43
|
+
* seconds (1735689600), unix milliseconds (1735689600000), or anything `dayjs()`
|
|
44
|
+
* can parse. Setting only one of datasetStart/datasetEnd throws.
|
|
45
|
+
*/
|
|
46
|
+
datasetStart?: string | number;
|
|
47
|
+
/**
|
|
48
|
+
* Explicit end of the dataset window. See `datasetStart` — both must be set together.
|
|
49
|
+
*/
|
|
50
|
+
datasetEnd?: string | number;
|
|
51
|
+
/** @deprecated Legacy alias internally aliased to datasetStart on validated config. Prefer `datasetStart`. */
|
|
27
52
|
epochStart?: number;
|
|
28
|
-
/**
|
|
53
|
+
/** @deprecated Legacy alias internally aliased to datasetEnd on validated config. Prefer `datasetEnd`. */
|
|
29
54
|
epochEnd?: number;
|
|
30
55
|
/** Target total number of events to generate across all users. Fallback when avgEventsPerUserPerDay is not set; otherwise derived from rate × numUsers × numDays. */
|
|
31
56
|
numEvents?: number;
|
|
@@ -58,7 +83,21 @@ export interface Dungeon {
|
|
|
58
83
|
hasAvatar?: boolean;
|
|
59
84
|
/** If true, events include geo properties (city, region, country, lat/lng). */
|
|
60
85
|
hasLocation?: boolean;
|
|
61
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* If true, events include UTM campaign properties (utm_source / utm_campaign / utm_medium /
|
|
88
|
+
* utm_content / utm_term).
|
|
89
|
+
*
|
|
90
|
+
* Default: false.
|
|
91
|
+
*
|
|
92
|
+
* Behavior:
|
|
93
|
+
* - false: no UTM stamping anywhere.
|
|
94
|
+
* - true + at least one event in `events[]` has `isAttributionEvent: true`: only the flagged
|
|
95
|
+
* events are eligible. Within those, ~25% are stamped with a randomly-picked campaign.
|
|
96
|
+
* - true + no event flagged: backwards-compat fallback — ~25% of ALL events are stamped
|
|
97
|
+
* with a randomly-picked campaign (legacy behavior).
|
|
98
|
+
*
|
|
99
|
+
* @see EventConfig.isAttributionEvent
|
|
100
|
+
*/
|
|
62
101
|
hasCampaigns?: boolean;
|
|
63
102
|
/** If true, generates ad spend data (impressions, clicks, cost). */
|
|
64
103
|
hasAdSpend?: boolean;
|
|
@@ -76,8 +115,47 @@ export interface Dungeon {
|
|
|
76
115
|
gzip?: boolean;
|
|
77
116
|
/** If true, prints progress to stdout during generation. */
|
|
78
117
|
verbose?: boolean;
|
|
79
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* @deprecated Prefer `avgDevicePerUser`. `true` is now an alias for `avgDevicePerUser: 1`
|
|
120
|
+
* (single sticky device per user, every event stamped with that `device_id`). `false`
|
|
121
|
+
* (default) leaves the engine in legacy "no device_id stamping" mode unless
|
|
122
|
+
* `avgDevicePerUser` is set.
|
|
123
|
+
*
|
|
124
|
+
* @see Dungeon.avgDevicePerUser
|
|
125
|
+
*/
|
|
80
126
|
hasAnonIds?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Number of distinct devices each user owns. Whole number ≥ 0. Default: 0 (legacy —
|
|
129
|
+
* no `device_id` stamping anywhere). `≤0` is coerced to `1` if `hasAnonIds: true` is
|
|
130
|
+
* also set; otherwise `0` keeps the engine in legacy mode for backwards compat.
|
|
131
|
+
*
|
|
132
|
+
* Behavior:
|
|
133
|
+
* - `0` (default): no `device_id` stamping. Every event gets `user_id` only. Same as
|
|
134
|
+
* pre-1.4 behavior when `hasAnonIds` is not set.
|
|
135
|
+
* - `1`: one device per user. All of that user's events that need a device share a
|
|
136
|
+
* single sticky `device_id`. `hasAnonIds: true` is an alias for this.
|
|
137
|
+
* - `>1`: per-user device pool sized via a normal distribution centered on this value
|
|
138
|
+
* (sd ≈ value/2, clamped ≥ 1, integer-rounded). Sessions are sticky to a single
|
|
139
|
+
* device drawn from the user's pool — every event in that session shares the same
|
|
140
|
+
* `device_id`. Cross-session events for the same user may differ.
|
|
141
|
+
*
|
|
142
|
+
* Identity stamping interactions (multi-device + auth + first funnel):
|
|
143
|
+
* - Pre-existing users (born before dataset window): every event gets both `user_id`
|
|
144
|
+
* and a per-session `device_id`.
|
|
145
|
+
* - Born-in-dataset users running their `isFirstFunnel`:
|
|
146
|
+
* * Pre-auth steps (steps before the first `isAuthEvent` in the funnel sequence):
|
|
147
|
+
* `device_id` only — no `user_id` yet.
|
|
148
|
+
* * The stitch step (the first `isAuthEvent`): both `user_id` AND `device_id`.
|
|
149
|
+
* * Post-auth steps in the same funnel: `user_id` only.
|
|
150
|
+
* * All later (non-firstFunnel) events: `user_id` + per-session sticky `device_id`.
|
|
151
|
+
* - Born-in-dataset users on a `Funnel.attempts` retry that does not reach `isAuthEvent`:
|
|
152
|
+
* every event in that failed attempt is `device_id` only (pre-auth, never stitched).
|
|
153
|
+
*
|
|
154
|
+
* @see Dungeon.hasAnonIds (deprecated alias when `true`)
|
|
155
|
+
* @see EventConfig.isAuthEvent
|
|
156
|
+
* @see Funnel.attempts
|
|
157
|
+
*/
|
|
158
|
+
avgDevicePerUser?: number;
|
|
81
159
|
/** If true, users get session IDs attached to events based on temporal clustering. */
|
|
82
160
|
hasSessionIds?: boolean;
|
|
83
161
|
/** Session timeout in minutes. Events with gaps exceeding this start a new session. Default: 30. Only used when hasSessionIds is true. */
|
|
@@ -128,16 +206,13 @@ export interface Dungeon {
|
|
|
128
206
|
engagementDecay?: EngagementDecay;
|
|
129
207
|
/** Data quality imperfections to inject (nulls, duplicates, bots, late-arriving events). */
|
|
130
208
|
dataQuality?: DataQuality;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
features?: FeatureConfig[];
|
|
139
|
-
/** Anomaly/outlier injection: extreme values, bursts, coordinated spikes. */
|
|
140
|
-
anomalies?: AnomalyConfig[];
|
|
209
|
+
|
|
210
|
+
// ── Removed in 1.4 (silently ignored, one deprecation warning per dungeon) ──
|
|
211
|
+
// The following config keys were removed from the engine in 1.4. Existing dungeon
|
|
212
|
+
// files that still set them will load and run — `validateDungeonConfig` strips them
|
|
213
|
+
// with a single deprecation warning per dungeon. Recreate these patterns as hooks
|
|
214
|
+
// (see `lib/hook-patterns/*` once Phase 4 lands).
|
|
215
|
+
// subscription, attribution, geo, features, anomalies
|
|
141
216
|
|
|
142
217
|
/** Allow arbitrary additional properties on the config. */
|
|
143
218
|
[key: string]: any;
|
|
@@ -279,8 +354,32 @@ export type hookTypes =
|
|
|
279
354
|
*/
|
|
280
355
|
export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
|
|
281
356
|
|
|
282
|
-
/**
|
|
283
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Time-window anchors present on every hook's `meta`. Use these to derive relative
|
|
359
|
+
* dates inside hooks (e.g. `dayjs.unix(meta.datasetStart).add(45, 'days')`). NEVER
|
|
360
|
+
* read wall-clock `dayjs()` inside a hook — it makes hooks non-deterministic.
|
|
361
|
+
*/
|
|
362
|
+
export interface HookMetaTimeAnchors {
|
|
363
|
+
/** Start of the dataset window (unix seconds). Same value the engine uses to bound event generation. */
|
|
364
|
+
datasetStart: number;
|
|
365
|
+
/** End of the dataset window (unix seconds). Same value the engine uses to bound event generation. */
|
|
366
|
+
datasetEnd: number;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Meta passed to the "event" hook.
|
|
371
|
+
*
|
|
372
|
+
* **Temporal-check warning:** `datasetStart`/`datasetEnd` are unix seconds in the
|
|
373
|
+
* shifted time frame, but `record.time` during the event hook is in the pre-shift
|
|
374
|
+
* fixed window. Comparing them directly (e.g., `dayjs(record.time).diff(dayjs.unix(meta.datasetStart))`)
|
|
375
|
+
* produces unreliable day-in-dataset values. Move any temporal check to the
|
|
376
|
+
* `everything` hook where both timestamps are in the same frame.
|
|
377
|
+
*
|
|
378
|
+
* Safe uses of the event hook: closure-based state (module-level Maps), event
|
|
379
|
+
* replacement (return a different object), simple property mutations not gated
|
|
380
|
+
* on time.
|
|
381
|
+
*/
|
|
382
|
+
export interface HookMetaEvent extends HookMetaTimeAnchors {
|
|
284
383
|
/** The user this event belongs to (only `distinct_id` is guaranteed). */
|
|
285
384
|
user: { distinct_id: string };
|
|
286
385
|
/** The fully-resolved dungeon config. */
|
|
@@ -288,7 +387,7 @@ export interface HookMetaEvent {
|
|
|
288
387
|
}
|
|
289
388
|
|
|
290
389
|
/** Meta passed to the "user" hook (fires when a user profile is created). */
|
|
291
|
-
export interface HookMetaUser {
|
|
390
|
+
export interface HookMetaUser extends HookMetaTimeAnchors {
|
|
292
391
|
/** The user object being constructed (mutate in place). */
|
|
293
392
|
user: UserProfile;
|
|
294
393
|
/** The fully-resolved dungeon config. */
|
|
@@ -298,7 +397,7 @@ export interface HookMetaUser {
|
|
|
298
397
|
}
|
|
299
398
|
|
|
300
399
|
/** Meta passed to the "scd-pre" hook (fires per SCD prop, before insertion). */
|
|
301
|
-
export interface HookMetaScdPre {
|
|
400
|
+
export interface HookMetaScdPre extends HookMetaTimeAnchors {
|
|
302
401
|
/** The user profile that owns these SCD entries. */
|
|
303
402
|
profile: UserProfile;
|
|
304
403
|
/** The SCD prop key being generated (e.g. "plan", "tier"). */
|
|
@@ -312,28 +411,77 @@ export interface HookMetaScdPre {
|
|
|
312
411
|
}
|
|
313
412
|
|
|
314
413
|
/** Meta passed to the "funnel-pre" hook (mutate funnel before generating events). */
|
|
315
|
-
export interface HookMetaFunnelPre {
|
|
414
|
+
export interface HookMetaFunnelPre extends HookMetaTimeAnchors {
|
|
316
415
|
user: { distinct_id: string };
|
|
317
416
|
profile: UserProfile;
|
|
318
417
|
scd: Record<string, SCDSchema[]>;
|
|
319
418
|
funnel: Funnel;
|
|
320
419
|
config: Dungeon;
|
|
321
|
-
/**
|
|
420
|
+
/**
|
|
421
|
+
* Unix seconds — temporal anchor for this funnel run. For usage funnels, advances
|
|
422
|
+
* after each run so successive funnels spread across the user's active window.
|
|
423
|
+
* For first-funnel attempts, matches the attempt cursor. Use this to implement
|
|
424
|
+
* temporal conversion trends (e.g., "conversion increases after day 30").
|
|
425
|
+
*/
|
|
322
426
|
firstEventTime: number;
|
|
427
|
+
/** True if this funnel is the user's `isFirstFunnel`. */
|
|
428
|
+
isFirstFunnel: boolean;
|
|
429
|
+
/** True if the user's account creation falls inside the dataset window. */
|
|
430
|
+
isBorn: boolean;
|
|
431
|
+
/** Resolved attempts config for this funnel run, or null if attempts is not configured. */
|
|
432
|
+
attemptsConfig: AttemptsConfig | null;
|
|
433
|
+
/** 1-indexed attempt number for this run (1..totalAttempts). */
|
|
434
|
+
attemptNumber: number;
|
|
435
|
+
/** Total number of attempts (failed priors + 1 final). When attempts is omitted, this is 1. */
|
|
436
|
+
totalAttempts: number;
|
|
437
|
+
/** True if this is the final attempt (attemptNumber === totalAttempts). */
|
|
438
|
+
isFinalAttempt: boolean;
|
|
439
|
+
/** The user's assigned persona (if `personas` is configured), or null. */
|
|
440
|
+
persona: Persona | null;
|
|
441
|
+
/** Experiment context for this funnel run, or null if no experiment / pre-start-date. */
|
|
442
|
+
experiment: HookMetaExperiment | null;
|
|
323
443
|
}
|
|
324
444
|
|
|
325
445
|
/** Meta passed to the "funnel-post" hook (mutate generated funnel events in place). */
|
|
326
|
-
export interface HookMetaFunnelPost {
|
|
446
|
+
export interface HookMetaFunnelPost extends HookMetaTimeAnchors {
|
|
327
447
|
user: { distinct_id: string };
|
|
328
448
|
profile: UserProfile;
|
|
329
449
|
scd: Record<string, SCDSchema[]>;
|
|
330
450
|
funnel: Funnel;
|
|
331
451
|
config: Dungeon;
|
|
452
|
+
/** Unix seconds — temporal anchor for this funnel run (see HookMetaFunnelPre.firstEventTime). */
|
|
453
|
+
firstEventTime: number;
|
|
454
|
+
/** True if this funnel is the user's `isFirstFunnel`. */
|
|
455
|
+
isFirstFunnel: boolean;
|
|
456
|
+
/** True if the user's account creation falls inside the dataset window. */
|
|
457
|
+
isBorn: boolean;
|
|
458
|
+
/** Resolved attempts config for this funnel run, or null if attempts is not configured. */
|
|
459
|
+
attemptsConfig: AttemptsConfig | null;
|
|
460
|
+
/** 1-indexed attempt number for this run (1..totalAttempts). */
|
|
461
|
+
attemptNumber: number;
|
|
462
|
+
/** Total number of attempts. */
|
|
463
|
+
totalAttempts: number;
|
|
464
|
+
/** True if this is the final attempt. */
|
|
465
|
+
isFinalAttempt: boolean;
|
|
466
|
+
/** The user's assigned persona (if `personas` is configured), or null. */
|
|
467
|
+
persona: Persona | null;
|
|
468
|
+
/** Experiment context for this funnel run, or null if no experiment / pre-start-date. */
|
|
469
|
+
experiment: HookMetaExperiment | null;
|
|
332
470
|
}
|
|
333
471
|
|
|
334
|
-
/**
|
|
335
|
-
|
|
336
|
-
|
|
472
|
+
/**
|
|
473
|
+
* Meta passed to the "everything" hook — most powerful hook (sees all events for one user).
|
|
474
|
+
*
|
|
475
|
+
* **Ordering within the hook matters.** When multiple effects coexist:
|
|
476
|
+
* 1. SuperProp stamping (profile values → events)
|
|
477
|
+
* 2. Non-temporal mutations and event cloning/injection
|
|
478
|
+
* 3. Event filtering (churn, retention, rate-limit drops)
|
|
479
|
+
* 4. Temporal value mutations (price spikes, error windows) — run LAST
|
|
480
|
+
* so cloned events that land in the window also get the mutation
|
|
481
|
+
* 5. Sort by time
|
|
482
|
+
*/
|
|
483
|
+
export interface HookMetaEverything extends HookMetaTimeAnchors {
|
|
484
|
+
/** The user's profile, including any merged persona properties. */
|
|
337
485
|
profile: UserProfile;
|
|
338
486
|
/** All SCD entries for this user, keyed by prop name. */
|
|
339
487
|
scd: Record<string, SCDSchema[]>;
|
|
@@ -341,6 +489,22 @@ export interface HookMetaEverything {
|
|
|
341
489
|
config: Dungeon;
|
|
342
490
|
/** True if the user's account creation falls inside the dataset window. */
|
|
343
491
|
userIsBornInDataset: boolean;
|
|
492
|
+
/**
|
|
493
|
+
* Unix milliseconds of the stitch event (the first `isAuthEvent` in the user's stream).
|
|
494
|
+
* `null` if this user never authed (pre-existing users have no stitch event in the
|
|
495
|
+
* dataset window — they're already authed; born-in-dataset users who never converted
|
|
496
|
+
* remain pre-auth forever).
|
|
497
|
+
*/
|
|
498
|
+
authTime: number | null;
|
|
499
|
+
/**
|
|
500
|
+
* Predicate bound to this user's `authTime`. Returns true if the event happened before
|
|
501
|
+
* the stitch (i.e. the user was anonymous at that point). Returns false for pre-existing
|
|
502
|
+
* users (they're considered authed throughout). For born-in-dataset users that never
|
|
503
|
+
* authed, returns true for every event.
|
|
504
|
+
*/
|
|
505
|
+
isPreAuth: (event: EventSchema) => boolean;
|
|
506
|
+
/** The user's assigned persona (if `personas` is configured), or null. */
|
|
507
|
+
persona: Persona | null;
|
|
344
508
|
}
|
|
345
509
|
|
|
346
510
|
export interface hookArrayOptions<T> {
|
|
@@ -463,10 +627,14 @@ export interface Context {
|
|
|
463
627
|
/** Pre-built UTM campaign pool (used when `hasCampaigns: true`). */
|
|
464
628
|
campaigns: Record<string, ValueValid>[];
|
|
465
629
|
runtime: RuntimeState;
|
|
630
|
+
/** End of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetEnd`, or fallback `today_start`. */
|
|
466
631
|
FIXED_NOW: number;
|
|
632
|
+
/** Start of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetStart`, or fallback `today_start - numDays`. */
|
|
467
633
|
FIXED_BEGIN?: number;
|
|
468
|
-
|
|
469
|
-
|
|
634
|
+
/** Alias of `FIXED_BEGIN` — surfaced on hook `meta.datasetStart`. */
|
|
635
|
+
DATASET_START_SECONDS: number;
|
|
636
|
+
/** Alias of `FIXED_NOW` — surfaced on hook `meta.datasetEnd`. */
|
|
637
|
+
DATASET_END_SECONDS: number;
|
|
470
638
|
|
|
471
639
|
// State update methods
|
|
472
640
|
incrementOperations(): void;
|
|
@@ -483,10 +651,6 @@ export interface Context {
|
|
|
483
651
|
incrementUserCount(): void;
|
|
484
652
|
incrementEventCount(): void;
|
|
485
653
|
isBatchMode(): boolean;
|
|
486
|
-
|
|
487
|
-
// Time helper methods
|
|
488
|
-
getTimeShift(): number;
|
|
489
|
-
getDaysShift(): number;
|
|
490
654
|
}
|
|
491
655
|
|
|
492
656
|
/**
|
|
@@ -495,7 +659,7 @@ export interface Context {
|
|
|
495
659
|
export interface EventConfig {
|
|
496
660
|
/** The event name (e.g., "page viewed", "purchase completed"). */
|
|
497
661
|
event?: string;
|
|
498
|
-
/** Relative frequency weight (1-10). Higher = more likely to be selected
|
|
662
|
+
/** Relative frequency weight (1-10, clamped by validator). Higher = more likely to be selected for standalone event generation. Does NOT control funnel event frequency — funnels generate their own events. 0 is clamped to 1. Default: 1 */
|
|
499
663
|
weight?: number;
|
|
500
664
|
/** Properties to attach to this event type. Values can be arrays (random pick), functions, or primitives. */
|
|
501
665
|
properties?: Record<string, ValueValid>;
|
|
@@ -509,8 +673,49 @@ export interface EventConfig {
|
|
|
509
673
|
isSessionStartEvent?: boolean;
|
|
510
674
|
/** Internal: timing offset in milliseconds (set by funnel system, not user-configured). */
|
|
511
675
|
relativeTimeMs?: number;
|
|
512
|
-
/** If true, this event
|
|
676
|
+
/** If true, this event appears ONLY in explicitly-defined funnels that reference it — excluded from standalone event generation and auto-generated funnels. Use for events that should only occur in funnel context (e.g., "application approved" only after "application submitted"). Also useful to suppress standalone generation of events with weight > 0 that you only want from funnels. */
|
|
513
677
|
isStrictEvent?: boolean;
|
|
678
|
+
/**
|
|
679
|
+
* If true, this event marks the moment a user transitions from anonymous (pre-auth)
|
|
680
|
+
* to identified (post-auth) — typically the "Sign Up" or "Login" event. Multiple events
|
|
681
|
+
* in a dungeon may carry this flag; the engine looks at the first occurrence in a user's
|
|
682
|
+
* stream to determine the identity stitch moment.
|
|
683
|
+
*
|
|
684
|
+
* Default: false.
|
|
685
|
+
*
|
|
686
|
+
* Behavior, when in a funnel marked `isFirstFunnel: true`:
|
|
687
|
+
* - All steps before the first `isAuthEvent` step in the funnel sequence are stamped
|
|
688
|
+
* with `device_id` only (pre-auth).
|
|
689
|
+
* - The `isAuthEvent` step itself is the stitch — it carries BOTH `user_id` AND
|
|
690
|
+
* `device_id`. Exactly one such record per converted born-in-dataset user.
|
|
691
|
+
* - Steps after the stitch in that funnel get `user_id` only.
|
|
692
|
+
*
|
|
693
|
+
* Behavior outside `isFirstFunnel`: the flag has no extra effect — those events follow
|
|
694
|
+
* the usual identity rules for that user (per `avgDevicePerUser`).
|
|
695
|
+
*
|
|
696
|
+
* Behavior on born-in-dataset users whose `Funnel.attempts` retries fail to reach the
|
|
697
|
+
* `isAuthEvent`: every event in those failed attempts is `device_id` only (pre-auth,
|
|
698
|
+
* never stitched). If their final attempt also fails, they remain pre-auth forever.
|
|
699
|
+
*
|
|
700
|
+
* @see Dungeon.avgDevicePerUser
|
|
701
|
+
* @see Funnel.isFirstFunnel
|
|
702
|
+
* @see Funnel.attempts
|
|
703
|
+
*/
|
|
704
|
+
isAuthEvent?: boolean;
|
|
705
|
+
/**
|
|
706
|
+
* If true, this event is eligible to carry UTM campaign properties when
|
|
707
|
+
* `Dungeon.hasCampaigns: true`. ~25% of flagged events get a randomly-picked campaign
|
|
708
|
+
* stamped (utm_source / utm_campaign / utm_medium / utm_content / utm_term).
|
|
709
|
+
*
|
|
710
|
+
* Default: false.
|
|
711
|
+
*
|
|
712
|
+
* Backwards compat: if `Dungeon.hasCampaigns: true` but no event carries this flag,
|
|
713
|
+
* ~25% of ALL events are stamped (legacy behavior, preserved). Opt-in by flagging at
|
|
714
|
+
* least one event.
|
|
715
|
+
*
|
|
716
|
+
* @see Dungeon.hasCampaigns
|
|
717
|
+
*/
|
|
718
|
+
isAttributionEvent?: boolean;
|
|
514
719
|
}
|
|
515
720
|
|
|
516
721
|
export interface GroupEventConfig extends EventConfig {
|
|
@@ -595,14 +800,137 @@ export interface Funnel {
|
|
|
595
800
|
*/
|
|
596
801
|
conditions?: Record<string, ValueValid>;
|
|
597
802
|
/**
|
|
598
|
-
*
|
|
803
|
+
* Experiment configuration for this funnel.
|
|
804
|
+
*
|
|
805
|
+
* - `true` — backward-compatible shorthand: 3 variants (Variant A = worse, Variant B = better, Control),
|
|
806
|
+
* active for the entire dataset.
|
|
807
|
+
* - `ExperimentConfig` object — custom variant names, conversion/TTC multipliers, temporal gating,
|
|
808
|
+
* and distribution weights.
|
|
599
809
|
*
|
|
810
|
+
* Variant assignment is **deterministic per user** (hash of user_id + experiment name), so the same
|
|
811
|
+
* user is in the same variant across all funnel runs. `$experiment_started` is prepended to the
|
|
812
|
+
* sequence for every post-start-date funnel run.
|
|
813
|
+
*
|
|
814
|
+
* Hook meta (`meta.experiment`) exposes the resolved variant in `funnel-pre` and `funnel-post`
|
|
815
|
+
* hooks, enabling variant-specific story injection.
|
|
816
|
+
*
|
|
817
|
+
* @see ExperimentConfig
|
|
600
818
|
*/
|
|
601
|
-
experiment?: boolean;
|
|
819
|
+
experiment?: boolean | ExperimentConfig;
|
|
602
820
|
/**
|
|
603
821
|
* optional: if set, in sequential funnels, this will determine WHEN the property is bound to the rest of the events in the funnel
|
|
604
822
|
*/
|
|
605
823
|
bindPropsIndex?: number;
|
|
824
|
+
/**
|
|
825
|
+
* Multi-attempt iteration for this funnel. Models real users who land, abandon, come
|
|
826
|
+
* back, and try again. Additive — omit for legacy single-attempt behavior.
|
|
827
|
+
*
|
|
828
|
+
* @see AttemptsConfig
|
|
829
|
+
*/
|
|
830
|
+
attempts?: AttemptsConfig;
|
|
831
|
+
/** @internal Resolved experiment config set by config-validator. */
|
|
832
|
+
_experiment?: { name: string; variants: Array<{ name: string; conversionMultiplier: number; ttcMultiplier: number; weight: number }>; startUnix: number | null };
|
|
833
|
+
/** @internal Set by funnels.js during experiment handling. */
|
|
834
|
+
_experimentName?: string;
|
|
835
|
+
/** @internal Set by funnels.js during experiment handling. */
|
|
836
|
+
_experimentVariant?: string;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Per-funnel multi-attempt config. `attempts.min`/`attempts.max` describe the count of
|
|
841
|
+
* **failed prior attempts** (NOT total attempts). The engine picks an integer
|
|
842
|
+
* `failedPriors = chance.integer({min, max})` then runs `failedPriors + 1` total
|
|
843
|
+
* passes through the funnel. The last pass is the "final attempt" — it converts per
|
|
844
|
+
* `attempts.conversionRate ?? funnel.conversionRate`. Each prior attempt is a truncated
|
|
845
|
+
* pre-auth pass that drops out at a random step before reaching any `isAuthEvent`.
|
|
846
|
+
*
|
|
847
|
+
* Identity interaction (when the funnel is `isFirstFunnel`):
|
|
848
|
+
* - Failed prior attempts: every event stamped with `device_id` only — never reach the
|
|
849
|
+
* stitch step, so `user_id` is never assigned.
|
|
850
|
+
* - Final attempt: follows the standard pre-auth → stitch → post-auth identity model.
|
|
851
|
+
* If the final attempt also fails, the user remains pre-auth forever.
|
|
852
|
+
*
|
|
853
|
+
* For non-`isFirstFunnel` funnels, each attempt is treated as an independent usage
|
|
854
|
+
* session (e.g. abandon-cart). Identity stamping uses the user's normal post-auth model.
|
|
855
|
+
*
|
|
856
|
+
* @example single attempt (default behavior)
|
|
857
|
+
* { attempts: { min: 0, max: 0 } } // exactly one pass — equivalent to omitting attempts
|
|
858
|
+
*
|
|
859
|
+
* @example up to 3 failed retries before a 60% final conversion
|
|
860
|
+
* { conversionRate: 60, attempts: { min: 0, max: 3 } }
|
|
861
|
+
*
|
|
862
|
+
* @example heavy churn before final attempt with overridden conversion rate
|
|
863
|
+
* { conversionRate: 80, attempts: { min: 1, max: 5, conversionRate: 30 } }
|
|
864
|
+
*/
|
|
865
|
+
export interface AttemptsConfig {
|
|
866
|
+
/** Lower bound on the number of FAILED PRIOR attempts. 0 = a single attempt is possible. Whole number, ≥ 0. Default: 0. */
|
|
867
|
+
min?: number;
|
|
868
|
+
/** Upper bound on the number of FAILED PRIOR attempts (inclusive). Whole number, ≥ min. Default: 0. */
|
|
869
|
+
max?: number;
|
|
870
|
+
/**
|
|
871
|
+
* Conversion rate (0–100) applied to the FINAL attempt only — overrides
|
|
872
|
+
* `funnel.conversionRate` if set. Omit to inherit `funnel.conversionRate`.
|
|
873
|
+
* Matches the existing `Funnel.conversionRate` scale (0–100, NOT 0–1).
|
|
874
|
+
*/
|
|
875
|
+
conversionRate?: number;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Experiment configuration for a funnel. Controls variant assignment, naming,
|
|
880
|
+
* conversion/TTC modifiers, and temporal gating.
|
|
881
|
+
*
|
|
882
|
+
* @example A/B test starting 30 days before dataset end
|
|
883
|
+
* {
|
|
884
|
+
* name: "Checkout Redesign",
|
|
885
|
+
* startDaysBeforeEnd: 30,
|
|
886
|
+
* variants: [
|
|
887
|
+
* { name: "Control" },
|
|
888
|
+
* { name: "New Checkout", conversionMultiplier: 1.25, ttcMultiplier: 0.8 },
|
|
889
|
+
* ]
|
|
890
|
+
* }
|
|
891
|
+
*/
|
|
892
|
+
export interface ExperimentConfig {
|
|
893
|
+
/** Human-readable experiment name. Default: `funnel.name + " Experiment"`. */
|
|
894
|
+
name?: string;
|
|
895
|
+
/**
|
|
896
|
+
* Variant definitions. Each variant gets a deterministic share of users.
|
|
897
|
+
* Default (when omitted): 3 variants — Variant A (worse), Variant B (better), Control.
|
|
898
|
+
*/
|
|
899
|
+
variants?: ExperimentVariant[];
|
|
900
|
+
/**
|
|
901
|
+
* Days before dataset end that the experiment starts. Funnel runs before
|
|
902
|
+
* the start date skip experiment logic entirely (no variant, no $experiment_started).
|
|
903
|
+
* Default: 0 (entire dataset).
|
|
904
|
+
*/
|
|
905
|
+
startDaysBeforeEnd?: number;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/** A single variant in an experiment. */
|
|
909
|
+
export interface ExperimentVariant {
|
|
910
|
+
/** Display name — appears in the "Variant name" property on $experiment_started. */
|
|
911
|
+
name: string;
|
|
912
|
+
/** Multiplier applied to funnel.conversionRate. 1.0 = unchanged. Default: 1.0. */
|
|
913
|
+
conversionMultiplier?: number;
|
|
914
|
+
/** Multiplier applied to funnel.timeToConvert. 1.0 = unchanged. Default: 1.0. */
|
|
915
|
+
ttcMultiplier?: number;
|
|
916
|
+
/** Distribution weight. Default: 1 (equal split across variants). */
|
|
917
|
+
weight?: number;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/** Experiment context exposed in funnel-pre and funnel-post hook meta. */
|
|
921
|
+
export interface HookMetaExperiment {
|
|
922
|
+
/** Experiment name. */
|
|
923
|
+
name: string;
|
|
924
|
+
/** Name of the assigned variant. */
|
|
925
|
+
variantName: string;
|
|
926
|
+
/** 0-based index of the assigned variant. */
|
|
927
|
+
variantIndex: number;
|
|
928
|
+
/** Conversion multiplier applied for this variant. */
|
|
929
|
+
conversionMultiplier: number;
|
|
930
|
+
/** TTC multiplier applied for this variant. */
|
|
931
|
+
ttcMultiplier: number;
|
|
932
|
+
/** Unix seconds of experiment start, or null if active for entire dataset. */
|
|
933
|
+
startDate: number | null;
|
|
606
934
|
}
|
|
607
935
|
|
|
608
936
|
/**
|
|
@@ -822,196 +1150,12 @@ export interface DataQuality {
|
|
|
822
1150
|
emptyEvents?: number;
|
|
823
1151
|
}
|
|
824
1152
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
/** Monthly price. 0 for free tier. */
|
|
832
|
-
price: number;
|
|
833
|
-
/** If true, users start on this plan. */
|
|
834
|
-
default?: boolean;
|
|
835
|
-
/** Trial period in days before requiring payment. */
|
|
836
|
-
trialDays?: number;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* Subscription lifecycle rates.
|
|
841
|
-
*/
|
|
842
|
-
export interface SubscriptionLifecycle {
|
|
843
|
-
/** Rate of trial-to-paid conversion (0-1). */
|
|
844
|
-
trialToPayRate?: number;
|
|
845
|
-
/** Monthly upgrade rate (0-1). */
|
|
846
|
-
upgradeRate?: number;
|
|
847
|
-
/** Monthly downgrade rate (0-1). */
|
|
848
|
-
downgradeRate?: number;
|
|
849
|
-
/** Monthly churn/cancellation rate (0-1). */
|
|
850
|
-
churnRate?: number;
|
|
851
|
-
/** Rate of churned users who come back (0-1). */
|
|
852
|
-
winBackRate?: number;
|
|
853
|
-
/** Days before win-back attempt. */
|
|
854
|
-
winBackDelay?: number;
|
|
855
|
-
/** Rate of payment failures (0-1). */
|
|
856
|
-
paymentFailureRate?: number;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* Subscription configuration.
|
|
861
|
-
*/
|
|
862
|
-
export interface Subscription {
|
|
863
|
-
/** Available plans, ordered from lowest to highest tier. */
|
|
864
|
-
plans: SubscriptionPlan[];
|
|
865
|
-
/** Lifecycle transition rates. */
|
|
866
|
-
lifecycle?: SubscriptionLifecycle;
|
|
867
|
-
/** Event names for subscription lifecycle events. */
|
|
868
|
-
events?: {
|
|
869
|
-
trialStarted?: string;
|
|
870
|
-
subscribed?: string;
|
|
871
|
-
upgraded?: string;
|
|
872
|
-
downgraded?: string;
|
|
873
|
-
renewed?: string;
|
|
874
|
-
cancelled?: string;
|
|
875
|
-
paymentFailed?: string;
|
|
876
|
-
wonBack?: string;
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
/**
|
|
881
|
-
* Attribution campaign definition.
|
|
882
|
-
*/
|
|
883
|
-
export interface AttributionCampaign {
|
|
884
|
-
/** Campaign name. */
|
|
885
|
-
name: string;
|
|
886
|
-
/** UTM source (e.g., "google", "facebook"). */
|
|
887
|
-
source: string;
|
|
888
|
-
/** UTM medium (e.g., "search_ad", "social"). */
|
|
889
|
-
medium?: string;
|
|
890
|
-
/** UTM content (e.g., "variant_a", "hero_image"). */
|
|
891
|
-
utm_content?: string;
|
|
892
|
-
/** UTM term (e.g., "running+shoes", "best+deals"). */
|
|
893
|
-
utm_term?: string;
|
|
894
|
-
/** Active days range [startDay, endDay] relative to dataset start. */
|
|
895
|
-
activeDays: [number, number];
|
|
896
|
-
/** Daily budget range [min, max]. */
|
|
897
|
-
dailyBudget?: [number, number];
|
|
898
|
-
/** Fraction of impressions that become users (0-1). */
|
|
899
|
-
acquisitionRate?: number;
|
|
900
|
-
/** Persona weight biases for users acquired by this campaign. */
|
|
901
|
-
userPersonaBias?: Record<string, number>;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
/**
|
|
905
|
-
* Connected attribution configuration.
|
|
906
|
-
*/
|
|
907
|
-
export interface Attribution {
|
|
908
|
-
/** Attribution model type. */
|
|
909
|
-
model?: "last_touch" | "first_touch" | "linear" | "time_decay";
|
|
910
|
-
/** Attribution window in days. */
|
|
911
|
-
window?: number;
|
|
912
|
-
/** Campaign definitions. */
|
|
913
|
-
campaigns: AttributionCampaign[];
|
|
914
|
-
/** Fraction of users who arrive organically (no campaign) (0-1). */
|
|
915
|
-
organicRate?: number;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
/**
|
|
919
|
-
* Geographic region definition.
|
|
920
|
-
*/
|
|
921
|
-
export interface GeoRegion {
|
|
922
|
-
/** Region name (e.g., "north_america"). */
|
|
923
|
-
name: string;
|
|
924
|
-
/** Country codes in this region. */
|
|
925
|
-
countries: string[];
|
|
926
|
-
/** Weight for user assignment (higher = more users). */
|
|
927
|
-
weight: number;
|
|
928
|
-
/** UTC timezone offset for this region (e.g., -5 for EST). */
|
|
929
|
-
timezoneOffset: number;
|
|
930
|
-
/** Properties injected for users in this region. */
|
|
931
|
-
properties?: Record<string, ValueValid>;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
/**
|
|
935
|
-
* Regional feature launch definition.
|
|
936
|
-
*/
|
|
937
|
-
export interface RegionalLaunch {
|
|
938
|
-
/** Region name to match. */
|
|
939
|
-
region: string;
|
|
940
|
-
/** Feature name. */
|
|
941
|
-
featureName: string;
|
|
942
|
-
/** Day the feature launches in this region. */
|
|
943
|
-
startDay: number;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
/**
|
|
947
|
-
* Geographic intelligence configuration.
|
|
948
|
-
*/
|
|
949
|
-
export interface GeoConfig {
|
|
950
|
-
/** If true, users keep their location across all events (default: false for backwards compat). */
|
|
951
|
-
sticky?: boolean;
|
|
952
|
-
/** Region definitions with timezone offsets and properties. */
|
|
953
|
-
regions?: GeoRegion[];
|
|
954
|
-
/** Regional feature launches. */
|
|
955
|
-
regionalLaunches?: RegionalLaunch[];
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
/**
|
|
959
|
-
* Progressive feature adoption configuration.
|
|
960
|
-
*/
|
|
961
|
-
export interface FeatureConfig {
|
|
962
|
-
/** Feature name (e.g., "dark_mode", "ai_recommendations"). */
|
|
963
|
-
name: string;
|
|
964
|
-
/** Day the feature launches (relative to dataset start). */
|
|
965
|
-
launchDay: number;
|
|
966
|
-
/** Adoption curve speed or custom logistic params. */
|
|
967
|
-
adoptionCurve?: "fast" | "slow" | "instant" | { k: number; midpoint: number };
|
|
968
|
-
/** Property name to inject on events. */
|
|
969
|
-
property: string;
|
|
970
|
-
/** Possible values for the property. First value is the "before" default if defaultBefore not set. */
|
|
971
|
-
values: ValueValid[];
|
|
972
|
-
/** Default value before the feature launches. If not set, property doesn't exist before launch. */
|
|
973
|
-
defaultBefore?: ValueValid;
|
|
974
|
-
/** Which events are affected ("*" for all, or array of event names). */
|
|
975
|
-
affectsEvents?: string[] | "*";
|
|
976
|
-
/** Conversion rate lift for users who adopted the feature. */
|
|
977
|
-
conversionLift?: number;
|
|
978
|
-
/** Resolved logistic curve params (set by config-validator). */
|
|
979
|
-
_resolvedCurve?: { k: number; midpoint: number };
|
|
980
|
-
/** Pre-computed adopted values (set by config-validator). */
|
|
981
|
-
_adoptedValues?: ValueValid[];
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
/**
|
|
985
|
-
* Anomaly/outlier configuration.
|
|
986
|
-
*/
|
|
987
|
-
export interface AnomalyConfig {
|
|
988
|
-
/** Type of anomaly. */
|
|
989
|
-
type: "extreme_value" | "burst" | "coordinated";
|
|
990
|
-
/** Event name this anomaly applies to. */
|
|
991
|
-
event: string;
|
|
992
|
-
/** For extreme_value: property to modify. */
|
|
993
|
-
property?: string;
|
|
994
|
-
/** For extreme_value: fraction of events affected (0-1). */
|
|
995
|
-
frequency?: number;
|
|
996
|
-
/** For extreme_value: multiplier applied to the property value. */
|
|
997
|
-
multiplier?: number;
|
|
998
|
-
/** Tag property added to anomalous events. */
|
|
999
|
-
tag?: string;
|
|
1000
|
-
/** For burst/coordinated: day when the anomaly occurs. */
|
|
1001
|
-
day?: number;
|
|
1002
|
-
/** For burst: duration in days (0.083 = ~2 hours). */
|
|
1003
|
-
duration?: number;
|
|
1004
|
-
/** For burst/coordinated: time window in days (0.01 = ~15 minutes). */
|
|
1005
|
-
window?: number;
|
|
1006
|
-
/** For burst/coordinated: number of events to inject. */
|
|
1007
|
-
count?: number;
|
|
1008
|
-
/** Properties injected on anomalous events. */
|
|
1009
|
-
properties?: Record<string, ValueValid>;
|
|
1010
|
-
/** Resolved absolute start time in unix seconds (set by config-validator). */
|
|
1011
|
-
_startUnix?: number;
|
|
1012
|
-
/** Resolved absolute end time in unix seconds (set by config-validator). */
|
|
1013
|
-
_endUnix?: number;
|
|
1014
|
-
}
|
|
1153
|
+
// ── Removed types in 1.4 ──
|
|
1154
|
+
// Subscription, SubscriptionPlan, SubscriptionLifecycle, Attribution, AttributionCampaign,
|
|
1155
|
+
// GeoConfig, GeoRegion, RegionalLaunch, FeatureConfig, AnomalyConfig were removed from
|
|
1156
|
+
// the engine in 1.4. Recreate these patterns via hooks (see lib/hook-patterns/* and the
|
|
1157
|
+
// `write-hooks` skill once Phase 4/5 land). The killed config keys are silently stripped
|
|
1158
|
+
// by `validateDungeonConfig` with a single deprecation warning per dungeon.
|
|
1015
1159
|
|
|
1016
1160
|
/**
|
|
1017
1161
|
* dungeon-master: generate realistic Mixpanel data at scale
|
|
@@ -1357,3 +1501,68 @@ export interface TestContext {
|
|
|
1357
1501
|
runtime: RuntimeState;
|
|
1358
1502
|
[key: string]: unknown;
|
|
1359
1503
|
}
|
|
1504
|
+
|
|
1505
|
+
// ── Subpath module declarations ──
|
|
1506
|
+
|
|
1507
|
+
declare module '@ak--47/dungeon-master/hook-helpers' {
|
|
1508
|
+
export function binUsersByEventCount(events: EventSchema[], eventName: string, bins: Record<string, [number, number]>): string;
|
|
1509
|
+
export function binUsersByEventInRange(events: EventSchema[], eventName: string, startTime: number | string, endTime: number | string, bins: Record<string, [number, number]>): string;
|
|
1510
|
+
export function countEventsBetween(events: EventSchema[], eventA: string, eventB: string): number;
|
|
1511
|
+
export function userInProfileSegment(profile: Record<string, unknown>, segmentKey: string, segmentValues: unknown[]): boolean;
|
|
1512
|
+
export function cloneEvent(template: EventSchema, overrides?: Partial<EventSchema>): EventSchema;
|
|
1513
|
+
export function dropEventsWhere(events: EventSchema[], predicate: (event: EventSchema) => boolean): number;
|
|
1514
|
+
export function scaleEventCount(events: EventSchema[], eventName: string, factor: number): void;
|
|
1515
|
+
export function scalePropertyValue(events: EventSchema[], predicate: (event: EventSchema) => boolean, propertyName: string, factor: number): void;
|
|
1516
|
+
export function shiftEventTime(event: EventSchema, deltaMs: number): EventSchema;
|
|
1517
|
+
export function scaleTimingBetween(events: EventSchema[], eventA: string, eventB: string, factor: number): void;
|
|
1518
|
+
export function scaleFunnelTTC(funnelEvents: EventSchema[], factor: number): void;
|
|
1519
|
+
export function findFirstSequence(events: EventSchema[], eventNames: string[], maxGapMin?: number): EventSchema[] | null;
|
|
1520
|
+
export function injectAfterEvent(events: EventSchema[], sourceEvent: EventSchema, templateEvent: EventSchema, gapMs: number, overrides?: Partial<EventSchema>): void;
|
|
1521
|
+
export function injectBetween(events: EventSchema[], eventA: EventSchema, eventB: EventSchema, templateEvent: EventSchema, overrides?: Partial<EventSchema>): void;
|
|
1522
|
+
export function injectBurst(events: EventSchema[], templateEvent: EventSchema, count: number, anchorTime: number | string, spreadMs: number): void;
|
|
1523
|
+
export function isPreAuthEvent(event: EventSchema, authTime: number | null): boolean;
|
|
1524
|
+
export function splitByAuth(events: EventSchema[], authTime: number | null): { preAuth: EventSchema[]; postAuth: EventSchema[]; stitch: EventSchema | null };
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
declare module '@ak--47/dungeon-master/hook-patterns' {
|
|
1528
|
+
export function applyFrequencyByFrequency(events: EventSchema[], profile: Record<string, unknown> | null, opts: { cohortEvent: string; bins: Record<string, [number, number]>; targetEvent: string; multipliers: Record<string, number> }): void;
|
|
1529
|
+
export function applyFunnelFrequencyBreakdown(allUserEvents: EventSchema[], profile: Record<string, unknown> | null, funnelEvents: EventSchema[], opts: { cohortEvent: string; bins: Record<string, [number, number]>; dropMultipliers: Record<string, number> }): void;
|
|
1530
|
+
export function applyAggregateByBin(events: EventSchema[], profile: Record<string, unknown> | null, opts: { cohortEvent: string; bins: Record<string, [number, number]>; event: string; propertyName: string; deltas: Record<string, number> }): void;
|
|
1531
|
+
export function applyTTCBySegment(funnelEvents: EventSchema[], profile: Record<string, unknown>, opts: { segmentKey: string; factors: Record<string, number> }): void;
|
|
1532
|
+
export function applyAttributedBySource(events: EventSchema[], profile: Record<string, unknown> | null, opts: { sourceEvent: string; sourceProperty: string; downstreamEvent: string; weights: Record<string, number>; model?: 'firstTouch' | 'lastTouch' }): void;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/**
|
|
1536
|
+
* Options for `emulateBreakdown`. Each `type` uses a different subset of fields.
|
|
1537
|
+
*
|
|
1538
|
+
* | type | Required fields | Optional |
|
|
1539
|
+
* |---|---|---|
|
|
1540
|
+
* | `frequencyByFrequency` | `metricEvent`, `breakdownByFrequencyOf` | `perUser` |
|
|
1541
|
+
* | `funnelFrequency` | `steps`, `breakdownByFrequencyOf` | — |
|
|
1542
|
+
* | `aggregatePerUser` | `event`, `property`, `breakdownByFrequencyOf` | `agg` (default: `'avg'`) |
|
|
1543
|
+
* | `timeToConvert` | `fromEvent`, `toEvent`, `breakdownByUserProperty`, `profiles` | — |
|
|
1544
|
+
* | `attributedBy` | `conversionEvent`, `attributionEvent`, `attributionProperty` | `model` (default: `'lastTouch'`) |
|
|
1545
|
+
*/
|
|
1546
|
+
export interface EmulateOptions {
|
|
1547
|
+
type: 'frequencyByFrequency' | 'funnelFrequency' | 'aggregatePerUser' | 'timeToConvert' | 'attributedBy';
|
|
1548
|
+
metricEvent?: string;
|
|
1549
|
+
breakdownByFrequencyOf?: string;
|
|
1550
|
+
perUser?: boolean;
|
|
1551
|
+
steps?: string[];
|
|
1552
|
+
event?: string;
|
|
1553
|
+
property?: string;
|
|
1554
|
+
agg?: 'avg' | 'sum' | 'count' | 'max' | 'min';
|
|
1555
|
+
fromEvent?: string;
|
|
1556
|
+
toEvent?: string;
|
|
1557
|
+
breakdownByUserProperty?: string;
|
|
1558
|
+
profiles?: UserProfile[];
|
|
1559
|
+
conversionEvent?: string;
|
|
1560
|
+
attributionEvent?: string;
|
|
1561
|
+
attributionProperty?: string;
|
|
1562
|
+
model?: 'firstTouch' | 'lastTouch';
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
declare module '@ak--47/dungeon-master/verify' {
|
|
1566
|
+
export function emulateBreakdown(events: EventSchema[], config: EmulateOptions): Array<Record<string, unknown>>;
|
|
1567
|
+
export function verifyDungeon(config: Dungeon, checks: Array<{ name: string; breakdown: EmulateOptions; assert: (rows: Array<Record<string, unknown>>, ctx: { events: EventSchema[]; profiles: UserProfile[] }) => { pass: boolean; detail?: string } }>): Promise<{ pass: boolean; results: Array<{ name: string; pass: boolean; detail?: string; rows?: Array<Record<string, unknown>> }> }>;
|
|
1568
|
+
}
|