recon-driven sql composer; pick → compose → execute; llm out of structural sql

This commit is contained in:
2026-06-03 11:01:02 -03:00
parent 61494362a3
commit 29c620b2c2
27 changed files with 1516 additions and 249 deletions

View File

@@ -0,0 +1,35 @@
You translate a business question into a typed Pick that an SQL composer
will execute deterministically. You do NOT write SQL — you choose from
finite, known sets and the composer builds the query.
OUTPUT FORMAT — JSON, no prose, no markdown fences:
{
"kind": "aggregate",
"metric": "<name from the candidate metrics list>",
"group_by": ["<column ref>", ...],
"where": [<filter>, ...],
"order_by": {"by": "metric"|"dimension", "direction": "asc"|"desc", "dimension": "<col ref>"?},
"limit": <integer>
}
FILTER SHAPES — exactly one value field per filter:
{"column": "<col ref>", "equals": "<value>"}
{"column": "<col ref>", "in_values": ["<v1>", "<v2>", ...]}
{"column": "<col ref>", "between": [<lo>, <hi>]}
{"column": "<col ref>", "date_range": "<YYYY|YYYY-Q1..Q4>"}
RULES:
- `metric` MUST be one of the candidate metric names. Don't invent.
- Every column ref in `group_by` and in `where[*].column` MUST appear in
the candidate columns list. If the same name lives on multiple tables,
it will be listed as `table.column` — pick the qualified form.
- Don't include `group_by` or `where` if not needed (empty list / omit).
- Only ONE filter per column. Don't repeat.
- If you cannot express the question using the candidate metrics +
columns + filter shapes, return:
{"error": "<one short sentence on what's missing>"}
- No SQL fragments, no raw expressions, no CASE, no subqueries.
- Output ONLY the JSON object. No explanation, no markdown.

10
api/prompts/pick.user.txt Normal file
View File

@@ -0,0 +1,10 @@
QUESTION:
{question}
CANDIDATE METRICS (pick one for `metric`):
{metrics_block}
CANDIDATE COLUMNS (pick from these for `group_by` and `where[*].column`):
{columns_block}
Return the Pick as JSON (or the error object). No other text.

View File

@@ -1,6 +1,7 @@
Question: {question}
Warehouse tables: {table_names}
Warehouse tables:
{tables_block}
Metric catalog:
{metrics_block}

View File

@@ -1,16 +0,0 @@
Task: translate the analyst's question into a single Postgres SELECT (or WITH … SELECT) against the BIRD `financial` schema (PKDD'99 Czech bank).
Output: exactly one fenced ```sql block, nothing else — no prose, no second block, no trailing semicolon required.
Constraints:
- search_path is set to `financial`; don't qualify table names with the schema.
- **Postgres folds unquoted identifiers to lowercase.** Always double-quote any column or table name that contains an uppercase letter — including all the `district` columns: `"A2"`, `"A3"`, `"A4"`, `"A5"`, `"A6"`, `"A7"`, `"A8"`, `"A9"`, `"A10"`, `"A11"`, `"A12"`, `"A13"`, `"A14"`, `"A15"`, `"A16"`. Mixing quoted and unquoted forms of the same column (`"A3"` and `A13`) will fail at runtime — be consistent.
- Quote identifiers that collide with SQL keywords (notably `"order"`).
- Dates in the source are YYMMDD integers (e.g. 950315 → 1995-03-15). Convert with `TO_DATE(LPAD(date::text, 6, '0'), 'YYMMDD')` whenever you need a real date for filtering, comparison, or display.
- Loan `status` values: 'A' finished OK, 'B' finished defaulted, 'C' running OK, 'D' running in debt.
- Transaction `type` values: 'PRIJEM' credit, 'VYDAJ' debit.
- Read-only: never emit DDL or DML.
When the question maps to a metric in the catalog you're given, copy the metric's SQL fragment verbatim — don't paraphrase it.
If the question is ambiguous, pick the most defensible interpretation and proceed; don't ask for clarification.

View File

@@ -1,10 +0,0 @@
Question:
{question}
Schema (only the tables that look relevant; you may use any of these):
{schema_block}
Metric catalog (copy the SQL verbatim when the question maps to one):
{metrics_block}
Return only the SQL in a ```sql fenced block.{retry_hint}