# Document dbt Models and Find Data Quality Gaps

Yes. An agent builds dbt-labs/jaffle-shop on DuckDB in a sandbox and writes models.md, flagging untested join and metric columns.

[Run this on Rush](https://cloud.getrush.ai/run/dbt-model-docs/dbt/).

## What the agent does

The agent clones https://github.com/dbt-labs/jaffle-shop and reads it before it builds. dbt_project.yml names the project jaffle_shop, version 3.0.0, require-dbt-version >= 2.0.0, profile default. Staging models are materialized as views and marts as tables. packages.yml installs dbt-labs/dbt_utils 1.4.1 and dbt-audit-helper from git revision main. There is no profiles.yml in the repo. The prompt says to change no project files, so the agent writes a DuckDB profile only under the sandbox home (type duckdb, a file path inside the sandbox) and points the dbt invocation at it with --profiles-dir, leaving dbt_project.yml, the models and packages.yml untouched.

Seeds are off unless asked. dbt_project.yml sets seeds.jaffle_shop.jaffle-data enabled to the var load_source_data, default false, and the README's load command is dbt seed --full-refresh --vars '{"load_source_data": true}'. The CSVs are seeds/jaffle-data/raw_customers.csv, raw_orders.csv, raw_items.csv, raw_stores.csv, raw_products.csv and raw_supplies.csv. models/staging/__sources.yml declares source ecom, schema raw, with those six tables. The agent runs dbt deps, that seed command, then dbt build, and pastes the result into the report. macros/cents_to_dollars.sql dispatches to default, postgres, bigquery and fabric, and the default casts with ::numeric(16, 2). There is no duckdb__cents_to_dollars. The report records whether DuckDB accepted that cast instead of editing the macro, because the prompt forbids file changes.

models.md walks each model from the YAML and the SQL. Staging grains: stg_customers one row per customer (raw_customers.id as customer_id), stg_orders one row per order (store_id becomes location_id, customer becomes customer_id, cents divided by 100, ordered_at truncated to the day), stg_order_items one row per item (sku as product_id), stg_products one row per SKU (type = 'jaffle' is is_food_item, type = 'beverage' is is_drink_item), stg_locations one row per store. Marts: orders joins order_items on order_id; order_items left-joins orders, stg_products and a supplies rollup on product_id; customers aggregates orders on customer_id. data-tests/ holds only .gitkeep. Tests that exist are data_tests and unit_tests on the model YAML.

The gap list is columns used in a join or a metric that have no data test, plus the test to add. orders.customer_id has a relationships test to stg_customers.customer_id, and orders.order_id is not_null and unique. orders.location_id is a foreign entity and has no relationships test to stg_locations. order_items.order_id relates to orders.order_id, but order_items.product_id, which order_items.sql joins to stg_products.product_id, has none. The revenue metric's expr is product_price, and neither the order_items YAML nor the products mart tests product_price; the products mart does not even test product_id, though stg_products does. locations.yml's average_tax_rate metric averages tax_rate, and the locations mart has no data test on location_id or tax_rate. supplies.product_id is the join key in the supply rollup and is untested. stg_orders.customer_id and location_id have no tests, while customers.sql groups on customer_id. product_type has no accepted_values, even though is_food_item and is_drink_item are type = 'jaffle' and type = 'beverage'.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Clone https://github.com/dbt-labs/jaffle-shop. Do not edit any file in the project. Install dbt that satisfies require-dbt-version >= 2.0.0 from dbt_project.yml, with the DuckDB adapter. Put a DuckDB profiles.yml outside the repo and pass --profiles-dir. Run dbt deps, then dbt seed --full-refresh --vars '{\"load_source_data\": true}', then dbt build. Write models.md: each model's purpose, grain, upstream source or ref, and columns; then every column used in a join or a metric that has no data test, and the test to add. Include the build output. If the cents_to_dollars default macro fails on DuckDB, record the error and do not patch the macro."
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

models.md is an artifact on the session. The session read returns a download url for that file, valid for an hour, with no key required; GET with the key at /o/{org}/sessions/{id}/artifacts/models.md works as well. The file has two parts. The first is a model dictionary: name, materialization (view for staging, table for marts), grain taken from the YAML description, the source or ref it selects from, and the columns the SQL exposes. The second is the untested join and metric columns, each with the SQL line that uses it and the data test that would cover it. The dbt build log is quoted, including a failure of ::numeric if DuckDB rejected cents_to_dollars, so a red build is written down rather than hidden.

A reviewer checks the dictionary against the files. orders.yml tests order_id, customer_id and two expression_is_true checks (order_items_subtotal = subtotal, order_total = subtotal + tax_paid). customers.yml tests customer_id, accepted_values of customer_type (new, returning), and lifetime_spend_pretax + lifetime_tax_paid = lifetime_spend. Unit tests already in the repo, including test_order_items_compute_to_bools_correctly and test_supply_costs_sum_correctly, are listed as existing. The gap that should still be there is product_id on the order_items join and product_price under the revenue metric.

Nothing in the clone is committed. The prompt forbids edits, so the report recommends tests and stops. A follow-up on the same session can ask the agent to add one relationships test and open a pull request; that is a different instruction, and the parked sandbox still has the DuckDB file and the build's target directory.

## Run it on every merge to the dbt project's default branch

When a pull request merges to the branch your warehouse project builds from, your server POSTs this body with the repository in resources instead of a public clone URL, and the merge commit as the Idempotency-Key. One repository per run. The agent starts from that commit, builds in the sandbox, and writes a fresh models.md, so the dictionary tracks the models that just landed.

webhook_url on the API key delivers run.completed to your endpoint within about 30 seconds, with session_url. Verify the Standard Webhooks signature, then attach models.md to the merge commit's discussion or to the data team's channel. If dbt build fails, the run can still complete with a report that quotes the error; treat a run.failed, where no file was written, as a broken sandbox rather than as a data-test gap.

## Why run it on Rush?

A laptop install means an adapter, a seed load, and a profiles.yml you must not commit. The README aims this repository at a warehouse and points a local DuckDB trial at jaffle_shop_duckdb. The sandbox installs dbt, writes a profile outside the clone, seeds the six CSVs, and runs dbt build without the warehouse named by dbt-cloud project-id in dbt_project.yml.

The gap list is only useful if it cites the YAML test that is missing and the SQL that uses the column. That citation is in the report, and the build log that shows whether DuckDB actually compiled cents_to_dollars is in the transcript. Several projects are several runs, bounded by max_concurrent on the key (default 4, then 429).

## Frequently asked questions

### Will it edit my models or add tests?

No. The prompt says not to edit the project, including the cents_to_dollars macro if DuckDB rejects ::numeric. profiles.yml is written outside the clone, which matches the README's note that profiles.yml is gitignored and should not be committed. The result is models.md.

### This repo expects a warehouse. Why DuckDB?

The seeds under seeds/jaffle-data are enough to compile. dbt_project.yml requires dbt >= 2.0.0 and sets profile: default, but ships no profile, so the sandbox uses DuckDB and quotes the build log, including a cents_to_dollars failure. The README points a supported local DuckDB project at jaffle_shop_duckdb; this run does not treat jaffle-shop itself as that project.

### Are the existing unit tests counted as coverage?

They are named in the dictionary so they are not proposed again. test_does_location_opened_at_trunc_to_date on stg_locations checks opened_date truncation. They are not a substitute for a relationships test on orders.location_id or a not_null test on product_price, which is what the gap list is for. data-tests/ itself is empty.

### Can it document a private dbt project?

Yes. Pass the repository as resources with the GitHub URL covered by your Rush GitHub App installation, and drop the public clone line from the prompt. The agent still must not edit files unless you change that sentence. A private warehouse profile is not part of this request; keep using a sandbox DuckDB profile unless you deliberately hand the run warehouse credentials.
