# 指标表达式 / 计算公式支持范围

本文件用于创建或编辑指标时约束 AI 生成公式的形态，覆盖：

- 原子指标 `formulaField.fdFormula`
- 复合指标 `formula`
- 衍生指标选择“组合衍生”时的 `deriveSetting.formula`

## 总体原则

- 用户给出中文字段名或指标名时，最终 payload 必须替换成 ID 引用：数据集字段写 `[fdId]`，指标写 `[metricId]`。
- 优先生成 Web 编辑器明确支持的简单表达式；复杂函数、嵌套函数、窗口函数、自定义 SQL 函数不要主动生成。
- 公式中使用英文半角符号：`+ - * / ( ) [ ]`。
- 不要把指标中文名直接提交到后端；中文名只用于向用户展示“可读公式”。
- 条件表达式优先使用 `CASE WHEN ... THEN ... ELSE ... END`。虽然底层数据集公式解析能识别部分 `IF(condition, trueValue, falseValue)` 写法，但 AI 生成指标表达式时默认转成 `CASE WHEN`，兼容性更稳定。
- 如果用户坚持使用本文件未列出的公式形态，先提示可能被前端/后端校验拦截，建议用户在 Web 公式编辑器中校验或先保存草稿验证。

## 原子指标表达式 `formulaField.fdFormula`

原子指标选择“使用表达式”时，表达式走数据集公式编辑器和后端数据集派生字段校验。后端会把公式当成数据集计算字段检查，并要求最终字段是聚合表达式；没有聚合函数会报“原子指标表达式必须包含聚合函数”。

遇到计算表达式时，先询问用户是否希望创建复合指标。用户确认创建复合指标后，再创建或复用表达式中的基础原子指标，并用 `[metricId]` 生成复合指标公式。用户选择原子表达式或明确要求数据集字段表达式时，才在 `formulaField.fdFormula` 中使用 `[fdId]`。

### 推荐支持形态

| 形态 | 示例 | 说明 |
| --- | --- | --- |
| 单聚合 | `SUM([fd_amount])` | 最常用 |
| 计数 | `COUNT([fd_order_id])` | 计数类表达式 |
| 聚合项加减 | `SUM([fd_amount]) - SUM([fd_refund])` | 多个聚合结果加减 |
| 聚合内部加减 | `SUM([fd_price] + [fd_tax] - [fd_discount])` | 字段先加减再聚合 |
| 条件聚合 | `SUM(CASE WHEN [fd_status] = 'paid' THEN [fd_amount] ELSE 0 END)` | 支持 `CASE WHEN`、比较、`AND/OR/NOT` |
| 条件计数 | `COUNT(CASE WHEN [fd_status] = 'paid' THEN 1 ELSE 0 END)` | 条件计数 |
| 动态参数 | `SUM(CASE WHEN [fd_amount] > [DYNAMIC_PARAMS.N] THEN [fd_amount] ELSE 0 END)` | 同时在顶层 `dynamicParameters` 写默认值 |

### 避免生成

- 无聚合表达式：`[fd_amount] + [fd_tax]`。
- 乘法：`SUM([fd_price] * [fd_qty])`、`SUM([fd_a]) * SUM([fd_b])`。
- 在未询问用户前，直接把可拆分的最终计算指标写成原子表达式。应先确认是否改用基础原子指标 + 复合指标。
- 非顶层除法：`SUM([fd_a]) + SUM([fd_b]) / SUM([fd_c])`。
- 多重除法：`SUM([fd_a]) / SUM([fd_b]) / SUM([fd_c])`。
- `AVG/MAX/MIN/FIRST/LAST/ANY_VALUE` 等聚合函数表达式；如果用户要平均值、最大值、最小值，优先走“来源字段 + `aggrType=AVG/MAX/MIN`”。
- 窗口函数、子查询、跨数据集字段、字段中文名直接入参。

## `IF` 语法转 `CASE WHEN`

当用户用自然语言或公式写出 `if` / `IF` 条件语法时，不要照抄 `IF(...)` 到指标 payload，默认转换为 `CASE WHEN`：

| 用户写法 | 生成写法 |
| --- | --- |
| `IF(condition, trueValue, falseValue)` | `CASE WHEN condition THEN trueValue ELSE falseValue END` |
| `SUM(IF([fd_status] = 'paid', [fd_amount], 0))` | `SUM(CASE WHEN [fd_status] = 'paid' THEN [fd_amount] ELSE 0 END)` |
| `COUNT(IF([fd_status] = 'paid', 1, NULL))` | `COUNT(CASE WHEN [fd_status] = 'paid' THEN 1 ELSE NULL END)` |
| `IF(SUM([fd_a]) > 0, SUM([fd_b]), 0)` | `CASE WHEN SUM([fd_a]) > 0 THEN SUM([fd_b]) ELSE 0 END` |

嵌套 `IF` 转多分支 `CASE WHEN`：

```text
IF(c1, v1, IF(c2, v2, v3))
```

转换为：

```text
CASE
  WHEN c1 THEN v1
  WHEN c2 THEN v2
  ELSE v3
END
```

转换注意事项：

- 必须保留原条件里的比较、`AND/OR/NOT`、字符串字面量和动态参数。
- 三个参数分别是条件、命中值、未命中值；遇到逗号嵌套在括号或字符串中时，不要错误拆分。
- 原子指标表达式仍必须满足聚合要求：最终表达式要包含 `SUM` 或 `COUNT` 等聚合上下文。
- 如果用户给出的 `IF` 结构无法可靠拆分，先向用户确认条件、命中值和未命中值，再生成 `CASE WHEN`。

## 复合指标公式 `formula`

复合指标公式组合已有指标。Web 公式编辑器会把 `[指标名称]` 转成 `[metricId]`；CLI 生成 payload 时必须直接使用 `[metricId]`。

### 推荐支持形态

| 形态 | 示例 |
| --- | --- |
| 单指标引用 | `[metric_sales]` |
| 加减乘除 | `[metric_sales] / [metric_orders]` |
| 数字常量 | `[metric_sales] * 2 + 100` |
| 括号分组 | `([metric_sales] - [metric_refund]) / [metric_orders]` |

### 引用限制

- 可引用已上线且当前用户可使用的原子指标、复合指标。
- 不要引用当前正在编辑的指标本身。
- 不要引用快捷引用指标（有 `linkMetricId` 的指标）。
- 公式中的 `[xxx]` 会被后端识别为上游指标 ID，并参与循环引用校验。
- 复合指标适用维度必须来自公式中引用指标的共有维度；没有共有维度时会报错。

### 避免生成

- 指标中文名入参：`[销售额] / [订单数]` 只能用于展示，payload 必须替换成 `[metricId] / [metricId]`。
- 数据集字段引用：`[fd_amount]`。
- 复杂函数、窗口函数、SQL 片段、字符串拼接。

## 衍生指标：组合衍生 `deriveSetting.formula`

组合衍生公式也是指标公式，但引用范围比复合指标更窄。

### 推荐支持形态

同复合指标：`+ - * / ( )`、数字常量、`[metricId]`。

示例：

```text
[metric_recent_sales] / [metric_recent_orders]
([metric_xtd_sales] - [metric_xtd_refund]) / [metric_last_inventory]
```

### 引用限制

- 可引用原子指标。
- 可引用普通衍生指标中 `RECENT_X`（最近 N）、`XTD`（累计计算）、`LAST`（期末值）。
- 不要引用组合衍生指标。
- 不要引用复合指标、快捷引用指标或当前正在编辑的指标。
- 公式引用的指标必须存在共有时间维度；没有共有时间维度时 Web 会提示“源指标没有共有时间维度，不可配置衍生指标”。

### 避免生成

- 任意数据集字段引用。
- 函数、窗口函数、SQL 片段。
- 引用同比/环比、业务限定、组合衍生等不在允许范围内的衍生指标。

## 后端公式合法性校验的安全子集

部分场景（例如指标树归因的贡献分析）会使用更严格的后端公式合法性校验。为了让新建指标后尽量兼容这些场景，AI 默认按以下安全子集生成：

- 原子表达式：`SUM`、`COUNT`、加减、条件表达式；如果有除法，整个表达式必须是一个顶层 `分子 / 分母`，且分子分母分别满足前述规则。
- 复合指标：最终展开到原子指标后，也应能落到上述安全子集。
- 衍生组合：优先只做指标间的四则运算；需要参与归因时，优先避免乘法和非顶层除法。

如果用户明确不需要归因/贡献分析，可以按 Web 创建表单的基础限制放宽，但仍应先保存草稿或 dry-run/校验。
