---
title: Real-time segments definition
slug: data-platform/docs/realtimesegments-defining-real-time-segments
docTags: 
createdAt: 2026-08-10T12:05:19.000Z
---

:::hint{type="info"}
**Note**
The terms *audiences* and *segments* are interchangeable. Optimizely is currently working on updating the UI to display all *segments* as *audiences*.
:::

Real-time segments are constructed from an arbitrary logical tree of [conditions](docId:7-qDl01uxXDPl5_jFVF1E) and an optional set of [collections](docId:7-qDl01uxXDPl5_jFVF1E). The conditions determine which users belong to a segment and allow extraction of context from the interactions that led them to belong.

:::hint{type="info"}
**Note**
There is an [example segment definition](docId:7-qDl01uxXDPl5_jFVF1E) we reference and expound upon at the end.
:::

## Conditions

Membership in a segment is decided by evaluating each user against a condition tree. At the core of a segment definition is its **root condition**. Conditions currently come in three varieties: [combination conditions](docId:7-qDl01uxXDPl5_jFVF1E), [customer conditions](docId:7-qDl01uxXDPl5_jFVF1E), and [sequence conditions](docId:7-qDl01uxXDPl5_jFVF1E).

### Combination conditions

Combination conditions provide support for logical trees of other conditions. These allow you to combine a set of conditions using AND or OR.

### Customer conditions

A customer condition continuously evaluates a customer filter [expression](docId:7-qDl01uxXDPl5_jFVF1E). All fields referenced by this expression must be rooted at the **customer** but may include join-through values (for example, `customer.email` or `customer.last_email_metadata.consent`). The expression as a whole must produce a boolean output type. It is always evaluated against the current customer record (and its latest join-through values). The condition is matched if (and only if) the customer filter expression evaluates to `true`. As soon as this is no longer true, the condition returns to an unmatched state. Be aware that expressions follow sql-like null rules, such that it is possible for the output to be null instead (neither `true` nor `false`) in the presence of nulls. A null filter output results in an unmatched condition.

### Continuations

A customer condition optionally defines a `goal` alongside or from a `disqualifier` contiunation. Customer condition continuations support `timeout_source` continuations and `combination` continuations composed of timeout sources.

A `goal` continuation on a customer condition delays matching until a specified point in time. A `disqualifier` continuation causes the customer condition to become unmatched at a specified point in time. The `timeout_source` expression must evaluate to a number representing the moment in time, in epoch seconds, at which the continuation is satisfied. The expression can reference any customer-rooted fields available to the customer condition.

:::hint{type="warning"}
**Important**
When using continuations on a customer condition, the `customer_filter` expression must include a non-null check for every field path referenced by `timeout_source` expressions. This can be a simple `is_not_missing` check or any comparison that inherently guarantees a non-null value, such as `GREATER_THAN_OR_EQUAL`. The Segments API rejects any segment definition that references a path in a timeout source without a corresponding non-null guard in the customer filter, ensuring the timeout source always evaluates to a valid timestamp.
:::

For example, the following customer condition matches customers who signed up at least seven days ago. The `customer_filter` checks that `customer.signup_ts` is not missing, and the `goal` timeout source shifts the signup timestamp forward by 7 days:

```json
{
  "customer_condition": {
    "customer_filter": {
      "is_not_missing": {
        "source": {"path_reference": {"value": "customer.signup_ts"}}
      }
    },
    "goal": {
      "timeout_source": {
        "shift_time": {
          "source": {"path_reference": {"value": "customer.signup_ts"}},
          "unit": "DAY",
          "amount": 7,
          "timezone": {"string_literal": {"value": "America/New_York"}}
        }
      }
    }
  }
}
```

You can combine multiple timeout sources using a combination continuation. The following example uses a `disqualifier` to expire the condition when a customer's subscription end date is reached. Both `customer.signup_ts` and `customer.subscription_end_ts` have `is_not_missing` guards in the customer filter, as required by the API:

```json
{
  "customer_condition": {
    "customer_filter": {
      "combination": {
        "conjunction": "AND",
        "expressions": [
          {
            "is_not_missing": {
              "source": {"path_reference": {"value": "customer.signup_ts"}}
            }
          },
          {
            "is_not_missing": {
              "source": {"path_reference": {"value": "customer.subscription_end_ts"}}
            }
          }
        ]
      }
    },
    "goal": {
      "timeout_source": {
        "shift_time": {
          "source": {"path_reference": {"value": "customer.signup_ts"}},
          "unit": "DAY",
          "amount": 7,
          "timezone": {"string_literal": {"value": "America/New_York"}}
        }
      }
    },
    "disqualifier": {
      "timeout_source": {
        "path_reference": {"value": "customer.subscription_end_ts"}
      }
    }
  }
}
```

In this example, the condition matches only if seven days passed since signup (goal), and automatically becomes unmatched when the subscription reaches the end date (disqualifier).

### Inverse flag

A customer condition supports an optional `inverse` flag (boolean, defaults to `false`). When set to `true`, the condition logic inverts. Customers who match the customer filter and its continuations, if present, are excluded from the audience. In other words, the condition matches for every customer for whom the customer filter and continuations do not evaluate to `true`.

This provides API-level support for building exclusions based on customer filters without manually negating complex filter expressions or complex continuations. This is useful when combining inclusion and exclusion criteria within the same segment definition using a [combination condition](docId:7-qDl01uxXDPl5_jFVF1E).

For example, the following segment includes high-value customers, whose total revenue >= $250, but excludes those flagged as churned:

```json
{
  "root_condition": {
    "combination": {
      "conjunction": "AND",
      "conditions": [
        {
          "customer_condition": {
            "customer_filter": {
              "comparison": {
                "lhs": {"path_reference": {"value": "customer.observations.total_revenue"}},
                "comparator": "GREATER_THAN_OR_EQUAL",
                "rhs": {"number_literal": {"value": "250"}}
              }
            }
          }
        },
        {
          "customer_condition": {
            "inverse": true,
            "customer_filter": {
              "comparison": {
                "lhs": {"path_reference": {"value": "customer.is_churned"}},
                "comparator": "EQUAL",
                "rhs": {"boolean_literal": {"value": true}}
              }
            }
          }
        }
      ]
    }
  }
}
```

In this example, the second customer condition uses `"inverse": true` to exclude all customers where `customer.is_churned` is `true`. Only customers who are both high-value and not churned belong to the segment.

### Sequence conditions

A sequence condition continuously evaluates whether a given [sequence](docId:7-qDl01uxXDPl5_jFVF1E) meets some criteria, which currently support a minimum count (and optional maximum count) of qualified sequence instances.

## Sequences

Sequences are the core value proposition of real-time segments, as they enable detection of behavioral patterns over time. At its simplest, a sequence is comprised of an `entry_event_filter` [expression](docId:7-qDl01uxXDPl5_jFVF1E) (which must evaluate to a boolean) and a maximum age (between 1 second and 28 days). You can then optionally extend a sequence with a **goal** and/or **disqualifier** [continuation](docId:7-qDl01uxXDPl5_jFVF1E). You can also make it [unique](docId:7-qDl01uxXDPl5_jFVF1E) (for counting purposes) and keep a set of [tracked data](docId:7-qDl01uxXDPl5_jFVF1E). The entry event filter expression may reference fields rooted at either the **event** or the **customer** (including all available join-through values), but it implicitly only reacts to events.

### Sequence lifecycle

Before getting into all of the other details, it is important to understand the lifecycle of a sequence. Within the context of evaluating a given user's stream of events, any event that matches the entry event filter of a "root" sequence (more on that distinction [later](docId:7-qDl01uxXDPl5_jFVF1E)) spawns a new instance of that sequence. Each instance of a sequence has its own lifecycle that starts at the entry event and ends no later than `max_age_seconds` after that entry event.

If the sequence does **not** have a `goal`, this sequence instance is automatically "qualified", meaning it will count toward the criteria of its associated [sequence condition](docId:7-qDl01uxXDPl5_jFVF1E). If there **is** a goal defined, then this instance does not become qualified until that goal is met. This qualified status is not permanent, however. If at any time the goal is no longer met, the instance is no longer qualified. Such an instance can gain or lose its qualified status any number of times throughout its lifecycle.

If the sequence has a `disqualifier`, when that disqualifier is met, the instance is immediately destroyed and no longer counts toward the criteria of its sequence condition. The instance is also destroyed as soon as `max_age_seconds` have elapsed since its entry event (regardless of disqualifier existence or status).

:::hint{type="info"}
**Note**
The goal and disqualifier are not evaluated within a brand new sequence instance (that is, at the moment of its creation). Once an instance exists, these are  evaluated with each new stimulus (event, customer-related update, or timeout).
:::

### Root and child sequences

Any sequence defined by a condition that is immediately within the `root_condition` tree is a **root** sequence. Any sequence defined within a `goal` or `disqualifier` continuation tree is a **child** sequence, belonging to its **parent** sequence. A child sequence may in turn be the parent of other child sequences, and so on. The lifecycle of a child sequence is tied to the lifecycle of its parent instance. So a child sequence cannot spawn new instances until after its parent has been spawned. And child instances are automatically destroyed when their parent instance is destroyed.

### Unique sequences

There are many scenarios in which a condition should be based on the number of **unique** interactions, according to some definition of uniqueness. Sequences can define a set of `unique_key_sources`, which are expressions that may evaluate to any type. This does not affect the lifecycle of a sequence, but when evaluating the count for a sequence condition, only one instance is counted per unique set of values.

### Tracked data

Sometimes it is useful to keep track of some data from the entry event that spawned a sequence instance. To do this, a sequence may define a set of `tracked_data`. Each tracked data must have a `key` (unique within a segment definition) and extracts a set of `field_sub_paths` from a `source_path` available during evaluation of the entry event. You can then reference the tracked data (like `key.sub.path`) by expressions within [continuations](docId:7-qDl01uxXDPl5_jFVF1E) (and their continuations, and so on) and/or extract it via [collections](docId:7-qDl01uxXDPl5_jFVF1E).

### Continuations

A sequence may define a `goal` and/or `disqualifier` continuation tree. There are currently four types of continuations: [combination continuations](docId:7-qDl01uxXDPl5_jFVF1E), [event filter continuations](docId:7-qDl01uxXDPl5_jFVF1E), [sequence condition continuations](docId:7-qDl01uxXDPl5_jFVF1E), and [timeout source continuations](docId:7-qDl01uxXDPl5_jFVF1E).

**Combination continuations**

Combination continuations provide support for logical trees of other continuations. These allow you to combine a set of continuations with AND or OR.

**Event filter continuations**

An event filter continuation is satisfied as soon as any event matches the `event_filter` expression (which must evaluate to a boolean). This is useful when the final step of a sequence is a singular event, and there is no need to track its data or age it off before its parent sequence. The event filter expression has access to all tracked data defined by any of its ancestor sequences.

**Sequence condition continuations**

A sequence condition continuation enables tracking and counting of a sequence that cannot begin until after its parent sequence has been spawned. This is useful (compared to an event filter continuation) when the continuation sequence either has its own continuations, needs to track its own data, needs age off before its parent, or has specific counting requirements. Every expression within a continuation sequence has access to all tracked data defined by any of its ancestor sequences.

**Timeout source continuations**

A timeout source continuation allows you to trigger a goal or disqualifier after some amount of time has elapsed. More precisely, the `timeout_source` expression must evaluate to a number representing (in epoch seconds) the moment in time that the continuation is satisfied. Since the timeout source expression is evaluated at the moment of entry (and outside of the newly spawned instance), this expression does not have access to the tracked data defined by its immediate parent sequence. It can, however, still access those same fields directly from the entry event, just not via tracked data key. It also has access to all tracked data defined by any of its parent sequence's ancestors.

:::hint{type="warning"}
**Important**
You must take care when designing timeout source expressions to ensure that the largest possible look-forward does not create an end-to-end time exceeding the maximum age of its root sequence. Failure to do so may produce unexpected results.
:::

Timeout source continuations can be useful in a goal when the sequence should not be considered until some minimum amount of time has elapsed. For example, *in the last 28 days* (sequence `max_age_seconds`) or *added to cart at least 4 hours ago* (goal `timeout_source`).

They can also be useful in a disqualifier when the sequence's max age should be dynamic based on input. In this case, set `max_age_seconds` to some reasonable upper bound, then provide a disqualifier timeout source to compute the actual value. One common scenario where you might encounter this is tracking the number of days on which something occurred. You can achieve this by using the [day-truncation](docId:7-qDl01uxXDPl5_jFVF1E) of event timestamp as the unique key source, then [shifting](docId:7-qDl01uxXDPl5_jFVF1E) that forward by some number of days for the timeout source.

The end result is that each instance ages off `N` days after the start of the day in which it occurred (as opposed to the constant `N * 86400` seconds after the event, which would not achieve the desired behavior).

The `timeout_source` expression also references datetime fields reachable from the event's dimension. For example, if an event has a join-through to a dimension containing an `expires_at` field, you can use that field as the timeout source, such as `event.product.expires_at`. This enables scenarios where the timeout is driven by data associated with the event rather than computed from the event timestamp itself.

:::hint{type="warning"}
**Important**
Any field path used in a `timeout_source` expression must be validated with a non-null check in the `entry_event_filter`. For example, use an `is_not_missing` check or a comparison such as `GREATER_THAN_OR_EQUAL` that inherently guarantees the value is not null. Without this guard, a null value in the timeout source field can lead to unexpected results.
:::

The following example displays a sequence targeting customers who initiated product events for products that expired within the last 10 days. It uses `event.product.expires_at` from the event's dimension for both the `goal` and `disqualifier` timeout sources. The `entry_event_filter` includes an `is_not_missing` check on `event.product.expires_at` to satisfy the non-null requirement. The goal meets at the start of the day (UTC) of the product's expiration, meaning the sequence instance qualifies only when the product actually expired. The disqualifier removes the instance 10 days after that expiration, keeping the audience scoped to recently expired products:

```json
{
  "sequence_condition": {
    "sequence": {
      "entry_event_filter": {
        "combination": {
          "conjunction": "AND",
          "expressions": [
            {
              "comparison": {
                "lhs": {"path_reference": {"value": "event.event_type"}},
                "comparator": "EQUAL",
                "rhs": {"string_literal": {"value": "product"}}
              }
            },
            {
              "is_not_missing": {
                "source": {"path_reference": {"value": "event.product.expires_at"}}
              }
            },
            {
              "path_reference": {"value": "event.active_event"}
            }
          ]
        }
      },
      "unique_key_sources": [{"boolean_literal": {"value": true}}],
      "max_age_seconds": 2419200,
      "goal": {
        "timeout_source": {
          "truncate_time": {
            "source": {"path_reference": {"value": "event.product.expires_at"}},
            "unit": "DAY",
            "timezone": {"string_literal": {"value": "UTC"}}
          }
        }
      },
      "disqualifier": {
        "timeout_source": {
          "shift_time": {
            "source": {"path_reference": {"value": "event.product.expires_at"}},
            "unit": "DAY",
            "timezone": {"string_literal": {"value": "UTC"}},
            "amount": {"number_literal": {"value": "10"}}
          }
        }
      }
    },
    "min_count": 1
  }
}
```

Instead of an explicit `is_not_missing` check, you can use a comparison that inherently guarantees a non-null value. The following example is equivalent to the one above but replaces the `is_not_missing` check with a `GREATER_THAN_OR_EQUAL` comparison against January 1, 2026, with epoch `1767225600`. Because a null value can never satisfy a `GREATER_THAN_OR_EQUAL` comparison, this is as an implicit non-null guard for `event.product.expires_at`. The `goal` and `disqualifier` timeout sources remain the same:

```json
{
  "sequence_condition": {
    "sequence": {
      "entry_event_filter": {
        "combination": {
          "conjunction": "AND",
          "expressions": [
            {
              "comparison": {
                "lhs": {"path_reference": {"value": "event.event_type"}},
                "comparator": "EQUAL",
                "rhs": {"string_literal": {"value": "product"}}
              }
            },
            {
              "comparison": {
                "lhs": {"path_reference": {"value": "event.product.expires_at"}},
                "comparator": "GREATER_THAN_OR_EQUAL",
                "rhs": {"number_literal": {"value": "1767225600"}}
              }
            },
            {
              "path_reference": {"value": "event.active_event"}
            }
          ]
        }
      },
      "unique_key_sources": [{"boolean_literal": {"value": true}}],
      "max_age_seconds": 2419200,
      "goal": {
        "timeout_source": {
          "truncate_time": {
            "source": {"path_reference": {"value": "event.product.expires_at"}},
            "unit": "DAY",
            "timezone": {"string_literal": {"value": "UTC"}}
          }
        }
      },
      "disqualifier": {
        "timeout_source": {
          "shift_time": {
            "source": {"path_reference": {"value": "event.product.expires_at"}},
            "unit": "DAY",
            "timezone": {"string_literal": {"value": "UTC"}},
            "amount": {"number_literal": {"value": "10"}}
          }
        }
      }
    },
    "min_count": 1
  }
}
```

In this example, the `GREATER_THAN_OR_EQUAL` comparison against `event.product.expires_at` in the `entry_event_filter` guarantees the field is not null, so no separate `is_not_missing` check is needed. The `goal` and `disqualifier` timeout sources can safely reference `event.product.expires_at` because the entry filter already ensures it is present and valid.

## Collections

When working with sequences, it is often useful to extract context about how a user qualified for the segment. For example, in a segment that finds users who have viewed at least three unique products more than once over the last week, it would likely be desirable to know which products they viewed in such a way.

Collections allow a segment to export the [tracked data](docId:7-qDl01uxXDPl5_jFVF1E) from qualified instances of a sequence. A collection can optionally reduce the fields exported from the tracked data to a set of `narrowed_sub_paths` and/or be configured to only produce the `unique` rows.

:::hint{type="warning"}
**Important**
You cannot collect tracked data from any sequence underneath a disqualifier continuation tree.
:::

## Expressions

Expressions are the heart of interaction with data. Any given expression produces a specific output type (which may or may not be dependent on an input type). The possible types are **string**, **number**, or **boolean** (timestamps are treated as numbers containing unix epoch seconds). Expressions currently support 13 different varieties, detailed below.

:::hint{type="info"}
**Note**
Expressions follow sql-like null rules, such that it is possible for the output of a boolean-producing expression to be null (neither `true` nor `false`) in the presence of null inputs.
:::

### Combination expressions

Combination expressions provide support for logical trees of other expressions. These allow you to combine a set of expressions with AND or OR. This expression outputs a boolean, and all input `expressions` must output a boolean. There must be at least one input expression. Combinations with no input expressions will be rejected.

Behavior in the face of null inputs depends on the `conjuction`. For `AND`, if any expression evaluates to `false`, the output is `false`. Otherwise, if any expression evaluates to null, the output is null. For `OR`, if any expression evaluates to `true`, the output is `true`. Otherwise, if any expression evaluates to null, the output is null.

### `comparison` expressions

A `comparison` expression evaluates an `lhs` expression against an `rhs` expression according to the rules of a `comparator`. In general, the `lhs` and `rhs` expressions may evaluate to any type, but those types must be compatible, both with each other, and with the `comparator`. A comparison always produces a boolean output type. If either input expression evaluates to null, the output is null.

**General comparators**

You can use the following comparators with any input type:

- `EQUAL` – determines exact equality (case sensitive for strings)
- `NOT_EQUAL` – determines exact inequality (case sensitive for strings)

:::hint{type="info"}
**Note**
As noted previously, but worth calling out again: a null value is neither equal to, nor not-equal to, another null value. If either input is null, so is the output.
:::

**Numeric comparators**

You can only use the following comparators on number inputs:

- `LESS_THAN` – numeric `<`
- `LESS_THAN_OR_EQUAL` – numeric `<=`
- `GREATER_THAN` – numeric `>`
- `GREATER_THAN_OR_EQUAL` – numeric `>=`

**String comparators**

You can only use the following comparators on string inputs:

- `LIKE` – case-insensitive fuzzy match
- `NOT_LIKE` – case-insensitive fuzzy non-match

:::hint{type="info"}
**Note**
These comparators require a rigid input format. Specifically, the `rhs` expression must be a string literal (not just any expression that evaluates to a string output type). The `value` of this string literal defines the fuzzy-matching pattern according to the following rules (in order of precedence):

- `[_]` – matches a literal `_` character
- `*` – matches zero or more characters
- Any other sequence of characters matches that sequence of characters (case-insensitive)
- The beginning and end of the pattern are anchored to the beginning and end of the `lhs` string value
:::

For example, to match any Optimizely email address, the pattern would simply be `_@optimizely.com`. To match any string that contains a literal underscore, the pattern would be `_[_]_`. To match any string that contains the literal sequence `[_]`, the pattern would be `_[[_]]_`. To match any string that begins with the letter "a" (or "A"), the pattern would simply be `a*`.

**Set-membership comparators**

The following comparators evaluate to the presence or absence of some item in a set of strings or numbers.

- `any_of_string` is true if the supplied string is in the set of candidate strings
- `none_of_string` is false if the supplied string is in the set of candidate strings
- `any_of_number` is true if the supplied number is in the set of candidate numbers
- `none_of_number` is false if the supplied number is in the set of candidate numbers
- `like_any_of_string` is true if the supplied string is [LIKE](docId:7-qDl01uxXDPl5_jFVF1E) any of the candidate strings
- `like_none_of_string` is false if the supplied string is [LIKE](docId:7-qDl01uxXDPl5_jFVF1E) any of the candidate strings

### `path-reference` expressions

A `path_reference` expression simply pulls the datum stored at the given path `value`. Depending on context, a path may either be rooted at the `event`, the `customer`, or an available [tracked data](docId:7-qDl01uxXDPl5_jFVF1E) key. For example: `event.event_type`, `event.product.price`, `customer.email`, `customer.last_email_metadata.consent`, `my_tracked_data.product.parent_product.brand`.

The input path must resolve to an available datum within the expressions current context. All others are rejected. A path reference expression outputs the same type as its source datum.

### `string-literal` expressions

A `string_literal` expression always produces a string output type with the specified `value`.

### `number-literal` expressions

A `number_literal` expression always produces a number output type with the specified `value`. The value is given as a string to support arbitrary precision, but it must contain a numeric value (whether integer or decimal).

### `boolean-literal` expressions

A `boolean_literal` expression always produces a boolean output type with the specified `value`.

### `is-missing` expressions

An `is_missing` expression always produces a boolean output. The input expression may produce any output type. If the `source` expression evaluates to null, then it outputs `true`. For any other source value, it outputs `false`. It will never output null.

### `is-not-missing` expressions

An `is_not_missing` expression always produces a boolean output. The input expression may produce any output type. If the `source` expression evaluates to null, then it outputs `false`. For any other source value, it outputs `true`. It will never output null.

### `false-or-missing` expressions

A `false_or_missing` expression always produces a boolean output. The input expression must produce a boolean output type. If the `source` expression evaluates to either `false` or null, then it outputs `true`; otherwise `false`. It will never output null.

### `true-or-missing` expressions

A `true_or_missing` expression always produces a boolean output. The input expression must produce a boolean output type. If the `source` expression evaluates to either `true` or null, then it outputs `true`; otherwise `false`. It will never output null.

```json
{
  "entry_event_filter": {
    "combination": {
      "conjunction": "AND",
      "expressions": [
        {
          "comparison": {
            "lhs": {"path_reference": {"value": "event.event_type"}},
            "comparator": "EQUAL",
            "rhs": {"string_literal": {"value": "product"}}
          }
        },
        {
          "comparison": {
            "lhs": {"path_reference": {"value": "event.vdl_action"}},
            "comparator": "EQUAL",
            "rhs": {"string_literal": {"value": "detail"}}
          }
        },
        {
          "comparison": {
            "lhs": {"path_reference": {"value": "event.product.price"}},
            "comparator": "GREATER_THAN_OR_EQUAL",
            "rhs": {"number_literal": {"value": "100"}}
          }
        }
      ]
    }
  },
  "unique_key_sources": [{"path_reference": {"value": "event.product_id"}}],
  "tracked_data": [
    {
      "key": {"key": "pdp"},
      "source_path": "event",
      "field_sub_paths": ["product_id", "ts"]
    }
  ],
  "max_age_seconds": 2419200
}
```

### `coalesce` expressions

A `coalesce` expression takes between 2 and 5 (inclusive) ordered inputs and outputs the first one that is **not** null. If all of the inputs are null, it outputs null. The `sources` may evaluate to any output type, but the types must all be the same. This expression will output the same type as its inputs.

### `truncate-time` expressions

A `truncate_time` expression takes a `source` expression (that must produce a number output type) containing a timestamp in unix epoch seconds and truncates it to a give time `unit` (`MINUTE`, `HOUR`, or `DAY`). For `DAY`, the computation is performed in the given `timezone` expression, which must evaluate to a string containing a valid IANA timezone name. If the timezone name is not recognized, the computation is performed in UTC. This expression produces a number output type containing a timestamp in unix epoch seconds. If any of the input expressions evaluate to null, the output is null.

### `shift-time` expressions

A `shift_time` expression takes a `source` expression (that must produce a number output type) containing a timestamp in unix epoch seconds and shifts the timestamp by an `amount` number of time `unit` increments (`SECOND`, `MINUTE`, `HOUR`, or `DAY`). For `DAY`, the computation is performed in the given `timezone` expression, which must evaluate to a string containing a valid IANA timezone name. If the timezone name is not recognized, the computation is performed in UTC. This expression produces a number output type containing a timestamp in unix epoch seconds. If any of the input expressions evaluate to null, the output is null.

### `is_list_member` expressions

The `is_list_member` expression evaluates if a customer is subscribed to a particular list. As an input, it takes `list_id` of a list to which you are checking subscriptions. You can use this as a part of `customer_condition`.

The following example shows a segment definition that contains all customers who are subscribed to the list with `list_id` = `test_list`:

```json
{
  "definition": {
    "root_condition": {
      "customer_condition": {
        "customer_filter": {
          "is_list_member": {
            "list": "test_list"
          }
        }
      }
    }
  },
  "description": "Test segment for subscription to test_list"
}
```

### `is_not_list_member` expressions

The `is_not_list_member` expression evaluates if a customer is **not** subscribed to a particular list. As an input, it takes `list_id` of a list for which you want to check subscriptions. You can use this as a part of the `customer_condition`. When the expression returns `true`, either the customer is not subscribed to the list, or the list does not exist.

The following example shows a segment definition that contains all customers who are not subscribed to the list with `list_id` = `test_list` (if the list exists):

```json
{
  "definition": {
    "root_condition": {
      "customer_condition": {
        "customer_filter": {
          "is_not_list_member": {
            "list": "test_list"
          }
        }
      }
    }
  },
  "description": "Segment contains all customers who are not subscribed to test_list"
}
```

## Example segment definition

You can view the complete `SegmentDefinition` protobuf for this example below. For the purpose of exposition, this document will just pull out relevant snippets as needed.

```json
{
  "description": "high value customers who have abandoned valuable products",
  "definition": {
    "root_condition": {
      "combination": {
        "conjunction": "AND",
        "conditions": [
          {
            "customer_condition": {
              "customer_filter": {
                "comparison": {
                  "lhs": {"path_reference": {"value": "customer.observations.total_revenue"}},
                  "comparator": "GREATER_THAN_OR_EQUAL",
                  "rhs": {"number_literal": {"value": "250"}}
                }
              }
            }
          },
          {
            "sequence_condition": {
              "sequence": {
                "entry_event_filter": {
                  "combination": {
                    "conjunction": "AND",
                    "expressions": [
                      {
                        "comparison": {
                          "lhs": {"path_reference": {"value": "event.event_type"}},
                          "comparator": "EQUAL",
                          "rhs": {"string_literal": {"value": "product"}}
                        }
                      },
                      {
                        "comparison": {
                          "lhs": {"path_reference": {"value": "event.action"}},
                          "comparator": "EQUAL",
                          "rhs": {"string_literal": {"value": "detail"}}
                        }
                      },
                      {
                        "comparison": {
                          "lhs": {"path_reference": {"value": "event.product.price"}},
                          "comparator": "GREATER_THAN_OR_EQUAL",
                          "rhs": {"number_literal": {"value": "100"}}
                        }
                      }
                    ]
                  }
                },
                "unique_key_sources": [{"path_reference": {"value": "event.product_id"}}],
                "tracked_data": [
                  {
                    "key": {"key": "pdp"},
                    "source_path": "event",
                    "field_sub_paths": ["product_id", "ts"]
                  }
                ],
                "max_age_seconds": 2419200,
                "goal": {
                  "event_filter": {
                    "combination": {
                      "conjunction": "AND",
                      "expressions": [
                        {
                          "comparison": {
                            "lhs": {"path_reference": {"value": "event.event_type"}},
                            "comparator": "EQUAL",
                            "rhs": {"string_literal": {"value": "product"}}
                          }
                        },
                        {
                          "comparison": {
                            "lhs": {"path_reference": {"value": "event.action"}},
                            "comparator": "EQUAL",
                            "rhs": {"string_literal": {"value": "detail"}}
                          }
                        },
                        {
                          "comparison": {
                            "lhs": {"path_reference": {"value": "event.product_id"}},
                            "comparator": "EQUAL",
                            "rhs": {"path_reference": {"value": "pdp.product_id"}}
                          }
                        },
                        {
                          "comparison": {
                            "lhs": {
                              "truncate_time": {
                                "source": {"path_reference": {"value": "event.ts"}},
                                "unit": "DAY",
                                "timezone": {
                                  "coalesce": {
                                    "sources": [
                                      {"path_reference": {"value": "customer.timezone"}},
                                      {"string_literal": {"value": "America/New_York"}}
                                    ]
                                  }
                                }
                              }
                            },
                            "comparator": "NOT_EQUAL",
                            "rhs": {
                              "truncate_time": {
                                "source": {"path_reference": {"value": "pdp.ts"}},
                                "unit": "DAY",
                                "timezone": {
                                  "coalesce": {
                                    "sources": [
                                      {"path_reference": {"value": "customer.timezone"}},
                                      {"string_literal": {"value": "America/New_York"}}
                                    ]
                                  }
                                }
                              }
                            }
                          }
                        }
                      ]
                    }
                  }
                },
                "disqualifier": {
                  "event_filter": {
                    "combination": {
                      "conjunction": "OR",
                      "expressions": [
                        {
                          "combination": {
                            "conjunction": "AND",
                            "expressions": [
                              {
                                "comparison": {
                                  "lhs": {"path_reference": {"value": "event.event_type"}},
                                  "comparator": "EQUAL",
                                  "rhs": {"string_literal": {"value": "product"}}
                                }
                              },
                              {
                                "comparison": {
                                  "lhs": {"path_reference": {"value": "event.action"}},
                                  "comparator": "EQUAL",
                                  "rhs": {"string_literal": {"value": "add_to_cart"}}
                                }
                              },
                              {
                                "comparison": {
                                  "lhs": {"path_reference": {"value": "event.product_id"}},
                                  "comparator": "EQUAL",
                                  "rhs": {"path_reference": {"value": "pdp.product_id"}}
                                }
                              }
                            ]
                          }
                        },
                        {
                          "combination": {
                            "conjunction": "AND",
                            "expressions": [
                              {
                                "comparison": {
                                  "lhs": {"path_reference": {"value": "event.event_type"}},
                                  "comparator": "EQUAL",
                                  "rhs": {"string_literal": {"value": "order"}}
                                }
                              },
                              {
                                "comparison": {
                                  "lhs": {"path_reference": {"value": "event.action"}},
                                  "comparator": "EQUAL",
                                  "rhs": {"string_literal": {"value": "purchase"}}
                                }
                              }
                            ]
                          }
                        }
                      ]
                    }
                  }
                }
              },
              "min_count": 3
            }
          }
        ]
      }
    },
    "collections": [
      {
        "key": "high_interest_products",
        "source": {"key": "pdp"},
        "narrowed_sub_paths": ["product_id"],
        "unique": true
      }
    ]
  }
}
```

### Target audience

This segment is targeting customers who meet the following criteria:

1. Have a lifetime revenue of at least $250
2. Are exhibiting prolonged interest (PDP on 2+ days) in at least 3 different high-value products ($100+)
3. Have not added those products to cart since showing interest
4. Have not made any purchase since showing interest

From a fictitious marketer's perspective, this could be a chance to convert by offering some sort of incentive, or perhaps by helping them decide among the products that they have been considering.

### Condition breakdown

The four criteria above really map to two core **conditions**:

1. A `customer_condition` against the lifetime revenue observation
2. A `sequence_condition` to detect the prolonged interest (disqualified by adding it to cart or making any purchase)

Since these both must be met, the segment definition's `root_condition` must be a `combination` using `AND`:

```json
{
  "root_condition": {
    "combination": {
      "conjunction": "AND",
      "conditions": []
    }
  }
}
```

We can then add the customer and sequence conditions into the `conditions` array.

### High-value customer

Narrowing in on customers with lifetime revenue of at least $250 is quite simple with customer observations.

```json
{
  "customer_condition": {
    "customer_filter": {
      "comparison": {
        "lhs": {"path_reference": {"value": "customer.observations.total_revenue"}},
        "comparator": "GREATER_THAN_OR_EQUAL",
        "rhs": {"number_literal": {"value": "250"}}
      }
    }
  }
}
```

This compares to see if `customer.observations.total_revenue` (path reference) is at least `250` (number literal).

### Prolonged high-value interest

We are looking for prolonged interest in at least three different high-value products. Before thinking about how to detect prolonged interest, note a few key details from the previous statement. First, we are looking for "at least 3" occurrences, so that implies a [root](docId:7-qDl01uxXDPl5_jFVF1E) `sequence_condition` with `min_count` of `3`. Second, we are looking for unique occurrences per product, which would translate to use of `event.product_id` as a unique key. And third, there is no specific time-limit mentioned, so we will just start with the largest allowable `max_age_seconds`, which is 28 days (in seconds). With these pieces of information, we can start to outline the root sequence condition:

```json
{
  "sequence_condition": {
    "sequence": {
      "entry_event_filter": ...,
      "unique_key_sources": [{"path_reference": {"value": "event.product_id"}}],
      "max_age_seconds": 2419200
    },
    "min_count": 3
  }
}
```

Now fill in more of the `sequence`. We are defining "prolonged interest" as PDP on 2+ days. So we will start with a "high-value" PDP `entry_event_filter`. In sql-like syntax this is:

```sql
event.event_type = 'product' and event.vdl_action = 'detail' and event.product.price >= 100
```

We also need to hold onto which `event.product_id` was being viewed and when (`event.ts`), so we will need a `tracked_data`. The upgraded `sequence` is now:

```json
{
  "entry_event_filter": {
    "combination": {
      "conjunction": "AND",
      "expressions": [
        {
          "comparison": {
            "lhs": {"path_reference": {"value": "event.event_type"}},
            "comparator": "EQUAL",
            "rhs": {"string_literal": {"value": "product"}}
          }
        },
        {
          "comparison": {
            "lhs": {"path_reference": {"value": "event.vdl_action"}},
            "comparator": "EQUAL",
            "rhs": {"string_literal": {"value": "detail"}}
          }
        },
        {
          "comparison": {
            "lhs": {"path_reference": {"value": "event.product.price"}},
            "comparator": "GREATER_THAN_OR_EQUAL",
            "rhs": {"number_literal": {"value": "100"}}
          }
        }
      ]
    }
  },
  "unique_key_sources": [{"path_reference": {"value": "event.product_id"}}],
  "tracked_data": [
    {
      "key": {"key": "pdp"},
      "source_path": "event",
      "field_sub_paths": ["product_id", "ts"]
    }
  ],
  "max_age_seconds": 2419200
}
```

We now have a sequence that will detect **interest** in a product, but not **prolonged interest** (yet). Before moving onto the prolonged aspect, there are a couple of "have not" statements in our target audience (*have not added those products to cart since showing interest* and *have not made any purchase since showing interest*). You can satisfy these by adding a `disqualifier` continuation to our root sequence.

These criteria are stated as two separate things, but you can combine them into a single `event_filter` continuation. In sql-like syntax, we want to disqualify if any event matches the following filter:

```sql
(event.event_type = 'product' and event.vdl_action = 'add_to_cart' and event.product_id = pdp.product_id) or
(event.event_type = 'order' and event.vdl_action = 'purchase')
```

Note the reference to `pdp.product_id`. This is using the sequence's tracked data with key `pdp` to hone in on the specific product that spawned the current sequence instance.

So the translated `disqualifier` is:

```json
{
  "event_filter": {
    "combination": {
      "conjunction": "OR",
      "expressions": [
        {
          "combination": {
            "conjunction": "AND",
            "expressions": [
              {
                "comparison": {
                  "lhs": {"path_reference": {"value": "event.event_type"}},
                  "comparator": "EQUAL",
                  "rhs": {"string_literal": {"value": "product"}}
                }
              },
              {
                "comparison": {
                  "lhs": {"path_reference": {"value": "event.vdl_action"}},
                  "comparator": "EQUAL",
                  "rhs": {"string_literal": {"value": "add_to_cart"}}
                }
              },
              {
                "comparison": {
                  "lhs": {"path_reference": {"value": "event.product_id"}},
                  "comparator": "EQUAL",
                  "rhs": {"path_reference": {"value": "pdp.product_id"}}
                }
              }
            ]
          }
        },
        {
          "combination": {
            "conjunction": "AND",
            "expressions": [
              {
                "comparison": {
                  "lhs": {"path_reference": {"value": "event.event_type"}},
                  "comparator": "EQUAL",
                  "rhs": {"string_literal": {"value": "order"}}
                }
              },
              {
                "comparison": {
                  "lhs": {"path_reference": {"value": "event.vdl_action"}},
                  "comparator": "EQUAL",
                  "rhs": {"string_literal": {"value": "purchase"}}
                }
              }
            ]
          }
        }
      ]
    }
  }
}
```

And now we can move on to detecting **prolonged interest**. We have decided that the threshold here is performing the PDP on at least 3 different days. Something to consider here is the definition of day. In an ideal world, this would be based on the user's perspective. If that is not available, we could default to the shop owner's perspective (and our fictitious shop owner operates out of New York). With that in mind, we can compute the "day" of an event by [truncating](docId:7-qDl01uxXDPl5_jFVF1E) the timestamp to the day level in a timezone [coalesced](docId:7-qDl01uxXDPl5_jFVF1E) from the customer's timezone (which may not be known) and a default (America/New\_York).

With that in mind, we can detect prolonged interest by adding a `goal` continuation that is an `event_filter` looking for a PDP event with the same product but on a different day (a continuation implicitly happens after the parent's entry event, so a simple not-equal comparison of the days is sufficient).

```json
{
  "event_filter": {
    "combination": {
      "conjunction": "AND",
      "expressions": [
        {
          "comparison": {
            "lhs": {"path_reference": {"value": "event.event_type"}},
            "comparator": "EQUAL",
            "rhs": {"string_literal": {"value": "product"}}
          }
        },
        {
          "comparison": {
            "lhs": {"path_reference": {"value": "event.vdl_action"}},
            "comparator": "EQUAL",
            "rhs": {"string_literal": {"value": "detail"}}
          }
        },
        {
          "comparison": {
            "lhs": {"path_reference": {"value": "event.product_id"}},
            "comparator": "EQUAL",
            "rhs": {"path_reference": {"value": "pdp.product_id"}}
          }
        },
        {
          "comparison": {
            "lhs": {
              "truncate_time": {
                "source": {"path_reference": {"value": "event.ts"}},
                "unit": "DAY",
                "timezone": {
                  "coalesce": {
                    "sources": [
                      {"path_reference": {"value": "customer.timezone"}},
                      {"string_literal": {"value": "America/New_York"}}
                    ]
                  }
                }
              }
            },
            "comparator": "NOT_EQUAL",
            "rhs": {
              "truncate_time": {
                "source": {"path_reference": {"value": "pdp.ts"}},
                "unit": "DAY",
                "timezone": {
                  "coalesce": {
                    "sources": [
                      {"path_reference": {"value": "customer.timezone"}},
                      {"string_literal": {"value": "America/New_York"}}
                    ]
                  }
                }
              }
            }
          }
        }
      ]
    }
  }
}
```

### Extraction of products

With the audience fully in place, we now would like to know the high-value products in which a given user showed prolonged interest. For this, we need to add the `collections` list to the root of the segment definition. In our case, we only need one entry in this list. The products have already been captured via `tracked_data` keyed as `pdp`, but it also contains the timestamp of the event. We only care about the unique set of products, not the individual interactions with those products, so we can first narrow down to just the `product_id` field, and then request `unique` values.

```json
{
  "key": "high_interest_products",
  "source": {"key": "pdp"},
  "narrowed_sub_paths": ["product_id"],
  "unique": true
}
```

A GraphQL consumer can now access the `high_interest_products` via qualifications in the member audience, which also supports enrichment with additional product fields, including join-through fields from attached dimensions.
