{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/algenta-ai/json-schema/algenta-ai-query-request-schema.json",
  "title": "QueryRequest",
  "description": "Structured query execution request.\n\n═══════════════════════════════════════════════════════════\nPRIMARY PATH  —  Exact spec  (use after reading the schema)\n═══════════════════════════════════════════════════════════\nThe correct flow:\n  1. Call POST /v1/sources/register or POST /v1/sources/schema\n  2. LLM reads the returned schema (column names, roles, formulas)\n  3. LLM sends exact column names — Mojo validates and executes\n\nExample (top 5 customers by revenue):\n  {\n    \"source_name\": \"S0\",\n    \"metric_column\": \"measure_total\",\n    \"group_column\": \"dimension_label\",\n    \"aggregation\": \"sum\",\n    \"limit\": 5\n  }\n\nFor multi-hop joins:\n  {\n    \"source_name\": \"S0\",\n    \"metric_column\": \"measure_total\",\n    \"group_column\": \"dimension_label\",\n    \"join_path\": {\n      \"base_source\": \"S0\",\n      \"group_source\": \"S2\",\n      \"edges\": [\n        {\n          \"left_source\": \"S0\",\n          \"right_source\": \"S1\",\n          \"left_key\": \"k01_left\",\n          \"right_key\": \"k01_right\"\n        },\n        {\n          \"left_source\": \"S1\",\n          \"right_source\": \"S2\",\n          \"left_key\": \"k12_left\",\n          \"right_key\": \"k12_right\"\n        }\n      ]\n    },\n    \"aggregation\": \"sum\"\n  }\n\n═══════════════════════════════════════════════════════════\nFALLBACK PATH  —  Hint spec  (when schema is not yet known)\n═══════════════════════════════════════════════════════════\nThe engine resolves source and columns from structural role hints.\nUse this ONLY for exploratory queries. Always migrate to exact spec\nonce you have the schema — it is faster and never ambiguous.\n\nExample:\n  {\n    \"metric\": {\"role\": \"derived_measure\", \"hint\": \"total value\"},\n    \"group_by\": [\"segment\"],\n    \"aggregation\": \"sum\",\n    \"limit\": 5\n  }",
  "x-generated": "2026-11-01",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/api.algenta.ai-openapi.json#/components/schemas/QueryRequest",
  "properties": {
    "dataset_id": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Dataset Id",
      "description": "Public dataset ID alias for a registered source. When provided, query execution is scoped to that one registered dataset."
    },
    "source_name": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Source Name",
      "description": "EXACT source name from the registry (e.g. 'Bookings'). Bypasses source resolver entirely. Get this from POST /v1/sources/schema or the registration response."
    },
    "metric_column": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Metric Column",
      "description": "EXACT column name for the metric (e.g. 'EXT_PRICE'). Bypasses column resolver. The engine validates this column exists in source_name."
    },
    "group_column": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Group Column",
      "description": "EXACT column name for group-by (e.g. 'CUSTOMER'). Bypasses group resolver. Can be in source_name or in the terminal source of join_path."
    },
    "join_path": {
      "anyOf": [
        {
          "$ref": "#/$defs/JoinPathSpec"
        },
        {
          "type": "null"
        }
      ],
      "description": "Explicit multi-hop join graph. Breaking replacement for single-hop join fields. Defaults to a safe 4-hop budget; explicit opt-in can raise this to 6 hops (7 tables) with per-edge constraints and path-level confidence gates."
    },
    "sources": {
      "items": {
        "$ref": "#/$defs/DataSource"
      },
      "type": "array",
      "title": "Sources",
      "description": "Raw data sources (inline CSV/JSON). Usually empty — use registry.",
      "default": []
    },
    "metric": {
      "$ref": "#/$defs/MetricSpec"
    },
    "group_by": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "title": "Group By",
      "description": "Hint mode only: dimension words from the user's question (e.g. ['customer', 'region']). Ignored when group_column is set.",
      "default": []
    },
    "preferred_source": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Preferred Source",
      "description": "Hint mode only: source name the LLM believes is correct. Hard-locks the source resolver. Ignored when source_name is set."
    },
    "reject_on_ambiguity": {
      "type": "boolean",
      "title": "Reject On Ambiguity",
      "description": "Hint mode only: when True and resolver confidence < 0.40, return null result with source candidates instead of guessing.",
      "default": false
    },
    "min_source_confidence": {
      "type": "number",
      "maximum": 1.0,
      "minimum": 0.0,
      "title": "Min Source Confidence",
      "description": "Hint mode only: minimum resolver confidence threshold.",
      "default": 0.0
    },
    "aggregation": {
      "type": "string",
      "title": "Aggregation",
      "description": "sum | avg | count | max | min",
      "default": "sum"
    },
    "filter": {
      "anyOf": [
        {
          "$ref": "#/$defs/FilterSpec"
        },
        {
          "type": "null"
        }
      ]
    },
    "limit": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Limit",
      "description": "Top-N limit"
    },
    "order": {
      "type": "string",
      "title": "Order",
      "description": "desc | asc",
      "default": "desc"
    },
    "min_confidence": {
      "type": "number",
      "maximum": 1.0,
      "minimum": 0.0,
      "title": "Min Confidence",
      "default": 0.5
    },
    "constraints": {
      "$ref": "#/$defs/ConstraintSpec"
    },
    "resolved_plan": {
      "anyOf": [
        {
          "$ref": "#/$defs/ResolvedPlan-Input"
        },
        {
          "type": "null"
        }
      ],
      "description": "Exact plan returned by POST /v1/resolve."
    }
  },
  "additionalProperties": false,
  "type": "object",
  "$defs": {
    "ConstraintSpec": {
      "properties": {
        "requires_derived": {
          "type": "boolean",
          "title": "Requires Derived",
          "description": "Reject if metric column is not a derived_measure (formula-proven).",
          "default": false
        },
        "no_cross_source": {
          "type": "boolean",
          "title": "No Cross Source",
          "description": "Reject if query requires a cross-source join.",
          "default": false
        },
        "min_column_confidence": {
          "type": "number",
          "maximum": 1.0,
          "minimum": 0.0,
          "title": "Min Column Confidence",
          "description": "Reject if column_confidence component < this value.",
          "default": 0.0
        },
        "max_join_hops": {
          "type": "integer",
          "maximum": 6.0,
          "minimum": 1.0,
          "title": "Max Join Hops",
          "description": "Safe default join-path budget for planner-generated cross-source queries. Defaults to 4 hops; explicit opt-in can raise this to 6 hops (7 tables).",
          "default": 4
        }
      },
      "type": "object",
      "title": "ConstraintSpec"
    },
    "DataSource": {
      "properties": {
        "name": {
          "type": "string",
          "title": "Name",
          "description": "Label for this source (e.g., 'sales', 'orders')"
        },
        "dataset_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Dataset Id",
          "description": "Public dataset ID alias for a registered source."
        },
        "source_id": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Source Id",
          "description": "Registered source ID; fastest path and avoids re-uploading data."
        },
        "records": {
          "anyOf": [
            {
              "items": {
                "additionalProperties": true,
                "type": "object"
              },
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "title": "Records",
          "description": "Inline JSON records"
        },
        "csv": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Csv",
          "description": "Raw CSV text"
        },
        "json_str": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Json Str",
          "description": "Raw JSON array/object text (nested auto-flattened)"
        },
        "url": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Url",
          "description": "HTTP URL — format auto-detected"
        },
        "excel_b64": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Excel B64",
          "description": "Excel .xlsx file, base64-encoded"
        },
        "parquet_b64": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Parquet B64",
          "description": "Parquet file, base64-encoded"
        },
        "connection": {
          "anyOf": [
            {
              "additionalProperties": true,
              "type": "object"
            },
            {
              "type": "null"
            }
          ],
          "title": "Connection",
          "description": "Connector config dict — see DataSource docstring for all types"
        },
        "table": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Table",
          "description": "Legacy field — use connection instead"
        }
      },
      "type": "object",
      "required": [
        "name"
      ],
      "title": "DataSource",
      "description": "A data source to map. Every format normalizes to list[dict] internally.\n\nInline (no external dependency):\n  `records`    — JSON array of objects (fastest)\n  `csv`        — raw CSV text\n  `json_str`   — raw JSON array/object text (nested JSON is auto-flattened)\n  `url`        — HTTP(S) URL; format auto-detected from Content-Type / extension\n\nBinary files (base64-encoded for API transport):\n  `excel_b64`  — Excel .xlsx file, base64-encoded\n  `parquet_b64`— Parquet file, base64-encoded\n\nConnector config (routes through ConnectorRegistry):\n  `connection` — dict with `type` + connector-specific keys:\n    {\"type\": \"sql\",        \"connection_string\": \"postgresql+asyncpg://...\",\n     \"query\": \"SELECT ...\"}\n    {\"type\": \"sql\",        \"connection_string\": \"sqlite+aiosqlite:///db.sqlite\", \"query\": \"...\"}\n    {\"type\": \"rest_api\",   \"url\": \"https://...\", \"data_path\": \"data.items\"}\n    {\"type\": \"s3\",         \"bucket\": \"my-bucket\", \"key\": \"data/sales.parquet\"}\n    {\"type\": \"gcs\",        \"bucket\": \"my-bucket\", \"blob\": \"data/orders.csv\"}\n    {\"type\": \"azure_blob\", \"connection_string\": \"DefaultEndpointsProtocol=...\",\n                           \"container\": \"data\", \"blob\": \"sales.parquet\"}"
    },
    "FilterConditionSpec": {
      "properties": {
        "column": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Column",
          "description": "Exact source column name for this filter when schema is known."
        },
        "dimension_hint": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Dimension Hint",
          "description": "Weak semantic label for the filter dimension when the exact column name is not known yet. Best for status/type/category style filters."
        },
        "op": {
          "type": "string",
          "enum": [
            "eq",
            "in",
            "gt",
            "gte",
            "lt",
            "lte",
            "is_null",
            "is_not_null"
          ],
          "title": "Op",
          "description": "Deterministic filter operator.",
          "default": "eq"
        },
        "value": {
          "anyOf": [
            {},
            {
              "type": "null"
            }
          ],
          "title": "Value",
          "description": "Scalar comparison value for eq/gt/gte/lt/lte."
        },
        "values": {
          "anyOf": [
            {
              "items": {},
              "type": "array"
            },
            {
              "type": "null"
            }
          ],
          "title": "Values",
          "description": "List comparison values for in."
        }
      },
      "additionalProperties": false,
      "type": "object",
      "title": "FilterConditionSpec"
    },
    "FilterSpec": {
      "properties": {
        "time_filter": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Time Filter",
          "description": "Relative time window. One of: last_quarter, this_quarter, last_month, this_month, last_year, this_year."
        },
        "conditions": {
          "items": {
            "$ref": "#/$defs/FilterConditionSpec"
          },
          "type": "array",
          "title": "Conditions",
          "description": "Optional deterministic non-time predicates applied on the metric source. These are record predicates over normalized rows, not SQL clauses. Use exact column when schema is known, or dimension_hint for categorical dimensions when only the semantic label is known, including Redis and other non-SQL sources after normalization. Supported ops: eq, in, gt, gte, lt, lte, is_null, is_not_null."
        }
      },
      "type": "object",
      "title": "FilterSpec",
      "description": "Time or value filter."
    },
    "JoinEdgeSpec": {
      "properties": {
        "left_source": {
          "type": "string",
          "title": "Left Source",
          "description": "Source on the left side of the join edge."
        },
        "right_source": {
          "type": "string",
          "title": "Right Source",
          "description": "Source on the right side of the join edge."
        },
        "left_key": {
          "type": "string",
          "title": "Left Key",
          "description": "Exact join key column in left_source."
        },
        "right_key": {
          "type": "string",
          "title": "Right Key",
          "description": "Exact join key column in right_source."
        },
        "join_type": {
          "type": "string",
          "title": "Join Type",
          "description": "Join type. Initial implementation supports inner.",
          "default": "inner"
        },
        "min_confidence": {
          "type": "number",
          "maximum": 1.0,
          "minimum": 0.0,
          "title": "Min Confidence",
          "description": "Minimum acceptable confidence for this hop.",
          "default": 0.0
        },
        "expected_cardinality": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Expected Cardinality",
          "description": "Optional expected cardinality such as one_to_many, many_to_one, or many_to_many."
        },
        "allow_many_to_many": {
          "type": "boolean",
          "title": "Allow Many To Many",
          "description": "If false, planner/executor must reject many_to_many fanout on this hop.",
          "default": false
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "left_source",
        "right_source",
        "left_key",
        "right_key"
      ],
      "title": "JoinEdgeSpec",
      "description": "One explicit edge in a multi-hop join path."
    },
    "JoinPathSpec": {
      "properties": {
        "base_source": {
          "type": "string",
          "title": "Base Source",
          "description": "Source that owns the metric column and anchors the path."
        },
        "group_source": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Group Source",
          "description": "Final source expected to provide the group column, if different from base_source."
        },
        "edges": {
          "items": {
            "$ref": "#/$defs/JoinEdgeSpec"
          },
          "type": "array",
          "title": "Edges",
          "description": "Ordered join edges from base_source toward the dimension source."
        },
        "max_hops": {
          "type": "integer",
          "maximum": 6.0,
          "minimum": 1.0,
          "title": "Max Hops",
          "description": "Maximum allowed join hops for this request. Defaults to 4; explicit opt-in can raise this to 6 hops (7 tables).",
          "default": 4
        },
        "min_path_confidence": {
          "type": "number",
          "maximum": 1.0,
          "minimum": 0.0,
          "title": "Min Path Confidence",
          "description": "Minimum acceptable composed confidence across the full join path.",
          "default": 0.0
        },
        "reject_on_ambiguity": {
          "type": "boolean",
          "title": "Reject On Ambiguity",
          "description": "Reject if more than one viable path exists with insufficient score separation.",
          "default": true
        },
        "reject_on_fanout": {
          "type": "boolean",
          "title": "Reject On Fanout",
          "description": "Reject if estimated path fanout exceeds safe execution thresholds.",
          "default": true
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "base_source"
      ],
      "title": "JoinPathSpec",
      "description": "Explicit multi-hop join contract replacing single-hop join fields."
    },
    "MetricSpec": {
      "properties": {
        "role": {
          "type": "string",
          "title": "Role",
          "description": "Structural role. One of: base_measure, category, component, derived_measure, identifier, metric, ratio, text, time, unit_measure. derived_measure = computed aggregate (revenue, total value). base_measure = count/quantity. unit_measure = price/rate. ratio = percentage/margin. metric = best numeric column.",
          "default": "derived_measure"
        },
        "hint": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Hint",
          "description": "Optional weak signal — a word from the user's question. Used only as a tiebreaker when multiple columns share the same role. Never the primary signal. Examples: 'revenue', 'quantity', 'price'."
        }
      },
      "type": "object",
      "title": "MetricSpec",
      "description": "Structural role target. The LLM selects based on what the user wants.\n\nUse 'derived_measure' for revenue, spend, total value — the computed aggregate.\nUse 'base_measure' for counts, quantities, discrete amounts.\nUse 'unit_measure' for prices, rates, per-unit values.\nUse 'ratio' for percentages, margins, fill rates.\nUse 'metric' to let the engine pick the best numeric column."
    },
    "ResolvedPlan-Input": {
      "properties": {
        "source_name": {
          "type": "string",
          "title": "Source Name"
        },
        "metric_column": {
          "type": "string",
          "title": "Metric Column"
        },
        "aggregation": {
          "type": "string",
          "title": "Aggregation",
          "description": "sum | avg | count | max | min",
          "default": "sum"
        },
        "group_column": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Group Column"
        },
        "join_path": {
          "anyOf": [
            {
              "$ref": "#/$defs/JoinPathSpec"
            },
            {
              "type": "null"
            }
          ]
        },
        "filter": {
          "anyOf": [
            {
              "$ref": "#/$defs/FilterSpec"
            },
            {
              "type": "null"
            }
          ]
        },
        "limit": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "title": "Limit"
        },
        "order": {
          "type": "string",
          "title": "Order",
          "description": "desc | asc",
          "default": "desc"
        },
        "constraints": {
          "$ref": "#/$defs/ConstraintSpec"
        },
        "schema_revision": {
          "type": "string",
          "title": "Schema Revision"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "source_name",
        "metric_column",
        "schema_revision"
      ],
      "title": "ResolvedPlan"
    }
  }
}