Files
opencode-build/skills/logic-planner/SKILL.md
T
2026-06-18 13:59:01 +08:00

7.6 KiB
Raw Blame History

name, description
name description
logic-planner Use when turning a validated data requirement (candidate tables/fields/joins already confirmed) into a step-by-step execution plan that a SQL generator can implement mechanically. Triggers include "拆解逻辑", "执行计划", "logic plan", "SQL 编排", "怎么算", "指标拆解", "join 顺序". Consumes the Validation Result from metadata-validator and produces a Logic Plan in a fixed step taxonomy (Source / Filter / Join / Transform / Aggregate / Output). Does NOT write SQL.

Logic Planner

Overview

Decompose a validated data requirement into a deterministic pipeline of steps in a fixed taxonomy. The Logic Plan is the only input sql-context-builder needs to assemble a SQL Context — no further business reasoning is permitted downstream.

Core principle: decompose, don't translate. This skill never writes SQL syntax. It only describes what must happen at each step in business/logical terms. The translation to SQL is a separate concern.

When to Use

Use when:

  • metadata-validator returned status VALIDATED and you need to design how the query will run
  • The user asks "how should I compute this", "what's the logic", "step-by-step plan" for a data requirement
  • You need to disambiguate grain, metric formulas, deduplication, windowing, or ranking before SQL

Do NOT use when:

  • metadata-validator returned NEED_USER_CONFIRMATION — go back and resolve open questions first
  • The user has already provided a written execution plan and just needs SQL — go straight to sql-context-builder

Inputs

  1. Validation Result from metadata-validator (status VALIDATED)
  2. The original business requirement (often embedded in requirements-analysis output)
  3. field_mapping (mandatory) — every business field must be mapped to a physical (table, column, type) before planning starts

Step Taxonomy

Every Logic Plan is a list of steps. Each step has exactly one kind from this set. Reuse the same kind for repeated operations (e.g. multiple joins, multiple filters).

Kind Purpose Required fields
source Read from a table or subquery alias, table, columns[]
filter Apply predicates to a named alias alias, predicates[]
join Combine two aliases on keys left, right, keys[], type (inner/left/right/full/semi/anti)
transform Derive new columns (case-when, arithmetic, type cast) alias, expressions[]
aggregate Group and reduce alias, group_by[], metrics[] (each: expr, alias, agg)
dedupe DISTINCT or row_number-based dedup alias, keys[], strategy
window Ranking / running totals / lag-lead alias, partition_by[], order_by[], window_fn
output Final projection and ordering columns[], order_by[], limit?

Do not invent new kinds. If a transformation does not fit, surface it as pending_question and stop.

Workflow

digraph logic_planner {
    "Read Validation Result + field_mapping" [shape=box];
    "Identify fact vs dimension tables & choose primary source" [shape=box];
    "List filters (time range, status, business predicates)" [shape=box];
    "Plan joins (alias A ON key = alias B.key)" [shape=box];
    "Plan transforms (derived columns, type casts)" [shape=box];
    "Plan dedupe / window if needed" [shape=box];
    "Plan aggregate (grain + metrics)" [shape=box];
    "Plan output projection + order" [shape=box];
    "Any ambiguity?" [shape=diamond];
    "Emit Logic Plan" [shape=box];
    "Emit Logic Plan + pending_questions" [shape=box];

    "Read Validation Result + field_mapping" -> "Identify fact vs dimension tables & choose primary source";
    "Identify fact vs dimension tables & choose primary source" -> "List filters (time range, status, business predicates)";
    "List filters (time range, status, business predicates)" -> "Plan joins (alias A ON key = alias B.key)";
    "Plan joins (alias A ON key = alias B.key)" -> "Plan transforms (derived columns, type casts)";
    "Plan transforms (derived columns, type casts)" -> "Plan dedupe / window if needed";
    "Plan dedupe / window if needed" -> "Plan aggregate (grain + group_by)";
    "Plan aggregate (grain + group_by)" -> "Plan output projection + order";
    "Plan output projection + order" -> "Any ambiguity?";
    "Any ambiguity?" -> "Emit Logic Plan" [label="no"];
    "Any ambiguity?" -> "Emit Logic Plan + pending_questions" [label="yes"];
}

Required Reasoning (do not skip)

Before emitting the plan, you MUST explicitly state and record:

  1. Grain — what does one output row represent? (e.g. one customer, one customer×month, one order)
  2. Metric formula — for every metric, write the exact expression: numerator and denominator defined.
  3. Time grain — confirm the time field and granularity (day, month, hour). State the window inclusively.
  4. Join cardinality — for every join, predict 1:1 / 1:N / N:N. If N:N, mark it and ask the user whether to dedupe before joining.
  5. Dedup position — if a fact table has row multiplicity (e.g. one customer with many addresses), decide before the join which side gets deduped and on which key.
  6. Null strategy — for nullable join keys or measures, decide INNER vs LEFT + COALESCE.

If any of the above is unclear, add a pending_question and stop. Do not guess.

Output Contract

logic_plan:
  grain: "<one sentence describing what one output row represents>"
  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:
    - "<ambiguity not yet resolved, blocking SQL generation>"
  status: <PLANNED | NEED_USER_CONFIRMATION>

Forbidden Behaviors

Rationalization Reality
"I'll write the SELECT list directly, the user can read SQL" Logic Plan uses business terms; SQL is downstream.
"Aggregation is obvious, skip the formula" Explicit numerator/denominator avoids silent metric drift.
"1:N vs N:N doesn't matter for SELECT" It matters: it determines dedup and inflation risk.
"Time grain is the user's problem" Pick a grain. Wrong grain ⇒ wrong numbers.
"I'll add a HAVING clause at SQL time" Logic Plan must list all filters, including post-aggregation.

Completion Criteria

Status PLANNED requires:

  • Grain is one sentence, not a paragraph
  • Every field_mapping entry is referenced by exactly one step
  • Every join has a cardinality assumption
  • Every metric has an explicit agg and an alias
  • pending_questions is empty

Otherwise: NEED_USER_CONFIRMATION and stop. Do not proceed to sql-context-builder.