--- name: logic-planner description: 当需要把一份已校验的数据需求(候选表/字段/join 关联已确认)拆解为 SQL 生成器可机械执行的步骤计划时使用。触发词包括"拆解逻辑"、"执行计划"、"logic plan"、"SQL 编排"、"怎么算"、"指标拆解"、"join 顺序"。消费 metadata-validator 的 Validation Result,产出固定步骤分类法(Source / Filter / Join / Transform / Aggregate / Output)的 Logic Plan。**不写 SQL**。 --- # Logic Planner(逻辑规划器) ## 概述(Overview) 将一份已校验的数据需求分解为固定分类法下的确定性步骤流水线。Logic Plan 是 `sql-context-builder` 拼装 SQL Context **唯一**的输入 — 下游不允许再做任何业务推理。 核心原则:**只拆分,不翻译。** 本 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"]; } ``` ## 必须给出的推理(Required Reasoning,不可跳过) 在输出 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` 以上任意一项不清楚时,加入 `pending_question` 并停止。绝不臆测。 ## 输出契约(Output Contract) ```yaml logic_plan: grain: "<一句话描述输出一行代表什么>" fact_table: dimension_tables: - steps: - kind: source alias: o table: loan_order columns: [customer_id, loan_amount, apply_time, status] - kind: filter alias: o predicates: - "o.apply_time >= ''" - "o.apply_time < ''" - "o.status = 'SUCCESS'" - kind: join left: { alias: o } right: { alias: c, table: customer_info } keys: ["o.customer_id = c.customer_id"] type: left cardinality_assumption: 1:N # 已知时填;否则写 "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"] 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: ``` ## 禁止行为(Forbidden Behaviors) | 自我说服 | 现实 | |---|---| | "我直接写 SELECT 列表,用户能读懂 SQL" | Logic Plan 用业务术语,SQL 是下游的事。 | | "聚合太显然,公式就省了" | 显式给出分子/分母才能避免静默的指标漂移。 | | "1:N 还是 N:N 对 SELECT 来说无所谓" | 它决定了是否要去重以及是否有膨胀风险,影响很大。 | | "时间粒度是用户的问题" | 必须由规划器选定一个粒度,选错了数字就全错。 | | "HAVING 我到 SQL 那一步再加" | 所有过滤(含聚合后)都必须在 Logic Plan 中列出。 | ## 完成判定(Completion Criteria) 要输出状态 `PLANNED`,必须满足: - 粒度用一句话描述,不是一段话 - 每个 `field_mapping` 条目都被恰好一个 step 引用 - 每个 join 都有 cardinality 假设 - 每个指标都有显式 `agg` 和 `alias` - `pending_questions` 为空 否则:状态为 `NEED_USER_CONFIRMATION` 并停止。**不得**进入 `sql-context-builder`。