业务故事站
P2 Foundry

指标语义层:口径统一

总销售额到底含不含取消订单?客单价怎么算才对?指标语义层用"实体、维度、度量、指标"四层结构把口径搬进系统,按维度聚合实时出数,让财务、业务、数据三方从此不再为口径打架。看完这 4 个故事,你就能自己建指标、查指标、用目录对口径。

数据工程师 业务分析师 财务 指标语义层 口径统一 聚合查询 共 4 个故事

能 / 不能速览

✅ 这个主题能做
  • 用 entity / dimension / measure / metric 四层结构声明指标口径,formula 跟着定义走
  • 通过 POST /metrics/query 按维度 / 时间范围实时聚合,数据来自真实数据源
  • 用指标目录(GET /metrics/catalog)集中展示每个指标的口径、owner、版本、状态
  • 建 simple(SUM(amount))与 ratio(SUM(amount)/COUNT(*))两类度量,口径差异显性化
  • 指标创建自动打点"对象 → 指标"血缘,口径来源可追溯
⛔ 这个主题做不了
  • 维度 source_property 必须是实体引用对象的属性,region 在 customers 表上,sales 实体查不到
  • 查询只能按指标声明的允许维度分组,指标 OOL 翻译受对象查询同款限制(单对象、≤3 层深)
  • derived / cumulative 仅部分支持:derived 须定义分子/分母两个度量,cumulative 须 sum/count 基础聚合且按时间维度分组;时间窗口(移动平均 / trailing 累计)为 P2 预留拒绝
  • 指标变更无独立审批流,口径维护靠"更新 + 版本 +1",权限靠角色控制

适用角色

本主题面向三个角色:

  • 数据工程师:维护实体、维度、度量、指标四层定义,是口径的"所有者"。
  • 业务分析师:在指标查询页按维度自助聚合出数,消费 total_gmv / aov 等口径。
  • 财务 / 业务决策者:在指标目录核对口径,不直接操作,用目录作为"对账仲裁依据"。

能力速览(能做什么)

四层语义结构

实体(Entity,决定 grain)→ 维度(Dimension,分组 / 过滤)→ 度量(Measure,聚合值)→ 指标(Metric,业务口径),层层声明、口径留痕。

指标目录

每个指标的口径 formula、度量、维度、owner、版本、状态一张卡片说清,跨部门对账的仲裁依据。

实时聚合查询

POST /metrics/query 按维度 / 时间范围 / 过滤条件翻译为参数化 SQL 在真实数据源上执行,返回聚合结果 + 口径元信息。

simple / ratio / derived / cumulative

simple(SUM(amount))与 ratio(SUM(amount)/COUNT(*))全支持;derived(两度量派生)与 cumulative(按日 running total,如 cumulative_gmv)部分支持——指标类型分级 metric_type 落定义层,目录一眼可见。

血缘联动

指标创建自动打点"对象 → 指标"血缘边,结合管道 / Action 边形成"源 → 管道 → 本体 → 指标"链路。

调整指南(怎么调整)

  • 改口径:到"指标定义"里编辑过滤条件或同义词,保存后版本自动 +1,口径变更留痕。
  • 改维度:先在"实体模型"给实体添加维度(source_property 必须是实体引用对象的属性),再在指标里勾选允许维度。
  • 改分组范围:指标查询勾选多个维度即多维分组;时间范围走 primary_time_dimension(created_at)过滤。
  • 改度量类型:需要"平均 / 比率"先建 ratio 度量(numerator + denominator),再建引用它的指标。
  • 停用指标:删除即置为 deprecated(软删、幂等),历史问题与血缘边保留,查询会拒绝 deprecated 指标。

做得好的场景

指标语义层把"口径"从人脑 / Excel / 聊天记录里搬进系统,特别适合以下场景:
  • 跨部门对账:财务与业务各执一词时,指标目录把 formula 摊开,谁含 cancelled 谁不含一眼可见。
  • 业务自助取数:分析师不写 SQL,选指标、勾维度、点查询,秒级拿到聚合结果,口径不再靠人对。
  • 口径治理:每个指标有 owner、版本、状态,变更留痕,新同事上手看目录就懂"数怎么算的"。
  • 报表口径一致:所有报表引用同一个指标名,改口径只改定义一处,报表随之更新。

限制与不足

以下是明确的边界,使用前先知道:
  • 维度受对象属性限制:维度 source_property 必须是实体引用对象的属性,region 在 customers 表而非 order 对象上,sales 实体目前无法按区域聚合。
  • 允许维度白名单:查询分组只能在指标声明允许的维度内,越界返回校验错误;需先更新指标定义。
  • 度量类型部分支持:derived 需定义分子/分母两个度量、cumulative 需 sum/count 基础聚合且按时间维度分组(demo 的 cumulative_gmv 按日累计可查);带时间窗口(移动平均 / trailing)会拒绝(P2 预留)。
  • OOL 轻量版限制:指标聚合翻译走对象查询同款 OOL,单对象、最多 3 层深、禁点号引用。
  • demo 数据重建:指标与对象定义存在平台库(重启不丢),但 orders 等演示数据表每次启动重建,查询数值会随演示数据重置。

场景故事

故事 1 张工走通四层结构,亲手建出"有效订单额"指标
背景
张工是刚接手电商数据分析的数据工程师。他总听同事说指标分"实体、维度、度量、指标"四层,却一直没搞清四者关系。今天他在指标管理页把四层结构完整走一遍,并亲手建一个新指标 active_gmv(有效订单额),验证"口径"到底怎么落到系统里。
传统做法对比
以前算指标靠 Excel 公式或临时 SQL,口径全在个人脑子与聊天记录里,换个人就说不清;新建一个口径要么改报表代码、要么重跑整条数仓任务,动辄半天到一天。现在在指标管理页十几分钟就能声明一个新指标,口径(formula)跟着定义走。
角色
数据工程师(拥有指标管理权限);建好后由业务分析师直接消费。
操作步骤
  1. 登录平台(admin / admin1,端口 18081 或经 5173 的 /api 前缀),打开侧边栏"指标管理"
  2. "实体模型" tab 选中 sales 实体,看它的定义:对象=order、主时间维度=created_at(day)、维度=created_at、度量 gmv=SUM(amount) / count=COUNT(*) / aov=ratio
  3. 点"+ 添加维度",加 status 维度(source_property=status,非时间维度,保存后实体下出现 status 维度)
  4. 切到"指标定义" tab 点"新建指标":name=active_gmv、显示名=有效订单额、实体=sales、度量=gmv、维度勾选 created_at、加过滤条件 status != cancelled、同义词=有效销售额,剔除取消订单、状态 active
  5. 保存,再切到"指标目录"确认新指标卡片出现
系统响应
保存返回 {"code":0,"data":{"id":N}};指标目录出现 active_gmv 卡片:名称 active_gmv · 实体 sales · v1 · 口径 formula=SUM(amount) · 维度 created_at · 状态 active。查询 active_gmv 返回 5472.5(4 个有效订单)。
结果洞察
张工这下彻底看懂了四层结构:实体决定"从哪张对象上算"(order),维度决定"怎么分组"(created_at / status),度量决定"算什么"(SUM(amount)),指标决定"业务上叫啥、带不带过滤"(active_gmv)。同样 SUM(amount),total_gmv=6470.5(含 cancelled),active_gmv=5472.5(剔除取消),同一个字段、两种口径,从此在系统里都有据可查。
调整建议
维度、度量变了先在"实体模型"调整,再回到指标定义更新允许维度;指标编辑保存后版本自动 +1,口径变更留痕;指标名用稳定 api_name(小写字母 / 数字 / 下划线),重名会创建失败,先用指标目录查重。
动手试一试
登录:admin / admin1。页面路径:指标管理 → 实体模型(选中 sales)→ 添加 status 维度 → 指标定义 → 新建指标。输入内容:name=active_gmv、实体=sales、度量=gmv、过滤 status != cancelled。预期结果:目录出现 active_gmv(formula=SUM(amount)),查询得 5472.5。
限制提示
维度 source_property 必须是实体引用对象(order)的属性,region 在 customers 表上,sales 实体目前建不了 region 维度;查询只能按指标声明的允许维度分组;指标 OOL 翻译受对象查询同款限制(单对象、≤3 层深、禁点号)。
故事 2 财务与业务对"GMV 怎么算"吵架,用指标目录当场对齐
背景
周三经营分析会上,财务小周和业务王姐对"本月 GMV"各执一词:财务说 6470.5(5 笔全含),业务说 5472.5(剔除那笔 998 元的 cancelled 订单),差出一单。两人谁也不服谁,张工打开指标目录,问题当场说清。
传统做法对比
以前两边各有一张 Excel,口径写在表头注释里,谁也说服不了谁;对不上就再找人重算,来回一下午,最后往往"按老板定的那个数"了事。现在指标目录把每个指标的口径、owner、版本、状态集中展示,谁含 cancelled 谁不含,一眼可见。
角色
业务分析师(查看指标目录)、财务(消费口径)、数据工程师(维护口径定义)。
操作步骤
  1. 打开"指标管理"→"指标目录" tab
  2. 看 total_gmv 卡片:口径 formula=SUM(amount),无任何过滤条件
  3. 看 active_gmv 卡片:口径 SUM(amount) + 过滤 status != cancelled(若已建)
  4. 双方当场约定:对外报数统一用 active_gmv,口径以指标目录为准
系统响应
目录返回示例:
{
  "metrics": [
    { "name": "total_gmv", "display_name": "总销售额",
      "entity": "sales", "formula": "SUM(amount)",
      "dimensions": ["created_at"], "owner": "admin", "version": 1, "status": "active" },
    { "name": "aov", "display_name": "客单价",
      "entity": "sales", "formula": "SUM(amount)/COUNT(*)", "status": "active" },
    { "name": "cumulative_gmv", "display_name": "累计销售额",
      "entity": "sales", "formula": "cumulative(SUM(amount))",
      "metric_type": "cumulative", "status": "active" }
  ]
}
结果洞察
问题的根子不在"算错",而在"口径没有显性化"。total_gmv 明明白白写着 SUM(amount) 且无过滤,active_gmv 写着带过滤,谁含 cancelled 谁不含一查便知。会后双方约定对外统一用 active_gmv,报表引用的指标名写进目录,口径"对不上"从此有了仲裁依据。
调整建议
想改默认口径就在 active_gmv 上编辑过滤条件(版本 +1 留痕);给指标补同义词("销售额、GMV")方便语义检索命中;owner 填实际责任人,目录里能追到人。
动手试一试
登录:admin / admin1。页面路径:指标管理 → 指标目录。预期结果:看到 total_gmv(formula=SUM(amount))与 aov(formula=SUM(amount)/COUNT(*))两张卡片,各自带 owner / 版本 / 状态。
限制提示
指标目录只做展示与口径说明,口径变更无独立审批流(靠"更新 + 版本 +1"与角色权限约束);demo 的 orders 演示数据每次启动重建,查询数值会随数据重置,但指标定义保存在平台库、重启不丢。
故事 3 王姐想按区域看销售额,改走"按状态聚合"并搞清口径
背景
王姐被领导问"各区销售额是多少",她打开指标查询,想按 region 维度聚合 total_gmv,结果报"维度 region 未定义在 entity sales 中"。她找张工一起解决,最后用订单自身的 status 维度完成了聚合,也顺带搞明白了 GMV 口径。
传统做法对比
以前要么写 SQL join 两张表(orders 挂 customers 取 region),要么提工单等数据团队排期出一张分区报表,快则 1~2 天。现在指标查询页选指标、勾维度、点查询,秒级出结果;查不了的维度也立刻有明确报错,不用瞎猜。
角色
业务分析师(只读查询权限)+ 数据工程师(补维度 / 更新允许维度)。
操作步骤
  1. 王姐打开"指标管理"→"指标查询" tab,选指标 total_gmv,勾选维度 region 后点查询
  2. 系统报错"维度 region 未定义在 entity sales 中",她截图发给张工
  3. 张工在"实体模型"给 sales 加 status 维度(source_property=status),并更新 total_gmv 允许维度为 created_at / status
  4. 王姐重新查询:指标 total_gmv,维度勾选 status,点查询
  5. 看到按状态聚合的三行结果,顺手把口径(含不含 cancelled)也确认了
系统响应
聚合查询返回示例:
{
  "columns": ["status", "metric"],
  "rows": [["shipped", 4477.5], ["pending", 995], ["cancelled", 998]],
  "metric": { "name": "total_gmv", "display_name": "总销售额",
              "entity": "sales", "formula": "SUM(amount)",
              "agg_type": "sum", "type": "simple", "status": "active" }
}
结果洞察
查询按 status 分三组:shipped 4477.5、pending 995、cancelled 998,合计 6470.5。王姐由此看清两个事实:一是"想按区域看"目前做不到,因为 region 在 customers 表、不在订单实体上;二是 total_gmv 的口径把 cancelled 也计了进去——要"有效销售额"就用 active_gmv。维度与口径一起想清楚,汇报材料一次到位。
调整建议
真想按区域聚合,得先让 region 出现在订单实体上(见限制提示),或换一个含区域字段的对象建实体;想过滤某状态,在指标查询页加过滤条件或直接换带过滤的指标;多维分组勾多个维度即可。
动手试一试
登录:admin / admin1。页面路径:指标管理 → 实体模型(sales 加 status 维度)→ 指标定义(total_gmv 允许维度加 status)→ 指标查询。输入内容:指标 total_gmv、维度勾选 status。预期结果:返回 shipped 4477.5 / pending 995 / cancelled 998 三行,合计 6470.5。
限制提示
region 在 customers 表、不在 order 对象上,sales 实体建不了 region 维度(维度 source_property 必须是对象属性);想按区域聚合需先把 region 落到订单表或另建含区域字段的对象;指标 OOL 翻译是轻量版(单对象、≤3 层深),跨对象聚合不支持。
故事 4 客单价 aov 怎么算?跨部门对账用它当"共同语言"
背景
客单价(平均每单多少钱)是运营和财务的常用数。王姐用平台查 aov 得到 1294.1,财务小周在 Excel 里却算出 1492.5——两人一核对才发现:王姐的口径是全量 5 单(含 cancelled),小周只算了 3 单已发货的。张工用 aov 的定义把话说开。
传统做法对比
以前客单价各人各算:Excel 里 SUM 除以 COUNT 的手工公式,分子分母"分母到底含不含取消单"没人写在明处,每次对不上都要追半天。现在 aov 是 ratio 度量,分子 SUM(amount)、分母 COUNT(*),formula 在目录里白纸黑字。
角色
业务分析师(查询 aov)+ 财务(核对口径)+ 数据工程师(维护 ratio 度量定义)。
操作步骤
  1. 王姐打开"指标查询",选指标 aov,点查询
  2. 看返回里的口径 formula=SUM(amount)/COUNT(*),确认分子分母
  3. 财务对照目录里的 aov 卡片,指出自己 Excel 只取了 3 单已发货
  4. 双方约定:通用客单价统一用 aov(全量口径),要看已发货单单独建"有效客单价"指标
系统响应
查询 aov 返回 {"columns":["metric"],"rows":[[1294.1]],"metric":{"name":"aov","display_name":"客单价","entity":"sales","formula":"SUM(amount)/COUNT(*)","agg_type":"sum","type":"ratio","status":"active"}};6470.5 / 5 = 1294.1。
结果洞察
1294.1 与 1492.5 的差异根源不在计算,而在"分母口径":aov 是全量 5 单的客单价(1294.1),小周只取 3 单已发货(4477.5/3=1492.5)。ratio 度量把分子分母分开声明,谁都能看出分母是 COUNT(*),不含任何隐含条件。跨部门对账用同一个指标名 + 同一份 formula,就不再各说各话。
调整建议
需要"有效客单价"(剔除取消单)时,复用 aov 的分子分母,新建带过滤 status != cancelled 的 ratio 指标即可;给 aov 补同义词"客单价,平均订单金额"方便检索;比率指标先小范围核验分子分母再推广。
动手试一试
登录:admin / admin1。页面路径:指标管理 → 指标查询。输入内容:指标 aov。预期结果:返回 1294.1,metric.formula=SUM(amount)/COUNT(*)、type=ratio;目录里 aov 卡片同样显示该公式。
限制提示
derived / cumulative 仅部分支持:derived 需分子/分母两个度量、cumulative 需 sum/count 且按时间维度分组,带时间窗口(移动平均 / trailing)会拒绝(P2 预留);aov 无默认过滤(全量含 cancelled),要"有效客单价"需另建带过滤的 ratio 指标;demo 数据每次启动重建,比值会随演示订单重置。

常见问题

实体、维度、度量、指标到底什么关系?

实体(Entity)决定"从哪张对象上算",如 sales 挂在 order 对象上;维度(Dimension)决定"怎么分组",如 created_at(时间)、status(状态);度量(Measure)决定"算什么",如 SUM(amount)、COUNT(*);指标(Metric)是业务口径,把度量 + 维度 + 过滤条件包装成一个业务名,如 total_gmv。

为什么我按 region 分组会报错?

维度 source_property 必须是实体引用对象的属性。sales 实体挂在 order 对象上,order 的属性只有 order_id / customer_id / amount / status / created_at,region 在 customers 表,所以 sales 实体建不了 region 维度。想按区域聚合需先把区域落到订单表或另建含区域字段的对象。

total_gmv 和 active_gmv 有什么区别?

两者都用 gmv 度量(SUM(amount)),区别只在过滤:total_gmv 无过滤(含 cancelled,6470.5),active_gmv 带过滤 status != cancelled(5472.5)。指标目录里各自的 formula 与过滤一目了然,这就是"口径显性化"。

指标定义重启后会不会丢?

不会。指标、实体、维度、度量的定义存在平台库中,重启不丢;demo 种子只在"不存在"时创建(幂等)。会被重建的只有 orders / customers / products 这些演示数据表,所以查询数值会随演示数据重置。

指标查询和对象查询有什么区别?

指标查询是"聚合 + 分组":返回 SUM / COUNT / 比率这类聚合结果,并附带口径说明;对象查询是"明细过滤":返回每一行记录。要总数看指标,要逐单明细看对象查询。

主题小结

一句话:指标语义层把"口径"变成系统里可查、可改、可追的四层资产——实体定对象、维度定分组、度量定算法、指标定业务名(metric_type 分级落定义层)。记住几个边界:维度必须是实体引用对象的属性、查询只能按允许维度分组、derived/cumulative 仅部分支持(时间窗口 P2 预留)、region 这类跨对象维度目前做不了。