"""JSON IR (intermediate representation) Pydantic models. See ARCHITECTURE.md ยง7 for the schema. Scope: filter, group_by, agg, order_by, limit, plus a single-level `joins` to related tables in the SAME source (KM-652 T4). having, offset, and boolean tree filters are still deferred. Joins are supported on database (schema) sources only; the validator rejects them on tabular sources (cross-file merge is a later step). With joins, `select` / `group_by` / `filters` / `order_by` may reference a `column_id` from the base table OR any joined table (column_ids are globally unique), so a measure in one table can be grouped by a dimension in a related one. """ from typing import Any, Literal from pydantic import BaseModel, Field FilterOp = Literal[ "=", "!=", "<", "<=", ">", ">=", "in", "not_in", "is_null", "is_not_null", "like", "between", ] AggFn = Literal["count", "count_distinct", "sum", "avg", "min", "max"] ValueType = Literal["int", "decimal", "string", "datetime", "date", "bool"] SortDir = Literal["asc", "desc"] JoinType = Literal["inner", "left"] class Join(BaseModel): """A single equi-join to another table in the same source. `left_column_id` is a column already in the query (base table or an earlier join); `right_column_id` is a column in `target_table_id`. The validator requires the pair to match a foreign key declared in the catalog. """ target_table_id: str left_column_id: str right_column_id: str type: JoinType = "inner" class ColumnSelect(BaseModel): kind: Literal["column"] = "column" column_id: str alias: str | None = None class AggSelect(BaseModel): kind: Literal["agg"] = "agg" fn: AggFn column_id: str | None = None alias: str | None = None SelectItem = ColumnSelect | AggSelect class FilterClause(BaseModel): column_id: str op: FilterOp value: Any value_type: ValueType class OrderByClause(BaseModel): column_id: str dir: SortDir = "asc" class QueryIR(BaseModel): ir_version: str = "1.0" source_id: str table_id: str joins: list[Join] = Field(default_factory=list) select: list[SelectItem] filters: list[FilterClause] = Field(default_factory=list) group_by: list[str] = Field(default_factory=list) order_by: list[OrderByClause] = Field(default_factory=list) limit: int | None = None