segments

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Segments

Segment

A segment is a saved audience: one row per record of a root cube, kept when it matches a filter. It's defined over the semantic model (cubes), not over a table — tables only borrow a segment to scope their rows.
You reach it through the RevOS MCP server's generic tools on the
segments
resource:
api_read
lists and gets,
api_write
creates (no
id
) and updates (with
id
).
api_details({ resource: "segments", operation: "create" })
is the authority on the body shape; this skill covers what that schema can't tell you.
The thing to keep in mind throughout: the server barely validates a segment when you save it. Member names, join paths, and whether a condition makes sense for the root are only checked by the RevOS UI's segment editor. Over the API a broken segment saves with a 200 and then fails, or worse, quietly matches the wrong records when it's evaluated. So the checks below are yours to do before writing — and reading the segment back after writing is how you find out what was actually saved.
Segment 是一种已保存的受众:根 cube 的每条记录对应一行,只有匹配 filter 时才会保留。它基于语义模型(cube)定义,而不是基于 table 定义——table 只是借用 Segment 来限定其行范围。
你通过 RevOS MCP server 的通用工具访问
segments
resource:
api_read
用于列出和获取,
api_write
用于创建(不带
id
)和更新(带
id
)。
api_details({ resource: "segments", operation: "create" })
是请求体结构的权威依据;本 skill 介绍的是该 schema 无法说明的内容。
需要始终记住的一点是:**保存 Segment 时,服务器几乎不会进行验证。**member 名称、join 路径,以及某个条件对 root 是否有意义,都只会由 RevOS UI 的 Segment 编辑器检查。通过 API 保存错误的 Segment 时,它可能返回 200,随后执行失败;更糟的是,在求值时悄悄匹配到错误的记录。因此,写入前的检查由你负责;而写入后重新读取 Segment,是确认实际保存结果的方式。

Calling the tools

调用工具

The argument shapes matter more than usual here, because a key in the wrong place is often ignored rather than rejected — a write with its body under the wrong key can return success, bump the segment's version, and change nothing.
json
// List / get / named reads — `params` for query-string filters, `body` for a named read that takes one
api_read  { "resource": "segments", "params": { "filter": "name == \"Enterprise accounts\"" } }
api_read  { "resource": "segments", "id": "<segment id>" }
api_read  { "resource": "cubes", "method": "meta", "params": { "filter": "name == \"crm_deals\"" } }
api_read  { "resource": "cubes", "method": "query",
            "body": { "query": { "dimensions": ["crm_deals.stage"], "limit": 20 } } }

// Create (no id) / update (with id) — the record always goes under `data`
api_write { "resource": "segments", "data": { "name": "…", "type": "DYNAMIC", "cubes": [ … ], "filter": { … } } }
api_write { "resource": "segments", "id": "<segment id>", "data": { "filter": { … } } }
method
(not
operation
) selects a named read like
meta
or
query
; a cubes query goes under
body.query
, never
params
.
这里的参数结构比平时更重要,因为放错位置的 key 往往会静默生效,而不是直接报错。某个写入请求若把 body 放在错误的 key 下,可能返回成功、递增 Segment 的 version,却什么也没有修改。
json
// List / get / named reads — `params` for query-string filters, `body` for a named read that takes one
api_read  { "resource": "segments", "params": { "filter": "name == \"Enterprise accounts\"" } }
api_read  { "resource": "segments", "id": "<segment id>" }
api_read  { "resource": "cubes", "method": "meta", "params": { "filter": "name == \"crm_deals\"" } }
api_read  { "resource": "cubes", "method": "query",
            "body": { "query": { "dimensions": ["crm_deals.stage"], "limit": 20 } } }

// Create (no id) / update (with id) — the record always goes under `data`
api_write { "resource": "segments", "data": { "name": "…", "type": "DYNAMIC", "cubes": [ … ], "filter": { … } } }
api_write { "resource": "segments", "id": "<segment id>", "data": { "filter": { … } } }
选择
meta
或
query
这类命名读取时,应使用
method
,而不是
operation
;cube 查询必须放在
body.query
下,绝不能放在
params
中。

Creating a segment

创建 Segment

1. Pick the root cube — it decides what one "member" is

1. 选择根 cube——它决定一个“成员”是什么

"Companies that have a won deal" has companies as members; "won deals" has deals as members. Same data, different audience. Choose the cube whose rows are the things the user wants to end up with, and confirm with the user when it's genuinely ambiguous — the root can't be changed after creation (doing that means making a new segment).
Read the catalogue first, then the root's full definition:
json
{ "resource": "cubes", "method": "meta", "params": { "fields": "name,title,description", "pageSize": 100 } }
{ "resource": "cubes", "method": "meta", "params": { "filter": "name == \"crm_companies\"" } }
The full definition gives you the members (always written
<cube>.<member>
), each dimension's
type
, which dimension has
primaryKey: true
,
meta.nameDimension
when the cube declares one, and the cube's
joins
. Never guess a member name — they're per-organization.
Prefer business-named cubes over generated plumbing — helper cubes with a sparse or missing description and a mechanical-looking name — unless the user is clearly asking about one of those.
“有赢单的公司”以公司为成员;“赢单”则以商机为成员。数据相同,受众不同。选择其行代表用户最终想要获得的实体的 cube;如果确实存在歧义,应向用户确认——root 创建后无法更改(要更改就必须新建 Segment)。
先读取 catalogue,然后读取 root 的完整定义:
json
{ "resource": "cubes", "method": "meta", "params": { "fields": "name,title,description", "pageSize": 100 } }
{ "resource": "cubes", "method": "meta", "params": { "filter": "name == \"crm_companies\"" } }
完整定义会给出 member(始终写为
<cube>.<member>
)、每个 dimension 的
type
、哪个 dimension 带有
primaryKey: true
、cube 声明的
meta.nameDimension
,以及 cube 的
joins
。绝不要猜测 member 名称——它们因组织而异。
相比自动生成的管道型 cube,应优先选择具有业务含义的名称——除非用户明确要求使用某个辅助 cube,这类 cube 的描述通常稀疏或缺失,名称也较为机械。

2. Build
cubes
— the root first, then one entry per other cube you filter on

2. 构建
cubes
——先放 root,再为每个用于筛选的其他 cube 添加一个条目

json
"cubes": [
  { "joinPath": "crm_companies", "primaryKeyDimension": "id", "nameDimension": "name" },
  { "joinPath": "crm_companies.crm_deals" }
]
  • cubes[0]
    is the root. Its
    joinPath
    is just the cube name. Set
    primaryKeyDimension
    to the root's primary-key dimension (the member name without the cube prefix) and
    nameDimension
    to
    meta.nameDimension
    when the cube declares one, otherwise to a dimension literally called
    name
    if the cube has it — table creation from a segment uses both to build its
    id
    and
    name
    columns. If the root has no primary-key dimension, say so: the segment will evaluate on
    id
    , which may not exist.
  • Every other cube a condition references gets an entry whose
    joinPath
    is the dotted path from the root to it, following
    joins
    :
    "root.middle.target"
    . Check each hop against a cube's
    joins
    — and remember a cube only lists joins it declares, so if
    A
    doesn't list
    B
    , look at
    B
    's joins too before concluding there's no path.
  • When there are two genuinely different routes to the same cube, the path you pick changes the answer. Don't silently choose — ask which relationship the user means.
json
"cubes": [
  { "joinPath": "crm_companies", "primaryKeyDimension": "id", "nameDimension": "name" },
  { "joinPath": "crm_companies.crm_deals" }
]
  • cubes[0]
    是 root。它的
    joinPath
    就是 cube 名称。将
    primaryKeyDimension
    设置为 root 的主键 dimension(不含 cube 前缀的 member 名称);如果 cube 声明了
    meta.nameDimension
    ,则将
    nameDimension
    设置为它,否则,如果 cube 存在字面名称为
    name
    的 dimension,也可使用该名称——从 Segment 创建 table 时,会同时使用这两个值构建
    id
    和
    name
    列。如果 root 没有主键 dimension,应明确告知用户:Segment 将基于
    id
    求值,而该字段可能并不存在。
  • 条件引用的每个其他 cube 都需要一个条目,其
    joinPath
    是遵循
    joins
    从 root 指向该 cube 的点分路径:
    "root.middle.target"
    。逐跳对照 cube 的
    joins
    进行检查——还要注意,一个 cube 只会列出它自己声明的 join,因此如果
    A
    没有列出
    B
    ,在断定不存在路径之前,也应查看
    B
    的 joins。
  • 当同一个 cube 确实存在两条不同路径时,你选择的路径会改变结果。不要擅自选择——应询问用户指的是哪一种关系。

3. Write the filter — and watch for fan-out

3. 编写
filter
——并警惕 fan-out

filter
is RevOS's own condition format (not a Cube.js query):
json
"filter": {
  "combinationMode": "AND",
  "items": [
    { "member": "crm_companies.industry", "type": "EQUAL", "value": "Software" },
    { "filterType": "Group", "combinationMode": "OR", "items": [
        { "member": "crm_companies.country", "type": "EQUAL", "value": "DE" },
        { "member": "crm_companies.country", "type": "EQUAL", "value": "AT" } ] }
  ]
}
The operator vocabulary, which operators fit which member types, date values, and the validation rules the JSON schema doesn't show are in references/filter-syntax.md — read it before writing any condition beyond a simple
EQUAL
. Two rules come up constantly: there is no multi-value "in" (
value
is one string or number, so "DE or AT" is an OR group as above), and
IS
/
IS_NOT
only take
TODAY
,
IN_THE_FUTURE
, or
IN_THE_PAST
.
Check the stored values before any
EQUAL
/
NOT_EQUAL
on a string.
Matching is exact and case-sensitive, and the user's words ("active", "enterprise", "won") are often not how the source stores them (
ACTIVE
,
Enterprise
,
closedwon
). A wrong value doesn't error — the segment just comes out empty. One cheap query shows what's really there:
json
api_read { "resource": "cubes", "method": "query",
           "body": { "query": { "dimensions": ["billing_subscriptions.status"], "limit": 20 } } }
Use the stored spelling, and if nothing matches the user's term, ask rather than guess.
Fan-out — the trap that saves fine and answers wrong. If the root has many of another cube (the root's
joins
entry, or the reverse, says
hasMany
/
one_to_many
), a dimension condition on that cube doesn't mean "members that have at least one such row" in any reliable way — it fans the root out across the child rows and the UI refuses to save it for exactly this reason. The API won't stop you.
Rephrase the condition as a measure on the child cube instead, which aggregates per root row:
  • "Companies with at least 3 deals" →
    { "member": "crm_deals.count", "type": "GREATER_THAN_OR_EQUAL", "value": 3 }
  • "Companies with any deal" →
    crm_deals.count
    GREATER_THAN
    0
    .
If the child cube has no measure that expresses the condition (e.g. "has a deal in stage won" and there's no count-of-won-deals measure), don't write the fanning dimension condition anyway. Tell the user plainly that the semantic model doesn't support that condition from this root yet, and offer what it does support — a segment rooted on the child cube instead (won deals), or adding a measure to the cube.
Conditions on a cube the root has one of (
hasOne
/
belongsTo
) are fine as dimension conditions.
filter
使用 RevOS 自身的条件格式(不是 Cube.js query):
json
"filter": {
  "combinationMode": "AND",
  "items": [
    { "member": "crm_companies.industry", "type": "EQUAL", "value": "Software" },
    { "filterType": "Group", "combinationMode": "OR", "items": [
      { "member": "crm_companies.country", "type": "EQUAL", "value": "DE" },
      { "member": "crm_companies.country", "type": "EQUAL", "value": "AT" } ] }
  ]
}
操作符词汇表、哪些操作符适用于哪些 member 类型、日期值,以及 JSON schema 未显示的校验规则,都记录在 references/filter-syntax.md 中——在编写任何超出简单
EQUAL
的条件之前,应先阅读该文档。有两条规则尤其常见:不存在多值
in
(
value
是一个字符串或数字,因此“DE 或 AT”应像上面一样使用 OR group);
IS
/
IS_NOT
只接受
TODAY
、
IN_THE_FUTURE
或
IN_THE_PAST
。
对字符串使用任何
EQUAL
/
NOT_EQUAL
前,都要检查已存储的值。
匹配是精确且区分大小写的,而用户的说法(“active”“enterprise”“won”)通常与数据源中的存储形式不同(
ACTIVE
、
Enterprise
、
closedwon
)。值错误不会报错——Segment 只会得到空结果。只需进行一次简单查询,就能看到实际存在的值:
json
api_read { "resource": "cubes", "method": "query",
           "body": { "query": { "dimensions": ["billing_subscriptions.status"], "limit": 20 } } }
使用已存储的拼写;如果没有任何值与用户的说法匹配,应询问用户,而不是猜测。
**Fan-out——一个能成功保存却给出错误答案的陷阱。**如果 root 拥有另一个 cube 的多条记录(root 的
joins
条目或反向条目标记为
hasMany
/
one_to_many
),那么对该 cube 的 dimension 条件并不能可靠地表示“至少拥有一条此类记录的成员”——它会沿子记录将 root 扇出,UI 也正因如此拒绝保存。API 不会阻止你。
应将条件改写为子 cube 上的 measure,它会按 root 行进行聚合:
  • “至少有 3 笔商机的公司” →
    { "member": "crm_deals.count", "type": "GREATER_THAN_OR_EQUAL", "value": 3 }
  • “拥有任意一笔商机的公司” →
    crm_deals.count
    GREATER_THAN
    0
    。
如果子 cube 没有能够表达该条件的 measure(例如“有一笔处于 won 阶段的商机”,但不存在 won 商机计数 measure),不要仍然编写会产生扇出的 dimension 条件。应明确告知用户,语义模型目前尚不支持从该 root 表达此条件,并提供可行的替代方案:以子 cube 为 root 的 Segment(赢单),或为该 cube 添加 measure。
对于 root 拥有 one 的 cube(
hasOne
/
belongsTo
),使用 dimension 条件没有问题。

4. Choose
type
— DYNAMIC unless the user insists

4. 选择
type
——除非用户明确要求,否则使用 DYNAMIC

  • DYNAMIC (use this by default): re-evaluated automatically by RevOS a few times a day, so it follows the data. A new or edited DYNAMIC segment shows
    status: "PENDING"
    until the next scheduled evaluation — say so, so the user isn't surprised by
    objectCount
    being empty right after saving.
  • STATIC freezes the membership at evaluation time — but evaluating is something the MCP tools can't trigger. A STATIC segment created here stays
    PENDING
    until someone presses Evaluate in the RevOS UI, and while it's pending a table scoped to it shows all rows, unfiltered. Only create one when the user explicitly asks for a frozen snapshot, and tell them to run Evaluate in the UI before relying on it.
type
can't be changed after creation.
  • DYNAMIC(默认使用):RevOS 每天会自动重新求值数次,因此它会跟随数据变化。新建或编辑后的 DYNAMIC Segment 会显示
    status: "PENDING"
    ,直到下一次计划求值——应提前告知用户,以免其对保存后
    objectCount
    为空感到意外。
  • STATIC 会在求值时冻结成员集合——但 MCP 工具无法触发求值。通过此处创建的 STATIC Segment 会一直保持
    PENDING
    ,直到有人在 RevOS UI 中点击 Evaluate;在等待期间,使用该 Segment 限定范围的 table 会显示所有行,不进行筛选。只有当用户明确要求冻结快照时才应创建 STATIC Segment,并告知用户在依赖该 Segment 前,先在 UI 中执行 Evaluate。
type
创建后无法更改。

5. Name it, write it, report back

5. 命名、写入并反馈结果

name
is required (1–255 chars); add a one-line
description
of the audience in plain words. Before writing, check for a namesake —
api_read({ resource: "segments", params: { filter: "name == \"…\"" } })
— since segments can't be deleted from here, and a duplicate is clutter the user has to clean up in the UI.
Then
api_write({ resource: "segments", data: { name, description, type, cubes, filter } })
.
Read it back with
api_read
by the returned
id
and compare its
cubes
and
filter
to what you sent. They should match; if they don't, the write didn't land the way you intended — fix the call rather than reporting success.
Then tell the user, in a sentence or two: the root (what one member is), the conditions in plain language, the type, and that it's
PENDING
until evaluated. If you made a judgment call (a measure instead of a fanning dimension, a particular join path), say which and why.
name
为必填项(1–255 个字符);应使用通俗易懂的语言,为该受众添加一行
description
。写入前应检查是否存在同名 Segment——
api_read({ resource: "segments", params: { filter: "name == \"…\"" } })
——因为此处无法删除 Segment,而重复项只会成为需要用户在 UI 中清理的冗余数据。
然后执行
api_write({ resource: "segments", data: { name, description, type, cubes, filter } })
。
使用返回的
id
通过
api_read
将其读回
,并将
cubes
和
filter
与发送的内容进行比较。两者应该一致;如果不一致,说明写入没有按预期生效——应修正调用,而不是报告成功。
然后用一两句话告诉用户:root(即一个成员代表什么)、用通俗语言表述的条件、type,以及 Segment 在求值前会保持
PENDING
。如果你作出了判断性选择(例如用 measure 代替会产生扇出的 dimension,或选择特定 join 路径),应说明具体选择及其原因。

Editing a segment

编辑 Segment

api_write
with the segment's
id
and only the fields that change. The catch: each field you send replaces the stored one whole.
filter
and
cubes
are single fields — send a filter with one new condition and every condition you left out is gone.
  1. api_read({ resource: "segments", id })
    fresh, right before writing.
  2. Look up how any new value is stored before you add it. The user's word for it ("startup plan", "churned", "EMEA") came from conversation, not from the data, and the existing conditions only prove how their values are spelled. Query the dimension the new condition is on:
    json
    api_read { "resource": "cubes", "method": "query",
               "body": { "query": { "dimensions": ["crm_companies.plan"], "limit": 20 } } }
    Use the stored spelling. If no stored value matches what the user said, ask instead of writing a condition that matches nothing.
  3. Start from the current
    filter
    (and
    cubes
    ) and change only what the user asked for.
  4. If the new condition references a cube that isn't in
    cubes
    yet, add its
    joinPath
    entry and send the whole
    cubes
    array too. If you removed the last condition on some cube, drop that entry.
  5. Don't send
    type
    — it isn't updatable — and don't try to change the root (
    cubes[0]
    ); for a different root, create a new segment.
  6. After the write,
    api_read
    the segment again and check the
    filter
    is the one you meant to save — a bumped
    version
    alone doesn't prove the change landed.
Every update bumps the segment's
version
(the UI can restore older versions — worth mentioning if the user is nervous about an edit). A STATIC segment goes back to
PENDING
when its filter or cubes change.
使用 Segment 的
id
调用
api_write
,并且只发送需要更改的字段。需要注意:每个发送的字段都会整体替换原有存储值。
filter
和
cubes
都是单一字段——如果只发送包含一个新条件的 filter,所有未包含的条件都会消失。
  1. 写入前立即重新执行
    api_read({ resource: "segments", id })
    ,获取最新状态。
  2. **添加任何新值之前,先确认它的存储方式。**用户使用的说法(“初创计划”“已流失”“EMEA”)来自对话,而不是数据;现有条件只能证明其自身值的拼写方式。应查询新条件所在的 dimension:
    json
    api_read { "resource": "cubes", "method": "query",
               "body": { "query": { "dimensions": ["crm_companies.plan"], "limit": 20 } } }
    使用已存储的拼写。如果没有任何存储值与用户的说法匹配,应询问用户,而不是写入一个永远无法匹配任何内容的条件。
  3. 以当前
    filter
    (以及
    cubes
    )为基础,只修改用户要求的部分。
  4. 如果新条件引用了尚未包含在
    cubes
    中的 cube,应添加其
    joinPath
    条目,并同时发送完整的
    cubes
    数组。如果删除了某个 cube 上的最后一个条件,则移除该条目。
  5. 不要发送
    type
    ——它不可更新;也不要尝试更改 root(
    cubes[0]
    );如果需要不同的 root,应创建新的 Segment。
  6. 写入后,再次读取 Segment,并确认
    filter
    正是你想保存的内容——仅仅
    version
    增加并不能证明修改已生效。
每次更新都会递增 Segment 的
version
(UI 可以恢复较旧的版本——如果用户对编辑感到不安,值得向他说明)。当 STATIC Segment 的 filter 或 cubes 发生变化时,它会重新变为
PENDING
。

Scoping a table's rows with a segment

使用 Segment 限定 table 的行

Tables don't carry filters; their view does. To make a table show only a segment's members, point the table's default view at the segment:
  1. api_read({ resource: "table-views", method: "list", id: "<TABLE id>" })
    — the list hangs off the table, so it's the table's id here. Take the entry with
    isDefault: true
    .
  2. api_write({ resource: "table-views", id: "<that VIEW's id>", data: { "segmentId": "<segment id>" } })
    .
A view holds either a segment or its own ad-hoc filter, never both; attaching a segment is the better of the two.
"segmentId": null
detaches it. Use a segment whose root cube is the cube the table's rows come from (its main stream) — that's how the UI pairs them when it builds a table from a segment. If the segment is STATIC and not
READY
, warn that the table will show every row until it's evaluated.
Table 本身不携带 filter;它的 view 才有。要让 table 只显示某个 Segment 的成员,应将 table 的默认 view 指向该 Segment:
  1. api_read({ resource: "table-views", method: "list", id: "<TABLE id>" })
    ——该列表挂在 table 下,因此这里要使用 table 的 id。选取
    isDefault: true
    的条目。
  2. api_write({ resource: "table-views", id: "<that VIEW's id>", data: { "segmentId": "<segment id>" } })
    。
一个 view 只能持有 Segment 或自己的临时 filter,不能同时持有两者;在这两者中,附加 Segment 是更好的选择。
"segmentId": null
用于解除关联。应选择 root cube 与 table 行来源 cube(即其 main stream)一致的 Segment——从 Segment 创建 table 时,UI 正是通过这种方式将二者配对。如果 Segment 为 STATIC 且尚未处于
READY
状态,应警告用户在完成求值前,table 将显示所有行。

Troubleshooting a segment

排查 Segment

api_read
the segment and look at
status
:
  • PENDING
    — saved, not evaluated yet. Normal right after a create or edit; DYNAMIC catches up on its own schedule, STATIC needs Evaluate in the UI.
  • EVALUATING
    — in progress; check back.
  • FAILED
    —
    evaluationError
    has the reason, usually Cube's. The common causes, in order of likelihood: a member that doesn't exist (re-read the cubes'
    meta
    and correct the name), a
    joinPath
    hop that isn't a real join, or a condition value of the wrong kind for its member. Fix the segment with an edit as above; it'll be re-evaluated on the next run.
  • READY
    but the wrong members or count — the usual suspects are a fanning dimension condition (see step 3), a root cube that doesn't match what the user meant by "member", or a join path that took the other route.
读取 Segment 并检查
status
:
  • PENDING
    ——已保存,但尚未求值。创建或编辑后出现此状态很正常;DYNAMIC 会按照自己的计划自动更新,STATIC 则需要用户在 UI 中点击 Evaluate。
  • EVALUATING
    ——正在处理;稍后再检查。
  • FAILED
    ——
    evaluationError
    中给出了原因,通常来自 Cube。常见原因按可能性排序如下:member 不存在(重新读取 cubes 的
    meta
    并修正名称)、
    joinPath
    中某一跳不是真实 join,或者条件值的类型与 member 不匹配。按上述方式编辑并修复 Segment;它会在下次运行时重新求值。
  • READY
    ,但成员或数量错误——常见嫌疑包括会产生扇出的 dimension 条件(参见步骤 3)、root cube 与用户所说的“成员”不一致,或者 join path 选择了另一条路径。

Deleting

删除

Segments can't be deleted through these tools, on purpose. If the user wants one gone, point them to the RevOS UI — and note that the UI will refuse while a table view still uses it, so detach it first (
segmentId: null
) if that's the case.
这些工具刻意不支持删除 Segment。如果用户想删除某个 Segment,应引导他们前往 RevOS UI——同时说明,如果仍有 table view 使用该 Segment,UI 会拒绝删除;遇到这种情况,应先使用
segmentId: null
解除关联。