Files
opencode-build/skills/logic-planner/SKILL.md
T
2026-06-22 10:36:30 +08:00

7.5 KiB

name, description
name description
logic-planner 当需要把一份已校验的数据需求(候选表/字段/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-validatorValidation 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)

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)

logic_plan:
  grain: "<一句话描述输出一行代表什么>"
  fact_table: <table_name>
  dimension_tables:
    - <table_name>
  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"]
      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"]
      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>

禁止行为(Forbidden Behaviors)

自我说服 现实
"我直接写 SELECT 列表,用户能读懂 SQL" Logic Plan 用业务术语,SQL 是下游的事。
"聚合太显然,公式就省了" 显式给出分子/分母才能避免静默的指标漂移。
"1:N 还是 N:N 对 SELECT 来说无所谓" 它决定了是否要去重以及是否有膨胀风险,影响很大。
"时间粒度是用户的问题" 必须由规划器选定一个粒度,选错了数字就全错。
"HAVING 我到 SQL 那一步再加" 所有过滤(含聚合后)都必须在 Logic Plan 中列出。

完成判定(Completion Criteria)

要输出状态 PLANNED,必须满足:

  • 粒度用一句话描述,不是一段话
  • 每个 field_mapping 条目都被恰好一个 step 引用
  • 每个 join 都有 cardinality 假设
  • 每个指标都有显式 aggalias
  • pending_questions 为空

否则:状态为 NEED_USER_CONFIRMATION 并停止。不得进入 sql-context-builder