Skip to content

Value Stream — Domain Model and Glossary

This document fixes the vocabulary used everywhere else in Value Stream docs and code. Every term that has a specific meaning in the platform appears here exactly once, with a definition, key relationships, and a small example.

A reader who works through this document and the reference/expression-dsl.md grammar should be able to read every other Value Stream doc unambiguously.


1. Big picture

            Workspace
               |
               v
   +-------------------------------+
   |   Catalog (YAML)              |
   |   - Sources                   |
   |   - Dimensions                |
   |   - Processors                |
   |   - Metrics                   |
   |   - Dashboards                |
   +-------------------------------+
               |
               | drives
               v
   +-------------------------------+
   |   Ingestion                   |
   |   - File   -> Chunk          |
   |   - Chunk  -> Partial agg.    |
   |   - Partial-> Compacted agg.  |
   +-------------------------------+
               |
               | persisted in
               v
   +-------------------------------+
   |   Aggregate Store             |
   |   (Parquet, hive-partitioned) |
   +-------------------------------+
               |
               | read by
               v
   +-------------------------------+
   |   Query Layer                 |
   |   - Resolve metric -> processor + states
   |   - Plan -> physical aggregate
   |   - Execute -> Polars frame
   |   - Apply derive (DSL)
   +-------------------------------+
               |
               | rendered as
               v
   +-------------------------------+
   |   Surfaces                    |
   |   - Tile in Dashboard         |
   |   - REST response             |
   |   - SDK return value          |
   +-------------------------------+

2. Term graph

Lines mean "is composed of" or "refers to."

Workspace ─── Catalog ─── { Source, Dimension, Processor, Metric, Dashboard }
                           │
                           ▼
                     Source ── Reader ── ChunkPolicy
                          \─── Transform[]
                          \─── Defaults
                          \─── Schema (timestamp_col, natural_key, ...)
                           │
                           ▼
                     Processor ── Source (FK)
                                \── Dimension[] (FK)
                                \── Grain[]
                                \── State[]
                                \── (kind-specific) outcome / variant / properties / keys
                                │
                                ▼
                            Metric ── Processor (FK)
                                  \── kind ∈ { formula, approx_distinct_count,
                                              distribution, quantile, variant_compare,
                                              proportion_test, contingency_test,
                                              curve_from_digests,
                                              calibration_from_digests,
                                              lifecycle_summary }
                                  \── Expression (AST)
                                  \── outputs[]
                                  │
                                  ▼
                              Tile ── Metric (FK)
                                  \── Chart (kind)
                                  \── x / y / color / facets / etc.
                                  │
                                  ▼
                          DashboardPage
                                  │
                                  ▼
                            Dashboard

3. Definitions (alphabetical)

Aggregate

A row in a physical aggregate table, produced by a Processor. It contains group-by column values, state values, and provenance columns (pipeline_run_id, chunk_id, period, created_at, config_hash).

Aggregates are never raw rows. A row in aggregates/ih/engagement/daily/period=2024-08/part-00.parquet represents one (Day, Channel, PlacementType, Issue, Group, CustomerType, ModelControlGroup) tuple — never a single Pega interaction.

Aggregate Store

The directory tree under <workspace>/aggregates/ holding all physical aggregate Parquet files. The only place persisted business data lives.

AST (Abstract Syntax Tree)

The internal representation of an expression in the closed expression DSL. AST nodes are JSON-shaped (e.g. {op: safe_div, num: {col: P}, den: {col: N}}). The evaluator turns an AST into a Polars expression. See reference/expression-dsl.md.

Backfill

A one-shot operation that re-runs a Source's chunks within a time window, typically after a compatible-narrowing config change or to populate a new processor against historical chunks.

Calendar

A built-in time-grain helper. The default calendar emits Day, Month (YYYY-MM), Year (Int16), Quarter (YYYY_Q#) from a timestamp column. Custom calendars can add Week (ISO YYYY-Www) or FiscalQuarter.

Catalog

The set of YAML files in <workspace>/catalog/ that define everything declarative about the workspace: pipelines.yaml, processors.yaml, metrics.yaml, and dashboards.yaml. Loaded once at startup, validated, and hashed into a single config_hash.

Chart

A presentation-layer artifact bound to a Tile. Tiles are a strict chart-discriminated union: each chart admits only its own dimension and style roles. Every Metric publishes a read-only output contract. A metric-owned chart role persists one member of that contract in metric_output, including the single output of a scalar metric. Heatmap uses x and optional y; Sankey and Treemap use ordered path; Donut uses names; Combo uses secondary_metric. Pareto and Waterfall Y, Polar radius, Treemap color, Donut values, and Sankey link values are metric-owned. Waterfall accepts only x plus common presentation settings; its colors are semantic increase, decrease, and total. Retired chart aliases and source/target shorthand are rejected. See reference/chart-catalog.md.

Chunk

The unit of idempotent work in the ingestion engine. A chunk consists of: - a chunk_id (typically derived from a filename pattern, e.g. 2024-08-21), - one or more file paths whose contents combine to form the chunk's input, - a pipeline_run_id recording which run processed it, - a durable ledger status (ok | failed).

A chunk is processed end-to-end (read → transform → fan out to all processors → write all partials) before the next chunk starts. skipped is a result in the current run report when a prior committed chunk is reused; it does not create another durable chunk-ledger row. For a successful chunk, complete lineage commits before status=ok, making that row the durable chunk commit marker.

Chunk ledger

The DuckDB table at meta/chunks.duckdb recording every chunk processed by every run. Only an ok row whose parent run has reached a terminal state is query-visible and eligible for idempotent reuse. This includes successful chunks retained inside an otherwise failed run.

Compaction

The process of reducing aggregate states with groupby + state-merge. Ingestion uses it once to produce the processor's configured base-grain partial. Coarser Week, Month, Quarter, Year, and Summary answers are merged from that base aggregate at query time; duplicate coarser physical materializations are not written. Compaction never touches raw data.

Config hash

The sha256 of the canonicalized YAML for a Processor (or for the whole catalog, depending on context). Recorded on every aggregate row and returned by query surfaces where available. Used to: - detect when an aggregate was produced under a different config, - partition the aggregate store across coexisting configs, - reproduce or audit results.

Derived metric

A metric whose value is computed at query time from a Processor's persisted state. Examples: CTR = Positives / (Positives + Negatives), ROC_AUC = curve_from_digests(Propensity_tdigest_positives, Propensity_tdigest_negatives). Derived metrics are not stored; they live only in the metric DSL.

Group-by column

A transformed source column that a Processor preserves as a business grouping/filtering axis. Processors declare these directly with group_by: [Channel, PlacementType]; if a user-facing name should differ from the incoming source field, create that column in the pipeline transforms and use the transformed column name here.

Discovery

The phase of a pipeline run where the engine globs the Source's input folder, applies group_by_filename to assign each file to a chunk_id, and consults the chunk ledger to skip already-processed chunks.

Expression

A node in the AST that evaluates to a column or a scalar at query time. Used in: transforms[*].filter, transforms[*].derive_column, processors.<id>.filter, and metrics.<id>.expression (when kind: formula).

Freshness

The minimum gap between "the latest available chunk from upstream" and "the latest aggregate row served by the query layer," for a given (source, processor, grain). Freshness retains both the authored aggregate-period label and, when the stored grain provides one, the exact latest covered date. Report presets use the latest common exact coverage needed by their metrics so partial periods do not create empty relative windows. Freshness is surfaced on every dashboard tile and in the /api/v1/workspace response.

Grain

The time resolution of a Processor. time.grain is the one physical base grain stored by ingestion. Calendar columns at that grain and above remain valid group_by dimensions, so queries can request Day, Week, Month, Quarter, Year, or Summary without storing a copy for each resolution.

CPC state

A binary state (Apache DataSketches CPC sketch) used by default to estimate distinct counts. Generated states use lg_k=11; merge rule: union. CPC exposes estimate bounds but does not support intersection or difference. See reference/algorithms.md.

HLL state

A binary state (Apache DataSketches HLL_4 or HLL_8 sketch) available as an explicit distinct-count alternative. Merge rule: union. See reference/algorithms.md.

Ingestion engine

The component that turns files into aggregates. Implemented as a sequence of phases: discovery → reader → transforms → processor fan-out → partial write → compaction → ledger update.

KLL state

A binary state (Apache DataSketches KLL sketch) used for quantile estimation with strong error guarantees. Merge rule: KLL merge. Alternative to t-digest where guaranteed error matters.

Lineage

The mapping from each aggregate row to the chunks and config that produced it. Stored in meta/lineage.duckdb. Surfaced in the Ops UI.

Mergeable state

A state column whose values can be combined under a deterministic, associative-commutative rule across rows. The state catalog defines: - count → SUM - row count when no selector is set - non-null or distinct count when source_column is set - outcome/stage counts only when the corresponding typed selector is set - value_sum → SUM - min, max → MIN/MAX - pooled_mean → SUM-of-(value*weight) / SUM-of-weight, with explicit source_column or computation recipe and weight state - pooled_variance → Welford-merge using explicit mean and weight companion states - tdigest, kll → sketch merge - cpc, hll, theta → sketch union (intersect/diff for theta only) - topk → frequent-items merge

Metric

A derived business measurement bound to a Processor. A metric has a kind and inputs from the Processor's explicit state contract. Examples: CTR (formula), ROC_AUC (curve_from_digests), Lift (variant_compare), ResponseTime_Distribution (distribution), Median_ResponseTime (quantile), UniqueCustomers (approx_distinct_count), and a lifecycle summary whose four input states are explicitly bound. State identifiers never imply metric semantics.

Every metric may also carry display metadata: a friendly label, unit, default value_format, and direction (higher_is_better, lower_is_better, or neutral). Display metadata never changes the formula, processor computation hash, or stored aggregate state.

A metric installed from a KPI Recipe may carry recipe: {id, version} provenance. The installed metric remains authoritative and does not track future recipe edits automatically.

Partial

A Parquet file produced by one Processor processing one chunk. Lives at aggregates/<src>/<proc>/daily/period=YYYY-MM/part-<pipeline_run_id>.parquet. A "partial" is conceptually the smallest unit of write; compaction merges multiple partials into one.

Period

The hive partition key used inside the processor's base-grain aggregate directory. Daily and monthly bases use period = YYYY-MM; finer or coarser bases use the smallest useful partition label for their configured resolution.

Pipeline run

One end-to-end execution of an ingestion against a Source. Has an id (UUID), a config hash, a status (running | ok | failed | partial), counts of total / ok / failed chunks, and start/end timestamps. Recorded in meta/pipeline_runs.duckdb. The row is inserted as running after discovery and before the first chunk. While it is running, finished_at is null and none of that run's new chunks are visible. Normal completion transitions the same row to ok or failed; any failed chunk makes the run failed, even when other chunks committed successfully. partial is reserved for an interrupted run whose committed chunks were later verified during recovery. If the process dies first, the next caller holding the source lock verifies committed chunks and performs the terminal transition; it never creates a second row for the interrupted run. Run-level rows_in and rows_kept totals include only chunks whose durable marker finishes as ok; failed, recovery-rejected, and skipped chunks do not inflate the published-row totals shown by operational surfaces.

Plan

The result of the Query Layer's planner: which physical aggregate to scan, what predicates to push down, what columns to read, what derive function to apply. Deterministic for a given input + config_hash.

Processor

The most important concept in Value Stream. A Processor is a typed function (Source, config) → Aggregate(s) at one or more Grains. Built-in kinds: - binary_outcome — counts and rates from positive/negative outcomes (engagement, conversion, experiments) - numeric_distribution — moments, min/max, t-digest of numeric properties (descriptive) - score_distribution — score-vs-outcome distributions, ROC/AP/calibration (model_ml_scores) - entity_lifecycle — order/holding aggregates per entity for CLV/RFM - entity_set — CPC/HLL/Theta distinct-count sketches, with Theta also supporting set algebra - funnel — per-stage counts for funnel KPIs - snapshot — periodic / accumulating snapshots for state KPIs

Processors implement the interface in reference/processors.md §1.

Reader

A built-in component that turns a list of file paths into a Polars LazyFrame. Built-in readers: - parquet - pega_ds_export (zip/tar.gz/gz containing JSON or NDJSON) - csv (with delimiter auto-detection) - xlsx

See reference/readers-and-formats.md.

KPI Recipe

A versioned, inert authoring artifact that connects a business question to required Processor capabilities, a metric template, calculation/method guidance, and recommended report presentation. It is not part of a workspace Catalog and cannot execute. An explicit install maps its roles to persisted aggregate states/stages and materializes ordinary Metric and optional Tile YAML. See reference/kpi-recipes.md.

Run

Shorthand for a pipeline run.

Snapshot

A row representing the state of a business entity at a given as_of_date. Two flavors: - periodic: written daily/weekly/monthly; one row per (as_of, dim_tuple). - accumulating: one row per business-entity, updated through milestones.

Snapshots live under <workspace>/snapshots/<id>/as_of=YYYY-MM-DD/snapshot.parquet.

Source

A logical input to Value Stream. Each Source has an id, a description, a reader, a transforms list, a defaults map, and a schema block (timestamp column, natural key, columns to drop). Multiple Processors can be bound to the same Source.

Concrete Sources in the canonical workspace: ih (Pega CDH Interaction History), holdings (Product Holdings), and any number of optional Sources (subscriptions, tickets, …) for snapshot processors.

State

A column on a Processor's aggregate output, with a declared state type. State types determine the merge rule. Examples: Count {type: count}, Propensity_tdigest_positives {type: tdigest}, UniqueCustomers_cpc {type: cpc, lg_k: 11}.

State type

The catalog of mergeable kinds: count, value_sum, min, max, pooled_mean, pooled_variance, tdigest, kll, cpc, hll, theta, topk. See §3 of design/replacement-design.md and reference/algorithms.md.

t-digest state

A binary state holding a serialized datasketches.tdigest_double (compression k=500 by default). Used for quantiles, ROC/AP, calibration. Merge rule: deserialize-merge-reserialize.

Theta state

A binary state holding a Theta sketch from Apache DataSketches. Used when set algebra (intersection / difference) is required, e.g. cohort retention, audience overlap.

Tile

A single chart on a Dashboard page, bound to one metric and one chart kind. Required fields depend on the chart kind (e.g. line requires x; treemap requires path and derives Color from its metric). Typed common fields include description, placement, kpi, scale_mode, and value_format; chart-specific extras remain permissive. placement: kpi_strip is valid only for an ungrouped kpi_card. A KPI query must resolve to one numeric scalar and can request a target, an equal-length previous period, and an aggregate time-series sparkline. scale_mode is presentation-only and supports absolute, index_100, and percent_change for line/stacked-area charts.

Page filter

An interactive Reports-page control authored under page.filters, with a safe inference fallback for older catalogs. Each filter declares its aggregate field, label, primary/secondary placement, control type, and either all_tiles or compatible_tiles scope. Validation proves the declared coverage against each tile's processor. Partial filters remain usable, but the Reports UI identifies unsupported tiles instead of silently skipping the filter. Adding a filter over an already-persisted group_by column is presentation-compatible; adding a new processor group_by column still requires raw replay.

Time filter

A page-level list of supported date presets plus one default preset. Time selection becomes query-layer start/end bounds and never bypasses aggregate routing. all_time is the canonical default.

Time grain

The Processor-level storage contract declared by time.property and time.grain. Calendar columns at or above that base grain are persisted as ordinary group_by dimensions when selected. For example, a daily processor may expose Day, Week, Month, Quarter, and Year to report roles without adding a separate time-role system.

Transform

A typed step in the Source's transforms list. Each transform takes a Polars LazyFrame and returns a LazyFrame. Built-in transforms: rename_capitalize, parse_datetime, derive_calendar, derive_action_id, filter, dedup, defaults, derive_column, cast, drop_columns. Transforms operate before processors fan out, so they affect every Processor bound to the Source.

Variant

Two distinct meanings; context disambiguates: 1. A/B test variant — the Test / Control arm of an experiment, defined by the Processor's variant_column and variant_role_map. 2. Workspace variant — a separate workspace for a different deployment (e.g. BDT, RBB, NBS, Demo). Each variant is its own filesystem root and its own catalog.

Workspace

A self-contained Value Stream environment. Has its own: - catalog/ (YAML), - aggregates/ (Parquet), - snapshots/ (Parquet), - meta/ (DuckDB metadata), - duckdb/valuestream.duckdb (views).

A workspace is the unit of deployment, security boundary, and backup.


4. Identity rules

Identifier Where Format Example
workspace top-level YAML [a-z][a-z0-9_-]+ bdt
source.id pipelines.yaml snake_case ih, holdings
group_by column processors.yaml transformed column name Channel, CustomerType
processor.id processors.yaml snake_case engagement, model_ml_scores
metric key metrics.yaml PascalCase or snake_case, free CTR, ROC_AUC, CLV_Summary
dashboard.id, page.id, tile.id dashboards.yaml snake_case marketing_overview, engagement, daily_ctr
chunk_id derived from filename string 2024-08-21
pipeline_run_id UUIDv4 hex 8a32fa3d-…
config_hash sha256 of canonical YAML 64-char hex f1c2…

Reserved IDs (do not use as user identifiers): __all__ (planner placeholder), _manifest (filesystem reserved), period, pipeline_run_id, chunk_id, created_at, config_hash.

5. Compatibility classes for config changes

When the Catalog changes, the planner classifies the change against the existing aggregate store:

Class Examples Behavior
Compatible widening Add a new Processor; add a new Metric; add a new finer Grain New aggregates are populated forward; optional backfill; nothing invalidated
Compatible narrowing Remove a group-by column; coarsen a Grain; remove a State Existing aggregates can be re-compacted directly; no raw replay needed
Incompatible Add a group-by column; change a filter; change positive_values; change CPC/HLL lg_k; switch sketch type Re-process from raw; old aggregates remain readable under their config_hash until vacuumed
Catalog removal Remove a Source from the Builder The confirmed cascade removes its Processors, transitive dependent Metrics, bound Tiles, unsupported page filters, and related Chat descriptions from configuration. Persisted aggregates and run audit history remain untouched by the catalog edit.

Metric display metadata, dashboard/page/tile presentation, KPI comparison settings, and filters over existing aggregate dimensions are compatible changes and do not require reprocessing.

Adding a State to an existing Processor changes its computation hash. Queries under the updated Catalog read only aggregate partials carrying that new hash; older files remain available for the older configuration hash but are never mixed with the new schema. Until ingestion publishes the new contract, report tiles show that a source replay is required. Historical replay is never permission to derive a missing state from old aggregate files.

Authoring revision (UI-local)

An authoring revision is a UI lifecycle object, not another catalog entity and not a durable raw-data store. It consists of a canonical proposed catalog object or multi-file bundle, its revision digest, one validation verdict for that digest, and explicit review state. Editing changes the digest and clears the prior verdict/review. Applying a reviewed revision produces ordinary YAML catalog files and a new catalog hash inside the catalog transaction.

Configuration Builder may checkpoint the non-secret, JSON-safe subset of its draft registry under workspace meta/ for seven days. The checkpoint is recovery metadata rather than a Catalog entity: it never includes prompts, credentials, provider/sample payloads, bytes, or DataFrames, and restore still requires current-baseline reconciliation and validation before Apply. Removing the final recoverable draft removes the checkpoint.

Displayed state Domain evidence Permitted transition
Editing draft Proposed canonical object differs from applied YAML Validate or discard
Ready for review Revision-keyed validation has zero blocking issues Review dependency-closed changes
Reviewed User accepted a valid dependency-consistent set for that revision Apply transactionally
Applied Catalog write and post-write validation succeeded Classify materialization impact
Data refresh required Source/processor computation contract changed Explicit handoff to Data Load
Report ready Existing aggregates satisfy the applied catalog Open Reports through the query layer

Uploaded Studio samples and their derived preview frames remain session-local inputs to authoring. They do not become production source files or persisted aggregates merely because a draft references their schema.

6. Examples

6.1 Source → Chunk → Aggregate

Files in folder:
  /data/ih/2024-08-21/interaction-XYZ-20240821000000_001.json.zip
  /data/ih/2024-08-21/interaction-XYZ-20240821000000_002.json.zip
  /data/ih/2024-08-22/interaction-XYZ-20240822000000_001.json.zip

group_by_filename: '\d{8}(?=\d{6}_)'

Chunks:
  20240821 -> [2 files]
  20240822 -> [1 file]

Run: 8a32fa3d-...

Per chunk, for processor 'engagement':
  -> aggregates/ih/engagement/daily/period=2024-08/part-8a32fa3d.parquet (rows for both days)

After compaction:
  -> aggregates/ih/engagement/monthly/period=2024-08/part-8a32fa3d.parquet
  -> aggregates/ih/engagement/summary/period=2024-08/part-8a32fa3d.parquet

6.2 Metric resolution

User asks: tile.metric = "CTR"

Resolver:
  CTR -> processor=engagement, kind=formula, expression=safe_div(Positives, Positives+Negatives)

Planner:
  tile fields = {grain: Day, x: Day, color: Channel}; page date bounds = 2024-08
  -> aggregates/ih/engagement/daily, period=2024-08

Executor:
  read Positives, Negatives by (Day, Channel)
  -> Polars DataFrame

Derive (formula):
  CTR = safe_div(Positives, Positives + Negatives)

Return: rows + plan + freshness + config_hash

6.3 State catalog snippet

States produced by 'engagement' processor:

  Count               type=count            (sum-merge)
  Positives           type=count            (sum-merge)
  Negatives           type=count            (sum-merge)
  UniqueCustomers_cpc type=cpc, lg_k=11     (cpc-union merge)
  UniqueActions_cpc   type=cpc, lg_k=11     (cpc-union merge)

6.4 Group-by and Time Binding

processors.yaml:
  engagement:
    group_by: [Day, Week, Month, Quarter, Year, Channel, PlacementType, ...]
    time:
      property: OutcomeTime
      grain: daily

dashboards.yaml:
  daily_ctr:
    grain: Day
    x: Day
    color: Channel

Transforms define the user-facing schema. If a nicer business name is needed, create it in the pipeline with rename_capitalize or derive_column, then use that column everywhere: processors, reports, filters, and CLI queries.


7. Concept relationships (one big table)

From To Relationship Cardinality
Workspace Catalog has-one 1 : 1
Workspace Aggregate store has-one 1 : 1
Catalog Source contains 1 : N
Catalog Dimension contains 1 : N
Catalog Processor contains 1 : N
Catalog Metric contains 1 : N
Catalog Dashboard contains 1 : N
KPI recipe library KPI Recipe contains 1 : N
KPI Recipe Metric materializes on explicit install 1 : 1
KPI Recipe Tile recommends on explicit install 1 : 0..1
KPI Recipe State proposes when selected field/algorithm is not configured 1 : 0..N
Source Reader uses 1 : 1
Source Transform applies 1 : N (ordered)
Source Processor feeds 1 : N
Processor Dimension groups by N : N
Processor State produces 1 : N
Processor Base grain materializes at 1 : 1
Processor Metric sourced by 1 : N
Metric Tile bound to 1 : N
Tile Dashboard belongs to N : 1
Tile Chart uses 1 : 1
Run Chunk processes 1 : N
Chunk Aggregate (partial) produces 1 : N (one per processor)
Aggregate Run tagged with N : 1

This table is the contract between docs and code: any data structure or API must respect these cardinalities.