7.6 KiB
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-validatorreturned statusVALIDATEDand 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-validatorreturnedNEED_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
- Validation Result from
metadata-validator(statusVALIDATED) - The original business requirement (often embedded in
requirements-analysisoutput) 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:
- Grain — what does one output row represent? (e.g. one customer, one customer×month, one order)
- Metric formula — for every metric, write the exact expression: numerator and denominator defined.
- Time grain — confirm the time field and granularity (day, month, hour). State the window inclusively.
- 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.
- 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.
- Null strategy — for nullable join keys or measures, decide
INNERvsLEFT + 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_mappingentry is referenced by exactly one step - Every join has a cardinality assumption
- Every metric has an explicit
aggand analias pending_questionsis empty
Otherwise: NEED_USER_CONFIRMATION and stop. Do not proceed to sql-context-builder.