@guandata/guancli 1.0.62 → 1.0.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## @guandata/guancli 1.0.63 - 2026-09-24
4
+
5
+ - 指标查询新增 `--explain-requests -f json`,只读展示实际请求顺序与请求体,方便检查参数组合;不会执行取数。
6
+ - 完善筛选、排序和高级计算组合校验,避免无效查询计划被静默接受。
7
+
3
8
  ## @guandata/guancli 1.0.62 - 2026-09-19
4
9
 
5
10
  - 安装包按平台拆分:主包只保留启动器、Skill 与文档,原生程序由同版本的 `@guandata/guancli-<平台>-<架构>` 可选依赖提供,安装时只下载当前系统的一份,安装命令与 CLI 行为不变。
package/README.md CHANGED
@@ -45,6 +45,11 @@ guancli chatbi query --theme-name "经营主题" --message "最近30天营业收
45
45
 
46
46
  ## 版本更新
47
47
 
48
+ ### @guandata/guancli 1.0.63
49
+
50
+ - 指标查询新增 `--explain-requests -f json`,只读展示实际请求顺序与请求体,方便检查参数组合;不会执行取数。
51
+ - 完善筛选、排序和高级计算组合校验,避免无效查询计划被静默接受。
52
+
48
53
  ### @guandata/guancli 1.0.61
49
54
 
50
55
  - 指标取数会校验 `--dim`、`--filter` 和排序字段:未命中指标详情字段时返回明确错误,不再静默忽略条件。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guandata/guancli",
3
- "version": "1.0.62",
3
+ "version": "1.0.63",
4
4
  "description": "观远 BI 命令行工具 - 通过 API 操作 ETL、数据集、页面、表单等资源,并提供 ChatBI 问数/洞察能力",
5
5
  "bin": {
6
6
  "guancli": "bin/run.js"
@@ -42,11 +42,11 @@
42
42
  "node": ">=14"
43
43
  },
44
44
  "optionalDependencies": {
45
- "@guandata/guancli-darwin-arm64": "1.0.62",
46
- "@guandata/guancli-darwin-x64": "1.0.62",
47
- "@guandata/guancli-linux-x64": "1.0.62",
48
- "@guandata/guancli-linux-arm64": "1.0.62",
49
- "@guandata/guancli-win32-x64": "1.0.62"
45
+ "@guandata/guancli-darwin-arm64": "1.0.63",
46
+ "@guandata/guancli-darwin-x64": "1.0.63",
47
+ "@guandata/guancli-linux-x64": "1.0.63",
48
+ "@guandata/guancli-linux-arm64": "1.0.63",
49
+ "@guandata/guancli-win32-x64": "1.0.63"
50
50
  },
51
51
  "publishConfig": {
52
52
  "registry": "https://registry.npmjs.org/",
@@ -66,12 +66,15 @@ guancli 是 BI 资源的**查询分析入口**,并支持 Form 表单填报 CRU
66
66
  | Form CRUD、子表、ChatBI 主题问数/洞察 | `references/form-chatbi.md` |
67
67
  | 指标与指标树全量命令 | `references/metric-commands.md` |
68
68
  | 指标泛化查询参数与示例 | `references/metric-generic-query.md` |
69
+ | 指标 query 参数模式、互斥矩阵与真实请求体 | `references/metric-query-recipes.md` |
69
70
  | 本地 CSV/JSON 规范化、对齐、基础计算、TopN | `references/analysis-primitives.md` |
70
71
 
71
72
  ## 常用查询入口
72
73
 
73
74
  **同轮指标查询规则**:本轮已确定、彼此互不依赖,且参数能被 `batch-query` 完整表达的多个基础指标查询,必须写成一个结构化查询列表并执行一次 `guancli metric batch-query --input queries.json`。同比、XTD、recent、占比、排名等高级计算继续使用单条 `metric query`;同轮其余基础查询仍应单独组成 batch。不得生成 Shell `for` 循环逐项执行基础指标查询,也不自行设计 `&`、`xargs -P` 等并发脚本或调整并发参数。CLI 自动调度并逐项返回结果;需要全部成功后再处理结果时加 `--fail-on-error`,可直接用 `&&` 衔接;默认模式仍需用 `id` 对齐每项 `status`。后续查询依赖本轮结果时,读取本轮结果后再生成下一批。搜索并需要详情时使用 `metric search --with-detail`,避免逐项 get。批量输入、分页与支持边界见 [metric-batch-query.md](references/metric-batch-query.md)。复用已确认的输入契约,缺少信息或遇到参数错误时再读取该参考,不固定增加帮助查询。
74
75
 
76
+ 用户明确要求比较两组指标数值是否一致时,不能把两次查询默认返回的前 50 行按数组位置直接比较。新旧指标必须使用相同的显式维度/日期粒度、筛选范围、排序、分页和 `valueFormat=raw`;结果按维度组合键对齐后再比较指标值。任一查询失败、结果可能截断、维度键不唯一或覆盖范围不一致时,结论只能是 `inconclusive`,不能写成 `different`。这属于显式的数值对比任务,不是每次指标创建都要执行的默认验收;完整 recipe 见 [metric-batch-query.md](references/metric-batch-query.md) 的“指标数值等价对比”。
77
+
75
78
  **减少模型往返**:同一授权和查询轮次内,已确认的指标口径、字段与能力直接复用。将确定性的文件读取、状态检查、裁剪和计算合并成一次本地执行,返回需要判断的摘要;仅在结果引入新决策时再规划下一轮。只读查询可与结果保存、检查和摘要合并执行;确认查询成功后再消费,不为补齐运行轨迹拆分命令或重跑查询。
76
79
 
77
80
  ```bash
@@ -156,7 +159,7 @@ guancli 在这条链路上只出只读的那一环:`guancli page search` / `pa
156
159
  - query 的业务列可以是中文,每个中文层级都用 `["字段名"]`:例如 `.data[] | .["组织"]["名称"]`,每一个中文层级都要引用,不能只改顶层。search/get 的 canonical 英文字段与 query 的业务列名要分开理解。文件模式的 `path` 指向行数组,行数组没有 `rowCount`;先按 `resultMode` 分支再消费,示例见 `references/metric-commands.md`。
157
160
  - 必须解析的命令不得用 `2>/dev/null` 隐藏 stderr,也不得用 `2>&1` 将错误文本送入 JSON 解码器。不要用会让末端命令成功掩盖上游失败的管道;允许组合执行时用 `&&` 衔接“输出到文件 → 解析摘要”,不为读取文件另起一轮推理,必须使用管道时先启用 `set -o pipefail`。
158
161
  - 同类语法错误只允许出现一次;第一次失败后先执行对应命令的 `--help` 或读取本 Skill 的参考文档,再按真实契约纠正,不连续尝试相近写法。
159
- - 普通 `--filter`、可重复 `--filter-json`、单个 `--filter-file` 三选一,禁止混用。复杂筛选优先使用 `--filter-json`(可重复)或 `--filter-file` 条件树。参数和口径明确时直接查询;需要检查计划或排查复杂参数时才用 `--dry-run -f json`。dry-run 只读取指标详情做查询维度、筛选字段和类型校验,不执行指标取数,也不支持普通文件或 stderr 输出目标;保存卡片高级计算列不在指标详情中,因此这类排序候选在 dry-run 中不会被猜测为有效,真实执行时才读取既有卡片编辑配置校验。不支持的 `--dim`、普通 `--filter` 的字段、`--sort-asc`/`--sort-desc` 的字段都按指标详情中的字段(适用维度 / 时间维度 / 指标字段名)校验,未命中返回 `METRIC_UNSUPPORTED_DIMENSION`;结构化 `--filter-json`/`--filter-file` 在解析阶段按同一份字段集合校验,未命中返回 `FILTER_UNKNOWN_FIELD`。两类错误都不会静默忽略条件或透传未知字段。普通展示别名只对 `--dim` 与排序生效;若指标详情的 `publicDimensionMap` 把唯一公共维度名映射到一个适用维度,则 `--dim`、排序和筛选也可使用该公共维度名,CLI 会保留物理 `fdId` 并把公共维度名作为 alias;同名映射到多个物理字段时拒绝猜测。仅由 CLI 为排序注入、以指标 ID 充当 `fdId` 的“指标名伪字段”不能用于筛选,若指标详情的 `fdInfos` 本身含同名原始字段和真实 `fdId`,仍可正常筛选。`--sort-asc`/`--sort-desc` 额外接受保存卡片中真实存在的高级计算列,以及泛化查询的结果列名 `<指标名>_<后缀>`(`_yoy`/`_mom`/`_qoq`/`_wow`/`_dod`/`_ytd`/`_qtd`/`_mtd`/`_wtd`/`_dtd`/`_last`/`_recent_*`/`_percentage`/`_rank`)。遇到这些错误时先用 `guancli metric get` 读取适用维度再改字段。条件组使用 `combineType: AND|OR` 和 `conditions` 数组,叶子使用 `field/operator/value|values`;`IN` 至少一个值,`BT` 恰好两个值。当前指标取数接口不能无损表达多分支 OR,真实执行会在取数前返回 `FILTER_OR_EXECUTION_UNSUPPORTED`,不得拆成多次聚合查询后拼接。失败时按稳定错误码修正,不猜错误文本。
162
+ - 普通 `--filter`、可重复 `--filter-json`、单个 `--filter-file` 三选一,禁止混用。复杂筛选优先使用 `--filter-json`(可重复)或 `--filter-file` 条件树。Agent 执行任意单条 `metric query`(包括无参数的基础查询)前都必须先用同一组查询参数追加 `--explain-requests -f json`,核对真实接口顺序和最终 request body;只有 `valid=true`、`executable=true` 且 body 符合业务意图时,才移除 explain 执行真实查询。`batch-query` 是独立批量契约,不支持 explain,不套用这条单条查询门禁;它仍按上文同轮批量规则使用。只需人工快速检查维度/筛选时可用 `--dry-run -f json`,但不能替代 Agent 的 explain-first 门禁。explain 会调用与真实执行相同的 request builder:泛化查询给出最终 `metric-data/v2` body,基础查询只读保存的 card edit meta 后给出完整 session/modify/data 计划;它不创建 edit session、不执行 POST、不取数。完整模式矩阵见 `references/metric-query-recipes.md`。dry-run 只读取指标详情做查询维度、筛选字段和类型校验,不执行指标取数,也不支持普通文件或 stderr 输出目标;保存卡片高级计算列不在指标详情中,因此这类排序候选在 dry-run 中不会被猜测为有效,explain/真实执行会读取既有 card 配置校验。不支持的 `--dim`、普通 `--filter` 的字段、`--sort-asc`/`--sort-desc` 的字段都按指标详情中的字段(适用维度 / 时间维度 / 指标字段名)校验,未命中返回 `METRIC_UNSUPPORTED_DIMENSION`;结构化 `--filter-json`/`--filter-file` 在解析阶段按同一份字段集合校验,未命中返回 `FILTER_UNKNOWN_FIELD`。两类错误都不会静默忽略条件或透传未知字段。普通展示别名只对 `--dim` 与排序生效;若指标详情的 `publicDimensionMap` 把唯一公共维度名映射到一个适用维度,则 `--dim`、排序和筛选也可使用该公共维度名,CLI 会保留物理 `fdId` 并把公共维度名作为 alias;同名映射到多个物理字段时拒绝猜测。仅由 CLI 为排序注入、以指标 ID 充当 `fdId` 的“指标名伪字段”不能用于筛选,若指标详情的 `fdInfos` 本身含同名原始字段和真实 `fdId`,仍可正常筛选。`--sort-asc`/`--sort-desc` 额外接受保存卡片中真实存在的高级计算列,以及泛化查询的结果列名 `<指标名>_<后缀>`(`_yoy`/`_mom`/`_qoq`/`_wow`/`_dod`/`_ytd`/`_qtd`/`_mtd`/`_wtd`/`_dtd`/`_last`/`_recent_*`/`_percentage`/`_rank`)。遇到这些错误时先用 `guancli metric get` 读取适用维度再改字段。条件组使用 `combineType: AND|OR` 和 `conditions` 数组,叶子使用 `field/operator/value|values`;`IN` 至少一个值,`BT` 恰好两个值。当前指标取数接口不能无损表达多分支 OR,真实执行会在取数前返回 `FILTER_OR_EXECUTION_UNSUPPORTED`,不得拆成多次聚合查询后拼接。失败时按稳定错误码修正,不猜错误文本。
160
163
  - 搜索并同时查看业务口径、数据集和适用维度等详情时,优先使用 `guancli metric search --fuzzy <keyword> --limit <N> --with-detail --brief -f json`,不要先 search 再逐个 get,也不要为终端阅读额外拼 jq。只查单个指标详情时优先使用 `guancli metric get <metric_id> --brief`;只裁剪指标查询结果列时使用 `metric query --columns`。可复制模板见 `references/metric-commands.md`。
161
164
  - 业务词不能直接命中指标时,候选发现必须遵守单批预算:一次性生成“原词 + 最多 4 个必要同义词/等价表达”,按 trim、空白归一化和大小写不敏感语义去重,保留原词在首位,然后只执行一次 `metric search --fuzzy`。不得把同义词拆成多次查询,不得通过调换顺序、改写等价词或查询子集重复搜索;如果单批无候选,立即结束本轮候选发现并说明已尝试关键词、搜索范围、无结果原因和 `stop_reason=no_candidate_within_budget`。后端最多接受 10 个模糊关键词,但 Agent 候选发现预算默认最多 5 个;不要缓存或跨请求复用“未找到指标”的结论。完整协议见 `references/metric-commands.md`。
162
165
  - 指标结果加工前,先读取成功 query envelope 的实际 `columns` 和行对象 keys,按业务含义选择列;禁止硬编码或猜测指标展示列名(包括高级计算后缀),也不能按列位置取值。使用 `python3 references/examples/metric-query-rows.py <envelope.json> --list-columns` 发现列,再用 `--column "<已确认的实际列名>"` 精确选择;候选缺失或多义时停止并返回实际列集合,不猜近似名称。该参数属于消费 recipe,不是 `guancli metric query` 的参数。
@@ -104,6 +104,53 @@
104
104
  "missing 没有金额列时停止;ambiguous 存在销售额和销售净额时停止并询问业务口径",
105
105
  "不抛 KeyError,不用缺失字段默认零或第一列掩盖歧义"
106
106
  ]
107
+ },
108
+ {
109
+ "id": 9,
110
+ "prompt": "帮我查销售额指标按订单日期逐月的同比增长率,只看华东。这个 query 参数比较多,先确认最终会请求哪个接口、body 里的日期字段和高级计算是否正确,再执行真实查询。",
111
+ "expected_output": "代理先用 metric get 确认真实时间字段和地区字段,再构造唯一 compare 模式命令,使用 --explain-requests -f json 检查 metric-data/v2 的 dimensions、filters 和 advCalc;确认 valid/executable 后才移除 explain 执行。",
112
+ "files": [],
113
+ "expectations": [
114
+ "先用 metric get 确认时间字段和筛选字段,不默认字段名",
115
+ "查询组合使用 --dim \"订单日期 toMonth\" --compare yoy --compare-value rate,并只选择一个主模式",
116
+ "真实查询前使用 --explain-requests -f json,检查 data.requestPlan.requests 中的 metric-data/v2 body",
117
+ "只有 valid=true、executable=true 且 body 符合意图时才移除 --explain-requests"
118
+ ]
119
+ },
120
+ {
121
+ "id": 10,
122
+ "prompt": "我想把 --compare yoy 和 --adv-calc-json '{\"advType\":\"XTD\",\"advValue\":{\"advType\":\"XTD\",\"granularity\":\"YEAR\"}}' 一起传给 metric query,顺便看看最终请求体。",
123
+ "expected_output": "代理识别 adv-calc-json 是完整主计算定义,不能与 compare 组合;不要执行含歧义的查询,要求二选一,并分别说明可用 explain 查看请求体。",
124
+ "files": [],
125
+ "expectations": [
126
+ "明确指出 --adv-calc-json 与 compare/xtd/last/recent/percentage/rank 主模式互斥",
127
+ "不生成同时包含 --compare 和 --adv-calc-json 的可执行命令",
128
+ "建议选定一种模式后使用 --explain-requests -f json,而不是直接真实取数"
129
+ ]
130
+ },
131
+ {
132
+ "id": 11,
133
+ "prompt": "把 guancli metric query 的所有参数组合和最终 API 请求体都告诉我,我要避免 Agent 以后乱用。",
134
+ "expected_output": "代理不做无限参数笛卡尔积,而是按 base、compare、xtd、last、recent、percentage、rank、adv-calc-json 八类语义模式说明互斥/依赖,并把公共修饰参数单列;真实指标请求体通过 --explain-requests 解析。",
135
+ "files": [],
136
+ "expectations": [
137
+ "解释重复维度、筛选、recent 数值、rank-top 和任意 adv JSON 使字面上的全部组合无法穷举",
138
+ "完整列出八类主模式,并把筛选三选一、排序二选一和客户端输出参数作为公共矩阵",
139
+ "区分 card 链路与 metric-data/v2 链路",
140
+ "给出 --explain-requests -f json 作为真实 body 的权威入口"
141
+ ]
142
+ },
143
+ {
144
+ "id": 12,
145
+ "prompt": "帮我查一下销售额指标现在的值,没有维度和筛选。",
146
+ "expected_output": "即使是无参数的基础单指标查询,代理也先执行同一 metric_id 的 --explain-requests -f json,检查 valid/executable 以及 session/data 请求计划,再移除 explain 执行真实查询。",
147
+ "files": [],
148
+ "expectations": [
149
+ "无参数基础 metric query 也遵守 explain-first,不因参数简单而直接取数",
150
+ "检查 data.requestPlan 中 valid、executable 以及 card session/data 请求",
151
+ "只有计划有效、可执行且请求体符合意图时才执行真实查询",
152
+ "不把 batch-query 的独立批量契约误写成支持 --explain-requests"
153
+ ]
107
154
  }
108
155
  ]
109
156
  }
@@ -50,3 +50,33 @@ lite/kit 执行会在 replay 轮汇集独立请求;只读请求可标为 paral
50
50
  复用 `metric query` 的接口链:`GET /api/metric-platform/metrics/{metricId}`、`GET /api/card/{cardId}/edit/session`、`POST /api/card/{cardId}/edit/{sessionId}/data`;显式维度或排序时还会调用 `GET /api/card/{cardId}/edit` 和 `POST /api/oriput/card/{cardId}/edit/{sessionId}/modify`。代理还原后的 Spring 路由均已确认存在。指标详情要求 `METRIC_PLATFORM/read`(PAT pId 90/read);卡片会话、modify 和取数要求 `BASE/read`,读取编辑配置还要求 `PAGE/update`(PAT 仪表板 pId 1/update)。所以仅查询卡片保存配置时 pId 90/read 可用,显式 dimensions 或 sorting 时 PAT 还要包含仪表板 update;权限不足会逐项返回 403,不能扩大用户资源权限。
51
51
 
52
52
  每项读取指标关联卡片并在独立编辑会话中取数,不拼接多个聚合结果。未显式提供 dimensions 时保留卡片默认分组;提供 dimensions 或 sorting 时只覆盖该项会话中的对应配置,filters 作为本次取数条件传入。指标详情、卡片配置与取数之间不保证快照;改名、发布或权限变化由服务端最终裁决。当前 batch 输入不包含同比、XTD、recent、占比和排名等高级计算;这些仍由单条 `metric query` 的 v2 分支处理,后续若加入 batch 必须沿用相同分流。
53
+
54
+ ## 指标数值等价对比
55
+
56
+ 仅当用户明确要求比较新旧指标、迁移前后指标或两组候选指标的数值时使用本节;普通 `guanmetric flow verify` 的“可用”验收只需证明指标能成功查询,不默认承担数值等价证明。
57
+
58
+ 对每一对指标构造参数完全对称的查询:
59
+
60
+ - 使用相同的显式 `dimensions`;日期维度必须使用双方都支持的相同粒度,如 `日期 toMonth`。
61
+ - 对所有分组维度设置确定性排序;单个日期维度通常使用同一表达式作为 `sortAsc`。没有稳定排序时禁止按数组位置比较。
62
+ - 使用相同的 `filters` 固定业务范围和时间范围。涉及“今天”、动态窗口或卡片默认筛选时,优先改成双方相同的固定区间,避免执行时点和默认卡片配置影响结果。
63
+ - 使用 `valueFormat: "raw"`,避免格式、精度、千分位或单位换算制造展示差异。
64
+ - 显式设置足够的 `limit`,但不要把“传了 10000”理解为结果一定完整;后端或关联卡片仍可能存在更小的返回上限。
65
+
66
+ 消费结果时:
67
+
68
+ 1. 逐项检查 `status=success`,任一侧失败则该对为 `query_error`,不能继续比较。
69
+ 2. 从 `columns` 找出维度列和指标列,不能按固定列名或固定位置猜测业务列。
70
+ 3. 用全部维度值组成复合键,把双方行转换为 `dimensionKey -> raw metric value` 后比较;指标展示名称不同不影响比较。维度键重复、缺失或多指标列无法唯一选择时停止并报告实际列。
71
+ 4. 双方键集合相同后再比较相同键的原始值。未获得用户认可时不要擅自增加浮点容差;确需容差时在结论中写明绝对/相对阈值。
72
+ 5. 若行数达到已知/疑似服务端或卡片上限、双方行数不同且较短一侧是较长一侧的精确前缀,或无法证明查询覆盖完整范围,状态必须为 `inconclusive_truncated`,不能判为 `different`。按日期等稳定维度拆成不重叠固定区间,确保每个分片低于返回上限后再合并比较。
73
+
74
+ 禁止使用以下证据直接下“数值不一致”结论:
75
+
76
+ - 未排序的默认前 50 行顺序不同;
77
+ - 仅第一行、前 N 行或数组位置不同;
78
+ - 一侧结果是另一侧的精确前缀;
79
+ - 指标列标题、关联卡片配置或默认展示维度不同;
80
+ - 只观察到指标引用了某个旧 ID,而没有对相同维度键的实际值做比较。
81
+
82
+ 最终结果至少区分 `same`、`different`、`query_error`、`inconclusive_truncated`。汇报 `different` 时给出第一个真正相同维度键下的值差异;汇报 `inconclusive_truncated` 时给出双方行数、限制参数和建议的分片方式。
@@ -245,6 +245,7 @@ guancli metric query <metric_id> [flags]
245
245
  | `--filter-file string` | 无 | 从 JSON 文件读取一棵结构化条件树;与 `--filter`、`--filter-json` 互斥 |
246
246
  | `--combine-type string` | `AND` | 多条件组合方式:`AND` 或 `OR` |
247
247
  | `--dry-run` | `false` | 只返回规范化查询计划、字段类型和稳定错误码,不执行指标取数;必须同时使用 `-f json`,且不支持普通文件或 stderr 输出目标 |
248
+ | `--explain-requests` | `false` | 只读解析真实执行链路及最终 method/path/body;调用真实 request builder,但不创建 edit session、不 POST、不取数;必须同时使用 `-f json` |
248
249
  | `--sort-asc string` | 无 | 按列升序排序;排序目标可以是适用维度 / 时间维度、指标名本身、保存卡片中真实存在的高级计算列,或泛化结果列名 `<指标名>_<后缀>`(`_yoy`/`_mom`/`_qoq`/`_wow`/`_dod`/`_ytd`/`_qtd`/`_mtd`/`_wtd`/`_dtd`/`_last`/`_recent_*`/`_percentage`/`_rank`);日期子类型使用与 `--dim` 相同的粒度表达式;未命中返回 `METRIC_UNSUPPORTED_DIMENSION`;与 `--sort-desc` 互斥 |
249
250
  | `--sort-desc string` | 无 | 按列降序排序;合法排序目标与 `--sort-asc` 相同(适用维度 / 时间维度、指标名、泛化结果列名);日期子类型使用与 `--dim` 相同的粒度表达式;未命中返回 `METRIC_UNSUPPORTED_DIMENSION`;与 `--sort-asc` 互斥 |
250
251
  | `--columns strings` | 无 | 只显示指定列,逗号分隔,在 CLI 端执行 |
@@ -318,6 +319,7 @@ guancli metric query <metric_id> --profile default --dim 省份 --filter "省份
318
319
  guancli metric query <metric_id> --profile default --dim 省份 --filter "省份 EQ 河南省" --filter "省份 EQ 青海省" --combine-type OR --dry-run -f json
319
320
  guancli metric query <metric_id> --profile default --filter-json '{"field":"省份","operator":"IN","values":["河南省","青海省"]}' --dry-run -f json
320
321
  guancli metric query <metric_id> --profile default --filter-file filters.json --dry-run -f json
322
+ guancli metric query <metric_id> --profile default --dim 省份 --sort-desc 订购数量 --explain-requests -f json
321
323
  guancli metric query <metric_id> --profile default --dim 省份 --sort-desc 订购数量
322
324
  guancli metric query <metric_id> --profile default --dim 省份 --columns "省份,订购数量"
323
325
  guancli metric query <metric_id> --profile default --dim 省份 -f csv
@@ -328,6 +330,8 @@ guancli metric query <metric_id> --profile default --dim 省份 --raw
328
330
 
329
331
  多分支 OR 的 dry-run 会返回 `valid=true`、`executable=false`、`requestCount=0` 和错误码 `FILTER_OR_EXECUTION_UNSUPPORTED`。当前指标取数接口不能在单次请求中无损表达这类 OR,真实执行会在取数前拒绝,不会拆成多次聚合查询后拼接。
330
332
 
333
+ 参数模式、互斥/依赖关系、card 与 `metric-data/v2` 的完整请求体结构见 [指标查询参数模式与请求体](metric-query-recipes.md)。Agent 执行任意单条查询前都先使用同参数的 `--explain-requests -f json`,检查 `data.requestPlan.requests[]`;只有计划有效、可执行且 body 符合业务意图时才移除 explain。`batch-query` 使用独立批量契约且不支持 explain。
334
+
331
335
  #### 可复制模板:筛选并裁剪查询结果
332
336
 
333
337
  ```bash
@@ -8,6 +8,8 @@
8
8
  guancli metric query <metric_id> [flags]
9
9
  ```
10
10
 
11
+ 所有主模式的互斥/依赖矩阵以及最终 `POST /api/metric-platform/metric-data/v2` body 见 [指标查询参数模式与请求体](metric-query-recipes.md)。Agent 对任意单条查询都先追加 `--explain-requests -f json`;该模式会运行最终 request builder,但不会 POST 或取数。只有计划有效、可执行且 body 符合业务意图时才移除 explain 执行。
12
+
11
13
  ## 进入泛化模式的参数
12
14
 
13
15
  只要传入以下任一参数,CLI 就会进入泛化查询模式:
@@ -0,0 +1,358 @@
1
+ # 指标查询参数模式与请求体
2
+
3
+ 本文是 `guancli metric query` 的 Agent 选参事实源。目标不是枚举无限的参数笛卡尔积,而是覆盖所有会改变查询语义或 BI 请求体的有限模式。
4
+
5
+ 需要确认某个真实指标最终会发什么请求时,不要手工替换本文占位符,直接执行:
6
+
7
+ ```bash
8
+ guancli metric query <metric_id> --profile <profile> <查询参数> --explain-requests -f json
9
+ ```
10
+
11
+ 该命令会解析真实指标字段和保存的 card 配置,但不会创建 edit session、不会发送 modify、不会取数。读取 `data.requestPlan.requests[]` 中的 `method/path/body`。
12
+
13
+ ## 1. 先选择唯一主模式
14
+
15
+ | 模式 | 进入参数 | 必需/依赖参数 | 可选参数 | 默认值 | 不能同时出现的主模式 |
16
+ | --- | --- | --- | --- | --- | --- |
17
+ | 基础查询 | 不传任何泛化参数 | 无 | 所有公共修饰参数 | card 模式 | 全部泛化模式 |
18
+ | 同比/环比 | `--compare` | `yoy/mom/qoq/wow/dod` | `--compare-value value/rate/rawdata` | `value` | xtd/last/recent/percentage/rank/adv-calc-json |
19
+ | 累计 | `--xtd` | `ytd/qtd/mtd/wtd/dtd` | 无专属附属参数 | 无 | compare/last/recent/percentage/rank/adv-calc-json |
20
+ | 期末值 | `--last` | 指标必须有时间维度 | 无专属附属参数 | 无 | compare/xtd/recent/percentage/rank/adv-calc-json |
21
+ | 最近 N 周期 | `--recent` | `<正整数>d/w/m/q/y` | `--recent-base YYYY-MM-DD` | 基准日由后端当前上下文决定 | compare/xtd/last/percentage/rank/adv-calc-json |
22
+ | 占比 | `--percentage` | 无 | `--percentage-dim <维度>` | 自动选择层级 | compare/xtd/last/recent/rank/adv-calc-json |
23
+ | 排名 | `--rank-top <正整数>` | `rank-top > 0` | `--rank-order asc/desc`、`--rank-dim <维度>` | `desc`,不限定范围维度 | compare/xtd/last/recent/percentage/adv-calc-json |
24
+ | 原始高级计算 | `--adv-calc-json` | 合法 JSON object | JSON 内字段由 BI 协议决定 | 原样使用 | 不再组合其它主模式 |
25
+
26
+ 附属参数不能单独使用:
27
+
28
+ - `--compare-value` 依赖 `--compare`。
29
+ - `--recent-base` 依赖 `--recent`。
30
+ - `--percentage-dim` 依赖 `--percentage`。
31
+ - `--rank-order`、`--rank-dim` 依赖 `--rank-top`。
32
+
33
+ `--adv-calc-json` 是专家入口。它的结构无法由 CLI 穷举,传入的 object 会原样进入 `metricItems[0].advCalc`。普通 Agent 优先使用结构化参数。
34
+
35
+ ## 2. 公共修饰参数矩阵
36
+
37
+ | 参数族 | 合法组合 | 影响位置 | 说明 |
38
+ | --- | --- | --- | --- |
39
+ | 维度 | `--dim` 可重复,也可用逗号/中文逗号/顿号分隔 | card: modify body 的 `zoneData.row`;泛化: body 的 `dimensions` | 日期粒度写作 `<时间字段> toMonth` 等;必须来自指标详情 |
40
+ | 筛选 | `--filter` / `--filter-json` / `--filter-file` 三选一 | data body 的 `filters` | 不得混用;字段必须能解析真实 `fdId` |
41
+ | 条件关系 | `--combine-type AND/OR` | card data body 的 `combineType`;结构化输入先规范化 | 多分支 OR 当前不可执行,只能查看 dry-run 计划 |
42
+ | 排序 | `--sort-asc` / `--sort-desc` 二选一 | card: modify body 的 `zoneData.sorting`;泛化: body 的 `sorting` | 泛化结果列必须是当前模式生成的精确 alias |
43
+ | 行数 | `--limit` | card data body 或 v2 body 的 `limit` | 默认 50 |
44
+ | 结果裁剪 | `--columns` | 仅 CLI 本地 | 不进入 BI 请求体 |
45
+ | 数值展示 | `--precision`、`--value-format` | 仅 CLI 本地渲染 | 不进入 BI 请求体 |
46
+ | 输出 | `-f`、`-o`、`--raw` | 仅 CLI 输出层 | 不改变查询语义;`--raw` 与 explain/dry-run 不兼容 |
47
+ | 计划 | `--dry-run -f json` | 仅查询指标详情 | 快速校验维度/筛选;不构造最终请求体 |
48
+ | 请求解释 | `--explain-requests -f json` | 只读解析详情与 card meta | 调用真实 builder,返回 method/path/body,不执行 POST |
49
+
50
+ `--dry-run` 和 `--explain-requests` 是两个互斥的计划模式,不能同时传。
51
+
52
+ ## 3. 每种模式的最小合法命令
53
+
54
+ ```bash
55
+ # 基础
56
+ guancli metric query <metric_id> --dim 城市 --filter '城市 = 上海' --limit 20
57
+
58
+ # compare:5 × 3 个有意义的值组合
59
+ guancli metric query <metric_id> --dim "订单日期 toMonth" --compare yoy --compare-value rate
60
+
61
+ # XTD:5 个累计粒度
62
+ guancli metric query <metric_id> --dim 日期 --xtd ytd
63
+
64
+ # last
65
+ guancli metric query <metric_id> --dim 城市 --last
66
+
67
+ # recent:5 个单位;可选固定基准日
68
+ guancli metric query <metric_id> --dim "订单日期 toMonth" --recent 3m --recent-base 2026-04-30
69
+
70
+ # percentage:自动层级 / 指定层级
71
+ guancli metric query <metric_id> --dim 城市 --percentage
72
+ guancli metric query <metric_id> --dim 城市 --percentage --percentage-dim 城市
73
+
74
+ # rank:2 个顺序;可选范围维度
75
+ guancli metric query <metric_id> --dim 城市 --rank-top 10 --rank-order desc --rank-dim 城市
76
+
77
+ # 原始高级计算
78
+ guancli metric query <metric_id> --adv-calc-json '{"advType":"XTD","advValue":{"advType":"XTD","granularity":"YEAR","includeCurrentValue":true}}'
79
+ ```
80
+
81
+ 公共维度、筛选、排序、limit 可以叠加到任一模式,但仍受第 2 节约束。不要把同一个公共修饰项复制成新的“主模式”。
82
+
83
+ ## 4. 基础查询的真实请求链
84
+
85
+ 无维度、无排序时:
86
+
87
+ ```text
88
+ GET /api/metric-platform/metrics/{metricId}
89
+ GET /api/card/{cardId}/edit/session
90
+ POST /api/card/{cardId}/edit/{sessionId}/data
91
+ ```
92
+
93
+ 存在维度或排序时增加 card meta 的读取与隔离会话修改:
94
+
95
+ ```text
96
+ GET /api/metric-platform/metrics/{metricId}
97
+ GET /api/card/{cardId}/edit/session
98
+ GET /api/card/{cardId}/edit
99
+ POST /api/oriput/card/{cardId}/edit/{sessionId}/modify
100
+ POST /api/card/{cardId}/edit/{sessionId}/data
101
+ ```
102
+
103
+ 命令:
104
+
105
+ ```bash
106
+ guancli metric query metric_card \
107
+ --dim 城市 --filter '城市 = 上海' --sort-desc 销售额 --limit 10
108
+ ```
109
+
110
+ data 请求体:
111
+
112
+ ```json
113
+ {
114
+ "limit": 10,
115
+ "filters": [
116
+ {
117
+ "name": "城市",
118
+ "fdId": "fd-city",
119
+ "fdType": "STRING",
120
+ "dsId": "ds-1",
121
+ "filterType": "EQ",
122
+ "filterValue": ["上海"]
123
+ }
124
+ ],
125
+ "combineType": "AND"
126
+ }
127
+ ```
128
+
129
+ modify 请求体不是一个固定小 patch,而是保存 card edit meta 的完整读模型,其中以下位置会被真实 builder 改写:
130
+
131
+ ```json
132
+ {
133
+ "content": {
134
+ "meta": {
135
+ "chartMain": {
136
+ "zoneData": {
137
+ "row": [
138
+ {
139
+ "name": "城市",
140
+ "alias": "城市",
141
+ "dsId": "ds-1",
142
+ "fdId": "fd-city",
143
+ "fdType": "STRING",
144
+ "metaType": "DIM",
145
+ "key": "fd-city"
146
+ }
147
+ ],
148
+ "sorting": [
149
+ {
150
+ "name": "销售额",
151
+ "fdId": "fd-sales",
152
+ "fdType": "DOUBLE",
153
+ "metaType": "METRIC",
154
+ "key": "fd-sales",
155
+ "ordering": "desc"
156
+ }
157
+ ]
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ 真实 body 还会保留 card meta 的其它字段,所以必须以 `--explain-requests` 输出为准,不能把上面的裁剪示例直接 POST。
166
+
167
+ ## 5. 泛化查询的统一请求体
168
+
169
+ 所有泛化模式都使用:
170
+
171
+ ```text
172
+ GET /api/server-version
173
+ GET /api/metric-platform/metrics/{metricId}
174
+ POST /api/metric-platform/metric-data/v2
175
+ ```
176
+
177
+ 统一外层:
178
+
179
+ ```json
180
+ {
181
+ "projectId": "project-1",
182
+ "metricItems": [
183
+ {
184
+ "name": "销售额",
185
+ "alias": "销售额_<模式后缀>",
186
+ "advCalc": {"<见下表>": "<模式请求体>"}
187
+ }
188
+ ],
189
+ "dimensions": [],
190
+ "filters": [],
191
+ "sorting": [],
192
+ "limit": 50
193
+ }
194
+ ```
195
+
196
+ 空的 `dimensions/filters/sorting` 会省略。以下列出所有结构化主模式生成的 `advCalc`。
197
+
198
+ ### compare
199
+
200
+ `--compare` 映射:`yoy → YEAR`、`mom → MONTH`、`qoq → QUARTER`、`wow → WEEK`、`dod → DAY`。`--compare-value` 映射:`value → VALUE`、`rate → RATE`、`rawdata → RAWDATA`。
201
+
202
+ ```json
203
+ {
204
+ "advType": "COMPARATIVE",
205
+ "advValue": {
206
+ "advType": "COMPARATIVE",
207
+ "dateFdId": "fd-date",
208
+ "valueType": "RATE",
209
+ "offset": 1,
210
+ "offsetType": "YEAR",
211
+ "granularity": "YEAR"
212
+ }
213
+ }
214
+ ```
215
+
216
+ alias 为 `<指标名>_yoy/mom/qoq/wow/dod`。
217
+
218
+ ### XTD
219
+
220
+ `ytd/qtd/mtd/wtd/dtd` 分别映射 `YEAR/QUARTER/MONTH/WEEK/DAY`。
221
+
222
+ ```json
223
+ {
224
+ "advType": "XTD",
225
+ "advValue": {
226
+ "advType": "XTD",
227
+ "granularity": "YEAR",
228
+ "includeCurrentValue": true
229
+ }
230
+ }
231
+ ```
232
+
233
+ alias 为 `<指标名>_ytd/qtd/mtd/wtd/dtd`。
234
+
235
+ ### last
236
+
237
+ ```json
238
+ {
239
+ "advType": "LAST",
240
+ "advValue": {
241
+ "advType": "LAST",
242
+ "dateFdId": "fd-date"
243
+ }
244
+ }
245
+ ```
246
+
247
+ alias 为 `<指标名>_last`。
248
+
249
+ ### recent
250
+
251
+ `d/w/m/q/y` 分别映射 `DAY/WEEK/MONTH/QUARTER/YEAR`,`baselineOffset` 为 `[N-1, 0]`。
252
+
253
+ ```json
254
+ {
255
+ "advType": "RECENT_X",
256
+ "advValue": {
257
+ "advType": "RECENT_X",
258
+ "granularity": "MONTH",
259
+ "baselineOffset": [2, 0],
260
+ "includeCurrentValue": true,
261
+ "baseline": "2026-04-30",
262
+ "actualFinestDimGranularityOpt": "DAY"
263
+ }
264
+ }
265
+ ```
266
+
267
+ `baseline` 仅在传 `--recent-base` 时出现;查询维度比 recent 窗口更细时才出现 `actualFinestDimGranularityOpt`。指定 base 时还会自动把对应日期 `BT` 范围加入外层 `filters`,并在没有显式排序时按日期倒序。alias 为 `<指标名>_recent_<原 recent 值>`。
268
+
269
+ ### percentage
270
+
271
+ 自动层级:
272
+
273
+ ```json
274
+ {"advType":"LEVEL_PERCENTAGE","advValue":{"advType":"LEVEL_PERCENTAGE","auto":true}}
275
+ ```
276
+
277
+ 指定 `--percentage-dim 城市`:
278
+
279
+ ```json
280
+ {"advType":"LEVEL_PERCENTAGE","advValue":{"advType":"LEVEL_PERCENTAGE","auto":false,"tfdId":"fd-city"}}
281
+ ```
282
+
283
+ alias 为 `<指标名>_percentage`。
284
+
285
+ ### rank
286
+
287
+ ```json
288
+ {
289
+ "advType": "RANK",
290
+ "advValue": {
291
+ "advType": "RANK",
292
+ "calDimension": "FIELD",
293
+ "rankType": "RANK",
294
+ "sorting": "DESC",
295
+ "tfdId": "fd-city",
296
+ "topN": {
297
+ "topType": "CUSTOM_NUMBER",
298
+ "count": 10,
299
+ "calculationType": "COUNT"
300
+ }
301
+ }
302
+ }
303
+ ```
304
+
305
+ 不传 `--rank-dim` 时省略 `tfdId`;`asc/desc` 映射 `ASC/DESC`。alias 为 `<指标名>_rank`。
306
+
307
+ ## 6. `--explain-requests` 输出契约
308
+
309
+ 输出仍是 metric JSON envelope,`command=metric.query.explain`。关键字段:
310
+
311
+ ```json
312
+ {
313
+ "schemaVersion": "1.0",
314
+ "command": "metric.query.explain",
315
+ "resultMode": "inline",
316
+ "data": {
317
+ "valid": true,
318
+ "executable": true,
319
+ "queryMode": "card",
320
+ "requestPlan": {
321
+ "fullyResolved": false,
322
+ "httpCallCount": 5,
323
+ "requests": [
324
+ {
325
+ "method": "POST",
326
+ "path": "/api/oriput/card/card-1/edit/{sessionId}/modify",
327
+ "purpose": "在隔离会话中应用维度与排序",
328
+ "mutating": true,
329
+ "placeholders": ["sessionId"],
330
+ "body": {}
331
+ }
332
+ ],
333
+ "clientOnlyOptions": {
334
+ "columns": [],
335
+ "precision": 6,
336
+ "valueFormat": "formatted",
337
+ "format": "json"
338
+ }
339
+ }
340
+ }
341
+ }
342
+ ```
343
+
344
+ - `fullyResolved=false` 只表示真实执行时生成的 `sessionId` 仍是占位符;body 已按当前详情/card meta 构造。
345
+ - `mutating=true` 描述真实执行时该接口会改变服务端状态,包括创建临时 edit session 或修改隔离编辑会话;不能按 HTTP method 推断。它不表示 explain 已执行该请求。
346
+ - explain 自身只发 GET。任何 POST 都只出现在计划里。
347
+ - 认证、Cookie、请求头以及为补齐认证上下文产生的辅助读取不属于 query 请求契约,不会出现在 `requests[]`。
348
+ - `clientOnlyOptions` 明确列出不进入 BI 请求体的选项,避免 Agent 把它们伪造进 body。
349
+ - explain 通过不代表权限、实时数据或后端执行一定成功;它只证明当前客户端能构造请求,并完成已执行的只读预检。
350
+
351
+ ## 7. Agent 固定工作流
352
+
353
+ 1. 用 `metric get <metric_id> --brief -f json` 确认真实维度、时间字段和指标名。
354
+ 2. 从第 1 节选择且只选择一个主模式。
355
+ 3. 按第 2 节添加公共修饰参数;筛选输入三选一、排序二选一。
356
+ 4. Agent 对任意单条 `metric query`(包括无参数基础查询)都先用同一组参数执行 `--explain-requests -f json`;`batch-query` 使用独立批量契约且不支持 explain。
357
+ 5. 只在 explain 返回 `valid=true`、`executable=true` 且请求体符合业务意图后,移除 `--explain-requests` 执行真实查询。
358
+ 6. 若 CLI 返回参数错误,只根据帮助、本文矩阵或稳定错误码修正一次;不要连续猜相近 flag。