segments
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSegments
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
resource: lists and gets, creates (no ) and updates
(with ). is
the authority on the body shape; this skill covers what that schema can't
tell you.
segmentsapi_readapi_writeididapi_details({ resource: "segments", operation: "create" })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 的通用工具访问 resource: 用于列出和获取, 用于创建(不带 )和更新(带 )。 是请求体结构的权威依据;本 skill 介绍的是该 schema 无法说明的内容。
segmentsapi_readapi_writeididapi_details({ resource: "segments", operation: "create" })需要始终记住的一点是:**保存 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": { … } } }methodoperationmetaquerybody.queryparams这里的参数结构比平时更重要,因为放错位置的 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": { … } } }选择 或 这类命名读取时,应使用 ,而不是 ;cube 查询必须放在 下,绝不能放在 中。
metaquerymethodoperationbody.queryparamsCreating 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
), each dimension's , which dimension has
, when the cube declares one, and the
cube's . Never guess a member name — they're per-organization.
<cube>.<member>typeprimaryKey: truemeta.nameDimensionjoinsPrefer 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(始终写为 )、每个 dimension 的 、哪个 dimension 带有 、cube 声明的 ,以及 cube 的 。绝不要猜测 member 名称——它们因组织而异。
<cube>.<member>typeprimaryKey: truemeta.nameDimensionjoins相比自动生成的管道型 cube,应优先选择具有业务含义的名称——除非用户明确要求使用某个辅助 cube,这类 cube 的描述通常稀疏或缺失,名称也较为机械。
2. Build cubes
— the root first, then one entry per other cube you filter on
cubes2. 构建 cubes
——先放 root,再为每个用于筛选的其他 cube 添加一个条目
cubesjson
"cubes": [
{ "joinPath": "crm_companies", "primaryKeyDimension": "id", "nameDimension": "name" },
{ "joinPath": "crm_companies.crm_deals" }
]- is the root. Its
cubes[0]is just the cube name. SetjoinPathto the root's primary-key dimension (the member name without the cube prefix) andprimaryKeyDimensiontonameDimensionwhen the cube declares one, otherwise to a dimension literally calledmeta.nameDimensionif the cube has it — table creation from a segment uses both to build itsnameandidcolumns. If the root has no primary-key dimension, say so: the segment will evaluate onname, which may not exist.id - Every other cube a condition references gets an entry whose is the dotted path from the root to it, following
joinPath:joins. Check each hop against a cube's"root.middle.target"— and remember a cube only lists joins it declares, so ifjoinsdoesn't listA, look atB's joins too before concluding there's no path.B - 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" }
]- 是 root。它的
cubes[0]就是 cube 名称。将joinPath设置为 root 的主键 dimension(不含 cube 前缀的 member 名称);如果 cube 声明了primaryKeyDimension,则将meta.nameDimension设置为它,否则,如果 cube 存在字面名称为nameDimension的 dimension,也可使用该名称——从 Segment 创建 table 时,会同时使用这两个值构建name和id列。如果 root 没有主键 dimension,应明确告知用户:Segment 将基于name求值,而该字段可能并不存在。id - 条件引用的每个其他 cube 都需要一个条目,其 是遵循
joinPath从 root 指向该 cube 的点分路径:joins。逐跳对照 cube 的"root.middle.target"进行检查——还要注意,一个 cube 只会列出它自己声明的 join,因此如果joins没有列出A,在断定不存在路径之前,也应查看B的 joins。B - 当同一个 cube 确实存在两条不同路径时,你选择的路径会改变结果。不要擅自选择——应询问用户指的是哪一种关系。
3. Write the filter — and watch for fan-out
3. 编写 filter
——并警惕 fan-out
filterfilterjson
"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 . Two rules come up constantly:
there is no multi-value "in" ( is one string or number, so "DE or
AT" is an OR group as above), and / only take ,
, or .
EQUALvalueISIS_NOTTODAYIN_THE_FUTUREIN_THE_PASTCheck the stored values before any / 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 (,
, ). A wrong value doesn't error — the segment just
comes out empty. One cheap query shows what's really there:
EQUALNOT_EQUALACTIVEEnterpriseclosedwonjson
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 entry, or the reverse, says
/), 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.
joinshasManyone_to_manyRephrase 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.countGREATER_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 (/) are fine
as dimension conditions.
hasOnebelongsTofilterjson
"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 中——在编写任何超出简单 的条件之前,应先阅读该文档。有两条规则尤其常见:不存在多值 ( 是一个字符串或数字,因此“DE 或 AT”应像上面一样使用 OR group);/ 只接受 、 或 。
EQUALinvalueISIS_NOTTODAYIN_THE_FUTUREIN_THE_PAST对字符串使用任何 / 前,都要检查已存储的值。
匹配是精确且区分大小写的,而用户的说法(“active”“enterprise”“won”)通常与数据源中的存储形式不同(、、)。值错误不会报错——Segment 只会得到空结果。只需进行一次简单查询,就能看到实际存在的值:
EQUALNOT_EQUALACTIVEEnterpriseclosedwonjson
api_read { "resource": "cubes", "method": "query",
"body": { "query": { "dimensions": ["billing_subscriptions.status"], "limit": 20 } } }使用已存储的拼写;如果没有任何值与用户的说法匹配,应询问用户,而不是猜测。
**Fan-out——一个能成功保存却给出错误答案的陷阱。**如果 root 拥有另一个 cube 的多条记录(root 的 条目或反向条目标记为 /),那么对该 cube 的 dimension 条件并不能可靠地表示“至少拥有一条此类记录的成员”——它会沿子记录将 root 扇出,UI 也正因如此拒绝保存。API 不会阻止你。
joinshasManyone_to_many应将条件改写为子 cube 上的 measure,它会按 root 行进行聚合:
- “至少有 3 笔商机的公司” →
{ "member": "crm_deals.count", "type": "GREATER_THAN_OR_EQUAL", "value": 3 } - “拥有任意一笔商机的公司” →
crm_deals.countGREATER_THAN。0
如果子 cube 没有能够表达该条件的 measure(例如“有一笔处于 won 阶段的商机”,但不存在 won 商机计数 measure),不要仍然编写会产生扇出的 dimension 条件。应明确告知用户,语义模型目前尚不支持从该 root 表达此条件,并提供可行的替代方案:以子 cube 为 root 的 Segment(赢单),或为该 cube 添加 measure。
对于 root 拥有 one 的 cube(/),使用 dimension 条件没有问题。
hasOnebelongsTo4. Choose type
— DYNAMIC unless the user insists
type4. 选择 type
——除非用户明确要求,否则使用 DYNAMIC
type- 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 until the next scheduled evaluation — say so, so the user isn't surprised by
status: "PENDING"being empty right after saving.objectCount - STATIC freezes the membership at evaluation time — but evaluating is
something the MCP tools can't trigger. A STATIC segment created here stays
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.
PENDING
type- DYNAMIC(默认使用):RevOS 每天会自动重新求值数次,因此它会跟随数据变化。新建或编辑后的 DYNAMIC Segment 会显示 ,直到下一次计划求值——应提前告知用户,以免其对保存后
status: "PENDING"为空感到意外。objectCount - STATIC 会在求值时冻结成员集合——但 MCP 工具无法触发求值。通过此处创建的 STATIC Segment 会一直保持 ,直到有人在 RevOS UI 中点击 Evaluate;在等待期间,使用该 Segment 限定范围的 table 会显示所有行,不进行筛选。只有当用户明确要求冻结快照时才应创建 STATIC Segment,并告知用户在依赖该 Segment 前,先在 UI 中执行 Evaluate。
PENDING
type5. Name it, write it, report back
5. 命名、写入并反馈结果
namedescriptionapi_read({ resource: "segments", params: { filter: "name == \"…\"" } })Then .
api_write({ resource: "segments", data: { name, description, type, cubes, filter } })Read it back with by the returned and compare its
and 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.
api_readidcubesfilterThen 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 until
evaluated. If you made a judgment call (a measure instead of a fanning
dimension, a particular join path), say which and why.
PENDINGnamedescriptionapi_read({ resource: "segments", params: { filter: "name == \"…\"" } })然后执行 。
api_write({ resource: "segments", data: { name, description, type, cubes, filter } })使用返回的 通过 将其读回,并将 和 与发送的内容进行比较。两者应该一致;如果不一致,说明写入没有按预期生效——应修正调用,而不是报告成功。
idapi_readcubesfilter然后用一两句话告诉用户:root(即一个成员代表什么)、用通俗语言表述的条件、type,以及 Segment 在求值前会保持 。如果你作出了判断性选择(例如用 measure 代替会产生扇出的 dimension,或选择特定 join 路径),应说明具体选择及其原因。
PENDINGEditing a segment
编辑 Segment
api_writeidfiltercubes-
fresh, right before writing.
api_read({ resource: "segments", id }) -
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. -
Start from the current(and
filter) and change only what the user asked for.cubes -
If the new condition references a cube that isn't inyet, add its
cubesentry and send the wholejoinPatharray too. If you removed the last condition on some cube, drop that entry.cubes -
Don't send— it isn't updatable — and don't try to change the root (
type); for a different root, create a new segment.cubes[0] -
After the write,the segment again and check the
api_readis the one you meant to save — a bumpedfilteralone doesn't prove the change landed.version
Every update bumps the segment's (the UI can restore older
versions — worth mentioning if the user is nervous about an edit). A STATIC
segment goes back to when its filter or cubes change.
versionPENDING使用 Segment 的 调用 ,并且只发送需要更改的字段。需要注意:每个发送的字段都会整体替换原有存储值。 和 都是单一字段——如果只发送包含一个新条件的 filter,所有未包含的条件都会消失。
idapi_writefiltercubes-
写入前立即重新执行,获取最新状态。
api_read({ resource: "segments", id }) -
**添加任何新值之前,先确认它的存储方式。**用户使用的说法(“初创计划”“已流失”“EMEA”)来自对话,而不是数据;现有条件只能证明其自身值的拼写方式。应查询新条件所在的 dimension:json
api_read { "resource": "cubes", "method": "query", "body": { "query": { "dimensions": ["crm_companies.plan"], "limit": 20 } } }使用已存储的拼写。如果没有任何存储值与用户的说法匹配,应询问用户,而不是写入一个永远无法匹配任何内容的条件。 -
以当前(以及
filter)为基础,只修改用户要求的部分。cubes -
如果新条件引用了尚未包含在中的 cube,应添加其
cubes条目,并同时发送完整的joinPath数组。如果删除了某个 cube 上的最后一个条件,则移除该条目。cubes -
不要发送——它不可更新;也不要尝试更改 root(
type);如果需要不同的 root,应创建新的 Segment。cubes[0] -
写入后,再次读取 Segment,并确认正是你想保存的内容——仅仅
filter增加并不能证明修改已生效。version
每次更新都会递增 Segment 的 (UI 可以恢复较旧的版本——如果用户对编辑感到不安,值得向他说明)。当 STATIC Segment 的 filter 或 cubes 发生变化时,它会重新变为 。
versionPENDINGScoping 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:
- — the list hangs off the table, so it's the table's id here. Take the entry with
api_read({ resource: "table-views", method: "list", id: "<TABLE id>" }).isDefault: true - .
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. 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 , warn that the table will
show every row until it's evaluated.
"segmentId": nullREADYTable 本身不携带 filter;它的 view 才有。要让 table 只显示某个 Segment 的成员,应将 table 的默认 view 指向该 Segment:
- ——该列表挂在 table 下,因此这里要使用 table 的 id。选取
api_read({ resource: "table-views", method: "list", id: "<TABLE id>" })的条目。isDefault: true - 。
api_write({ resource: "table-views", id: "<that VIEW's id>", data: { "segmentId": "<segment id>" } })
一个 view 只能持有 Segment 或自己的临时 filter,不能同时持有两者;在这两者中,附加 Segment 是更好的选择。 用于解除关联。应选择 root cube 与 table 行来源 cube(即其 main stream)一致的 Segment——从 Segment 创建 table 时,UI 正是通过这种方式将二者配对。如果 Segment 为 STATIC 且尚未处于 状态,应警告用户在完成求值前,table 将显示所有行。
"segmentId": nullREADYTroubleshooting a segment
排查 Segment
api_readstatus- — saved, not evaluated yet. Normal right after a create or edit; DYNAMIC catches up on its own schedule, STATIC needs Evaluate in the UI.
PENDING - — in progress; check back.
EVALUATING - —
FAILEDhas the reason, usually Cube's. The common causes, in order of likelihood: a member that doesn't exist (re-read the cubes'evaluationErrorand correct the name), ametahop 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.joinPath - 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.
READY
读取 Segment 并检查 :
status- ——已保存,但尚未求值。创建或编辑后出现此状态很正常;DYNAMIC 会按照自己的计划自动更新,STATIC 则需要用户在 UI 中点击 Evaluate。
PENDING - ——正在处理;稍后再检查。
EVALUATING - ——
FAILED中给出了原因,通常来自 Cube。常见原因按可能性排序如下:member 不存在(重新读取 cubes 的evaluationError并修正名称)、meta中某一跳不是真实 join,或者条件值的类型与 member 不匹配。按上述方式编辑并修复 Segment;它会在下次运行时重新求值。joinPath - ,但成员或数量错误——常见嫌疑包括会产生扇出的 dimension 条件(参见步骤 3)、root cube 与用户所说的“成员”不一致,或者 join path 选择了另一条路径。
READY
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 () if that's
the case.
segmentId: null这些工具刻意不支持删除 Segment。如果用户想删除某个 Segment,应引导他们前往 RevOS UI——同时说明,如果仍有 table view 使用该 Segment,UI 会拒绝删除;遇到这种情况,应先使用 解除关联。
segmentId: null