How this demo works

Architecture of the demo — a dlt pipeline, a hand-written Boring Semantic Layer model, and dashdown as the UI layer, with Mistral narrating the data.

Three tools, one clean seam between them: dlt loads the data and builds the star schema, the Boring Semantic Layer defines the metrics exactly once, and dashdown turns that semantic model into the dashboard you're reading — with mistral-medium-latest adding the inline commentary. (The setup follows dltHub's Autofilling the Boring Semantic Layer pipeline, with a UI layer where the blog has APIs and chatbots.)

flowchart LR
    A["Sakila OLTP\n(SQLite seed)"] -->|"dlt: load + schema,\nFKs, PII hints"| B["DuckDB warehouse\n+ star schema"]
    C["semantic/sakila.yml\n(BSL, written by hand)"] --> E["dashdown"]
    B --> E
    D["mistral-medium-latest\n(Ask / explain)"] --> E
    E -->|"dashdown build"| F["Static site\n(Cloudflare Pages)"]

1 · dlt loads the data — and shapes the warehouse #

python -m pipeline.run reads the classic Sakila movie-rental database with dlt's sql_database source. Because resolve_foreign_keys=True, dlt records the foreign-key graph alongside column types, and column hints mark PII:

customer = table("customer", "customer_id")     # sql_table(..., resolve_foreign_keys=True)
customer.apply_hints(columns={
    "first_name": {"x-annotation-pii": True},
    "email":      {"x-annotation-pii": True},
})

An Ibis transform step (pipeline/transform.py) then reshapes the snowflaked OLTP tables into the star schema we modeled — fact_rental joined one hop from dim_film, dim_customer and dim_storedropping every PII-hinted column on the way. This warehouse never contains a name or an email address.

2 · The semantic model — defined once, in YAML #

semantic/sakila.yml is the contract between the warehouse and every widget: four BSL models declaring the measures, dimensions and joins over the star schema. Plain, reviewable YAML:

rentals:
  connector: warehouse
  table: fact_rental
  dimensions:
    rented_at:
      expr: _.rented_at
      is_time_dimension: true
  measures:
    revenue:
      expr: _.amount.sum()
      metadata: {format: currency, currency: "$"}
  joins:
    films: {model: films, type: one, left_on: film_id, right_on: film_id}

Because a semantic model is executable, it's also checkable: python -m pipeline.validate builds every model against the live warehouse and runs each measure and dimension once, so a typo'd column, an unresolvable join, or a type slip (_.active == 1 on a text column instead of _.active == '1') fails loudly before the dashboard ever renders. CI runs it on every build.

3 · dashdown renders it #

dashdown's semantic backend is BSL — the YAML above is loaded natively, and a page references it with no SQL:

<BarChart metric={rentals.revenue} by={rentals.category}
          title="Revenue by film category" />

Behind that one tag, BSL joins fact_rental to dim_film, handles the fan-out, compiles to DuckDB SQL, and pushes the aggregation down to the warehouse. Filters become semantic filters automatically, and the definition of revenue lives in exactly one place. The proof:

4 · Mistral narrates it #

This is what MISTRAL_API_KEY is for: dashdown's <Ask /> component and the ✨ explain button on charts send a query's (capped) result to mistral-medium-latest and render the answer inline — the ✦ commentary you've seen on every page. It binds to semantic metrics with the same grammar the charts use:

<Ask metric={rentals.revenue} by={rentals.category}
     ask="Which film categories drive revenue?" />

Answers are cached, and dashdown build bakes them into the static export — the published site needs no server and no API key. Without the key everything still builds; the commentary cards simply say so.

Run it yourself #

git clone https://github.com/DirendAI/dashdown-bsl-dlt && cd dashdown-bsl-dlt
make setup                # uv sync
make pipeline             # load Sakila → DuckDB star schema
make serve                # live dashboard on :8000

export MISTRAL_API_KEY=  # optional: AI commentary via mistral-medium-latest
make check                # validate the semantic model + every page
make build                # static site → dist/ (what CI deploys)
Generated · built in 22.9s