update by wensicheng on 0629

This commit is contained in:
tao.chen
2026-07-01 17:46:08 +08:00
parent 0fc1c702d9
commit 2f406bd64d
12 changed files with 1012 additions and 1574 deletions
+123 -135
View File
@@ -1,161 +1,149 @@
---
name: logic-planner
description: 当需要把一份已校验的数据需求(候选表/字段/join 关联已确认)拆解为 SQL 生成器可机械执行步骤计划时使用。触发词包括"拆解逻辑"、"执行计划"、"logic plan"、"SQL 编排"、"怎么算"、"指标拆解"、"join 顺序"。消费 metadata-validator 的 Validation Result,产出固定步骤分类法(Source / Filter / Join / Transform / Aggregate / Output)的 Logic Plan。**不写 SQL**
description: 当需求和元数据都已经确认,需要把业务逻辑拆成 SQL 生成前的确定性执行步骤时使用。它只规划 source、filter、join、dedupe、transform、window、aggregate、output,不写 SQL,不调用 MCP
---
# Logic Planner(逻辑规划器)
# Logic Planner逻辑规划
## 概述(Overview)
## 目标
将一份已校验的数据需求分解为固定分类法下的确定性步骤流水线。Logic Plan 是 `sql-context-builder` 拼装 SQL Context **唯一**的输入 — 下游不允许再做任何业务推理
把已确认的 DRD 和元数据拆成可执行计算步骤,让后续 SQL 只是翻译计划,而不是重新猜业务逻辑
核心原则:**只拆分,不翻译。** 本 skill 绝不写 SQL 语法,只在业务/逻辑层面描述每一步要发生什么。SQL 翻译是另一回事。
## 输入
## 何时使用(When to Use)
**使用场景:**
- `metadata-validator` 返回状态 `VALIDATED`,需要设计查询如何执行
- 用户询问"这个应该怎么算"、"逻辑是啥"、"一步步怎么走"等数据需求问题
- 写 SQL 之前需要先消除粒度、指标公式、去重、窗口、排名的歧义
**不要使用场景:**
- `metadata-validator` 返回 `NEED_USER_CONFIRMATION` — 先回去解决未决问题
- 用户已经提供了书面执行计划,只是要 SQL — 直接进入 `sql-context-builder`
## 输入(Inputs)
1. 来自 `metadata-validator`**Validation Result**(状态为 `VALIDATED`)
2. 原始业务需求(通常嵌在 `requirements-analysis` 的输出中)
3. `field_mapping`(必备) — 每个业务字段在规划开始前必须先映射到物理 `(table, column, type)`
## 步骤分类法(Step Taxonomy)
每个 Logic Plan 都是一组步骤。每个步骤有且仅有一个 `kind`,且只能从下表选取。对重复操作(如多次 join、多次 filter)复用同一种 kind。
| Kind | 用途 | 必填字段 |
|---|---|---|
| `source` | 从表或子查询读取 | `alias`, `table`, `columns[]` |
| `filter` | 在指定 alias 上应用谓词 | `alias`, `predicates[]` |
| `join` | 在 key 上合并两个 alias | `left`, `right`, `keys[]`, `type` (inner/left/right/full/semi/anti) |
| `transform` | 派生新列(case-when、算术、类型转换) | `alias`, `expressions[]` |
| `aggregate` | 分组聚合 | `alias`, `group_by[]`, `metrics[]` (每个含 `expr`, `alias`, `agg`) |
| `dedupe` | DISTINCT 或基于 row_number 的去重 | `alias`, `keys[]`, `strategy` |
| `window` | 排名 / 累计 / lag-lead | `alias`, `partition_by[]`, `order_by[]`, `window_fn` |
| `output` | 最终投影和排序 | `columns[]`, `order_by[]`, `limit?` |
不要发明新 kind。如果某个变换无法归入上述分类,作为 `pending_question` 暴露并停止。
## 工作流(Workflow)
```dot
digraph logic_planner {
"读取 Validation Result + field_mapping" [shape=box];
"区分事实表 / 维度表,选定主源表" [shape=box];
"列出过滤条件(时间区间、状态、业务谓词)" [shape=box];
"规划 join(alias A ON key = alias B.key)" [shape=box];
"规划 transform(派生列、类型转换)" [shape=box];
"如需去重 / 窗口,一并规划" [shape=box];
"规划 aggregate(粒度 + 指标)" [shape=box];
"规划 output 投影 + 排序" [shape=box];
"还有歧义?" [shape=diamond];
"输出 Logic Plan" [shape=box];
"输出 Logic Plan + pending_questions" [shape=box];
"读取 Validation Result + field_mapping" -> "区分事实表 / 维度表,选定主源表";
"区分事实表 / 维度表,选定主源表" -> "列出过滤条件(时间区间、状态、业务谓词)";
"列出过滤条件(时间区间、状态、业务谓词)" -> "规划 join(alias A ON key = alias B.key)";
"规划 join(alias A ON key = alias B.key)" -> "规划 transform(派生列、类型转换)";
"规划 transform(派生列、类型转换)" -> "如需去重 / 窗口,一并规划";
"如需去重 / 窗口,一并规划" -> "规划 aggregate(粒度 + group_by)";
"规划 aggregate(粒度 + group_by)" -> "规划 output 投影 + 排序";
"规划 output 投影 + 排序" -> "还有歧义?";
"还有歧义?" -> "输出 Logic Plan" [label="no"];
"还有歧义?" -> "输出 Logic Plan + pending_questions" [label="yes"];
}
```yaml
requirements_output:
status: READY_FOR_METADATA
validation_result:
status: VALIDATED
field_mapping: {}
joins: []
```
## 必须给出的推理(Required Reasoning,不可跳过)
如果元数据没有 `VALIDATED`,停止并回到 `metadata-validator`
在输出 plan 之前,你必须显式陈述并记录:
## 步骤类型
1. **粒度(Grain)** — 输出一行代表什么?(例如:一个客户、一个客户×月、一笔订单)
2. **指标公式(Metric formula)** — 对每个指标写出精确表达式:分子与分母都要定义
3. **时间粒度(Time grain)** — 确认时间字段与粒度(天 / 月 / 小时),用闭/开区间说明窗口
4. **Join 基数(Join cardinality)** — 对每个 join,预测 1:1 / 1:N / N:N。如果是 N:N,必须标记并询问用户是否在 join 前去重
5. **去重位置(Dedup position)** — 如果事实表存在行重复(例如一个客户多个地址),在 join 之前决定哪一侧、在哪个 key 上先去重
6. **空值策略(Null strategy)** — 对可空 join key 或度量,决定用 `INNER` 还是 `LEFT + COALESCE`
只允许使用这些 `kind`
以上任意一项不清楚时,加入 `pending_question` 并停止。绝不臆测
- `source`:读取哪张表、哪些字段
- `filter`:时间、分区、状态、业务条件过滤。
- `join`:多表关联。
- `dedupe`:去重,通常在 join 或 aggregate 前。
- `transform`:派生字段、分类、类型转换、标志位。
- `window`:首笔、末笔、排名、累计、lag/lead。
- `aggregate`:分组和指标计算。
- `output`:最终列、排序、limit 或写入意图。
## 输出契约(Output Contract)
## 工作顺序
1. 定义输出粒度:一行代表什么。
2. 选事实表:承载核心事件或分母人群的表。
3. 列出 source:每张表只取必要字段。
4. 尽早放 filter:尤其是时间、分区和状态条件。
5. 固定时间窗口边界:下界包含、上界排除。
6. 规划 join:顺序、key、类型、基数假设。
7. 判断是否需要 dedupe/window:首登、首次注册、首笔成功事件等。
8. 规划 transform:标志位、事件顺序、空值处理。
9. 规划 aggregategroup by、指标公式、别名。
10. 规划 output:输出字段、排序、写入模式。
## 时间窗口硬规则
当需求为“近 N 天按 T-1 完整日”时,必须规划为:
```yaml
time_window:
lower_bound_sql: date_sub(current_date(), N)
upper_bound_sql: current_date()
boundary: left_closed_right_open
predicate_sql: event_time >= date_sub(current_date(), N) AND event_time < current_date()
```
禁止规划为 `BETWEEN ... AND date_add(current_date(), -1)`,尤其是 timestamp 字段。
## 转化/漏斗硬规则
转化类需求必须维护事件顺序。对“注册后成功付费”,推荐计划:
1. `first_login`:取窗口内每个用户的首次登录,形成分母。
2. `registered`:取用户注册事件,必要时去重到一个注册时间。
3. `qualified_payment`:在关联注册事件后筛选 `pay_status = 'SUCCESS' AND pay_time >= register_time`
4. `final_agg`:按维度计算分母、分子和转化率。
不要先对全量 `pay_order` 按用户取 `MIN(pay_time)` 再与 `register_time` 比较;这可能漏掉“历史早付费但注册后也付费”的用户。
## 必须显式写出的风险
- 1:N 或 N:N join 是否会放大事实行。
- 去重发生在 join 前还是 join 后。
- 指标是否需要 `count_distinct` 而不是 `count`
- 时间过滤是否能命中分区字段。
- left join 后维度缺失如何处理。
- 转化事件是否严格满足前后顺序。
- 分母为 0 时比率如何处理。
## 输出格式
```yaml
logic_plan:
grain: "<一句话描述输出一行代表什么>"
fact_table: <table_name>
dimension_tables:
- <table_name>
status: PLANNED | NEED_USER_CONFIRMATION
grain: "结果一行代表什么"
fact_table: "事实表"
dimension_tables: []
time_window:
lower_bound_sql: "date_sub(current_date(), N)"
upper_bound_sql: "current_date()"
predicate_sql: "event_time >= date_sub(current_date(), N) AND event_time < current_date()"
boundary: left_closed_right_open
steps:
- kind: source
alias: o
table: loan_order
columns: [customer_id, loan_amount, apply_time, status]
- kind: filter
alias: o
predicates:
- "o.apply_time >= '<window_start>'"
- "o.apply_time < '<window_end>'"
- "o.status = 'SUCCESS'"
- kind: join
left: { alias: o }
right: { alias: c, table: customer_info }
keys: ["o.customer_id = c.customer_id"]
- id: S1
kind: source
name: first_login
table: "表名"
columns: ["字段"]
reason: "为什么需要"
- id: S2
kind: filter
target: "表名或中间结果"
predicates: ["业务过滤条件"]
partition_pruning: true
- id: S3
kind: join
left: "左表或CTE"
right: "右表或CTE"
keys: ["左字段 = 右字段"]
type: left
cardinality_assumption: 1:N # if known; else state "unknown"
- kind: transform
alias: o
expressions:
- expr: "CASE WHEN o.loan_amount >= 100000 THEN 'large' ELSE 'small' END"
alias: loan_bucket
- kind: aggregate
alias: o
group_by: ["c.city"]
cardinality: "1:1 | 1:N | N:1 | N:N | unknown"
row_growth_risk: low | medium | high
- id: S4
kind: aggregate
group_by: ["维度字段"]
metrics:
- expr: "SUM(o.loan_amount)"
alias: total_loan
agg: sum
- expr: "COUNT(DISTINCT o.customer_id)"
alias: customer_cnt
agg: count_distinct
- kind: output
columns: ["c.city AS city", "total_loan", "customer_cnt"]
order_by: ["total_loan DESC"]
limit: 100
pending_questions:
- "<尚未解决的歧义,会阻塞 SQL 生成>"
status: <PLANNED | NEED_USER_CONFIRMATION>
- name: "指标名"
agg: count_distinct | sum | avg | min | max | ratio
expression: "业务表达式"
alias: "指标别名"
- id: S5
kind: output
columns: ["输出列"]
order_by: []
write_intent: query_only | insert_into | insert_overwrite
assumptions: []
risks: []
pending_questions: []
```
## 禁止行为(Forbidden Behaviors)
## 用户可见摘要
| 自我说服 | 现实 |
|---|---|
| "我直接写 SELECT 列表,用户能读懂 SQL" | Logic Plan 用业务术语,SQL 是下游的事。 |
| "聚合太显然,公式就省了" | 显式给出分子/分母才能避免静默的指标漂移。 |
| "1:N 还是 N:N 对 SELECT 来说无所谓" | 它决定了是否要去重以及是否有膨胀风险,影响很大。 |
| "时间粒度是用户的问题" | 必须由规划器选定一个粒度,选错了数字就全错。 |
| "HAVING 我到 SQL 那一步再加" | 所有过滤(含聚合后)都必须在 Logic Plan 中列出。 |
除了 YAML,必须输出这张表:
## 完成判定(Completion Criteria)
```markdown
## 逻辑计划
| 步骤 | 中间结果 | 类型 | 说明 | 关键条件 |
|---|---|---|---|---|
```
要输出状态 `PLANNED`,必须满足:
## 停止条件
- 粒度用一句话描述,不是一段话
- 每个 `field_mapping` 条目都被恰好一个 step 引用
- 每个 join 都有 cardinality 假设
- 每个指标都有显式 `agg``alias`
- `pending_questions` 为空
否则:状态为 `NEED_USER_CONFIRMATION` 并停止。**不得**进入 `sql-context-builder`
如果粒度、指标公式、join 基数、去重策略、空值策略、时间归属或写入模式不明确,输出 `NEED_USER_CONFIRMATION`