mcpField
S
segments

segments

github.com/revosai营销与增长0120 次安装5 天前更新
代码仓库

关于 segments

一种通过 RevOS MCP 服务创建、编辑和应用受众分群的技能。由于 API 写入可能不验证分群逻辑,它强调检查立方体元数据、存储值、关联路径和写入结果。

主要功能

  1. 01检查立方体元数据和关联
  2. 02构建受众筛选条件
  3. 03设置条件前核对存储值
  4. 04避免一对多关联导致的扩增
  5. 05创建和更新分群
  6. 06回读写入结果以验证

适用场景

  1. 01基于语义模型数据创建受众
  2. 02编辑分群条件并保留其他条件
  3. 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/hasOneone
other → root:one_to_many/hasMany 或 one_to_one/hasOneone
root → other:one_to_many/hasManymany
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.count GREATER_THAN 0。

如果子 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 只有一个新条件,所有未包含的条件都会被删除。

  1. 在写入前立即调用 api_read({ resource: "segments", id }) 获取最新数据。

  2. 添加任何新值之前,先查明它的存储形式。用户提到的词(“startup plan”、“churned”、“EMEA”)来自对话,而非数据;现有条件只能证明其中的值是如何拼写的。查询新条件所针对的维度:

    api_read { "resource": "cubes", "method": "query",
               "body": { "query": { "dimensions": ["crm_companies.plan"], "limit": 20 } } }
    

    使用数据中存储的拼写。如果没有与用户所说内容匹配的已存储值,请先询问,不要写入一个无法匹配任何内容的条件。

  3. 以当前的 filter(和 cubes)为基础,只修改用户要求的内容。

  4. 如果新条件引用的 cube 尚未包含在 cubes 中,请添加其 joinPath 条目,并一并发送完整的 cubes 数组。如果移除了某个 cube 上的最后一个条件,也要删除该条目。

  5. 不要发送 type — 它不可更新;也不要更改根(cubes[0]):API 虽然接受,但基于该 segment 构建的每个表和卡片都依赖原来的根。若要使用不同的根,请创建新的 segment。

  6. 写入后,再次调用 api_read 读取该 segment,并检查 filter 是否为你要保存的内容 — 仅凭 version 增加并不能证明修改已生效。

每次更新都会增加 segment 的 version(用户若担心编辑,可以提及 UI 能恢复旧版本)。如果 STATIC segment 的 filter 或 cubes 发生变化,它会恢复为 PENDING,此时所有受其限制的表都会显示所有行,直到有人在 UI 中点击 Evaluate — 此类编辑前请提醒用户。如果该 segment 限制了某个表的默认视图,请检查编辑后 meta 是否仍能编译。

使用 segment 限制表的行

表本身不携带筛选条件;筛选条件属于其视图。要让表仅显示某个 segment 的成员,请将该 segment 指定为表的默认视图:

  1. api_read({ resource: "table-views", method: "list", id: "<TABLE id>" }) — 列表挂在表上,因此这里使用表的 id。选择 isDefault: true 的条目。
  2. 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 细分,也会删除其成员快照。无法撤销。调用前:

  1. 读取细分并复述其名称、类型、根 cube、条件和成员数量。

  2. STATIC 细分的 cube。 首先检查这一项:它可能导致无法删除。STATIC 细分会编译为一个 cube:segment_<id, - → _>。在以下位置查找该名称:

    • 表的 streams、ids 和 joinPath(fields: "id,name,streams"):从该细分开始的表以它的 cube 为根;
    • 其他细分的 cubes 和 filter(完整记录);
    • 已存储 cube 的 joins(对 cubes 执行 api_read,使用 fields: "name,definition")。

    删除该细分后,以上任何内容都会停止编译,整个组织的模型也会因此无法使用。停止操作并报告发现的内容;在本次删除操作中,暂时不要更改其他任何内容。

  3. 视图。 只要有任何表视图附加了该细分,删除就会被拒绝(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)会使这些表显示所有行,因此只有得到用户同意后才能解除关联。

  4. 仪表板卡片。 这些卡片不计入 usageCount,也不会导致 409:细分删除后,卡片仍会保留其 segmentId,并在仪表板上显示为不可用。使用 api_read { "resource": "dashboard-cards", "params": { "filter": "segmentId == \"<segment id>\"" } } 查找,并告知用户哪些仪表板会失去哪些卡片。

  5. 明确询问用户是否同意删除此细分。用户同意删除一个细分,不代表也同意删除其他细分。

  6. 执行 api_delete,然后检查 meta 仍能编译,且列表中已不再包含该细分。如果 meta 失败,则无法恢复:读取错误信息,找出仍引用该细分 cube 的内容(步骤 2 中列出的位置),并告知用户需要更改什么。

即使细分是你在失败的尝试中亲自创建的,也不例外:说明这一点,并在删除前征求用户同意。

任何写入操作(创建、更新、删除或附加到视图)返回 403,都表示用户的角色无权更改细分;需要 Member 权限。这些工具不支持评估、复制和恢复旧版本——请引导用户使用 RevOS UI。

评论

支持 Markdown