{
  "markdown": "<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/ralforion/orionbelt-semantic-layer/main/docs/assets/ORIONBELT_Logo.png\" alt=\"OrionBelt Semantic Layer logo\" width=\"320\">\n</p>\n\n<h1 align=\"center\">OrionBelt&reg; Semantic Layer and Sidecar</h1>\n\n<p align=\"center\"><strong>Define your metrics once in YAML. Let agents and BI tools query them without ever touching your schema.</strong></p>\n\n<p align=\"center\">A <a href=\"https://ralforion.com/semantic-sidecar.html\">semantic sidecar</a>: it rides alongside the systems you already run instead of replacing them.</p>\n\n<p align=\"center\">\n<a href=\"https://orionbelt.ralforion.com/ui/?__theme=dark\"><img src=\"https://img.shields.io/badge/Live_Demo-Try_it_now-brightgreen?style=for-the-badge\" alt=\"Live Demo\"></a>\n</p>\n\n<p align=\"center\">\n<a href=\"https://github.com/ralforion/orionbelt-semantic-layer/releases\"><img src=\"https://img.shields.io/badge/version-2.26.0-purple.svg\" alt=\"Version 2.26.0\"></a>\n<a href=\"https://pypi.org/project/orionbelt-semantic-layer/\"><img src=\"https://img.shields.io/pypi/v/orionbelt-semantic-layer?logo=pypi&logoColor=white\" alt=\"PyPI\"></a>\n<a href=\"https://hub.docker.com/r/ralforion/orionbelt-semantic-layer-api\"><img src=\"https://img.shields.io/docker/pulls/ralforion/orionbelt-semantic-layer-api?logo=docker&logoColor=white&color=2496ED\" alt=\"Docker pulls\"></a>\n<a href=\"https://www.python.org/downloads/\"><img src=\"https://img.shields.io/badge/python-3.12+-blue.svg\" alt=\"Python 3.12+\"></a>\n<a href=\"https://github.com/ralforion/orionbelt-semantic-layer/blob/main/LICENSE\"><img src=\"https://img.shields.io/badge/License-BUSL--1.1-orange.svg\" alt=\"License: BUSL-1.1\"></a>\n</p>\n\n---\n\nAsk an LLM to write SQL against a raw star schema and sooner or later it joins two fact tables and hands you a revenue number inflated by a factor of eight. It looks right. Nobody catches it.\n\nOrionBelt is a **[semantic sidecar](https://ralforion.com/semantic-sidecar.html)**. You declare dimensions, measures, metrics, and joins in version-controlled YAML. OrionBelt compiles them into dialect-specific SQL through a real AST, and routes multi-fact queries through a Composite Fact Layer planner that [blocks the join paths that produce fan traps](https://ralforion.com/text-to-sql.html). Agents and BI tools ask for `\"Total Revenue\" by \"Country\"`. They never see a table name.\n\nNo BI tool in the middle. No runtime lock-in. Point it at what you already have.\n\nHere is TPC-DS query 98. Two measures over the same column, identical but for one line: `Class Revenue` is pinned to a coarser grain than the query asks for.\n\n```yaml\nmeasures:\n  Store Sales Amount:\n    columns: [{dataObject: Store Sales, column: Ext Sales Price}]\n    aggregation: sum\n\n  Class Revenue:\n    columns: [{dataObject: Store Sales, column: Ext Sales Price}]\n    aggregation: sum\n    grain: {mode: FIXED, keepOnly: [Class]}   # <- pin to Class, ignore query grain\n\nmetrics:\n  Revenue Ratio:\n    expression: \"{[Store Sales Amount]} * 100.0 / {[Class Revenue]}\"\n```\n\nThat one `grain` line is what becomes `SUM(...) OVER (PARTITION BY \"Class\")` below.\n\nThe query names business concepts. No tables, no joins, no SQL:\n\n```yaml\nselect:\n  dimensions: [Item ID, Item Description, Category, Class, Current Price]\n  measures: [Store Sales Amount, Revenue Ratio]\nwhere:\n  - {field: Category, op: inlist, value: [Sports, Books, Home]}\n  - {field: Order Date, op: between, value: [\"1999-02-22\", \"1999-03-24\"]}\n```\n\n```bash\npip install orionbelt-semantic-layer\nobsl compile tpcds.obml.yml -q Q98.yml -d duckdb\n```\n\n```sql\nWITH \"base\" AS (\n  SELECT\n    \"Item\".\"i_item_id\" AS \"Item ID\",\n    \"Item\".\"i_item_desc\" AS \"Item Description\",\n    \"Item\".\"i_category\" AS \"Category\",\n    \"Item\".\"i_class\" AS \"Class\",\n    \"Item\".\"i_current_price\" AS \"Current Price\",\n    CAST(SUM(\"Store Sales\".\"ss_ext_sales_price\") AS DECIMAL(18, 2)) AS \"Store Sales Amount\",\n    SUM(\"Store Sales\".\"ss_ext_sales_price\") AS \"Class Revenue\"\n  FROM \"main\".\"store_sales\" AS \"Store Sales\"\n  LEFT JOIN \"main\".\"item\" AS \"Item\"\n    ON \"Store Sales\".\"ss_item_sk\" = \"Item\".\"i_item_sk\"\n  LEFT JOIN \"main\".\"date_dim\" AS \"Date\"\n    ON \"Store Sales\".\"ss_sold_date_sk\" = \"Date\".\"d_date_sk\"\n  WHERE\n    \"Item\".\"i_category\" IN ('Sports', 'Books', 'Home')\n    AND \"Date\".\"d_date\" BETWEEN '1999-02-22' AND '1999-03-24'\n  GROUP BY ALL\n)\nSELECT\n  \"Item ID\" AS \"Item ID\",\n  \"Item Description\" AS \"Item Description\",\n  \"Category\" AS \"Category\",\n  \"Class\" AS \"Class\",\n  \"Current Price\" AS \"Current Price\",\n  \"Store Sales Amount\" AS \"Store Sales Amount\",\n  \"Store Sales Amount\" * 100.0 / NULLIF(SUM(\"Class Revenue\") OVER (PARTITION BY \"Class\"), 0) AS \"Revenue Ratio\"\nFROM \"base\" AS \"base\"\nORDER BY\n  \"Category\" ASC,\n  \"Class\" ASC,\n  \"Item ID\" ASC,\n  \"Item Description\" ASC,\n  \"Revenue Ratio\" ASC\n```\n\nYou did not write the join path, the window function over an aggregate, the `NULLIF` guard, or one table name. Change `-d duckdb` to `-d snowflake` and the same two files compile for Snowflake, or for any of eight dialects.\n\n**This is checked, not asserted.** 40 TPC-DS queries are built against a single OBML model and compared row by row against each engine's own reference SQL: 39 of 40 match on DuckDB at sf=1, 37 of 40 on ClickHouse at sf=10. Every one of the remaining differences traces to a reference variant rather than a compilation error, and each is documented. See [the sweep](https://ralforion.com/orionbelt-semantic-layer/examples/tpcds-sweep/), or the queries in [`examples/tpcds_queries/`](examples/tpcds_queries/).\n\nThe same model serves every surface you already use:\n\n- **Your BI tool**, over the PostgreSQL wire protocol on `:5432`. Tableau, Power BI, Superset, DBeaver, and `psql` connect with the Postgres driver they already ship. Dremio federates it as a Postgres source.\n- **Your [AI agents](https://ralforion.com/agentic-ai-data-access.html)**, over MCP. Works with Claude, Cursor, Copilot, and Windsurf.\n- **Your code**, over REST, Arrow Flight SQL, or PEP 249 drivers.\n\nCompiles to BigQuery, ClickHouse, Databricks, Dremio, DuckDB/MotherDuck, MySQL, PostgreSQL, and Snowflake.\n\n## Where OrionBelt fits\n\nOrionBelt is a sidecar, not a platform. It compiles a YAML model into correct SQL and exposes it over the protocols you already use. It does not run a cluster, own your cache, or ask you to adopt a cloud.\n\n**Reach for OrionBelt when:**\n\n- Agents query your data and a silently wrong number is unacceptable. Multi-fact queries route through a Composite Fact Layer planner that blocks fan-trap join paths instead of quietly summing across them.\n- You want your metric definitions in reviewable YAML, with no JavaScript or Python in the model layer.\n- Your BI tool should connect over the Postgres driver it already ships, with no new connector to install and no vendor runtime in the path.\n- You self-host, across more than one engine, and want one model to compile for all of them.\n\n**Reach for something else when:**\n\n- You need pre-aggregation and caching tuned for high-concurrency dashboards at scale. [Cube](https://cube.dev) has years of production hardening there that OrionBelt does not.\n- Your metrics already live in dbt and your team is happy there. [MetricFlow](https://github.com/dbt-labs/metricflow) keeps them where they are.\n- You want an exploratory analysis language rather than a serving layer. [Malloy](https://www.malloydata.dev) is a better fit.\n\n**[Try the live demo](https://orionbelt.ralforion.com/ui/?__theme=dark)** with a pre-loaded model, or [open the Colab notebook](https://colab.research.google.com/github/ralforion/orionbelt-semantic-layer/blob/main/examples/quickstart_colab.ipynb) and run it against TPC-H data.\n\n## Contents\n\n[Try it in 30 seconds](#try-it-in-30-seconds) · [Claude Desktop / MCP](#claude-desktop--mcp) · [Why OrionBelt?](#why-orionbelt) · [Features](#features) · [Example](#example) · [Documentation](#documentation) · [Roadmap](#status--roadmap) · [Commercial](#commercial-offerings) · [Development](#development)\n\n---\n\n## Try it in 30 Seconds\n\n### Option A: Live Demo (no install)\n\n**[Open the Live Demo](https://orionbelt.ralforion.com/ui/?__theme=dark)** — Gradio UI with a pre-loaded example model. Paste a query, pick a dialect, see SQL instantly.\n\nAPI explorer: [Swagger UI](https://orionbelt.ralforion.com/docs) | [ReDoc](https://orionbelt.ralforion.com/redoc)\n\n> **Want to try the PostgreSQL wire surface?** Cloud Run is HTTPS-only, so the public demo can't expose ports 5432 (pgwire) or 8815 (Flight SQL). Spin the same demo up locally in two commands — it includes the baked-in `orionbelt_1_commerce` DuckDB dataset and the full OBSQL surface:\n>\n> ```bash\n> docker run --rm -d --name orionbelt-demo \\\n>   -p 8080:8080 -p 5432:5432 -p 8815:8815 \\\n>   -e PGWIRE_ENABLED=true \\\n>   -e FLIGHT_ENABLED=true \\\n>   ralforion/orionbelt-semantic-layer-api:latest\n>\n> # REST + Gradio UI:   http://localhost:8080/ui\n> # pgwire (any psql / DBeaver / Tableau / Power BI):\n> psql \"host=localhost port=5432 user=obsl dbname=orionbelt_1_commerce sslmode=disable\" \\\n>   -c 'SELECT \"Client Name\", \"Total Sales\" LIMIT 5'\n> # Flight SQL smoke test:\n> uv run python examples/obsql.py 'SELECT \"Client Name\", \"Total Sales\" LIMIT 5'\n>\n> docker stop orionbelt-demo\n> ```\n>\n> The container ships with `PGWIRE_AUTH_MODE=trust` (default), so it's safe for `localhost` but **not** safe to expose to the public internet. For exposed deployments, set `AUTH_MODE=api_key` (shipped in v2.12.0): pgwire then negotiates SCRAM-SHA-256 (or cleartext over TLS) against the shared key store.\n\n### Option B: Google Colab (no install)\n\n[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ralforion/orionbelt-semantic-layer/blob/main/examples/quickstart_colab.ipynb) — Interactive notebook with TPC-H data: explore the model, compile queries across dialects, execute against DuckDB, and see results. Requires Python 3.12 runtime.\n\n### Option C: Install from PyPI\n\n```bash\npip install orionbelt-semantic-layer\n```\n\nThen paste into a Python REPL:\n\n```python\nfrom orionbelt.parser import ReferenceResolver, TrackedLoader\nfrom orionbelt.compiler.pipeline import CompilationPipeline\nfrom orionbelt.models.query import QueryObject, QuerySelect\n\nmodel_yaml = \"\"\"\nversion: 1.0\ndataObjects:\n  Orders:\n    code: ORDERS\n    columns:\n      Price: { code: PRICE, abstractType: float }\n      Country: { code: COUNTRY, abstractType: string }\ndimensions:\n  Country:\n    dataObject: Orders\n    column: Country\n    resultType: string\nmeasures:\n  Total Revenue:\n    resultType: float\n    aggregation: sum\n    expression: \"{[Orders].[Price]}\"\n\"\"\"\n\nloader = TrackedLoader()\nraw, source_map = loader.load_string(model_yaml)\nresolver = ReferenceResolver()\nmodel, result = resolver.resolve(raw, source_map)\n\nquery = QueryObject(select=QuerySelect(dimensions=[\"Country\"], measures=[\"Total Revenue\"]))\npipeline = CompilationPipeline()\noutput = pipeline.compile(query, model, \"postgres\")\nprint(output.sql)\n```\n\nOutput:\n\n```sql\nSELECT\n  \"Orders\".\"COUNTRY\" AS \"Country\",\n  CAST(SUM(\"Orders\".\"PRICE\") AS NUMERIC(18, 2)) AS \"Total Revenue\"\nFROM ORDERS AS \"Orders\"\nGROUP BY \"Orders\".\"COUNTRY\"\n```\n\nNo env file needed — the compilation pipeline is stateless.\n\n**Start the servers:**\n\n```bash\norionbelt-api                              # REST API on :8000 (Swagger UI at /docs, Gradio UI at /ui)\norionbelt-ui                               # standalone Gradio UI on :7860 (connects to API on :8000)\nFLIGHT_ENABLED=true orionbelt-api          # API + Arrow Flight SQL on :8815 (DBeaver, Tableau, Power BI)\nPGWIRE_ENABLED=true orionbelt-api          # API + PostgreSQL wire on :5432 (Tableau, DBeaver, Superset, psql, Dremio source)\n```\n\n### Option C2: Install with uv\n\n```bash\nuv pip install orionbelt-semantic-layer\n```\n\n```bash\nuv run orionbelt-api                       # REST API on :8000 (Swagger UI at /docs, Gradio UI at /ui)\nuv run orionbelt-ui                        # standalone Gradio UI on :7860 (connects to API on :8000)\nFLIGHT_ENABLED=true uv run orionbelt-api   # API + Arrow Flight SQL on :8815 (DBeaver, Tableau, Power BI)\nPGWIRE_ENABLED=true uv run orionbelt-api   # API + PostgreSQL wire on :5432 (Tableau, DBeaver, Superset, psql, Dremio source)\n```\n\n**Use the `obsl` CLI** (no server needed - compiles in-process):\n\n```bash\nobsl validate model.yaml                                  # lint a model (exit 1 on error, CI-friendly)\nobsl compile model.yaml -q query.json -d snowflake        # print the generated SQL\nobsl compile model.yaml --sql 'SELECT \"Region\", \"Sales\" FROM model'  # ... or from an OBSQL string\nobsl describe model.yaml                                   # overview of data objects + artefacts\nobsl diagram model.yaml                                    # Mermaid ER diagram\nobsl convert obml-to-osi model.yaml                        # OBML -> OSI (and osi-to-obml)\nobsl execute -q query.json --server http://host           # run against a deployed model (omit MODEL)\n```\n\nSee the [CLI guide](https://ralforion.com/orionbelt-semantic-layer/guide/cli/) for all commands.\n\n**Smoke-test the Flight SQL surface** without a BI tool:\n\n```bash\nuv run python examples/obsql.py 'SELECT version()'\nuv run python examples/obsql.py 'SHOW TABLES'\nuv run python examples/obsql.py 'SELECT \"Region\", \"Total Sales\" FROM sales LIMIT 5'\n\n# Multi-model deployment? Pick the model with -m:\nuv run python examples/obsql.py -m sales 'SHOW TABLES'\nuv run python examples/obsql.py --list   # discover loaded models via REST\n```\n\n### Try OBSQL in 30 seconds\n\n**OBSQL** — OrionBelt Semantic QL — is the SQL surface BI tools and humans actually write. Bare labels, `MEASURE()` markers, or matching aggregate wrappers; aggregation-match validation; `WITH ROLLUP` / `WITH CUBE`; no escape hatch to raw warehouse SQL. Same language over **Arrow Flight SQL** (v2.4+) and **PostgreSQL wire** (v2.5+):\n\n```bash\nPGWIRE_ENABLED=true uv run orionbelt-api &\n\n# Every BI tool already ships a Postgres ODBC/JDBC driver — point yours at :5432\npsql \"host=localhost port=5432 user=obsl dbname=sales sslmode=disable\" \\\n  -c 'SELECT \"Region\", \"Total Sales\" LIMIT 5'\n\n# All three measure forms compile to the same vendor SQL:\npsql \"...\" -c 'SELECT \"Region\", \"Total Sales\"        FROM sales LIMIT 5'  -- bare\npsql \"...\" -c 'SELECT \"Region\", MEASURE(\"Total Sales\") FROM sales LIMIT 5'  -- explicit marker\npsql \"...\" -c 'SELECT \"Region\", SUM(\"Total Sales\")   FROM sales LIMIT 5'  -- matching aggregate\n```\n\nSee the [OBSQL reference](https://ralforion.com/orionbelt-semantic-layer/guide/semantic-ql/) for the full grammar.\n\n### Option D: Docker\n\n**Stage 1 — Zero-config start** (models loaded later via API or UI):\n\n```bash\ndocker run -p 8080:8080 ralforion/orionbelt-semantic-layer-api\n```\n\nOpen [http://localhost:8080/docs](http://localhost:8080/docs) to explore the API.\n\n**Stage 2 — Realistic setup** with docker compose:\n\n```yaml\n# docker-compose.yml\nservices:\n  api:\n    image: ralforion/orionbelt-semantic-layer-api:2.26.0\n    ports: [\"8080:8080\"]\n    env_file: .env\n    volumes:\n      - ./models:/app/models:ro\n    environment:\n      MODEL_FILES: /app/models/my-model.obml.yml\n\n  ui:\n    image: ralforion/orionbelt-semantic-layer-ui:2.26.0\n    ports: [\"7860:7860\"]\n    environment:\n      API_BASE_URL: http://api:8080\n```\n\n```bash\ndocker compose up -d\n```\n\nSee [`.env.template`](.env.template) for the full environment variable reference.\n\n> **Docker notes:**\n> - `API_SERVER_HOST` is already `0.0.0.0` inside the container — no override needed.\n> - MCP via stdio does not work in Docker. Use the [MCP HTTP client](https://github.com/ralforion/orionbelt-semantic-layer-mcp) for containerized deployments.\n> - Mount models to `/app/models` (or any path) and set `MODEL_FILES` (comma-separated paths) to pre-load on startup.\n> - For production, pin a version tag (`:2.26.0`) rather than `:latest`.\n\n### Claude Desktop / MCP\n\nThe MCP server is a separate thin client that delegates to the REST API:\n\n**[orionbelt-semantic-layer-mcp](https://github.com/ralforion/orionbelt-semantic-layer-mcp)**\n\nAdd to your Claude Desktop `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"orionbelt\": {\n      \"command\": \"uvx\",\n      \"args\": [\"orionbelt-semantic-layer-mcp\"]\n    }\n  }\n}\n```\n\nAlso works with Copilot, Cursor, and Windsurf. See the [MCP repo](https://github.com/ralforion/orionbelt-semantic-layer-mcp) for full setup options.\n\n---\n\n## Why OrionBelt?\n\n| | OrionBelt | dbt Semantic Layer | Cube | Malloy |\n|---|---|---|---|---|\n| **Model format** | YAML-only (OBML) | Python + YAML | JavaScript | Custom DSL |\n| **SQL generation** | AST-based (injection-safe) | String templates | String templates | Compiler |\n| **Multi-dialect** | 8 dialects, no runtime lock-in | dbt Cloud required | Cube Cloud or self-host | BigQuery-focused |\n| **Multi-fact queries** | Star Schema + CFL planner (fan-trap prevention) | Limited | Pre-aggregations | Automatic joins |\n| **Integration surface** | REST API + MCP + Gradio UI | dbt Cloud API | REST + GraphQL | VS Code extension |\n| **Deployment** | Self-host anywhere, single binary | SaaS (Cloud) | SaaS or self-host | Library |\n| **License** | BUSL-1.1 (converts to Apache 2.0) | Apache 2.0 | AGPL / proprietary | MIT |\n\n---\n\n## Features\n\n### Semantic Modeling\n\n- **OBML Format** — YAML-based semantic models with data objects, dimensions, measures, metrics, and joins\n- **Cross-Schema Queries** — model data objects across multiple databases and schemas in a single model\n- **Static Model Filters** — mandatory WHERE conditions baked into the model, auto-applied with join extension\n- **OBSL Graph & SPARQL** — RDF graph export and read-only SPARQL querying for every loaded model\n- **OSI Interoperability** — bidirectional conversion between OBML and the Open Semantic Interchange format, now developed as [Apache Ossie (incubating)](https://github.com/apache/ossie)\n\n### SQL Compilation\n\n- **8 SQL Dialects** — BigQuery, ClickHouse, Databricks, Dremio, DuckDB/MotherDuck, MySQL, Postgres, Snowflake\n- **AST-Based Generation** — custom SQL AST ensures correct, injection-safe SQL (not string templates)\n- **Star Schema & CFL** — automatic join resolution with Composite Fact Layer for multi-fact queries\n- **Data Types & Precision** — automatic CAST wrapping with dialect-specific type rendering and precision clamping\n- **Display Formatting** — number format patterns (`#,##0.00`, `0.00%`) on measures/metrics with locale-aware rendering\n- **Timezone Settings** — auto-detect database session timezone with `defaultTimezone` fallback and ISO 8601 serialization\n- **sqlglot Validation** — post-generation syntax check across all supported dialects\n\n### Integration Surface\n\n- **REST API** — FastAPI endpoints for model management, validation, compilation, and execution\n- **MCP Server** — [separate thin client](https://github.com/ralforion/orionbelt-semantic-layer-mcp) for Claude, Copilot, Cursor, Windsurf\n- **AI Integrations** — LangChain, OpenAI Agents SDK, CrewAI, Google ADK, Vercel AI SDK, n8n, ChatGPT\n- **Gradio UI** — interactive web interface for model editing, query testing, and ER diagrams\n- **DB-API 2.0 + Flight SQL** — PEP 249 drivers and Arrow Flight SQL server for DBeaver, Tableau, Power BI; ships with `examples/obsql.py`, a tiny terminal CLI for testing the Flight surface without a BI tool\n- **PostgreSQL Wire Protocol** (v2.5.0+) — native Postgres-protocol surface on `:5432`. Every BI tool already ships a Postgres ODBC/JDBC driver, so the user side is \"point your existing connection at OBSL and go\" — Tableau, DBeaver, Superset, Power BI, plain `psql`, and **Dremio as a federated Postgres source** (Dremio → OBSL → optionally back to Dremio's lakehouse, full circle)\n\n### Agent-Facing API\n\n- **Model Health on Load** — every model load returns a `health` block with orphan dataObjects, fan-trap risks, and unreachable dimensions — agents skip the defensive second round trip\n- **Query Plan Endpoint** — `POST /query/plan` returns the planner's understanding (planner choice, physical tables, join path, `would_compile`) without compiling SQL or executing; opt-in `include_database_explain` adds the warehouse's raw EXPLAIN\n- **Structured Warnings** — every `warnings` list across the API uses a stable `{code, severity, message, path, hint, context}` shape with a documented code taxonomy; agents branch on codes instead of parsing messages\n- **Fuzzy `/find` Recovery** — when a search produces no exact or synonym hits, deterministic Levenshtein + trigram fallback returns near-miss candidates with scores and reasons\n- **Model Examples** — optional OBML `examples:` block of canonical queries; `GET /examples` (with `?intent=` filtering) gives agents one-round-trip discovery of what a model is designed to answer\n\n### Freshness-Driven Result Cache\n\n- **Source-level freshness contracts** — declare `refresh:` blocks on `dataObject` entries (interval / heartbeat / static); the cache derives query TTLs from the contracts of the physical tables a query touched, not from caller guesses\n- **Heartbeat invalidation** — one `POST /v1/heartbeat` to a physical table invalidates every cached query that depends on it, across every dataObject and session\n- **DuckDB metadata + Parquet results** — file-backed cache with type-precise serialization, lazy expiration, LRU capacity sweep; opt-in via `CACHE_BACKEND=file`\n- **Inverts the Cube/dbt/Looker pattern** — contracts live on the source, not the semantic abstraction; one source of truth across every cube/explore/saved query reading the table\n\n### Developer Experience\n\n- **Source-Position Errors** — validation errors report exact YAML line and column\n- **ER Diagrams** — interactive Mermaid diagrams with zoom and download (MD/PNG/Turtle)\n- **Session Management** — TTL-scoped sessions with thread-safe model isolation\n- **JSON Schema** — full OBML and query schema for IDE autocompletion (`yaml-language-server`)\n\n---\n\n## Example\n\n### Define a Semantic Model (OBML)\n\n```yaml\n# yaml-language-server: $schema=https://raw.githubusercontent.com/ralforion/orionbelt-semantic-layer/main/schema/obml-schema.json\nversion: 1.0\ndataObjects:\n  Customers:\n    code: CUSTOMERS\n    database: WAREHOUSE\n    schema: PUBLIC\n    columns:\n      Customer ID: { code: CUSTOMER_ID, abstractType: string }\n      Country:     { code: COUNTRY, abstractType: string }\n\n  Orders:\n    code: ORDERS\n    database: WAREHOUSE\n    schema: PUBLIC\n    columns:\n      Order Customer ID: { code: CUSTOMER_ID, abstractType: string }\n      Price:             { code: PRICE, abstractType: float }\n      Quantity:          { code: QUANTITY, abstractType: int }\n    joins:\n      - joinType: many-to-one\n        joinTo: Customers\n        columnsFrom: [Order Customer ID]\n        columnsTo: [Customer ID]\n\ndimensions:\n  Country:\n    dataObject: Customers\n    column: Country\n    resultType: string\n\nmeasures:\n  Revenue:\n    resultType: float\n    aggregation: sum\n    expression: \"{[Orders].[Price]} * {[Orders].[Quantity]}\"\n    dataType: \"decimal(18, 2)\"\n```\n\n### Compile via REST API\n\n```bash\n# Create a session\ncurl -s -X POST http://localhost:8080/v1/sessions | jq .session_id\n# -> \"a1b2c3d4\"\n\n# Load the model\ncurl -s -X POST http://localhost:8080/v1/sessions/a1b2c3d4/models \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model_yaml\": \"...\"}' | jq .model_id\n# -> \"abcd1234\"\n\n# Compile a query\ncurl -s -X POST http://localhost:8080/v1/sessions/a1b2c3d4/query/sql \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model_id\":\"abcd1234\",\"query\":{\"select\":{\"dimensions\":[\"Country\"],\"measures\":[\"Revenue\"]}},\"dialect\":\"postgres\"}' \\\n  | jq -r .sql\n```\n\n<details>\n<summary><strong>Generated SQL (Postgres)</strong></summary>\n\n```sql\nSELECT\n  \"Customers\".\"COUNTRY\" AS \"Country\",\n  CAST(SUM(\"Orders\".\"PRICE\" * \"Orders\".\"QUANTITY\") AS NUMERIC(18, 2)) AS \"Revenue\"\nFROM WAREHOUSE.PUBLIC.ORDERS AS \"Orders\"\nLEFT JOIN WAREHOUSE.PUBLIC.CUSTOMERS AS \"Customers\"\n  ON \"Orders\".\"CUSTOMER_ID\" = \"Customers\".\"CUSTOMER_ID\"\nGROUP BY \"Customers\".\"COUNTRY\"\n```\n\n</details>\n\nChange `dialect` to `bigquery`, `clickhouse`, `databricks`, `dremio`, `duckdb`, `mysql`, or `snowflake` for dialect-specific SQL.\n\n---\n\n## Gradio UI\n\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/ralforion/orionbelt-semantic-layer/main/docs/assets/ui-sqlcompiler-dark.png\" alt=\"OrionBelt Gradio UI showing side-by-side OBML model editor and compiled SQL output\" width=\"900\">\n</p>\n\n- **SQL Compiler** — side-by-side OBML model and query editors with syntax highlighting, 8 dialect selector, one-click compilation with formatted SQL output and query explain\n- **Query Execution** — execute compiled queries against a connected database, view results with locale-aware number formatting, response metadata panel, TSV download and clipboard copy (requires `QUERY_EXECUTE=true`)\n- **ER Diagram** — interactive Mermaid ER diagram with zoom, column toggle, and download (MD/PNG/Turtle)\n- **Ontology Graph** — interactive vis-network visualization of the OBML graph (data objects, dimensions, measures, metrics, joins) with toggleable layers and adjustable node spacing\n- **Editor Toolbar** — clear, undo, redo, upload, download, and copy buttons on all code editors\n- **OSI Import/Export** — convert between OBML and OSI formats\n- **Dark/Light Mode** — toggle via header button, state persisted across sessions\n\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/ralforion/orionbelt-semantic-layer/main/docs/assets/ui-ontology-graph-dark.png\" alt=\"OrionBelt Ontology Graph tab showing the semantic model as an interactive network of data objects, dimensions, measures, metrics, and join relationships\" width=\"900\">\n</p>\n\n**Embedded mode** — the UI is mounted at `/ui` on the API server:\n\n```bash\npip install orionbelt-semantic-layer && orionbelt-api\n# -> UI at http://localhost:8000/ui\n```\n\n**Standalone mode** — run API and UI as separate processes:\n\n```bash\norionbelt-api                                              # API on :8000\norionbelt-ui                                               # UI on :7860 (connects to API on :8000)\nAPI_BASE_URL=http://remote-api:8080 orionbelt-ui           # point UI to a remote API\n```\n\n---\n\n## Documentation\n\n| Topic | Link |\n|-------|------|\n| Full docs site | [ralforion.com/orionbelt-semantic-layer](https://ralforion.com/orionbelt-semantic-layer/) |\n| Installation | [getting-started/installation](https://ralforion.com/orionbelt-semantic-layer/getting-started/installation/) |\n| Quick Start | [getting-started/quickstart](https://ralforion.com/orionbelt-semantic-layer/getting-started/quickstart/) |\n| Docker & Deployment | [getting-started/docker](https://ralforion.com/orionbelt-semantic-layer/getting-started/docker/) |\n| Development | [getting-started/development](https://ralforion.com/orionbelt-semantic-layer/getting-started/development/) |\n| OBML Model Format | [guide/model-format](https://ralforion.com/orionbelt-semantic-layer/guide/model-format/) |\n| Query Language | [guide/query-language](https://ralforion.com/orionbelt-semantic-layer/guide/query-language/) |\n| SQL Dialects | [guide/dialects](https://ralforion.com/orionbelt-semantic-layer/guide/dialects/) |\n| Period-over-Period Metrics | [guide/period-over-period](https://ralforion.com/orionbelt-semantic-layer/guide/period-over-period/) |\n| Trend Analysis (rank / lag / lead / ntile, partitioned MAs, statistical aggregates) | [guide/trend-analysis](https://ralforion.com/orionbelt-semantic-layer/guide/trend-analysis/) |\n| Compilation Pipeline | [guide/compilation](https://ralforion.com/orionbelt-semantic-layer/guide/compilation/) |\n| OBSL Graph & SPARQL | [guide/obsl](https://ralforion.com/orionbelt-semantic-layer/guide/obsl/) |\n| Gradio UI | [guide/ui](https://ralforion.com/orionbelt-semantic-layer/guide/ui/) |\n| AI Integrations | [guide/integrations](https://ralforion.com/orionbelt-semantic-layer/guide/integrations/) |\n| OSI Interoperability | [guide/osi](https://ralforion.com/orionbelt-semantic-layer/guide/osi/) |\n| REST API Endpoints | [api/endpoints](https://ralforion.com/orionbelt-semantic-layer/api/endpoints/) |\n| DB-API Drivers & Flight SQL | [drivers](https://ralforion.com/orionbelt-semantic-layer/drivers/) |\n| Architecture | [reference/architecture](https://ralforion.com/orionbelt-semantic-layer/reference/architecture/) |\n| Configuration | [reference/configuration](https://ralforion.com/orionbelt-semantic-layer/reference/configuration/) |\n| Sales Model Walkthrough | [examples/sales-model](https://ralforion.com/orionbelt-semantic-layer/examples/sales-model/) |\n| Multi-Dialect Output | [examples/multi-dialect](https://ralforion.com/orionbelt-semantic-layer/examples/multi-dialect/) |\n| Multi-Fact: Sales & Returns | [examples/multi-fact](https://ralforion.com/orionbelt-semantic-layer/examples/multi-fact/) |\n| TPC-DS Benchmark | [examples/tpcds](https://ralforion.com/orionbelt-semantic-layer/examples/tpcds/) |\n| Quickstart Notebook | [examples/quickstart.ipynb](examples/quickstart.ipynb) |\n| **Comparison: Overview** | [comparison/](https://ralforion.com/orionbelt-semantic-layer/comparison/) |\n| Comparison: vs. dbt Semantic Layer | [comparison/dbt](https://ralforion.com/orionbelt-semantic-layer/comparison/dbt/) |\n| Comparison: vs. Malloy | [comparison/malloy](https://ralforion.com/orionbelt-semantic-layer/comparison/malloy/) |\n| Comparison: vs. LookML / Looker | [comparison/lookml](https://ralforion.com/orionbelt-semantic-layer/comparison/lookml/) |\n| Comparison: vs. Cube | [comparison/cube](https://ralforion.com/orionbelt-semantic-layer/comparison/cube/) |\n| Comparison: vs. AtScale | [comparison/atscale](https://ralforion.com/orionbelt-semantic-layer/comparison/atscale/) |\n\n---\n\n## Status & Roadmap\n\n| Status | Area |\n|--------|------|\n| Shipped | 8 SQL dialects, REST API, MCP server, Gradio UI, DB-API drivers, Flight SQL, **PostgreSQL wire protocol (v2.5.0+)** — Tableau / DBeaver / Superset / Power BI / `psql` / **Dremio as a federated Postgres source**, OBSL/SPARQL, **OSI v0.2 interop** with bidirectional schema validation, AI integrations (LangChain, CrewAI, ADK, etc.), model inheritance & extends, data types & numerical precision, timezone settings, grain & filter context overrides, **Trend Analysis** — partitioned rolling windows, `MetricType.WINDOW` for rank/lag/lead/ntile, 9 statistical aggregates (CORR, COVAR_*, REGR_*, STDDEV_*, VAR_*), **Unified authentication (v2.12.0)** across REST / Flight / pgwire / UI — `AUTH_MODE=api_key` with shared key store, pgwire SCRAM-SHA-256 + cleartext, **Artefacts Composability Resolution (ACR, v2.14.0)**: a `composables` endpoint that, given the query so far, returns which dimensions / measures / metrics can still be added (including CFL candidates), powering guided query building |\n| Planned | OIDC / SSO authentication & per-token authorization scopes, CLI for automation & CI/CD, DDL view generation (CREATE VIEW from queries), additional dialects, additional BI tool integrations, pre-aggregation / materialization layer |\n\n---\n\n## Commercial Offerings\n\nOrionBelt Semantic Layer is source-available under BUSL-1.1 until its Apache-2.0 conversion — the free distribution has full parity on the shipped v2.6 surface and is production-grade for self-hosted use. For teams that want production support, a managed runtime, or embedded analytics terms, RALFORION offers:\n\n- **Embedded analytics license** — relicensing terms for shipping OBSL inside a commercial product\n- **Commercial cloud offering** — managed OrionBelt runtime with SLAs\n- **Enterprise features** — capabilities tailored for enterprise deployments\n- **Consulting + support** — implementation, modeling, and production support\n\nContact [RALFORION d.o.o.](https://ralforion.com) for details.\n\n---\n\n## Companion Project\n\n### [OrionBelt Analytics](https://github.com/ralforion/orionbelt-analytics)\n\nAn ontology-based MCP server that analyzes relational database schemas and generates RDF/OWL ontologies. Together with OrionBelt Semantic Layer, it enables AI assistants to navigate your data landscape through ontologies and compile safe, dialect-aware analytical SQL.\n\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/ralforion/orionbelt-semantic-layer/main/docs/assets/architecture.png\" alt=\"Architecture diagram showing OrionBelt Analytics generating ontologies from database schemas, feeding into OrionBelt Semantic Layer for SQL compilation\" width=\"800\">\n</p>\n\n---\n\n## Development\n\nContributing to OrionBelt or running from source:\n\n```bash\ngit clone https://github.com/ralforion/orionbelt-semantic-layer.git\ncd orionbelt-semantic-layer\nuv sync                           # install all deps (dev, docs, ui, flight, drivers)\nuv run orionbelt-api              # start API on :8000\n```\n\n```bash\n# Quality\nuv run pytest                     # run tests\nuv run ruff check src/            # lint\nuv run ruff format src/ tests/    # format\nuv run mypy src/                  # type check\n\n# Docs\nuv sync --extra docs && uv run mkdocs serve  # docs on :8080\n\n# CI workflows\n./scripts/check-action-pins.sh              # verify every Action pin\n./scripts/check-action-pins.sh --offline    # skip the upstream tag lookups\n```\n\n### GitHub Actions pinning\n\nEvery `uses:` in `.github/workflows` is pinned to a 40-character commit SHA\nrather than a tag, because a tag such as `v7` is a movable label: it runs\nwhichever commit its owner has pointed it at when the job starts. The\n`# vX.Y.Z` comment beside each SHA names the exact patch release that SHA was\ncut from, and it has to be a patch release, since a major tag moves with every\nupstream bump.\n\nPinning fixes *which* code runs but makes the reference unreadable, so\n`scripts/check-action-pins.sh` keeps the SHA and its comment honest. It walks\nevery `uses:` line and requires a commit SHA, an owner from its `ALLOWED_OWNERS`\nallowlist, and an exact-patch version comment, then resolves that tag upstream\nwith `git ls-remote` and fails when the commit it names is not the one pinned.\nContainer actions must be pinned by digest; local `./` actions are skipped.\n`--offline` checks only the SHA and comment format, with no network calls.\n\nThe check runs as the `pins` job in CI, and as the first step after checkout in\nthe workflows that publish (Docker, PyPI, docs), so a tag can never ship\nartifacts built by steps whose pins were never verified. Adding an owner to\n`ALLOWED_OWNERS` is a deliberate decision: a SHA matching its own tag says\nnothing about whether that action belongs in this repo at all.\n\n---\n\n## License\n\nCopyright © 2026 [RALFORION d.o.o.](https://ralforion.com)\n\nOrionBelt® is a registered trademark of RALFORION d.o.o.\n\nLicensed under the [Business Source License 1.1](LICENSE) (SPDX: `BUSL-1.1`). The Licensed Work will convert to Apache License 2.0 on 2030-03-16.\n\nThird-party works redistributed by OrionBelt, and their terms, are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).\n\nBy contributing to this project, you agree to the [Contributor License Agreement](CLA.md).\n\nFor commercial licensing inquiries, contact: licensing@ralforion.com\n\n---\n\n<p align=\"center\">\n  <a href=\"https://ralforion.com\">\n    <img src=\"https://raw.githubusercontent.com/ralforion/orionbelt-semantic-layer/main/docs/assets/RALFORION_doo_Logo.png\" alt=\"RALFORION d.o.o.\" width=\"200\">\n  </a>\n</p>\n",
  "bytes": 35283,
  "sha": "a57ed93e492182156807000327738cab8685848b145de7110c61f161b5ac966d",
  "repo_slug": "ralfbecher/orionbelt-semantic-layer",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_ralfbecher_orionbelt_semantic__09fe191a/readme"
}