关于 segments
一种通过 RevOS MCP 服务创建、编辑和应用受众分群的技能。由于 API 写入可能不验证分群逻辑,它强调检查立方体元数据、存储值、关联路径和写入结果。
主要功能
- 01检查立方体元数据和关联
- 02构建受众筛选条件
- 03设置条件前核对存储值
- 04避免一对多关联导致的扩增
- 05创建和更新分群
- 06回读写入结果以验证
适用场景
- 01基于语义模型数据创建受众
- 02编辑分群条件并保留其他条件
- 03将表视图限定为某个分群
MCP 相关
通过 RevOS MCP 服务在 segments 资源上调用通用工具:api_read 用于列出和读取分群,api_write 用于创建和更新分群。立方体元数据和查询也通过这些工具访问。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ revosai/ skills --skill segments安装后在 Claude Code 中输入/segments确认已加载。
附带文件evals/evals.jsonreferences/filter-syntax.md
详细介绍
分群
分群是已保存的受众:根立方体中的每条记录占一行,并保留符合筛选条件的记录。分群基于语义模型(立方体)定义,而非基于表定义;表只是借用分群来限定其行。
通过 RevOS MCP 服务在 segments 资源上的通用工具访问分群:api_read 用于列出和获取分群,api_write 用于创建(不带 id)和更新(带 id)分群。api_details({ resource: "segments", operation: "create" }) 是请求体结构的权威说明;本指南介绍的是该架构无法说明的内容。
请始终牢记:**保存分群时,服务几乎不会对其进行验证。**成员名称、联接路径以及条件对于根立方体是否合理,都只有 RevOS UI 的分群编辑器会检查。一旦分群附加到表的默认视图,其条件就会编译进该表的视图中,因此错误的成员可能会破坏整个组织的模型,而不只是分群。 通过 API 保存有问题的分群时,接口会返回 200,但之后执行会失败;更糟的是,评估时可能会悄悄匹配到错误的记录。因此,写入前需要自行完成以下检查;写入后重新读取分群,则能确认实际保存的内容。
调用工具
这里的参数结构比平时更重要,因为键放错位置时通常会被忽略,而不是拒绝——请求体放在错误键下的写入操作可能会返回成功、增加分群的版本号,却什么也没改。
// 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(而非 operation)用于选择 meta 或 query 等具名读取操作;立方体查询应放在 body.query 下,而不是 params 中。
创建分群
1. 选择根立方体——它决定一个“成员”代表什么
“拥有已赢单商机的公司”以公司为成员;“已赢单的商机”则以商机为成员。数据相同,受众不同。选择其行代表用户最终想要获得的对象的立方体;如果确实有歧义,请向用户确认。创建后不要更改根立方体——API 不会阻止你,但所有基于该分群构建的内容都默认它不变;应改为创建新分群。
先读取目录,再读取根立方体的完整定义:
{ "resource": "cubes", "method": "meta", "params": { "fields": "name,title,description", "pageSize": 100 } }
{ "resource": "cubes", "method": "meta", "params": { "filter": "name == \"crm_companies\"" } }
完整定义会列出成员(始终写作 <cube>.<member>)、每个维度的 type、哪个维度具有 primaryKey: true、立方体声明了 meta.nameDimension 时对应的值,以及立方体的 joins。切勿猜测成员名称——它们因组织而异。
优先选择名称贴近业务的立方体,而非生成的底层辅助立方体(描述稀少或缺失、名称看起来像机器生成的立方体),除非用户明确询问其中某个辅助立方体。
2. 构建 cubes——根立方体放在最前面,然后为你要筛选的其他立方体各添加一项
"cubes": [
{ "joinPath": "crm_companies", "primaryKeyDimension": "id", "nameDimension": "name" },
{ "joinPath": "crm_companies.crm_deals" }
]
cubes[0]是根立方体。它的joinPath就是立方体名称。将primaryKeyDimension设为根立方体的主键维度(不带立方体前缀的成员名称);如果立方体声明了meta.nameDimension,则将nameDimension设为该值,否则若立方体有名为name的维度,就设为该维度——从分群创建表时,会用这两个维度生成id和name列。如果根立方体没有主键维度,请明确说明:分群会根据id进行评估,而该字段可能不存在。- 条件引用的其他立方体,都要添加一项,其
joinPath为从根立方体到该立方体、沿joins前进的点分隔路径:"root.middle.target"。逐段根据立方体的joins进行核对——并且要记住,立方体只会列出它自己声明的联接;如果A没有列出B,在断定不存在路径之前,也要查看B的joins。 - 如果到达同一立方体的路径确实有两条不同路线,那么你选择的路径会改变结果。不要擅自选择——应询问用户指的是哪种关系。
3. 编写筛选条件——注意扇出
filter 是 RevOS 自己的条件格式(不是 Cube.js 查询):
"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" } ] }
]
}
运算符词汇、适用于不同成员类型的运算符、日期值,以及 JSON schema 未展示的验证规则,都在 references/filter-syntax.md 中——编写简单的 EQUAL 以外的条件前,请先阅读。以下两条规则经常用到:没有多值“in”(value 只能是一个字符串或数字,因此“DE 或 AT”应像上面那样写成 OR 组),并且 IS/IS_NOT 只能接受 TODAY、IN_THE_FUTURE 或 IN_THE_PAST。
对字符串使用 EQUAL / NOT_EQUAL 前,先检查存储的值。 匹配是精确且区分大小写的,而用户的用词(“active”、“enterprise”、“won”)往往与源数据中的存储形式不同(ACTIVE、Enterprise、closedwon)。值写错不会报错——只是筛选组结果为空。下面这个简单查询就能查看实际存储的值:
api_read { "resource": "cubes", "method": "query",
"body": { "query": { "dimensions": ["billing_subscriptions.status"], "limit": 20 } } }
使用存储值的原样拼写;如果没有值与用户的说法匹配,请先询问,不要猜测。
扇出——这种情况可以顺利保存,却会给出错误答案。 如果根 Cube 对另一个 Cube 的关系是 many,那么针对该 Cube 的维度条件,并不能可靠地表示“至少有一条这样的记录的成员”——它会让根 Cube 的记录按子记录展开,界面也正是因此拒绝保存。API 不会阻止你这样做。关联可以定义在任一侧,因此请同时查看两个 Cube 的 joins(Cube 接受 snake_case 和 camelCase):
| 找到的关联 | 根 Cube 对应的关系…… |
|---|---|
root → other:many_to_one/belongsTo 或 one_to_one/hasOne | one |
other → root:one_to_many/hasMany 或 one_to_one/hasOne | one |
root → other:one_to_many/hasMany | many |
other → root:many_to_one/belongsTo(例如 crm_deals → crm_companies) | many |
改用子 Cube 上的度量来表达条件,这样就会按根记录进行聚合:
- “至少有 3 笔交易的公司” →
{ "member": "crm_deals.count", "type": "GREATER_THAN_OR_EQUAL", "value": 3 } - “有任意交易的公司” →
crm_deals.countGREATER_THAN0。
如果子 Cube 没有能表达该条件的度量(例如,“有一笔处于 won 阶段的交易”,但又没有“已赢交易数”度量),就不要仍然编写会造成扇出的维度条件。请明确告知用户,语义模型目前不支持从这个根 Cube 表达该条件,并提供模型当前支持的方案——以子 Cube 为根创建筛选组(已赢交易),或向 Cube 添加度量。
对于根 Cube 关联为 one 的 Cube,其条件可以使用维度条件。
4. 选择 type——默认使用 DYNAMIC,除非用户另有要求
- DYNAMIC(默认使用):RevOS 每天自动重新评估几次(每 6 小时一次),因此会随数据变化。新的 DYNAMIC 筛选组在下次计划评估前会显示
status: "PENDING"——请告知用户这一点,以免他们保存后立即看到objectCount为空而感到意外。编辑已有筛选组时,其旧的状态和objectCount会保留到下次评估,不过使用该筛选组的表格会立即应用新筛选条件。 - STATIC 会在评估时冻结成员范围——但 MCP 工具无法触发评估。在此创建的 STATIC 筛选组会一直处于
PENDING,直到有人在 RevOS 界面中点击 Evaluate;处于待处理状态时,使用该筛选组的表格会显示所有行,不会应用筛选。只有当用户明确要求冻结快照时才创建,并告知他们在依赖该筛选组之前,先在界面中运行 Evaluate。
创建后无法更改 type。
5. 命名、编写并反馈
name 为必填项(1–255 个字符);用通俗的语言添加一行 description,说明目标受众。编写前,先检查是否有同名项 —
api_read({ resource: "segments", params: { filter: "name == \"…\"" } })。
如果存在,请先询问用户是否指的是那个 segment,再创建第二个。
然后调用 api_write({ resource: "segments", data: { name, description, type, cubes, filter } })。
回读时,用返回的 id 调用 api_read,并将其 cubes 和 filter 与发送的内容进行比较。两者应当匹配;如果不匹配,说明写入结果并非预期 — 请修正调用,不要报告成功。
然后用一两句话告诉用户:根(一个成员代表什么)、用通俗语言描述的条件、类型,以及在评估前状态为 PENDING。如果你做出了判断(例如使用度量而非会扩散的维度,或选择了特定的连接路径),请说明选择了什么以及原因。
编辑 segment
使用 segment 的 id 和仅包含变更字段的内容调用 api_write。注意:你发送的每个字段都会整体替换已存储的字段。 filter 和 cubes 是单个字段 — 如果你发送的 filter 只有一个新条件,所有未包含的条件都会被删除。
-
在写入前立即调用
api_read({ resource: "segments", id })获取最新数据。 -
添加任何新值之前,先查明它的存储形式。用户提到的词(“startup plan”、“churned”、“EMEA”)来自对话,而非数据;现有条件只能证明其中的值是如何拼写的。查询新条件所针对的维度:
api_read { "resource": "cubes", "method": "query", "body": { "query": { "dimensions": ["crm_companies.plan"], "limit": 20 } } }使用数据中存储的拼写。如果没有与用户所说内容匹配的已存储值,请先询问,不要写入一个无法匹配任何内容的条件。
-
以当前的
filter(和cubes)为基础,只修改用户要求的内容。 -
如果新条件引用的 cube 尚未包含在
cubes中,请添加其joinPath条目,并一并发送完整的cubes数组。如果移除了某个 cube 上的最后一个条件,也要删除该条目。 -
不要发送
type— 它不可更新;也不要更改根(cubes[0]):API 虽然接受,但基于该 segment 构建的每个表和卡片都依赖原来的根。若要使用不同的根,请创建新的 segment。 -
写入后,再次调用
api_read读取该 segment,并检查filter是否为你要保存的内容 — 仅凭version增加并不能证明修改已生效。
每次更新都会增加 segment 的 version(用户若担心编辑,可以提及 UI 能恢复旧版本)。如果 STATIC segment 的 filter 或 cubes 发生变化,它会恢复为 PENDING,此时所有受其限制的表都会显示所有行,直到有人在 UI 中点击 Evaluate — 此类编辑前请提醒用户。如果该 segment 限制了某个表的默认视图,请检查编辑后 meta 是否仍能编译。
使用 segment 限制表的行
表本身不携带筛选条件;筛选条件属于其视图。要让表仅显示某个 segment 的成员,请将该 segment 指定为表的默认视图:
api_read({ resource: "table-views", method: "list", id: "<TABLE id>" })— 列表挂在表上,因此这里使用表的id。选择isDefault: true的条目。api_write({ resource: "table-views", id: "<that VIEW's id>", data: { "segmentId": "<segment id>" } })。
附加的 segment 会覆盖视图自身的临时筛选条件(该条件会保留,并在解除关联后恢复)— 如果视图已有筛选条件,请在设置 segmentId 时一并发送 "filter": {}。设置 "segmentId": null 可解除关联。使用根 cube 与表的行来源 cube(即主流)相同的 segment — UI 会据此配对,服务端不会检查。如果该 segment 为 STATIC 且状态不是 READY,请提醒用户表会显示所有行,直到完成评估。然后检查 meta 是否仍能编译:segment 的条件和连接现在已成为表视图的一部分。
排查细分
读取细分的 api_read 并查看 status:
PENDING— 已保存,尚未评估。创建或编辑后立即出现这种状态很正常; DYNAMIC 会按自身计划自动更新,STATIC 则需要在 UI 中点击 Evaluate。EVALUATING— 正在处理;稍后再检查。FAILED—evaluationError中包含原因,通常是 Cube 的问题。常见原因按可能性从高到低排列:成员不存在(重新读取各个 cube 的meta并更正名称),或条件值的类型与对应成员不匹配。按上述方式编辑细分以修复;它会在下次运行时重新评估。READY,但成员或数量不对 — 常见原因包括扇出维度条件(见步骤 3)、根 cube 与用户所说的“成员”含义不符,或连接路径选了另一条路线;也可能根本不存在真实连接:这种情况下,评估会静默丢弃细分的连接路径,改为沿 Cube 自身的路径计数,而不是报错。
删除
api_delete({ resource: "segments", id }) 会永久删除细分;如果是 STATIC 细分,也会删除其成员快照。无法撤销。调用前:
-
读取细分并复述其名称、类型、根 cube、条件和成员数量。
-
STATIC 细分的 cube。 首先检查这一项:它可能导致无法删除。STATIC 细分会编译为一个 cube:
segment_<id, - → _>。在以下位置查找该名称:- 表的
streams、ids和joinPath(fields: "id,name,streams"):从该细分开始的表以它的 cube 为根; - 其他细分的
cubes和filter(完整记录); - 已存储 cube 的
joins(对cubes执行api_read,使用fields: "name,definition")。
删除该细分后,以上任何内容都会停止编译,整个组织的模型也会因此无法使用。停止操作并报告发现的内容;在本次删除操作中,暂时不要更改其他任何内容。
- 表的
-
视图。 只要有任何表视图附加了该细分,删除就会被拒绝(
409)。usageCount表示此类视图的数量——只有list操作会返回该字段,get 不会返回:params: { filter: "name == \"<name>\"", fields: "id,name,usageCount" }。如果为 0,则跳到下一步。否则,找到这些视图:列出表,再逐个列出表视图(table-views,method: "list",使用 TABLE 的 id,filter: "segmentId == \"<segment id>\""),直到找到usageCount个视图。告知用户这些视图属于哪些表。解除关联(segmentId: null)会使这些表显示所有行,因此只有得到用户同意后才能解除关联。 -
仪表板卡片。 这些卡片不计入
usageCount,也不会导致409:细分删除后,卡片仍会保留其segmentId,并在仪表板上显示为不可用。使用api_read { "resource": "dashboard-cards", "params": { "filter": "segmentId == \"<segment id>\"" } }查找,并告知用户哪些仪表板会失去哪些卡片。 -
明确询问用户是否同意删除此细分。用户同意删除一个细分,不代表也同意删除其他细分。
-
执行
api_delete,然后检查meta仍能编译,且列表中已不再包含该细分。如果meta失败,则无法恢复:读取错误信息,找出仍引用该细分 cube 的内容(步骤 2 中列出的位置),并告知用户需要更改什么。
即使细分是你在失败的尝试中亲自创建的,也不例外:说明这一点,并在删除前征求用户同意。
任何写入操作(创建、更新、删除或附加到视图)返回 403,都表示用户的角色无权更改细分;需要 Member 权限。这些工具不支持评估、复制和恢复旧版本——请引导用户使用 RevOS UI。