数据分析 API · 简版设计(2 端点)
一套给 Agent 使用的数据分析 API:Agent 把用户口语转成端点 + 参数,数据团队负责实现端点、从真实库取数。 本文只讲 API 本身 + 每个参数 + Agent 如何匹配参数 + 数据团队如何实现。 约定:前缀
/pos/ai/v1;时间用 13 位毫秒时间戳(UTC+4);金额单位 AED。 设计沿用现网契约2026-06-10-aigent-AI对接说明(2).md§1.7agent-bi的「一个接口搞定 取数/趋势/环比」思路,再把客单价、维度拆分也收进同一取数端点。
1. 端点总览(2 个)
| 端点 | 用途 | 谁调 |
|---|---|---|
POST /metrics:query | 统一取数:汇总 / 趋势 / 同比 / 客单价 / 维度拆分,全靠参数区分 | Agent 主力 |
GET /metrics/capabilities | 能力发现:这家店支持哪些指标/维度 | Agent 取数前先问 |
为什么是 2 个不是 1 个:取数类(原 query/report/dimensions)都是"给我数据",可由参数合并;而 capabilities 是"问这家店有什么能力",响应结构不同、且要在取数前调用做门控,性质不同,单独保留。
端点收敛原则(与营销侧一致):端点选择 + 参数匹配由消费侧确定性纯函数完成 —— 不进 prompt、不靠 LLM 选,所以端点数与 prompt 长度/准确性解耦(实测数据专家 prompt 出现端点名 0 次)。只合真冗余(query/compare/trend/report/agent-bi → 一个
metrics:query),读端点性质不同的不强合。危险写(导出报表)走平台共享写端点:
POST /marketing:preview(kind=EXPORT)→commit(与营销活动/会员的危险写同一对,kind由消费侧确定性设置)。轻量取数无需 preview→commit,故本表不含。
2. 公共约定
2.1 响应外壳(两个端点统一)
{
"code": 0, // 0=成功,非 0 看 message
"message": "ok",
"data": { ... }, // 各端点的返回体
"meta": {
"currency": "AED",
"data_basis": "DAILY_REPORT" // 数据来源:DAILY_REPORT(日报直取) / ORDER_AGGREGATED(订单聚合) / NONE(无数据)
}
}
data_basis=NONE表示这家店这段时间无数据 —— Agent 应如实告知"暂无数据",不要把空当 0 汇报。
2.2 时间
- 一律 13 位毫秒时间戳,UTC+4。
- 区间
date_from/date_to含头含尾(如查 6 月,from=6/1 00:00,to=6/30 23:59:59)。
2.3 12 个指标(metrics[] 的取值)
| key | 含义 | 类型 |
|---|---|---|
income | 净收入(= 营业额 − 退款) | 金额 |
success_amount | 成功交易额(= 营业额/流水) | 金额 |
order_count | 订单总数 | 笔数 |
success_count | 成功笔数 | 笔数 |
refund_amount | 退款额 | 金额 |
refund_count | 退款笔数 | 笔数 |
fail_amount | 失败额 | 金额 |
fail_count | 失败笔数 | 笔数 |
settle_amount | 总结算额 | 金额 |
refund_rate | 退款率 | 比率(0–1) |
success_rate | 成功率 | 比率(0–1) |
avg_ticket | 客单价 | 金额 |
3. 端点详解
3.1 POST /metrics:query —— 统一取数
营业额、订单量、退款、客单价、趋势、同比、分渠道占比……全用这一个端点,靠参数区分要哪种数据。
请求参数
| 参数 | 类型 | 必填 | 说明 | 取值 |
|---|---|---|---|---|
store_id | string | 是 | 门店 ID | — |
metrics | string[] | 是 | 要查哪些指标,可多选 | 见 §2.3(含 avg_ticket) |
aggregation | string | 否 | 区间内怎么合并,默认 SUM | SUM 求和 / AVG 日均 / MAX 单日最高 / MIN 单日最低 |
time_granularity | string | 否 | 出数颗粒,不填=整段汇总成 1 行 | DAY 按天 / MONTH 按月 |
date_from | number | 是 | 区间起(毫秒) | — |
date_to | number | 是 | 区间止(毫秒) | — |
compare | string | 否 | 是否要同比,默认 NONE | NONE / WOW 周环比 / MOM 月环比 / YOY 年同比 |
dimension | string | 否 | 是否按维度拆分,不填=不拆 | CHANNEL 渠道 / PAYMENT 支付 / ORDER_SOURCE 来源 / MEMBER 会员 |
参数怎么组合 = 拿到哪种数据(这就是合并的核心)
| 你要的 | metrics | time_granularity | compare | dimension |
|---|---|---|---|---|
| 上周营业额(一个数) | ["success_amount"] | 不填 | NONE | 不填 |
| 近 7 天每天营业额(趋势) | ["success_amount"] | DAY | NONE | 不填 |
| 本周营业额 + 比上周(同比) | ["success_amount"] | 不填 | WOW | 不填 |
| 客单价 | ["avg_ticket"] | 不填 | NONE | 不填 |
| 分渠道营业额占比 | ["success_amount"] | 不填 | NONE | CHANNEL |
响应 data
① 不带 dimension(普通取数/趋势)
| 字段 | 类型 | 说明 |
|---|---|---|
series | array | 数据行;time_granularity 不填时 1 行,填了多行 |
series[].period | string | 周期标签,如 2026-06-10(汇总行可为空) |
series[].values | object | 每个指标一个值 {value, prev, pct} |
series[].values.<m>.value | number | 本期值 |
series[].values.<m>.prev | number|null | 对比期值(不带 compare 时 null) |
series[].values.<m>.pct | number|null | 变化率(不带 compare 时 null) |
② 带 dimension(维度拆分)
| 字段 | 类型 | 说明 |
|---|---|---|
breakdown | array | 各维度切片 |
breakdown[].dimension_value | string | 维度值,如 DINING_ROOM/PAY_BY_CARD |
breakdown[].value | number | 该切片的指标值 |
breakdown[].share | number | 占比 0–1 |
示例 A · 「上周每天的营业额」
// 请求
{ "store_id":"B177", "metrics":["success_amount"],
"time_granularity":"DAY", "date_from":1717977600000, "date_to":1718582399000 }
// 响应 data
{ "series":[
{"period":"2026-06-09","values":{"success_amount":{"value":2167,"prev":null,"pct":null}}},
{"period":"2026-06-10","values":{"success_amount":{"value":0,"prev":null,"pct":null}}}
/* …共 7 天… */
]}
示例 B · 「分渠道看营业额」
// 请求
{ "store_id":"B177", "metrics":["success_amount"], "dimension":"CHANNEL",
"date_from":1717977600000, "date_to":1718582399000 }
// 响应 data
{ "breakdown":[
{"dimension_value":"DINING_ROOM","value":1860,"share":0.86},
{"dimension_value":"STORE_DELIVERY","value":307,"share":0.14}
]}
三点约束:
avg_ticket(客单价)只有 263 家接入日报的门店有;其它店该指标available:false,不要用 income÷订单数现算。dimension=ORDER_SOURCE(来源)只有笔数没有金额,传金额类metric时按笔数口径返回。dimension和time_granularity一般不同时用(要么看趋势、要么看拆分)。
3.2 GET /metrics/capabilities —— 能力发现
Agent 取数前先问:这家店支持哪些指标/维度,避免对没数据的店瞎查。
请求参数:store_id(query 串)
响应 data
| 字段 | 类型 | 说明 |
|---|---|---|
metrics | array | 支持的指标 {key, label, available} |
dimensions | array | 支持的维度 {dimension, available} |
limits | object | {query_max_days:400} 区间上限 |
currency | string | AED |
4. Agent 如何调用与匹配参数
Agent 把用户一句话拆成几件事,分别填到 /metrics:query 的参数里:
| 用户说的 | 匹配到的参数 | 规则 |
|---|---|---|
| 「上周」「6 月」「近 7 天」 | date_from / date_to | 把时间词解析成两个毫秒时间戳 |
| 「日营业额」「每天」「趋势」 | time_granularity=DAY;「按月」→ MONTH;不说 → 不填 | 要不要逐日/逐月 |
| 「营业额」「订单量」「退款率」「客单价」 | metrics[] | 见下面"模糊词"表 |
| 「日均」「最高」 | aggregation=AVG/MAX | 不说 → SUM |
| 「比上周」「同比」 | compare=WOW/YOY | 不说 → NONE |
| 「分渠道」「各支付方式」 | dimension=CHANNEL/PAYMENT | 出现维度词就填这个参数 |
模糊词 → 指标(关键):「营业额」这种词不强行只给一个数 —— Agent 取一个主指标先回答,同时在数据卡列出相关指标。
| 用户词 | 主指标 |
|---|---|
| 营业额 / 销售额 / 成交额 / 流水 | success_amount |
| 净收入 / 利润 | income |
| 营收 / 收入 / 赚了多少(模糊) | success_amount(商家口语"收入"多指流水) |
| 订单 / 单量 | order_count |
| 客单价 | avg_ticket |
完整例子:用户问「上周的日营业额」
- 「上周」→
date_from/to= 上周一~周日 - 「日…」→
time_granularity=DAY - 「营业额」→ 主指标
success_amount - 无"日均/比较/分渠道" →
aggregation=SUM、compare=NONE、dimension不填 → 调POST /metrics:query,得到 7 天逐日营业额。
5. 数据团队如何实现
合并成一个取数端点后,数据团队在 /metrics:query 的实现里按参数分支取数,对应真实库表(以 ai-Recs 全表分析为准):
| 参数情形 / 指标 | 真实来源 | 实现口径 |
|---|---|---|
success_amount | report_daily_statistics.turnover | 直取(营业额=流水) |
income | turnover − refund | 派生(首选;如确认有 actual_income_no_tips 列再切换) |
order_count / success_count | report_daily_statistics.order_quantity | 直取(近似) |
refund_amount / refund_rate | refund / refund÷turnover | 直取 / 派生 |
refund_count / fail_count / success_rate | main_order.order_status(refunded/blankout/cancel/payed) | 订单表聚合(日报无笔数级) |
avg_ticket | report_daily_statistics.average_order_price | 直取(仅 263 日报店) |
time_granularity=DAY/MONTH | 按日/月分组 | 多行序列;不填则整段聚合 1 行 |
compare | 再拉一份上期同口径 | 每个指标补 prev/pct |
dimension=CHANNEL/PAYMENT/MEMBER | dining_room/togo/store_delivery、pay_by_*、member_* 列 | 预聚合列直取,算占比 |
dimension=ORDER_SOURCE | order_source_by_near/h5/shop | 只有笔数 |
实现要点:
- 三端点收一端:现网
agent-bi(§1.7) 已合并取数/趋势/环比;本方案再把 客单价(源average_order_price)和维度拆分(源分渠道/支付列)接进同一端点,即可让/metrics:query一口气覆盖原 query/report/dimensions。这些源 ai-Recs 已确认在真实日报表里存在。 - 覆盖度:日报只覆盖 263/5001 店。日报店走
report_daily_statistics(data_basis=DAILY_REPORT);其余店从main_order/daily_record按日聚合(ORDER_AGGREGATED);都没有 →NONE,不要返回 0。 - capabilities 要如实:哪些指标/维度这家店真有数据,就在
capabilities里标available,让 Agent 据此决定查不查。
6. 待数据团队确认(实现前先核实)
列(report_daily_statistics) | 用途 | 未确认前回退 |
|---|---|---|
actual_income_no_tips / tips / vat | income 精确口径(是否扣小费/税) | income 用 turnover − refund |
customer_number 是否有/填充率 | 客流量指标 | 暂不提供该指标 |
member_* 各列填充率 | 会员维拆分 | 填充率低 → available:false |