@ak--47/dungeon-master 1.6.5 → 1.8.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/.claude/skills/analyze-soup/SKILL.md +21 -11
- package/.claude/skills/create-dungeon/SKILL.md +49 -10
- package/.claude/skills/create-project/SKILL.md +22 -3
- package/.claude/skills/create-project/context.mjs +89 -0
- package/.claude/skills/create-project/provision.mjs +1 -60
- package/.claude/skills/headless-build/SKILL.md +18 -1
- package/.claude/skills/powertools/SKILL.md +20 -1
- package/.claude/skills/release-check/SKILL.md +99 -0
- package/.claude/skills/verify-dungeon/SKILL.md +71 -16
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +14 -0
- package/.claude/skills/verify-dungeon/references/report-format.md +18 -1
- package/.claude/skills/verify-dungeon/references/sql-recipes.md +36 -1
- package/.claude/skills/warehouse-metrics/GAPS-template.md +34 -0
- package/.claude/skills/warehouse-metrics/SKILL.md +105 -0
- package/.claude/skills/warehouse-metrics/deploy.mjs +651 -0
- package/.claude/skills/write-hooks/SKILL.md +33 -3
- package/CHANGELOG.md +331 -0
- package/HOOKS.md +154 -5
- package/README.md +357 -8
- package/docs/guides/1.7.0-upgrade-guide.md +154 -0
- package/docs/guides/1.8.0-upgrade-guide.md +151 -0
- package/dungeons/technical/warehouse.js +187 -0
- package/index.js +131 -2
- package/lib/core/config-validator.js +264 -13
- package/lib/core/context.js +39 -0
- package/lib/core/dungeon-loader.js +5 -2
- package/lib/core/storage.js +51 -3
- package/lib/generators/events.js +53 -7
- package/lib/generators/funnels.js +85 -11
- package/lib/generators/profiles.js +9 -4
- package/lib/generators/standalone.js +248 -0
- package/lib/generators/warehouse.js +828 -0
- package/lib/orchestrators/mixpanel-sender.js +39 -3
- package/lib/orchestrators/user-loop.js +240 -9
- package/lib/templates/story-spec.schema.json +41 -16
- package/lib/utils/conditions.js +62 -0
- package/lib/utils/json-evaluator.js +12 -2
- package/lib/utils/utils.js +115 -19
- package/lib/verify/index.js +1 -0
- package/lib/verify/schema-validator.js +8 -0
- package/lib/verify/story-runner.js +71 -8
- package/lib/verify/warehouse.js +683 -0
- package/package.json +5 -11
- package/scripts/verify-stories.mjs +150 -44
- package/types.d.ts +606 -38
package/types.d.ts
CHANGED
|
@@ -7,10 +7,60 @@
|
|
|
7
7
|
type Primitives = string | number | boolean | Date | Record<string, any>;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* v1.7.0 (P1-1) — context handed to every property value function. Every member
|
|
11
|
+
* is optional: group profiles, lookup tables, ad spend and mirror props have no
|
|
12
|
+
* user, and `event` / `time` exist only while an event is being built.
|
|
13
|
+
*
|
|
14
|
+
* Existing zero-arity value functions keep working untouched — JavaScript ignores
|
|
15
|
+
* the extra argument. A function that DECLARES a parameter (`(ctx) => …`) is
|
|
16
|
+
* treated as context-aware and is never served from the source-string cache, so
|
|
17
|
+
* its result may legitimately differ per user or per event.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* userProps: {
|
|
21
|
+
* plan: ['free', 'pro'],
|
|
22
|
+
* revenue: (ctx) => ctx.profile.plan === 'pro' ? 100 : 10, // profile keys resolve in declaration order
|
|
23
|
+
* },
|
|
24
|
+
* superProps: {
|
|
25
|
+
* plan_on_event: (ctx) => ctx.profile.plan, // or use `stickyEventProps: ['plan']`
|
|
26
|
+
* total: (ctx) => ctx.event.price * ctx.event.quantity, // event props resolve in declaration order
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
export interface ValueContext {
|
|
30
|
+
/** The user's resolved profile (partially built while userProps resolve). Undefined for group/lookup/ad-spend/mirror values. */
|
|
31
|
+
profile?: Record<string, any>;
|
|
32
|
+
/** The partially-built event record (identity + time set; earlier properties already resolved). Undefined outside event generation. */
|
|
33
|
+
event?: Record<string, any>;
|
|
34
|
+
/** The event's timestamp in unix milliseconds, when an event is being built. */
|
|
35
|
+
time?: number;
|
|
36
|
+
/** The validated dungeon config. */
|
|
37
|
+
config: Dungeon;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* v1.7.0 (P2-1) — declarative weighted value. The numbers ARE the distribution:
|
|
42
|
+
* `{ __weights: { free: 60, pro: 30, enterprise: 10 } }` draws `free` 60% of the
|
|
43
|
+
* time. No automatic power law, no per-run winner; zero-weight keys never draw.
|
|
44
|
+
* Keys are strings (object keys), so numeric values come back as strings.
|
|
45
|
+
*/
|
|
46
|
+
export interface WeightedValue {
|
|
47
|
+
__weights: Record<string, number>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A "validValue" can be a primitive, an array of valid values, a declarative
|
|
52
|
+
* weighted form, or a function that returns one. Configs use this everywhere
|
|
53
|
+
* properties are user-defined.
|
|
54
|
+
*
|
|
55
|
+
* **Arrays of 3–19 unique strings get an automatic power-law draw** (~45% / 25% /
|
|
56
|
+
* 15% / decaying tail, one stable winner per array per run). Repeated entries are
|
|
57
|
+
* the weights (`['card', 'card', 'apple_pay']` = 2:1) and skip the power law.
|
|
58
|
+
* Opt out globally with `autoPowerLaw: false`, or state the distribution with
|
|
59
|
+
* `{ __weights }`.
|
|
60
|
+
*
|
|
61
|
+
* v1.7.0: the function arm receives a `ValueContext`. Zero-arity functions still work.
|
|
12
62
|
*/
|
|
13
|
-
export type ValueValid = Primitives | ValueValid[] | (() => ValueValid);
|
|
63
|
+
export type ValueValid = Primitives | ValueValid[] | WeightedValue | ((ctx?: ValueContext) => ValueValid);
|
|
14
64
|
|
|
15
65
|
/**
|
|
16
66
|
* Mixpanel data residency region. Matches the set `mixpanel-import` accepts.
|
|
@@ -76,6 +126,12 @@ export interface DungeonSwitches {
|
|
|
76
126
|
hasBrowser?: boolean;
|
|
77
127
|
isAnonymous?: boolean;
|
|
78
128
|
alsoInferFunnels?: boolean;
|
|
129
|
+
/** v1.7.0 — see `Dungeon.singleCountry`. */
|
|
130
|
+
singleCountry?: string;
|
|
131
|
+
/** v1.7.0 — see `Dungeon.campaignPerUser`. */
|
|
132
|
+
campaignPerUser?: boolean;
|
|
133
|
+
/** v1.7.0 — see `Dungeon.stickyEventProps`. */
|
|
134
|
+
stickyEventProps?: string[];
|
|
79
135
|
}
|
|
80
136
|
|
|
81
137
|
/**
|
|
@@ -309,10 +365,63 @@ export interface Dungeon {
|
|
|
309
365
|
sessionTimeout?: number;
|
|
310
366
|
/** If true, auto-generates funnels from the events array in addition to any explicit funnels. */
|
|
311
367
|
alsoInferFunnels?: boolean;
|
|
312
|
-
/**
|
|
368
|
+
/**
|
|
369
|
+
* Restrict all location data (`hasLocation`) to one country. Accepts the ISO
|
|
370
|
+
* code (`"US"`, `"GB"`) or the full name (`"United States"`), case-insensitive.
|
|
371
|
+
*
|
|
372
|
+
* v1.7.0: a value that matches no country in the location template THROWS at
|
|
373
|
+
* validation. Before 1.7.0 a miss (including `"US"`, which only matched by full
|
|
374
|
+
* name) silently emptied the location pool and deleted every geo property from
|
|
375
|
+
* events and profiles. Also accepted inside `switches`.
|
|
376
|
+
*/
|
|
313
377
|
singleCountry?: string;
|
|
314
|
-
/**
|
|
378
|
+
/**
|
|
379
|
+
* If true, the run delivers exactly `numEvents` events (forces `concurrency: 1`).
|
|
380
|
+
*
|
|
381
|
+
* v1.7.0: exact. Per-user budgets are scaled during the run so the total lands on
|
|
382
|
+
* the target, and the last user's stream is trimmed with a seeded uniform sample
|
|
383
|
+
* so the count never exceeds `numEvents`. If the users' capacity (rate × active
|
|
384
|
+
* days, minus drops) cannot reach the target, the run stops short and
|
|
385
|
+
* `result.warnings` carries a `numEvents` entry with `requested` / `applied`.
|
|
386
|
+
* Before 1.7.0 the flag stopped on the generated count (drops included) and
|
|
387
|
+
* never topped up, landing ~6% short with 4x headroom available.
|
|
388
|
+
*
|
|
389
|
+
* Without this flag the count is approximate (rate × users × days, thinned by
|
|
390
|
+
* born-late users and drops).
|
|
391
|
+
*/
|
|
315
392
|
strictEventCount?: boolean;
|
|
393
|
+
/**
|
|
394
|
+
* v1.7.0 (P1-2) — profile properties projected onto every event of the user.
|
|
395
|
+
* Each key must be declared in `userProps`, a persona's `properties`, or
|
|
396
|
+
* `superProps` (schema-first; the validator throws otherwise). Keys from the
|
|
397
|
+
* profile copy the profile's value (after the `user` hook). Keys declared only
|
|
398
|
+
* in `superProps` are resolved ONCE per user and held constant instead of
|
|
399
|
+
* re-rolled per event. Sticky values land after `superProps` and before the
|
|
400
|
+
* `event` hook, so hooks remain the final authority.
|
|
401
|
+
*
|
|
402
|
+
* The declarative alternative to `superProps: { plan: (ctx) => ctx.profile.plan }`.
|
|
403
|
+
* Also accepted inside `switches`.
|
|
404
|
+
*/
|
|
405
|
+
stickyEventProps?: string[];
|
|
406
|
+
/**
|
|
407
|
+
* v1.7.0 (P1-4) — one acquisition campaign per user. With `hasCampaigns: true`,
|
|
408
|
+
* each user draws one campaign template at birth; its `utm_source`,
|
|
409
|
+
* `utm_campaign`, `utm_medium`, `utm_content`, `utm_term` are stamped on the
|
|
410
|
+
* profile and every sampled touchpoint carries those same values instead of a
|
|
411
|
+
* fresh random template per event. Any UTM key already on the profile (from a
|
|
412
|
+
* persona's `properties` or the `user` hook) wins over the draw, so
|
|
413
|
+
* `personas: [{ name: 'paid', properties: { utm_source: 'google', utm_medium: 'cpc' } }]`
|
|
414
|
+
* makes "paid search converts better" declarative. Default `false`. Also
|
|
415
|
+
* accepted inside `switches`.
|
|
416
|
+
*/
|
|
417
|
+
campaignPerUser?: boolean;
|
|
418
|
+
/**
|
|
419
|
+
* v1.7.0 (P2-1) — set `false` to turn off the automatic power-law draw on
|
|
420
|
+
* arrays of 3–19 unique strings for the whole run (uniform picks instead).
|
|
421
|
+
* Default `true` (pre-1.7 behavior). Prefer `{ __weights }` when you want a
|
|
422
|
+
* specific distribution.
|
|
423
|
+
*/
|
|
424
|
+
autoPowerLaw?: boolean;
|
|
316
425
|
/** Internal flag for UI-triggered jobs (affects SCD credential handling). */
|
|
317
426
|
isUIJob?: boolean;
|
|
318
427
|
|
|
@@ -344,6 +453,18 @@ export interface Dungeon {
|
|
|
344
453
|
groupProps?: Record<string, Record<string, ValueValid>>;
|
|
345
454
|
/** Lookup table definitions for dimension tables. */
|
|
346
455
|
lookupTables?: LookupTableSchema[];
|
|
456
|
+
/**
|
|
457
|
+
* v1.8.0 — identity-less metric snapshots. One record per cadence tick per
|
|
458
|
+
* dimension cross-product row, carrying NO `user_id` and NO `device_id`.
|
|
459
|
+
*
|
|
460
|
+
* Use for infrastructure and finance telemetry: daily CDN egress per region,
|
|
461
|
+
* weekly billing rollups per plan tier, hourly queue depth per cluster.
|
|
462
|
+
* `$ad_spend` (`hasAdSpend: true`) is the same idea hard-coded; this is the
|
|
463
|
+
* general form and it does not use a Mixpanel reserved event name.
|
|
464
|
+
*/
|
|
465
|
+
standaloneEvents?: StandaloneEventConfig[];
|
|
466
|
+
/** v1.8.0 — warehouse-backed metric source tables derived from the run's own event stream. */
|
|
467
|
+
warehouseMetrics?: WarehouseMetricConfig[];
|
|
347
468
|
/** TimeSoup configuration: shapes intra-week and intra-day rhythm (peaks, deviation, DOW/HOD weights). Pair with `macro` for big-picture trend control. */
|
|
348
469
|
soup?: soup;
|
|
349
470
|
/** Macro trend shape across the full dataset window: birth distribution + per-user event allocation. Default: "flat". Use "growth"/"viral"/"steady"/"decline" or a custom object. */
|
|
@@ -441,6 +562,16 @@ export interface Dungeon {
|
|
|
441
562
|
*
|
|
442
563
|
* Example: `{ day1: 0.40, day7: 0.20, day30: 0.08 }` produces a curve that
|
|
443
564
|
* approximates a typical product's 30-day retention.
|
|
565
|
+
*
|
|
566
|
+
* **Day 1 has a floor the curve cannot move (v1.7.0 doc, P1-6).** The curve
|
|
567
|
+
* picks which UTC days a user gets a SESSION; retention counts EVENTS. A funnel
|
|
568
|
+
* opened on the birth day spills its later steps across the following
|
|
569
|
+
* `timeToConvert` hours regardless of the day plan, so day-1 retention sits
|
|
570
|
+
* near 0.85 for funnel-driven dungeons no matter what `day1` says (measured
|
|
571
|
+
* 0.885 for an asked 0.15; days 7 and 30 followed the curve). Treat `day1` as
|
|
572
|
+
* governed by session and funnel structure and verify the curve from day 7 on.
|
|
573
|
+
* To lower day 1, shorten `timeToConvert` on funnels users enter on birth, or
|
|
574
|
+
* use an `everything` hook to drop next-day spill.
|
|
444
575
|
*/
|
|
445
576
|
retentionCurve?: {
|
|
446
577
|
type?: 'logarithmic' | 'linear';
|
|
@@ -532,13 +663,27 @@ export type MacroPreset = "flat" | "steady" | "growth" | "viral" | "decline";
|
|
|
532
663
|
|
|
533
664
|
/**
|
|
534
665
|
* Macro configuration object — fine-grained big-picture trend control.
|
|
666
|
+
*
|
|
667
|
+
* **Canonical spelling:** `macro: { preset, ...overrides }`. The top-level
|
|
668
|
+
* `bornRecentBias` / `percentUsersBornInDataset` / `preExistingSpread` keys are
|
|
669
|
+
* a legacy alias and win over the object when both are set.
|
|
670
|
+
*
|
|
671
|
+
* **Two shapes, two contracts (v1.7.0, R2-1):**
|
|
672
|
+
* - `{ preset: 'growth', percentUsersBornInDataset: 50 }` — a NAMED preset is a
|
|
673
|
+
* shape contract, so its born% cap applies (flat 12, steady 12, growth 30,
|
|
674
|
+
* viral 55, decline 5). Values above the cap are clamped and reported in
|
|
675
|
+
* `result.warnings` (`key: 'percentUsersBornInDataset'`).
|
|
676
|
+
* - `{ bornRecentBias: 0.3, percentUsersBornInDataset: 50 }` — NO `preset` is a
|
|
677
|
+
* custom macro: you own the shape, no cap applies, overrides are honored as
|
|
678
|
+
* written (missing fields fill from `flat`). Before 1.7.0 this shape was
|
|
679
|
+
* silently capped at 12.
|
|
535
680
|
*/
|
|
536
681
|
export type MacroConfig = {
|
|
537
|
-
/** Use a named macro preset as the base, then override individual fields. */
|
|
682
|
+
/** Use a named macro preset as the base, then override individual fields. Omit for a custom, uncapped macro. */
|
|
538
683
|
preset?: MacroPreset;
|
|
539
|
-
/** Bias for birth dates. -1..1; negative = early skew, positive = recent skew, 0 = uniform. */
|
|
684
|
+
/** Bias for birth dates. -1..1; negative = early skew, positive = recent skew, 0 = uniform. User-explicit values are clamped to [-0.5, 0.5]. */
|
|
540
685
|
bornRecentBias?: number;
|
|
541
|
-
/** Percentage of users born in dataset window (0..100). */
|
|
686
|
+
/** Percentage of users born in dataset window (0..100). Capped per named preset; uncapped without `preset`. */
|
|
542
687
|
percentUsersBornInDataset?: number;
|
|
543
688
|
/** "pinned" = pre-existing users stack at FIXED_BEGIN; "uniform" = spread across [FIXED_BEGIN-30d, FIXED_BEGIN]. */
|
|
544
689
|
preExistingSpread?: "pinned" | "uniform";
|
|
@@ -570,7 +715,7 @@ export interface ResolvedMacro {
|
|
|
570
715
|
* - "everything" — array of ALL events for one user (return array to replace; meta.profile available)
|
|
571
716
|
*
|
|
572
717
|
* Storage-only hooks (fire during hookPush, not in generators):
|
|
573
|
-
* - "ad-spend", "group", "mirror", "lookup"
|
|
718
|
+
* - "ad-spend", "group", "mirror", "lookup", "standalone", "warehouse"
|
|
574
719
|
*/
|
|
575
720
|
export type hookTypes =
|
|
576
721
|
| "event"
|
|
@@ -583,6 +728,8 @@ export type hookTypes =
|
|
|
583
728
|
| "funnel-pre"
|
|
584
729
|
| "funnel-post"
|
|
585
730
|
| "ad-spend"
|
|
731
|
+
| "standalone"
|
|
732
|
+
| "warehouse"
|
|
586
733
|
| "churn"
|
|
587
734
|
| "group-event"
|
|
588
735
|
| "everything"
|
|
@@ -599,7 +746,9 @@ export type hookTypes =
|
|
|
599
746
|
* - "event": return value REPLACES the event (must be the event object).
|
|
600
747
|
* - "everything": return an array to REPLACE the user's event list (filter/inject/dedupe).
|
|
601
748
|
* - "user", "scd-pre", "funnel-pre", "funnel-post": return value is IGNORED — mutate in place.
|
|
602
|
-
* - storage-only ("ad-spend", "group", "mirror", "lookup"): return
|
|
749
|
+
* - storage-only ("ad-spend", "group", "mirror", "lookup", "standalone"): return an object or array of records; undefined drops the record.
|
|
750
|
+
* - "warehouse": return value is IGNORED; mutate the row in place.
|
|
751
|
+
* - "standalone" runs before the user loop; "warehouse" runs after it. Neither receives person metadata or enters "everything".
|
|
603
752
|
*
|
|
604
753
|
* @param record - The data being processed (event, profile, array of events, funnel config, etc.).
|
|
605
754
|
* @param type - Which hook type is firing — see `hookTypes`.
|
|
@@ -775,6 +924,10 @@ export interface hookArrayOptions<T> {
|
|
|
775
924
|
concurrency?: number;
|
|
776
925
|
/** Generation context (config, runtime, defaults). */
|
|
777
926
|
context?: Context;
|
|
927
|
+
/** Warehouse metric name for warehouse containers. */
|
|
928
|
+
metricName?: string;
|
|
929
|
+
/** Fixed CSV column order for warehouse metric tables. */
|
|
930
|
+
fixedColumns?: string[];
|
|
778
931
|
}
|
|
779
932
|
|
|
780
933
|
/**
|
|
@@ -796,6 +949,10 @@ export interface HookedArray<T> extends Array<T> {
|
|
|
796
949
|
getWritePath: () => string;
|
|
797
950
|
/** Returns all file paths written by this container during the current run. */
|
|
798
951
|
getWrittenFiles: () => string[];
|
|
952
|
+
/** Storage hook type this array is configured for. */
|
|
953
|
+
type?: hookTypes | string;
|
|
954
|
+
/** Output serialization format for this array. */
|
|
955
|
+
format?: string;
|
|
799
956
|
/** SCD prop name this array carries (only set on SCD HookedArrays). */
|
|
800
957
|
scdKey?: string;
|
|
801
958
|
/** Entity type for SCDs ("user" or a group key). */
|
|
@@ -804,6 +961,10 @@ export interface HookedArray<T> extends Array<T> {
|
|
|
804
961
|
groupKey?: string;
|
|
805
962
|
/** Lookup table key this array carries (only set on lookup table HookedArrays). */
|
|
806
963
|
lookupKey?: string;
|
|
964
|
+
/** Warehouse metric name this array carries (only set on warehouse HookedArrays). */
|
|
965
|
+
metricName?: string;
|
|
966
|
+
/** Fixed CSV column order for warehouse metric tables. */
|
|
967
|
+
fixedColumns?: string[];
|
|
807
968
|
}
|
|
808
969
|
|
|
809
970
|
export type AllData =
|
|
@@ -821,8 +982,11 @@ export interface Storage {
|
|
|
821
982
|
mirrorEventData?: HookedArray<EventSchema>;
|
|
822
983
|
userProfilesData?: HookedArray<UserProfile>;
|
|
823
984
|
adSpendData?: HookedArray<EventSchema>;
|
|
985
|
+
standaloneEventData?: HookedArray<EventSchema>;
|
|
824
986
|
groupProfilesData?: HookedArray<GroupProfileSchema>[];
|
|
825
987
|
lookupTableData?: HookedArray<LookupTableSchema>[];
|
|
988
|
+
warehouseMetricData?: HookedArray<Record<string, any>>[];
|
|
989
|
+
warehouseManifestFile?: string;
|
|
826
990
|
scdTableData?: HookedArray<SCDSchema>[];
|
|
827
991
|
groupEventData?: HookedArray<EventSchema>;
|
|
828
992
|
}
|
|
@@ -835,6 +999,10 @@ export interface RuntimeState {
|
|
|
835
999
|
eventCount: number;
|
|
836
1000
|
storedEventCount: number;
|
|
837
1001
|
userCount: number;
|
|
1002
|
+
/** v1.7.0 (R2-5): profiles pushed to storage (ticks at push time; batch-mode safe). */
|
|
1003
|
+
profilesGenerated: number;
|
|
1004
|
+
/** v1.7.0 (R2-5): `_drop`-flagged profiles pushed to storage (never sent to /engage). */
|
|
1005
|
+
profilesDropped: number;
|
|
838
1006
|
isBatchMode: boolean;
|
|
839
1007
|
verbose: boolean;
|
|
840
1008
|
}
|
|
@@ -886,6 +1054,14 @@ export interface Context {
|
|
|
886
1054
|
FIXED_NOW: number;
|
|
887
1055
|
/** Start of the resolved dataset window (unix seconds). Equal to the user-supplied `datasetStart`, or fallback `today_start - numDays`. */
|
|
888
1056
|
FIXED_BEGIN?: number;
|
|
1057
|
+
/** Runtime accumulator for post-loop warehouse metric materialization. */
|
|
1058
|
+
warehouseAccumulator?: {
|
|
1059
|
+
warnings?: string[];
|
|
1060
|
+
ingest: (events: EventSchema[]) => void;
|
|
1061
|
+
getCell: (metricName: string, seriesKey: string, bucketStartSec: number) => any;
|
|
1062
|
+
};
|
|
1063
|
+
/** Manifest describing materialized warehouse tables for downstream tooling. */
|
|
1064
|
+
warehouseManifest?: WarehouseManifest;
|
|
889
1065
|
/** Alias of `FIXED_BEGIN` — surfaced on hook `meta.datasetStart`. */
|
|
890
1066
|
DATASET_START_SECONDS: number;
|
|
891
1067
|
/** Alias of `FIXED_NOW` — surfaced on hook `meta.datasetEnd`. */
|
|
@@ -897,6 +1073,14 @@ export interface Context {
|
|
|
897
1073
|
incrementUsers(): void;
|
|
898
1074
|
incrementStoredEvents(count?: number): void;
|
|
899
1075
|
setStorage(storage: Storage): void;
|
|
1076
|
+
/** v1.7.0 (P2-2): record or bump an aggregated runtime warning (keyed by `entry.key`). */
|
|
1077
|
+
addWarning(entry: EngineWarning): void;
|
|
1078
|
+
/** v1.7.0 (P2-2): aggregated runtime warnings collected so far. */
|
|
1079
|
+
getWarnings(): EngineWarning[];
|
|
1080
|
+
/** v1.7.0 (R2-5) */
|
|
1081
|
+
incrementProfilesGenerated(): void;
|
|
1082
|
+
/** v1.7.0 (R2-5) */
|
|
1083
|
+
incrementProfilesDropped(): void;
|
|
900
1084
|
|
|
901
1085
|
// State getter methods
|
|
902
1086
|
getOperations(): number;
|
|
@@ -926,7 +1110,15 @@ export interface EventConfig {
|
|
|
926
1110
|
properties?: Record<string, ValueValid>;
|
|
927
1111
|
/** If true, this is the user's first-ever event (e.g., "sign up"). Used to create onboarding funnels. */
|
|
928
1112
|
isFirstEvent?: boolean;
|
|
929
|
-
/**
|
|
1113
|
+
/**
|
|
1114
|
+
* If true, generating this event signals the user has churned. The user stops
|
|
1115
|
+
* producing further events unless returnLikelihood allows them to come back.
|
|
1116
|
+
*
|
|
1117
|
+
* A churn event in the standalone pool is drawn by weight like any other, so it
|
|
1118
|
+
* ends every user after roughly `total weight / its weight` events — which caps
|
|
1119
|
+
* per-user volume and washes out `personas[].eventMultiplier` (measured 1.04x
|
|
1120
|
+
* for an asked 3x). Keep its weight low, or drive churn from a hook.
|
|
1121
|
+
*/
|
|
930
1122
|
isChurnEvent?: boolean;
|
|
931
1123
|
/** Probability (0-1) that a churned user returns and continues generating events. 0 = permanent churn, 1 = always returns. Only used when isChurnEvent is true. Default: 0 */
|
|
932
1124
|
returnLikelihood?: number;
|
|
@@ -1070,10 +1262,33 @@ export interface Funnel {
|
|
|
1070
1262
|
*/
|
|
1071
1263
|
props?: Record<string, ValueValid>;
|
|
1072
1264
|
/**
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1265
|
+
* Profile conditions a user must satisfy to be offered this funnel (AND across
|
|
1266
|
+
* keys). The only engine mechanism that makes ONE segment convert differently on
|
|
1267
|
+
* ONE funnel — `personas[].conversionModifier` applies to every funnel.
|
|
1268
|
+
*
|
|
1269
|
+
* Each value is a scalar (strict equality) or an operator map:
|
|
1270
|
+
* `eq`, `neq`, `in`, `nin`, `gt`, `gte`, `lt`, `lte` (v1.7.0). Operators within
|
|
1271
|
+
* one key AND together. No `or`.
|
|
1272
|
+
*
|
|
1273
|
+
* The duplicate-funnel idiom: two funnels with the same `name` and `sequence`,
|
|
1274
|
+
* different `conditions` and rates.
|
|
1275
|
+
*
|
|
1276
|
+
* @example
|
|
1277
|
+
* userProps: { platform: ['iOS', 'Android'], seats: [1, 5, 10, 20] },
|
|
1278
|
+
* funnels: [
|
|
1279
|
+
* { name: 'Checkout', sequence: [...], conditions: { platform: 'iOS' }, conversionRate: 80 },
|
|
1280
|
+
* { name: 'Checkout', sequence: [...], conditions: { platform: 'Android' }, conversionRate: 40 },
|
|
1281
|
+
* { name: 'Upgrade', sequence: [...], conditions: { seats: { gte: 10 }, platform: { in: ['iOS', 'Android'] } } },
|
|
1282
|
+
* ]
|
|
1283
|
+
*
|
|
1284
|
+
* Validation (v1.7.0): function values, bare arrays, unknown operators, and
|
|
1285
|
+
* `in`/`nin` without an array THROW — those shapes silently never matched before.
|
|
1286
|
+
* A key not declared in `userProps` / `superProps` / any persona's `properties`
|
|
1287
|
+
* produces a `result.warnings` entry (only a `user` hook could supply it). Users
|
|
1288
|
+
* who match no funnel at all fall through to standalone events; the run reports
|
|
1289
|
+
* how many under `key: 'funnels.conditions'`.
|
|
1075
1290
|
*/
|
|
1076
|
-
conditions?:
|
|
1291
|
+
conditions?: FunnelConditions;
|
|
1077
1292
|
/**
|
|
1078
1293
|
* Experiment configuration for this funnel.
|
|
1079
1294
|
*
|
|
@@ -1131,7 +1346,9 @@ export interface Funnel {
|
|
|
1131
1346
|
}>;
|
|
1132
1347
|
|
|
1133
1348
|
/** @internal Resolved experiment config set by config-validator. */
|
|
1134
|
-
_experiment?: { name: string; variants: Array<{ name: string; conversionMultiplier: number; ttcMultiplier: number; weight: number }>; startUnix: number | null; sticky: boolean };
|
|
1349
|
+
_experiment?: { name: string; variants: Array<{ name: string; conversionMultiplier: number; ttcMultiplier: number; weight: number }>; startUnix: number | null; sticky: boolean; stampProfile: boolean };
|
|
1350
|
+
/** @internal v1.7.0 — set by the validator on the engine-synthesized catch-all funnel. */
|
|
1351
|
+
_catchAll?: boolean;
|
|
1135
1352
|
/** @internal Set by funnels.js during experiment handling. */
|
|
1136
1353
|
_experimentName?: string;
|
|
1137
1354
|
/** @internal Set by funnels.js during experiment handling. */
|
|
@@ -1214,8 +1431,43 @@ export interface ExperimentConfig {
|
|
|
1214
1431
|
* variants across passes.
|
|
1215
1432
|
*/
|
|
1216
1433
|
sticky?: boolean;
|
|
1434
|
+
/**
|
|
1435
|
+
* v1.7.0 (P0-2) — write the assigned variant onto the user profile as
|
|
1436
|
+
* `"Experiment: <name>": "<variant>"` so a funnel can be broken down by variant
|
|
1437
|
+
* without building a cohort from the exposure event. Default `true`. Stamped
|
|
1438
|
+
* lazily when the user is first exposed (respects `startDaysBeforeEnd`), so
|
|
1439
|
+
* never-exposed users carry no property and the `user` hook does not see it;
|
|
1440
|
+
* the `everything` hook does. Ignored when `sticky: false` (a re-rolled variant
|
|
1441
|
+
* has no single per-user value). The variant is NOT stamped on downstream funnel
|
|
1442
|
+
* step events (that would add undeclared columns).
|
|
1443
|
+
*/
|
|
1444
|
+
stampProfile?: boolean;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* v1.7.0 (P0-1) — one funnel condition: a scalar (strict equality) or an operator map.
|
|
1449
|
+
*/
|
|
1450
|
+
export type FunnelCondition = Primitives | FunnelConditionOperators;
|
|
1451
|
+
|
|
1452
|
+
/** Operator map for a funnel condition. Every operator present must hold (AND). */
|
|
1453
|
+
export interface FunnelConditionOperators {
|
|
1454
|
+
/** strict equality */
|
|
1455
|
+
eq?: Primitives;
|
|
1456
|
+
/** strict inequality (a missing profile key satisfies it) */
|
|
1457
|
+
neq?: Primitives;
|
|
1458
|
+
/** value is one of these */
|
|
1459
|
+
in?: Primitives[];
|
|
1460
|
+
/** value is none of these (a missing profile key satisfies it) */
|
|
1461
|
+
nin?: Primitives[];
|
|
1462
|
+
gt?: number | string;
|
|
1463
|
+
gte?: number | string;
|
|
1464
|
+
lt?: number | string;
|
|
1465
|
+
lte?: number | string;
|
|
1217
1466
|
}
|
|
1218
1467
|
|
|
1468
|
+
/** Map of profile key → condition. AND across keys. */
|
|
1469
|
+
export type FunnelConditions = Record<string, FunnelCondition>;
|
|
1470
|
+
|
|
1219
1471
|
/** A single variant in an experiment. */
|
|
1220
1472
|
export interface ExperimentVariant {
|
|
1221
1473
|
/** Display name — appears in the "Variant name" property on $experiment_started. */
|
|
@@ -1328,11 +1580,47 @@ export interface GroupProfileSchema {
|
|
|
1328
1580
|
*/
|
|
1329
1581
|
export interface ImportResults {
|
|
1330
1582
|
events: ImportResult;
|
|
1331
|
-
|
|
1583
|
+
/**
|
|
1584
|
+
* v1.7.0 (R2-5): mixpanel-import's receipt plus two engine counters, so the
|
|
1585
|
+
* consumer can reconcile without guessing:
|
|
1586
|
+
* `generated - dropped_anonymous - failed === success`.
|
|
1587
|
+
*/
|
|
1588
|
+
users: ImportResult & {
|
|
1589
|
+
/** Profiles the engine pushed to storage (bots included). */
|
|
1590
|
+
generated: number;
|
|
1591
|
+
/** `_drop`-flagged anonymous non-converters that were never sent to /engage. */
|
|
1592
|
+
dropped_anonymous: number;
|
|
1593
|
+
};
|
|
1332
1594
|
groups: ImportResult[];
|
|
1333
1595
|
}
|
|
1334
1596
|
type ImportResult = import("mixpanel-import").ImportResults;
|
|
1335
1597
|
|
|
1598
|
+
/**
|
|
1599
|
+
* v1.7.0 (P2-2) — one value the engine changed or flagged. `result.warnings` is
|
|
1600
|
+
* always present (empty array when nothing was touched), regardless of `verbose`.
|
|
1601
|
+
*
|
|
1602
|
+
* Validator clamps come first (`severity: 'clamp'`, `applied !== requested`),
|
|
1603
|
+
* then run-level aggregates: conversionRate saturation per funnel and source
|
|
1604
|
+
* (`key: 'funnels[<name>].conversionRate:<source>'`), users matching no
|
|
1605
|
+
* conditioned funnel (`'funnels.conditions'`), churn washing out a persona
|
|
1606
|
+
* multiplier (`'personas.eventMultiplier'`), and a strictEventCount shortfall
|
|
1607
|
+
* (`'numEvents'`). Aggregated entries carry `count`.
|
|
1608
|
+
*/
|
|
1609
|
+
export interface EngineWarning {
|
|
1610
|
+
/** Config path the entry is about, e.g. `percentUsersBornInDataset`, `funnels[Checkout].conversionRate:persona "power" conversionModifier`. */
|
|
1611
|
+
key: string;
|
|
1612
|
+
/** What the config (or a modifier) asked for. Undefined when nothing was requested (auto-set). */
|
|
1613
|
+
requested?: unknown;
|
|
1614
|
+
/** What the engine used. Equals `requested` for pure warnings. */
|
|
1615
|
+
applied?: unknown;
|
|
1616
|
+
/** Plain-language explanation and what to change. */
|
|
1617
|
+
reason: string;
|
|
1618
|
+
/** `clamp` = a value was changed; `warn` = flagged, nothing changed. */
|
|
1619
|
+
severity: 'clamp' | 'warn';
|
|
1620
|
+
/** Number of occurrences folded into this entry (runtime aggregates only). */
|
|
1621
|
+
count?: number;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1336
1624
|
/**
|
|
1337
1625
|
* the end result of the data generation
|
|
1338
1626
|
*/
|
|
@@ -1347,12 +1635,23 @@ export type Result = {
|
|
|
1347
1635
|
scdTableData: SCDSchema[][];
|
|
1348
1636
|
/** Ad-spend events (only populated when `hasAdSpend: true`). */
|
|
1349
1637
|
adSpendData: EventSchema[];
|
|
1638
|
+
/** Identity-less metric snapshots (only populated when `standaloneEvents` is set). v1.8.0. */
|
|
1639
|
+
standaloneEventData: EventSchema[];
|
|
1640
|
+
/** Materialized warehouse metric tables keyed by metric name. */
|
|
1641
|
+
warehouseMetricData: Record<string, Record<string, any>[]>;
|
|
1642
|
+
/** Warehouse table manifest surfaced whenever `warehouseMetrics` is configured. */
|
|
1643
|
+
warehouseManifest?: WarehouseManifest;
|
|
1350
1644
|
/** Group profiles — one inner array per group key. */
|
|
1351
1645
|
groupProfilesData: GroupProfileSchema[][];
|
|
1352
1646
|
/** Lookup tables — one inner array per table. */
|
|
1353
1647
|
lookupTableData: LookupTableData[][];
|
|
1354
1648
|
/** Mixpanel import results (only populated when a token was provided). */
|
|
1355
1649
|
importResults?: ImportResults;
|
|
1650
|
+
/**
|
|
1651
|
+
* v1.7.0 (P2-2): every value the engine clamped or flagged this run. Always
|
|
1652
|
+
* present, even when empty. See `EngineWarning`.
|
|
1653
|
+
*/
|
|
1654
|
+
warnings: EngineWarning[];
|
|
1356
1655
|
/** Absolute paths of all files written to disk. */
|
|
1357
1656
|
files?: string[];
|
|
1358
1657
|
/** Timing information. */
|
|
@@ -1454,34 +1753,38 @@ export interface Persona {
|
|
|
1454
1753
|
name: string;
|
|
1455
1754
|
/** Relative weight for persona assignment (higher = more users get this persona). */
|
|
1456
1755
|
weight: number;
|
|
1457
|
-
/**
|
|
1756
|
+
/**
|
|
1757
|
+
* Multiplier on the persona's whole per-user event budget (1.0 = normal). The
|
|
1758
|
+
* budget drives BOTH usage-funnel passes and standalone events, so a 3x persona
|
|
1759
|
+
* runs ~3x as many funnel passes (measured 2.9–3.2x).
|
|
1760
|
+
*
|
|
1761
|
+
* **`isChurnEvent` caps it.** A churn event in the standalone pool ends each
|
|
1762
|
+
* user after roughly the same number of events regardless of budget, so the
|
|
1763
|
+
* multiplier washes out (measured 1.04x for an asked 3x with a weight-1 churn
|
|
1764
|
+
* event among 16 weight units). When more than half the users churn and a
|
|
1765
|
+
* persona multiplier is in play, `result.warnings` carries
|
|
1766
|
+
* `key: 'personas.eventMultiplier'`. Lower the churn event's weight, raise
|
|
1767
|
+
* `returnLikelihood`, or drive churn from a hook.
|
|
1768
|
+
*/
|
|
1458
1769
|
eventMultiplier?: number;
|
|
1459
|
-
/** Multiplier for funnel conversion rates (1.0 = normal, 1.3 = 30% better). */
|
|
1770
|
+
/** Multiplier for funnel conversion rates (1.0 = normal, 1.3 = 30% better). Applies to every funnel; for one segment on one funnel use `Funnel.conditions`. */
|
|
1460
1771
|
conversionModifier?: number;
|
|
1461
1772
|
/**
|
|
1462
|
-
*
|
|
1463
|
-
*
|
|
1464
|
-
*
|
|
1465
|
-
* atoms + `engagementDecay`).
|
|
1773
|
+
* v1.7.0 (P1-3) — multiplier for funnel `timeToConvert` (1.0 = normal,
|
|
1774
|
+
* 0.5 = converts twice as fast). Composes after an experiment's `ttcMultiplier`
|
|
1775
|
+
* and before the `funnel-pre` hook. Must be a positive number.
|
|
1466
1776
|
*/
|
|
1467
|
-
|
|
1777
|
+
ttcModifier?: number;
|
|
1468
1778
|
/** Properties merged into user profiles for this persona. */
|
|
1469
1779
|
properties?: Record<string, ValueValid>;
|
|
1470
1780
|
/**
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1474
|
-
*
|
|
1781
|
+
* Per-persona engagement decay override. This one IS implemented
|
|
1782
|
+
* (`user-loop.js` reads `persona.engagementDecay` before the global one).
|
|
1783
|
+
*
|
|
1784
|
+
* v1.7.0 removed the never-implemented `churnRate`, `activeWindow`, and
|
|
1785
|
+
* `soupOverride` from this type. The validator still accepts and warns on them.
|
|
1475
1786
|
*/
|
|
1476
|
-
activeWindow?: { maxDays: number };
|
|
1477
|
-
/** Per-persona engagement decay override. */
|
|
1478
1787
|
engagementDecay?: EngagementDecay;
|
|
1479
|
-
/**
|
|
1480
|
-
* Per-persona soup/timing override.
|
|
1481
|
-
* @deprecated — unimplemented; no-op. Declared surface only; nothing in lib/
|
|
1482
|
-
* reads it. Use the top-level `soup` config for timing shape.
|
|
1483
|
-
*/
|
|
1484
|
-
soupOverride?: SoupConfig;
|
|
1485
1788
|
}
|
|
1486
1789
|
|
|
1487
1790
|
/**
|
|
@@ -1496,7 +1799,18 @@ export interface WorldEvent {
|
|
|
1496
1799
|
startDay: number;
|
|
1497
1800
|
/** Duration in days (0.25 = 6 hours, null = permanent from startDay onward). */
|
|
1498
1801
|
duration?: number | null;
|
|
1499
|
-
/**
|
|
1802
|
+
/**
|
|
1803
|
+
* Volume multiplier during this event (3.0 = 3x events, 0.1 = 90% drop).
|
|
1804
|
+
*
|
|
1805
|
+
* Below 1: affected events are dropped at random so volume falls to the multiple.
|
|
1806
|
+
* Above 1 (v1.7.0, P0-3): affected in-window events are CLONED — `floor(m - 1)`
|
|
1807
|
+
* copies plus one more with probability `frac(m)` (2.5 = one guaranteed clone
|
|
1808
|
+
* and a 50% second) — each with a fresh `insert_id` and a timestamp spread
|
|
1809
|
+
* uniformly across the window (never past the dataset end). Measured 3.06x for
|
|
1810
|
+
* an asked 3x; before 1.7.0 values above 1 were a silent no-op (measured 1.08x).
|
|
1811
|
+
* Clones exist before `engagementDecay` and every hook, so they are visible to
|
|
1812
|
+
* `everything`. The same rule applies to `aftermath.volumeMultiplier`.
|
|
1813
|
+
*/
|
|
1500
1814
|
volumeMultiplier?: number;
|
|
1501
1815
|
/** Conversion rate modifier during this event. */
|
|
1502
1816
|
conversionModifier?: number;
|
|
@@ -1738,9 +2052,10 @@ export interface StoryAssertion {
|
|
|
1738
2052
|
/**
|
|
1739
2053
|
* Byte-compatible with `emulateBreakdown` / `verifyDungeon` args — or the
|
|
1740
2054
|
* `{ type: 'duckdb', sql }` escape hatch (disk mode only; `{{PREFIX}}` in
|
|
1741
|
-
* the SQL is substituted with the run's data prefix path)
|
|
2055
|
+
* the SQL is substituted with the run's data prefix path), or warehouse
|
|
2056
|
+
* verification rows via `{ type: 'warehouse' | 'warehouse-stats', table }`.
|
|
1742
2057
|
*/
|
|
1743
|
-
breakdown: Record<string, unknown> & { type: string; sql?: string };
|
|
2058
|
+
breakdown: Record<string, unknown> & { type: string; sql?: string; table?: string };
|
|
1744
2059
|
select?: StorySelect;
|
|
1745
2060
|
expect?: StoryExpect;
|
|
1746
2061
|
/**
|
|
@@ -2056,13 +2371,266 @@ export interface WritePaths {
|
|
|
2056
2371
|
eventFiles: string[];
|
|
2057
2372
|
userFiles: string[];
|
|
2058
2373
|
adSpendFiles: string[];
|
|
2374
|
+
standaloneFiles: string[];
|
|
2059
2375
|
scdFiles: string[];
|
|
2060
2376
|
mirrorFiles: string[];
|
|
2061
2377
|
groupFiles: string[];
|
|
2062
2378
|
lookupFiles: string[];
|
|
2379
|
+
warehouseFiles: string[];
|
|
2063
2380
|
folder: string;
|
|
2064
2381
|
}
|
|
2065
2382
|
|
|
2383
|
+
// ============= Standalone (identity-less) Events — v1.8.0 =============
|
|
2384
|
+
|
|
2385
|
+
/**
|
|
2386
|
+
* An identity-less metric snapshot stream.
|
|
2387
|
+
*
|
|
2388
|
+
* The engine emits one record per cadence tick per dimension cross-product row.
|
|
2389
|
+
* Records carry `event`, `time`, `insert_id`, `distinct_id`, every dimension as
|
|
2390
|
+
* a flat property, and every resolved entry in `properties`. They never carry
|
|
2391
|
+
* `user_id` or `device_id`, because they describe a system, not a person.
|
|
2392
|
+
*
|
|
2393
|
+
* @example
|
|
2394
|
+
* standaloneEvents: [{
|
|
2395
|
+
* event: 'cdn_egress',
|
|
2396
|
+
* cadence: 'day',
|
|
2397
|
+
* dimensions: { region: ['us-east', 'us-west', 'eu', 'apac'] },
|
|
2398
|
+
* distinctIdFrom: 'region',
|
|
2399
|
+
* properties: {
|
|
2400
|
+
* gb_out: (ctx) => 400 + ctx.tickIndex * 3,
|
|
2401
|
+
* cost_usd: (ctx) => (400 + ctx.tickIndex * 3) * 0.085,
|
|
2402
|
+
* p95_ms: [120, 140, 160],
|
|
2403
|
+
* },
|
|
2404
|
+
* }]
|
|
2405
|
+
*/
|
|
2406
|
+
export interface StandaloneEventConfig {
|
|
2407
|
+
/** Event name as it lands in Mixpanel. Must be unique across `standaloneEvents`. */
|
|
2408
|
+
event: string;
|
|
2409
|
+
/**
|
|
2410
|
+
* How often a snapshot fires. Ticks start at the dataset start and step by
|
|
2411
|
+
* the cadence; the last tick is the final one at or before the dataset end.
|
|
2412
|
+
* Default: `'day'`.
|
|
2413
|
+
*/
|
|
2414
|
+
cadence?: 'hour' | 'day' | 'week';
|
|
2415
|
+
/**
|
|
2416
|
+
* Dimension values to cross-product. Each key becomes a flat property on the
|
|
2417
|
+
* record. `{ region: ['us','eu'], tier: ['a','b'] }` emits 4 records per tick.
|
|
2418
|
+
* Omit for a single record per tick.
|
|
2419
|
+
*/
|
|
2420
|
+
dimensions?: Record<string, any[]>;
|
|
2421
|
+
/**
|
|
2422
|
+
* Which dimension supplies the synthetic `distinct_id`. Must name a declared
|
|
2423
|
+
* dimension. When omitted, `distinct_id` is the event name. The id exists so
|
|
2424
|
+
* Mixpanel accepts the record; it never maps to a person.
|
|
2425
|
+
*/
|
|
2426
|
+
distinctIdFrom?: string;
|
|
2427
|
+
/**
|
|
2428
|
+
* Snapshot metrics. Same `ValueValid` forms as event properties, and value
|
|
2429
|
+
* functions receive a `StandaloneValueContext` so a metric can shape a trend
|
|
2430
|
+
* across the window.
|
|
2431
|
+
*/
|
|
2432
|
+
properties?: Record<string, ValueValid>;
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
/** @internal Normalized `StandaloneEventConfig` produced by the validator. */
|
|
2436
|
+
export interface ResolvedStandaloneEventConfig {
|
|
2437
|
+
event: string;
|
|
2438
|
+
cadence: 'hour' | 'day' | 'week';
|
|
2439
|
+
dimensions: Record<string, any[]>;
|
|
2440
|
+
distinctIdFrom: string | null;
|
|
2441
|
+
properties: Record<string, ValueValid>;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
/**
|
|
2445
|
+
* Context handed to every standalone property value function.
|
|
2446
|
+
* Shares `time` and `config` with `ValueContext`, so a function written for a
|
|
2447
|
+
* normal event property still works unchanged.
|
|
2448
|
+
*/
|
|
2449
|
+
export interface StandaloneValueContext {
|
|
2450
|
+
/** Tick timestamp in unix MILLISECONDS. */
|
|
2451
|
+
time: number;
|
|
2452
|
+
/** The full validated dungeon config. */
|
|
2453
|
+
config: Dungeon;
|
|
2454
|
+
/** This row's dimension values, e.g. `{ region: 'us-east' }`. */
|
|
2455
|
+
dimensions: Record<string, any>;
|
|
2456
|
+
/** Zero-based index of this tick within the window. Use it to shape a trend. */
|
|
2457
|
+
tickIndex: number;
|
|
2458
|
+
/** Total number of ticks in the window. `tickIndex / (tickCount - 1)` is window progress. */
|
|
2459
|
+
tickCount: number;
|
|
2460
|
+
/** The cadence this stream fires on. */
|
|
2461
|
+
cadence: 'hour' | 'day' | 'week';
|
|
2462
|
+
/** The partially built record (`event`, `time`, `insert_id`, `distinct_id`, dimensions). */
|
|
2463
|
+
event: Record<string, any>;
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
/**
|
|
2467
|
+
* Meta passed to the `"standalone"` hook.
|
|
2468
|
+
*
|
|
2469
|
+
* Storage-only: return the record or an array of records to retain them.
|
|
2470
|
+
* Returning undefined drops the record. Warehouse hooks instead ignore returns.
|
|
2471
|
+
*/
|
|
2472
|
+
export interface HookMetaStandalone extends HookMetaTimeAnchors {
|
|
2473
|
+
/** The resolved config for the stream this record belongs to. */
|
|
2474
|
+
spec: ResolvedStandaloneEventConfig;
|
|
2475
|
+
/** The full validated dungeon config. */
|
|
2476
|
+
config: Dungeon;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
export interface WarehouseMetricSource {
|
|
2480
|
+
/** Source event names whose bucketed measure contributes positively to the series. */
|
|
2481
|
+
event: string | string[];
|
|
2482
|
+
/** Source event names whose bucketed measure is subtracted from the series. */
|
|
2483
|
+
minus?: string | string[];
|
|
2484
|
+
/** Per-bucket measure. Default: `'count'`. */
|
|
2485
|
+
measure?: 'count' | 'sum' | 'avg' | 'dau' | 'users';
|
|
2486
|
+
/** Required when `measure` is `'sum'` or `'avg'`. */
|
|
2487
|
+
property?: string;
|
|
2488
|
+
/** Optional row filter over flat event records. */
|
|
2489
|
+
where?: ((event: Record<string, any>) => boolean) | null;
|
|
2490
|
+
/** Optional dimension columns copied from source event or super prop keys. */
|
|
2491
|
+
groupBy?: string | string[];
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
export interface WarehouseMetricConfig {
|
|
2495
|
+
/**
|
|
2496
|
+
* @example
|
|
2497
|
+
* warehouseMetrics: [{
|
|
2498
|
+
* name: 'daily_active_subscriptions',
|
|
2499
|
+
* type: 'point-in-time',
|
|
2500
|
+
* source: {
|
|
2501
|
+
* event: 'subscription_started',
|
|
2502
|
+
* minus: 'subscription_cancelled',
|
|
2503
|
+
* measure: 'count',
|
|
2504
|
+
* },
|
|
2505
|
+
* baseline: 40,
|
|
2506
|
+
* timeColumn: 'date',
|
|
2507
|
+
* valueColumn: 'active_subscriptions',
|
|
2508
|
+
* }]
|
|
2509
|
+
*/
|
|
2510
|
+
/** Unique metric/table name. Must match `/^[a-z][a-z0-9_]{0,63}$/`. */
|
|
2511
|
+
name: string;
|
|
2512
|
+
/** Metric family: additive sums per bucket vs point-in-time carried levels. Default: `'additive'`. */
|
|
2513
|
+
type?: 'additive' | 'point-in-time';
|
|
2514
|
+
/** Bucket grain. Default: `'day'`. */
|
|
2515
|
+
grain?: 'day' | 'week' | 'month';
|
|
2516
|
+
/** Point-in-time only: emit only the first bucket and changed values. Default: `false`. */
|
|
2517
|
+
sparse?: boolean;
|
|
2518
|
+
/** Declarative source spec describing how to derive the table from generated events. */
|
|
2519
|
+
source: WarehouseMetricSource;
|
|
2520
|
+
/** Output time column name. Default: `'date'`. */
|
|
2521
|
+
timeColumn?: string;
|
|
2522
|
+
/** Output value column name. Default: `'value'`. */
|
|
2523
|
+
valueColumn?: string;
|
|
2524
|
+
/** Point-in-time starting level at the dataset window start. Default: `0`. */
|
|
2525
|
+
baseline?: number;
|
|
2526
|
+
/** Multiplier applied after bucket aggregation. Default: `1`. */
|
|
2527
|
+
scale?: number;
|
|
2528
|
+
/** Seeded jitter fraction clamped to `[0, 0.5]`. Default: `0`. */
|
|
2529
|
+
noise?: number;
|
|
2530
|
+
/** Grain periods of backfill before the dataset window. Default: `0`. */
|
|
2531
|
+
history?: number;
|
|
2532
|
+
/** Extra declared output columns, preserved in declaration order. */
|
|
2533
|
+
columns?: Record<string, ValueValid | ((ctx: WarehouseValueContext) => ValueValid)>;
|
|
2534
|
+
/** Output file format. Defaults to the dungeon format, else `'csv'`. */
|
|
2535
|
+
format?: 'csv' | 'json';
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
/** @internal Normalized `WarehouseMetricConfig` produced by the validator. */
|
|
2539
|
+
export interface ResolvedWarehouseMetricConfig {
|
|
2540
|
+
name: string;
|
|
2541
|
+
type: 'additive' | 'point-in-time';
|
|
2542
|
+
grain: 'day' | 'week' | 'month';
|
|
2543
|
+
sparse: boolean;
|
|
2544
|
+
source: {
|
|
2545
|
+
event: string[];
|
|
2546
|
+
minus: string[];
|
|
2547
|
+
measure: 'count' | 'sum' | 'avg' | 'dau' | 'users';
|
|
2548
|
+
property: string | null;
|
|
2549
|
+
where: ((event: Record<string, any>) => boolean) | null;
|
|
2550
|
+
groupBy: string[];
|
|
2551
|
+
};
|
|
2552
|
+
timeColumn: string;
|
|
2553
|
+
valueColumn: string;
|
|
2554
|
+
baseline: number;
|
|
2555
|
+
scale: number;
|
|
2556
|
+
noise: number;
|
|
2557
|
+
history: number;
|
|
2558
|
+
columns: Record<string, ValueValid | ((ctx: WarehouseValueContext) => ValueValid)>;
|
|
2559
|
+
format: 'csv' | 'json';
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
export interface WarehouseValueContext {
|
|
2563
|
+
/** Final bucket value after scale and noise. */
|
|
2564
|
+
value: number;
|
|
2565
|
+
/** Partially built row so later columns can depend on earlier ones. */
|
|
2566
|
+
row: Record<string, any>;
|
|
2567
|
+
/** Bucket start in unix milliseconds. */
|
|
2568
|
+
time: number;
|
|
2569
|
+
/** Zero-based chronological bucket index within this series, including backfill buckets and sparse gaps when present. */
|
|
2570
|
+
bucketIndex: number;
|
|
2571
|
+
/** Total chronological buckets in this series, including history buckets even when sparse rows are skipped. */
|
|
2572
|
+
bucketCount: number;
|
|
2573
|
+
/** Bucket grain for this metric. */
|
|
2574
|
+
grain: 'day' | 'week' | 'month';
|
|
2575
|
+
/** True when this row was synthesized before the dataset window by `history`. */
|
|
2576
|
+
isBackfill: boolean;
|
|
2577
|
+
/** Stable joined dimension key for this series. Empty string when undimensioned. */
|
|
2578
|
+
seriesKey: string;
|
|
2579
|
+
/** The resolved metric spec for this table. */
|
|
2580
|
+
spec: ResolvedWarehouseMetricConfig;
|
|
2581
|
+
/** The full validated dungeon config. */
|
|
2582
|
+
config: Dungeon;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
export interface HookMetaWarehouse extends HookMetaTimeAnchors {
|
|
2586
|
+
/** The resolved config for the metric this row belongs to. */
|
|
2587
|
+
spec: ResolvedWarehouseMetricConfig;
|
|
2588
|
+
/** The full validated dungeon config. */
|
|
2589
|
+
config: Dungeon;
|
|
2590
|
+
/** Metric/table name. */
|
|
2591
|
+
metricName: string;
|
|
2592
|
+
/** Zero-based chronological bucket index within this series, including history buckets and sparse gaps. */
|
|
2593
|
+
bucketIndex: number;
|
|
2594
|
+
/** Total chronological buckets in this series, including history buckets even when sparse rows are skipped. */
|
|
2595
|
+
bucketCount: number;
|
|
2596
|
+
/** Bucket grain for the metric. */
|
|
2597
|
+
grain: 'day' | 'week' | 'month';
|
|
2598
|
+
/** Stable joined dimension key for this series. Empty string when undimensioned. */
|
|
2599
|
+
seriesKey: string;
|
|
2600
|
+
/** True when the row belongs to the `history` backfill before the dataset window. */
|
|
2601
|
+
isBackfill: boolean;
|
|
2602
|
+
/** Raw bucket contributions before scale/noise and before point-in-time carry-forward. */
|
|
2603
|
+
raw: {
|
|
2604
|
+
plus: { count: number; sum: number; users: number };
|
|
2605
|
+
minus: { count: number; sum: number; users: number };
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
export interface WarehouseManifestColumn {
|
|
2610
|
+
name: string;
|
|
2611
|
+
bqType: 'DATE' | 'FLOAT64' | 'BOOL' | 'STRING';
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
export interface WarehouseManifestTable {
|
|
2615
|
+
table: string;
|
|
2616
|
+
file: string;
|
|
2617
|
+
format: 'csv' | 'json';
|
|
2618
|
+
grain: 'day' | 'week' | 'month';
|
|
2619
|
+
type: 'additive' | 'point-in-time';
|
|
2620
|
+
timeColumn: string;
|
|
2621
|
+
valueColumn: string;
|
|
2622
|
+
dimensionColumns: string[];
|
|
2623
|
+
columns: WarehouseManifestColumn[];
|
|
2624
|
+
recommendedAggregation: 'sum' | 'last value';
|
|
2625
|
+
sql: string;
|
|
2626
|
+
refreshHint: string;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
export interface WarehouseManifest {
|
|
2630
|
+
configName: string;
|
|
2631
|
+
tables: WarehouseManifestTable[];
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2066
2634
|
/**
|
|
2067
2635
|
* Configuration for TimeSoup time distribution function
|
|
2068
2636
|
*/
|