# Dashboards as Code for your dbt Project

[dbt](https://www.getdbt.com/) is great for transforming data.
You write modular SQL, track everything in Git, and run automated tests to make sure your data is correct.

**But dbt doesn't help you visualize your data.**

When it's time to build dashboards on top of a dbt project, you usually end up clicking around in a separate BI tool - outside of Git and disconnected from your code workflow.

That's where [Shaper](https://taleshape.com/shaper/docs) comes in:
**In Shaper, dashboards are also just SQL files stored in Git.**

<PrettyImg alt="Clinic Overview Dashboard Preview" src={dashboardPreview1} />

Recently, [Jarich](https://www.jbanalytica.com/) (creator of [model2data](https://www.jbanalytica.com/model2data)) and I built a demo project to show how simple it is to go from a data model to a tested **dbt project** and an interactive **Shaper dashboard**.

We auto-generated the dbt project with model2data then, **generated the Shaper dashboard with a single AI prompt.**

You can check out the live dashboard and the code right here:

[Live Demo: Healthcare Clinic Overview](https://demo.taleshape.com/view/clinic-overview)
[GitHub: model2data-shaper-demo](https://github.com/taleshape-com/model2data-shaper-demo)

## Generating the dbt Project and Test Data

When building a demo, fake data is usually a pain.
If you just generate random values, all your charts look flat and unrealistic. Or the data doesn't even make sense - like invoices dated before an appointment was booked, or payments on cancelled visits.

That's the problem Jarich solved with **[model2data](https://www.jbanalytica.com/model2data)**.

For our demo, we modeled a network of 8 healthcare clinics with practitioners, treatments, insurers, patients, and appointments in a single schema file.

With *model2data*, you define your schema once and specify how the generated data should behave:

```yaml
# Excerpt from healthcare_clinics.model2data.yml
appointments:
  role: fact
  columns:
    status:
      type: appointment_status
      not_null: true
      generate:
        weights: {completed: 16, cancelled: 3, no_show: 1}
    booked_at:
      type: timestamp
      not_null: true
      generate: {growth: 0.5}
    scheduled_for:
      type: timestamp
      not_null: true
      generate: {after: booked_at, business_hours: true}
    actual_duration_minutes:
      type: int
      generate:
        min: 10
        max: 120
        when: {status: [completed]}
        distribution: {kind: normal, mean: 35, stddev: 12}
    billed_amount:
      type: numeric
      generate:
        min: 25
        max: 600
        when: {status: [completed]}
        distribution: {kind: lognormal, median: 95, spread: 0.5}
    paid_at:
      type: timestamp
      generate:
        after: checked_in_at
        when: {status: [completed]}
        null_rate: 0.3
```

From this single file, *model2data* generates:
1. A **[DuckDB](https://duckdb.org/) database** with 1,500 patients and 6,000 appointments scheduled during realistic weekday business hours.
2. A complete **dbt project** with staging models and **83 automated dbt data tests** - checking primary keys, foreign key relationships, non-null constraints, and valid status values.

## Dashboards as Code for dbt

Once you have a ready dbt project, building dashboards in Shaper follows the exact same workflow.
Shaper directly connects to the database generated by model2data and queries the exact tables dbt defined.

Shaper is directly built on top of DuckDB and lets you **build interactive dashboards completely in SQL**:
- Dashboards live as `.dashboard.sql` files right next to your dbt models in [Git](https://taleshape.com/shaper/docs/git-workflow/).
- You use standard DuckDB SQL and turn results into filters, metrics, and charts using [custom SQL types](https://taleshape.com/shaper/docs/dashboard-sql-reference/) like `::DROPDOWN`, `::BARCHART_STACKED`, or `::LINECHART`.
- Because dbt already tests your foreign keys and constraints, you can join tables and calculate metrics without worrying about broken references or messy data.

Here is how [`Clinic Overview.dashboard.sql`](https://github.com/taleshape-com/model2data-shaper-demo/blob/main/Clinic%20Overview.dashboard.sql) sets up filters at the top and caches the filtered dbt tables in a temporary table for fast queries across the dashboard:

```sql
-- 1. Main Header & Controls
SELECT 'Healthcare Clinic Network'::SECTION, 'Executive Overview & Clinical Operations'::SUBTITLE;

SELECT
  min(scheduled_for)::DATE::DATEPICKER_FROM AS start_date,
  max(scheduled_for)::DATE::DATEPICKER_TO AS end_date
FROM staging.stg_appointments;

SELECT 'Clinic'::LABEL;
SELECT 'All Clinics'::DROPDOWN AS selected_clinic
UNION ALL
(SELECT name FROM staging.stg_clinics ORDER BY name);

-- 2. Cache filtered dataset in memory
CREATE TEMP TABLE filtered_appointments AS (
  SELECT
    a.id AS appointment_id,
    a.status,
    a.channel,
    a.booking_source,
    a.scheduled_for,
    a.actual_duration_minutes,
    a.billed_amount,
    a.paid_at,
    c.name AS clinic_name,
    p.specialty AS practitioner_specialty,
    t.name AS treatment_name,
    COALESCE(i.name, 'Uninsured / Self-Pay') AS insurer_name
  FROM staging.stg_appointments a
  JOIN staging.stg_practitioners p ON a.practitioner_id = p.id
  JOIN staging.stg_clinics c ON p.clinic_id = c.id
  JOIN staging.stg_treatments t ON a.treatment_id = t.id
  JOIN staging.stg_patients pat ON a.patient_id = pat.id
  LEFT JOIN staging.stg_insurers i ON pat.insurer_id = i.id
  WHERE a.scheduled_for::DATE BETWEEN getvariable('start_date') AND getvariable('end_date')
    AND (getvariable('selected_clinic') = 'All Clinics' OR c.name = getvariable('selected_clinic'))
);
```

From there, every chart on the dashboard is just a simple SQL query:

```sql
SELECT 'Monthly Appointments by Status'::LABEL;
SELECT
  date_trunc('month', scheduled_for)::DATE::XAXIS,
  status::CATEGORY,
  count(*)::BARCHART_STACKED
FROM filtered_appointments
GROUP BY ALL
ORDER BY ALL;
```

## Generating the Dashboard in a Single Prompt

What's not in the repo is how I created the dashboard.

I didn't write those 300 lines of SQL by hand.

Instead, I used the **[Shaper AI Skills](https://taleshape.com/shaper/docs/ai-agent-workflow/)**.
The skills teach your AI coding agent (like Claude Code, Cursor, or Codex) how to inspect the database schema, write Shaper SQL, validate queries, and preview dashboards via the Shaper CLI.

With Shaper connected to the DuckDB database from the dbt project, I gave the agent a single prompt:

> *I got this DBT project. Shaper is connected to the database from this project. Please generate me an overview dashboard for the data in this project. ask me for suggestions of what to show as different sections.*

1. The agent then read the dbt project and ran `shaper schema` to inspect the tables and columns in DuckDB.
2. It suggested 6 sections for the dashboard and I didn't have to adjust anything. I just replied **"ok"**.
4. The agent generated `Clinic Overview.dashboard.sql`, ran `shaper validate` in the terminal to verify that all SQL queries run without errors, and opened the live dashboard in my browser using `shaper preview`.

<PrettyImg alt="Clinic Overview Dashboard Preview" src={dashboardPreview2} />

Because dbt projects are well-structured and Shaper dashboards are just SQL validated via the CLI, **the AI agent created a working dashboards without me having to fix anything.**

And since `shaper schema` and `shaper validate` only check the schema and SQL syntax locally, **the LLM never even sees your actual data.**

## Try It Yourself

If you want to try the demo locally, all you need is the [Shaper CLI](https://taleshape.com/shaper/docs/installing-shaper/):

1. Clone the demo repository:
   ```bash
   git clone https://github.com/taleshape-com/model2data-shaper-demo.git
   cd model2data-shaper-demo
   ```
2. Start Shaper with the included DuckDB file and deploy the dashboard:
   ```bash
   shaper -d .shaperdata --duckdb healthcare_clinics.duckdb --deploy .
   ```
3. Open [http://localhost:5454/dashboards/clinic-overview](http://localhost:5454/dashboards/clinic-overview) in your browser.

To use the AI workflow on your own dbt project, check out the [AI Agent Workflow docs](https://taleshape.com/shaper/docs/ai-agent-workflow/).
And if you need realistic test data and dbt models from a simple schema definition, definitely check out Jarich's [model2data](https://www.jbanalytica.com/model2data).

<div class="flex justify-center">
  [taleshape-com/shaper](https://github.com/taleshape-com/shaper)
</div>

<NewsletterSignup />