# Indicator Query

> 通过预置指标API查询HR数据。当用户需要查询涉及计算逻辑的数据（如比率、占比、平均值、人均、趋势对比、流入流出率等）时优先使用本Skill。指标口径经过业务验证，比手写SQL更准确。

- Skill: `infometa/indicator-query` (Agent Skill)
- Install (CLI): `npx skillmds@latest add infometa/indicator-query`
- Raw SKILL.md: https://api.skillmd.com/api/skills/infometa/indicator-query/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: infometa (https://skillmd.com/u/infometa)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/infometa/indicator-query

---


## 数据资源说明（indicator）

本 Skill 基于 `indicator` 资源（一个个预置指标的 API）执行查询：

- **内容覆盖度**：数量较少，主要覆盖占比、比例、均值等**有上级组织对比分析需求**的指标。
- **授权特点**：授权角色有限，主要授予 BP 相关角色。行权限在负责组织范围基础上，**支持上级组织指标查询**——如负责某部门的 BP，可通过本路径查询上级线、BG 的对比数据（这是相对 table/SQL 的关键优势）。
- **查询特点**：支持基于每个指标预定义好的维度进行条件查询或分组统计，相比 SQL 查询更简单、高效。

> 当问题涉及「向上对比 / 本组织范围外的统计」而 table 行权限不足时，应优先选择本路径。

### `slang_query`（业务术语）的使用边界

- **指标整体统计口径无需查询术语**：indicator 每个指标的统计口径（分子/分母定义、计算公式，如"离职率"的分子分母如何界定）已由其 `api_code` 在系统内封装完毕，直接调用即得到准确结果，**不需要**像 `hr-data-sql-builder` 那样再调用 `slang_query` 去确认整体统计口径。
- **仅用于参数取值环节的业务术语映射**：当 Step-3 处理某个参数的值时，若用户问题中使用的是业务简称/俗称（而非维度表规范码值本身），应调用 `slang_query` 查询该术语定义，据此确定其对应的规范维度值，再进行取值匹配——例如用户问"校招离职率"，"校招"本身不是 `recruitmentType`（招聘类型）维度下的字面码值，需先用 `slang_query` 确认"校招"对应"校园招聘"，再以"校园招聘"去匹配维度取值，而不是直接拿"校招"两字去模糊匹配码值表，避免匹配不准。

## 指标权限项编码对照表

各指标在权限中台对应有独立的权限项编码，可用于在**已知用户身份**的情况下核实用户是否拥有对应指标的访问权限：

| 指标名称 | 权限项编码 |
|---|---|
| 在职人数各维度占比 | `Menu_Insight_Indicator_ER_Staff_Count_Rate_By_Dimensions` |
| 离职人次数各维度占比 | `Menu_Insight_Indicator_ER_Dimission_Count_Rate_By_Dimensions` |
| 入职人次数各维度占比 | `Menu_Insight_Indicator_ER_Hire_Count_Rate_By_Dimensions` |
| 调入人次数各维度占比 | `Menu_Insight_Indicator_ER_Transfer_In_Count_Proportion` |
| 调出人次数各维度占比 | `Menu_Insight_Indicator_ER_Transfer_Out_Count_Proportion` |
| 流入人次数各维度占比 | `Menu_Insight_Indicator_ER_InflowCountProportion` |
| 流出人次数各维度占比 | `Menu_Insight_Indicator_ER_OutflowCountProportion` |
| 流出率 | `Menu_Insight_Indicator_ER_OutflowRate` |
| 流入率 | `Menu_Insight_Indicator_ER_InflowRate` |
| 离职率 | `Menu_Insight_Indicator_ER_DimissionRate` |
| 平均年龄 | `Menu_Insight_Indicator_ER_AvgAge` |
| 平均工龄 | `Menu_Insight_Indicator_ER_AvgCareerDuration` |
| 平均司龄 | `Menu_Insight_Indicator_ER_AvgSeniorityDuration` |
| 离职率（带梯队分析） | `Menu_Insight_Indicator_ER_DimissionRate_Talent` |
| 离职人次数各维度占比（带梯队分析） | `Menu_Insight_Indicator_ER_Dimission_Count_Rate_By_Dimensions_Talent` |

> 当前尚无独立的权限项编码查询工具，本表暂作为参考映射维护；待具备基于编码核实用户权限的查询能力后，可将其补充为 Step-1 指标匹配后的前置权限判断依据。现阶段指标的实际授权情况仍以 Step-6 对 `rowPowerResult`/`checkPowerResult` 的实时校验结果为准。

# 指标查询工作流

加载本 Skill 后，严格按以下工作流顺序执行，禁止跳步。

---

## Step-1：读取指标列表 + 匹配（含指标簇识别与候选排序）

**执行动作：**
1. 检查 MCP 服务 `hr_data_service_v1` 是否可用（不可用 → 引导用户连接，终止）
2. 读取 MCP resource `data-view://indicators`（可用指标列表），将用户问题与返回的 `indicator_name` / `indicator_definition` 语义匹配，找到合适的指标及其 `api_code`、`table_name`
3. 通过 `data-view://indicators/{api_code}` 获取该指标的详细参数定义（`parameters[]`）

> ⚠️ **匹配范围要宽松，不要用字面关键词卡死**：语义匹配应基于 `indicator_name`/`indicator_definition` 的含义判断，而非要求用户问题逐字命中指标名。例如"离职率""流失率"等问题应命中名为"离职率"的指标，不要求问题中出现"率"以外的额外字面提示词。若第一次匹配为空，应再检查是否因用词差异（同义词、简称）导致漏判，而非直接判定无匹配。

**分支：**
- **无任何匹配** → 终止本 Skill，切换到 `hr-data-sql-builder`
- **匹配成功，且仅一个候选** → 记录 `api_code`、`table_name`、`parameters[]`，并对照「指标权限项编码对照表」记录该指标的权限项编码（供参考，暂不作为前置判断依据），进入 Step-2
- **匹配成功，且存在「指标簇」（同一核心指标的多个版本）** → 执行下方「指标簇识别与候选排序」，产出**有序候选列表**，取列表首位进入 Step-2；其余候选保留供 Step-6 权限失败时依次重试

### 指标簇识别与候选排序

**识别规则**：若语义匹配命中的多个指标，其核心统计口径相同、仅命名上存在"基础版"与"扩展版"关系（扩展版名称通常在基础版名称后追加括注，如"XX（带梯队分析）"），则判定为同一「指标簇」。典型示例（参见「指标权限项编码对照表」）：
- 离职率 / 离职率（带梯队分析）
- 离职人次数各维度占比 / 离职人次数各维度占比（带梯队分析）

**候选排序规则**：
1. 若用户问题**明确提及**扩展版对应的额外维度语义（如提到"梯队""九宫格"等）→ 扩展版排在候选列表首位，基础版排第二。
2. 若用户问题**未提及**扩展版的额外维度语义 → 基础版排候选列表首位（维度更通用、权限覆盖角色通常更广），扩展版排第二作为备选（当基础版权限失败时，扩展版的更大维度集合仍有机会覆盖同一统计需求）。
3. 两个候选均保留在列表中，不因排序丢弃任一候选。

**产出**：`candidates = [{api_code, table_name, parameters[]}, ...]`（按上述规则排序），进入 Step-2 时仅处理 `candidates[0]`，并记录 `triedIndex = 0` 供 Step-6 使用。

---

## Step-2：理解用户意图

**执行动作：** 分析用户问题，提取三类信息：

```
1. 统计范围：在什么范围内计算？
   → 组织（如"人力资源平台部"）、时间（如"2025年Q1"、"当月"）
   
2. 筛选子集：用户关心的是哪个子集？
   → 如"校招员工""女性""T9以上"
   → 若无特定子集 → 标记为"无筛选"

3. 分组维度：用户是否要按某维度拆分查看？
   → 识别关键词："各XX""按XX""XX分布""XX维度"
   → 如"各部门""按职级""各离职原因"
   → 若无分组需求 → 标记为"无分组"
```

**产出：** 三元组（范围、筛选、分组）的自然语言描述


## Step-3：逐参数处理

**执行动作：** 读取 MCP resource `data-view://dimensions`。然后遍历 Step-1 中匹配指标的 `parameters`，对每个参数**一次性完成**取值、定角色、放位置三件事。

> 跳过 `staffId`（工具自动注入）。

**对每个参数，执行以下流水线：**

```
─── ① 该参数与用户问题有关吗？ ───
    特殊参数优先判断（命中即按对应规则处理，不进入下方"无关/相关"通用判断）：

    a) org（组织）参数：必填，固定对应维度表 `a370651772b848cfa5dc7ef602243d69`
        → 跳过本步其余判断及①.5，直接进入②按「取值优先级」取值

    b) 默认口径参数（managerUnit / staffSubType）：
        IF 用户问题中未显式提及该参数对应内容（即未主动指定管理主体/员工子类型，如未说"外包""子公司"等）：
            → 直接注入固定默认值：managerUnit = ["腾讯集团本部"]，staffSubType = ["正式聘用制"]
            → 跳过①.5、②（默认值为固定字符串，无需术语映射/查库），带值直接进入③
            → 进入③后按该参数自身的 common_query / is_required 属性走正常分支判断（可能落入 commonParam，也可能落入角色判断），
              **唯一强制要求**：若最终落入角色判断分支，必须归为角色A"范围"，且不得被③末尾"用户没有提到的非必填参数→跳过"兜底规则跳过（该值已由①强制赋值，视同"已有值"，不属于兜底规则适用范围）
        ELSE（用户显式提及，要求放开默认口径）：
            → 视为用户已提供该参数取值，正常进入①.5→②→③（按用户实际指定值处理，不再套用默认值）

    其余参数，按下方通用规则判断：
    无关且非必填 → SKIP
    无关但必填   → 使用 default_value（无则向用户确认）

─── ①.5 参数值是否为业务简称/俗称？（先于②取值） ───
    IF 用户问题中该参数对应的值是业务简称/俗称（非维度表规范码值本身，如"校招""T9+"）：
        → 调用 slang_query 查询该术语定义，确认其对应的规范维度值
        → 用规范值替换简称，再进入②按规范值取值/匹配
    ELSE（用户已用规范表述，或该参数是org/时间等无需术语映射的类型）：
        → 跳过，直接进入②

    ⚠️ 此处 slang_query 仅用于**参数取值的术语映射**，不用于确认指标整体统计口径
      （指标口径已由 api_code 封装完毕，无需重复查术语定义，详见文首「slang_query 的使用边界」）

─── ② 取值 ───
    IF parameter_type == "List<String>":
        绝对禁止用中文名！
        在 data-view://dimensions 中找 dimension_code == parameter_code 的记录
        取 sql_script + 添加 WHERE 条件匹配用户关键词（若①.5已转换为规范值，用规范值匹配）
        调用 starrocks_query 执行 → 提取 ID
        值 = ["ID"]（数组格式）
    
    ELSE (string/date/int/Float):
        先在 data-view://dimensions 中查找 dimension_code == parameter_code 的记录
        IF 找到且有 sql_script 或 dimension_desc:
            → 参考 sql_script 查询可用值，或参考 dimension_desc 了解取值规则
        ELSE:
            → 从用户输入直接提取
        值 = "字符串" 或 数字

        ⚠️ 若该参数为 org（组织）类型（已在①中确认跳过其余判断，直接进入本步取值）：
        indicator API 内部使用 8 位补零组织码（orgId8，如 "00002234"）。

        取值优先级（命中即停，不再往下走）：
        1. **会话级缓存**：本轮对话中此前已解析过的"组织名→orgId8"映射，直接复用（首次通过②③级拿到值后即记入此缓存，仅本轮对话有效；候选簇重试时同样优先查此缓存，见 Step-6）。
        2. **静态缓存表**：见文末「附录：组织ID静态缓存表」（覆盖顶级节点+9个BG/职能线+40个线级/子公司实体，共50个组织），命中直接用缓存ID，无需查库。
        3. **查库**：①②均未命中时（如具体部门/中心/组等更细粒度组织），才查 `sql_script` → `starrocks_query`；若问题涉及**多个**组织名，须**合并为一次SQL**批量取值（`IN` 精确匹配或多个 `LIKE` 以 `OR` 拼接），禁止逐个查询；查得结果记入会话级缓存供后续复用。

        无论通过以上哪一级拿到的原始ID，若来源非 orgId8 格式（如来自 `get_current_user_data_permission` 返回的 dataScopes.Org 短码"2234"），必须先左侧补 "0" 转换为8位后再赋值（"2234" → "00002234"）。特殊值 "Org-All"、"global" 按原样处理，不参与补零。

─── ③ 定角色 + 放位置 ───
    IF parameter.common_query == true:
        → commonParam[code] = 值（结束）

    ELSE IF parameter.is_required == true AND (parameter.fenzi_query == true OR parameter.fenmu_query == true):
        → 必填参数优先规则，跳过下方角色判断：
        → numeratorParams[code] = 值
        → denominatorParams[code] = 值（与分子同值）

    ELSE 根据该参数在用户问题中的角色：
    
        角色A "范围"（org、时间、managerUnit、staffSubType 参数 — 界定统计范围）：
            → numeratorParams[code] = 值
            → denominatorParams[code] = 值
        
        角色B "筛选"（用户要看占比的那个子集，如招聘类型、性别）：
            → numeratorParams[code] = 值
            →（不放 denominatorParams）
        
        角色C "分组"（用户说"各XX"要按此维度拆分）：
            前置校验：parameter.group_by_flag == true？
            校验通过 → groupByList.push(code)
            同时根据指标类型决定是否也作为范围条件放入分子分母

    ※ 角色判断规则：
    - common_query==true 的参数 → 无需判断角色，直接放 commonParam
    - is_required==true 且 fenzi_query/fenmu_query 任一为 true → 分子分母都放且同值，不进入角色判断
    - org、时间、managerUnit、staffSubType 参数 → 永远是"范围"（后两者的默认值注入已在①处理，此处不受"用户没提到→跳过"兜底规则影响）
    - 用户没有提到的非必填参数（不含 managerUnit/staffSubType，二者已由①保底注入默认值） → 跳过
    - 占比/率类指标中，用户提到的人群特征（校招/女性/T9等）→ "筛选"
    - 用户说"各XX"对应的维度 → "分组"
```

> 💡 在本流程中使用 `starrocks_query` 查维度表是正常步骤，不是切换到 hr-data-sql-builder。

**产出：**
```json
{
  "commonParam": { ... },
  "numeratorParams": { ... },
  "denominatorParams": { ... },
  "groupByList": [ ... ]
}
```

---

## Step-4：自检

**逐条确认，全部通过才可调用：**

1. 所有 `List<String>` 值为 ID 格式且用 `[]` 包裹？ → 否：回 Step-3 ②
2. 所有 `is_required=true` 参数已有值？ → 否：补充
3. `is_required=true` 且支持分子/分母查询的参数，是否已**同时**放入 numeratorParams 和 denominatorParams 且**值相同**？ → 否：补齐两边
4. 占比/率类指标：用户的筛选子集条件**只在分子**、未误入分母？ → 否：从 denominatorParams 移除
5. `groupByList` 中的 code 在 parameters 中 `group_by_flag=true`且是否需要分组查询？ → 否：修正
6. 未包含 `staffId`？ → 否：删除
7. org 类型参数是否已转换为8位补零格式（`orgId8`），而非来自权限元数据的短码直传？ → 否：按补零规则转换后再赋值
8. **默认口径已注入？**指标若支持 `managerUnit`/`staffSubType`，是否已注入默认值 `["腾讯集团本部"]`/`["正式聘用制"]`（或用户已显式放开）？ → 否：补注入
9. **分子分母键名对称校验？** numeratorParams 与 denominatorParams 中"范围"类参数的**键名必须逐字一致**（严防把 `managerUnit` 误写成 `managerType`、`flowInBeginDate` 写错等 typo 只污染一侧导致口径不对齐）。逐一比对两侧的 key 集合是否完全相同、同名 key 的值是否一致。 → 否：修正拼写并对齐两侧

---

## Step-5：调用 indicator_query

```
调用工具: indicator_query
参数:
  apiCode = Step-1 的 api_code
  queryParams = JSON.stringify(Step-3 产出的对象)
  userQuestion = 用户当前问题原文
```

---

## Step-6：处理返回结果

> **说明**：本步骤（尤其 `indicatorPowerResult`/`checkPowerResult`/`rowPowerResult` 判断）是 indicator 权限校验的**唯一实时验证点**——因 indicator 授权范围目前缺乏独立的前置查询工具（参考「指标权限项编码对照表」，待具备基于编码核实权限的能力后可补充为前置判断依据），故权限是否可用需通过实际调用结果确认。三者任一为 `false` 即代表探测到当前候选指标的真实权限边界，但**不代表整条 indicator 路径不可行**——若 Step-1 识别出该问题命中「指标簇」（存在未尝试的其余候选），必须先切换候选重试，簇内全部候选权限失败后才进入 Step-7 降级。

```
IF statusCode == 200 AND data.data 非空非null:
    → 表格展示 + 说明口径 + 洞察 → 结束

IF statusCode != 200:
    → 读 message，修正参数，回 Step-5 重试1次
    → 仍失败 → 走「候选簇重试判断」

IF statusCode == 200 BUT data.data 为空/null:
    → 检查 List<String> 参数是否误用了中文名？
      是 → 回 Step-3 ② 查维度表获取 ID，再回 Step-5
    → 检查 indicatorPowerResult / checkPowerResult
      false → 走「候选簇重试判断」
    → 重试1次仍为空 → 走「候选簇重试判断」
```

> 单个候选最多重试 1 次，禁止循环。

### 候选簇重试判断（权限失败/查询失败后，先于 Step-7 执行）

```
IF candidates 中存在 index > triedIndex 的未尝试候选:
    → triedIndex += 1
    → 取 candidates[triedIndex] 的 api_code / table_name / parameters
    → 回 Step-3 重新完成参数映射，但**仅需处理"新候选相对上一候选新增/不同的参数"**
      （同簇候选的 org、时间等"范围"类参数通常完全相同，直接复用上一候选已解析的值/已解析ID，
       不要重新查库；仅新候选独有的参数，如扩展版多出的"梯队"维度，需要新走一次①②③流水线）
    → 回 Step-5 调用
    → 结果按本 Step-6 规则重新判断（该候选同样只重试1次）
ELSE（候选簇已全部尝试，或本就只有单一候选无簇）:
    → 记录本次探测到的权限/查询失败原因（用于最终告知用户）
    → 进入 Step-7 降级
```

> ❌ 禁止在候选簇尚有未尝试候选时，就以"无权限"为由提前终止流程并直接告知用户；必须遍历完候选簇才可判定 indicator 路径不可行。
> 💡 同簇候选参数复用与「会话级动态缓存」共享同一份"组织名→orgId8"映射，无需重复维护。

---

## Step-7：降级到 hr-data-sql-builder

**触发条件：** Step-6「候选簇重试判断」确认——候选簇（含单候选场景）已全部尝试且均失败。

**执行动作：**
1. 取最后一次尝试候选的 `table_name`（数据底表）
2. 切换到 `hr-data-sql-builder`，指定目标表为 `table_name`
3. 基于用户原始需求构建 SQL 并执行
4. **合并输出**：无论 SQL 执行结果是成功、为空、还是脱敏，均需与"indicator 侧各候选的失败原因"合并组织成一份连贯回答呈现给用户，不要仅停留在"指标无权限"这一句就结束——SQL 兜底结果是本次回答的必要组成部分，而非可选项。

> ❌ 禁止未走完 Step-6「候选簇重试判断」就直接写 SQL。
> ❌ 禁止在给出 SQL 结果前，先单独把"指标无权限"作为最终答案发给用户；两者必须合并为一次完整回答。

---

## 附录：组织ID静态缓存表（orgId8格式，Step-3②取值时直接查此表，命中无需查库）

覆盖范围：腾讯公司顶级节点、9个一级BG/职能线、40个线级/子公司实体，共50个组织。

| 组织名称 | orgId8 | 层级/所属体系 |
|---|---|---|
| 腾讯公司（顶级节点，全公司） | `OA000001` | 顶级节点 |
| TEG技术工程事业群 | `00000958` | BG |
| CSIG云与智慧产业事业群 | `00029294` | BG |
| IEG互动娱乐事业群 | `00000956` | BG |
| WXG微信事业群 | `00014129` | BG |
| PCG平台与内容事业群 | `00029292` | BG |
| CDG企业发展事业群 | `00000953` | BG |
| S1职能系统－职能线 | `00000078` | 职能线 |
| S2职能系统－财经线 | `00002233` | 职能线 |
| S3职能系统－HR与管理线 | `00002234` | 职能线 |
| 子公司组织 | `00055580` | BG级，直属腾讯公司 |
| 腾娱 | `00057238` | 40线，属子公司组织 |
| 运营子公司 | `00058563` | 40线，属子公司组织 |
| 腾卓 | `00061092` | 40线，属子公司组织 |
| 财付通子公司 | `00064053` | 40线，属子公司组织 |
| 恒智信 | `00079597` | 40线，属子公司组织 |
| 腾佳 | `00049518` | 50部门，属子公司组织 |
| 瑞驰 | `00052650` | 50部门，属子公司组织 |
| 实娱商管 | `00055767` | 50部门，属子公司组织 |
| 北境 | `00056518` | 50部门，属子公司组织 |
| 锦鹏 | `00056519` | 50部门，属子公司组织 |
| 内蒙古网信 | `00056723` | 50部门，属子公司组织 |
| 云链 | `00056724` | 50部门，属子公司组织 |
| 大地通途 | `00056725` | 50部门，属子公司组织 |
| 云雀 | `00056767` | 50部门，属子公司组织 |
| 世纪鲲鹏 | `00057433` | 50部门，属子公司组织 |
| 大丰证券 | `00061440` | 50部门，属子公司组织 |
| 萨罗斯 | `00072802` | 50部门，属子公司组织 |
| 银之心 | `00072805` | 50部门，属子公司组织 |
| 光合 | `00072904` | 50部门，属子公司组织 |
| 核娱 | `00072908` | 50部门，属子公司组织 |
| 随乐 | `00102605` | 50部门，属子公司组织 |
| 阿纳海姆 | `00109601` | 50部门，属子公司组织 |
| 互动娱乐发行线 | `00010307` | 40线，属IEG |
| 天美工作室群 | `00015568` | 40线，属IEG |
| 光子工作室群 | `00015576` | 40线，属IEG |
| 魔方工作室群 | `00015593` | 40线，属IEG |
| 北极光工作室群 | `00015613` | 40线，属IEG |
| 支付基础平台与金融应用线 | `00018424` | 40线，属CDG |
| 腾讯音乐娱乐 | `00021526` | 40线，属公司其他组织 |
| 微信支付线 | `00028599` | 40线，属WXG |
| 广告营销服务线 | `00029295` | 40线，属CDG |
| IEG Global | `00041298` | 40线，属IEG |
| 国内发行线 | `00043448` | 40线，属IEG |
| 在线视频BU | `00045280` | 40线，属PCG |
| 可持续社会价值事业部 | `00045320` | 40线，属CDG |
| 社交平台与应用线 | `00046606` | 40线，属PCG |
| PCG技术与内容平台 | `00047728` | 40线，属PCG |
| 腾讯新闻 | `00047790` | 40线，属PCG |
| IEG公共技术线 | `00114443` | 40线，属IEG |

**匹配规则**：用户提到"腾讯公司/全公司/集团整体" → 用 `OA000001`；提到BG/线/子公司的全称或简称（如"CSIG"、"云与智慧产业事业群"、"腾娱"）→ 模糊匹配上表「组织名称」列命中对应ID。

**数据来源与维护**：BG/职能线数据经 `starrocks_query` 实查组织维表 `a370651772b848cfa5dc7ef602243d69` 核实。

