add-dbt-model
Status: v0.10.0 (pre-test) — conventions + templates complete, with incremental-by-default for fact/event marts on BigQuery (cost control). Conventions and decision-tree references written; copy-paste templates (staging, marts dimension, marts incremental fact, schema, sources) included plus the incremental-and-cost.md reference; the Phase A–D playbook (classify → write → run/verify → commit) is in place.
What this skill does
Adds a new dbt model to the client's dbt project. Decides where the model belongs (staging / intermediate / marts) based on what the user describes, writes the SQL, adds tests, and runs it once to verify before committing.
Preflight
if [ ! -f .agentic-data-engineer.json ]; then
echo "[abort] not a managed MDS deployment"
exit 1
fi
# Confirm dbt is configured in this stack
jq -e '.stack.transform == "dbt_vps"' .agentic-data-engineer.json > /dev/null || {
echo "[abort] this MDS doesn't have dbt configured"
echo "Phase 2 of create-mds adds dbt — run that first"
exit 1
}
Playbook outline
Phase A — Classify the model
Ask the user what they want to compute. Then decide:
| Layer |
When |
staging (stg_<source>_<table>) |
One-to-one with a raw source table, applies cleanup (rename columns, cast types, filter junk rows). One staging model per raw table. |
intermediate (int_<concept>) |
Reusable logic that several marts will consume (e.g. int_orders_with_customer). Not exposed to end users. |
marts (analytics-ready, plain names like orders, revenue_monthly) |
The deliverable. End users and BI tools query these. |
See references/staging-vs-marts.md and references/dbt-naming-conventions.md.
Materialization is a cost decision on BigQuery (bills by bytes scanned). Default by layer: staging = view; dimensions / small reports = table; fact/event marts = incremental (partitioned + clustered, insert_overwrite). A full-refresh table on a growing fact re-scans all history every run — the surprise-bill risk this default prevents. See references/incremental-and-cost.md.
Phase B — Write the model
- SSH to the VPS, locate the dbt project.
- Create the SQL file in the right folder.
- Write the
SELECT with explicit column lists (never SELECT * in marts).
- Add a
schema.yml entry with tests (not_null, unique where applicable, accepted_values for known categories).
Phase C — Run and verify
dbt run --select <model> in the venv on the VPS.
dbt test --select <model>.
- Spot-check the resulting table in BigQuery (
bq query or SELECT * LIMIT 10).
Phase D — Commit
- Push to the client repo.
- Update marker history.
References
Conventions (complete):
references/dbt-naming-conventions.md — file/model/column naming, SQL style, tests pattern, canonical model shapes
references/staging-vs-marts.md — decision tree for which layer a model belongs in
references/incremental-and-cost.md — BigQuery cost control: when to go incremental, partition/cluster choice, maximum_bytes_billed, the GCP budget alert backstop, and the light cross-db convention (prefer dbt macros, flatten structs early — no adapter.dispatch framework)
Templates (complete) — copy into the client's dbt project and fill the <PLACEHOLDER> markers:
templates/staging.sql.template — canonical staging model: view materialization, source + renamed CTEs, explicit casts, ingest-tool-agnostic load metadata (dlt's _dlt_load_id → load_id, join _dlt_loads for the timestamp; Airbyte legacy _airbyte_extracted_at → loaded_at), where <pk> is not null
templates/marts.sql.template — DIMENSION / small-report mart: table materialization, one CTE per ref() input, a joined CTE, explicit final select. Heavily commented on when to use table (dim) vs incremental (fact)
templates/marts_incremental.sql.template — FACT / event mart (BigQuery default): incremental + insert_overwrite + partition_by + cluster_by + on_schema_change, with the is_incremental() look-back guard
templates/schema.yml.template — model docs + tests (not_null/unique PK, not_null FK, accepted_values enum, optional monetary check) plus optional incremental-fact guards (partition not_null, recency, row-count)
templates/sources.yml.template — raw sources: declaration (database <project>, schema raw_<source>, freshness warn 26h / error 50h, loaded_at_field)
1---2name: add-dbt-model3description: Add a new dbt model (staging, intermediate, or marts) to an existing MDS deployment. Invoke when the user wants to transform raw data, build an analytics table, or expose a new metric in BigQuery.4---56# add-dbt-model78> **Status**: v0.10.0 (pre-test) — conventions + templates complete, with **incremental-by-default for fact/event marts** on BigQuery (cost control). Conventions and decision-tree references written; copy-paste templates (staging, marts dimension, marts incremental fact, schema, sources) included plus the `incremental-and-cost.md` reference; the Phase A–D playbook (classify → write → run/verify → commit) is in place.910## What this skill does1112Adds a new dbt model to the client's dbt project. Decides where the model belongs (staging / intermediate / marts) based on what the user describes, writes the SQL, adds tests, and runs it once to verify before committing.1314## Preflight1516```bash17if [ ! -f .agentic-data-engineer.json ]; then18 echo "[abort] not a managed MDS deployment"19 exit 120fi2122# Confirm dbt is configured in this stack23jq -e '.stack.transform == "dbt_vps"' .agentic-data-engineer.json > /dev/null || {24 echo "[abort] this MDS doesn't have dbt configured"25 echo "Phase 2 of create-mds adds dbt — run that first"26 exit 127}28```2930## Playbook outline3132**Phase A — Classify the model**3334Ask the user what they want to compute. Then decide:3536| Layer | When |37|---|---|38| **staging** (`stg_<source>_<table>`) | One-to-one with a raw source table, applies cleanup (rename columns, cast types, filter junk rows). One staging model per raw table. |39| **intermediate** (`int_<concept>`) | Reusable logic that several marts will consume (e.g. `int_orders_with_customer`). Not exposed to end users. |40| **marts** (analytics-ready, plain names like `orders`, `revenue_monthly`) | The deliverable. End users and BI tools query these. |4142See `references/staging-vs-marts.md` and `references/dbt-naming-conventions.md`.4344**Materialization is a cost decision on BigQuery (bills by bytes scanned).** Default by layer: staging = `view`; dimensions / small reports = `table`; **fact/event marts = `incremental`** (partitioned + clustered, `insert_overwrite`). A full-refresh `table` on a growing fact re-scans all history every run — the surprise-bill risk this default prevents. See `references/incremental-and-cost.md`.4546**Phase B — Write the model**47481. SSH to the VPS, locate the dbt project.492. Create the SQL file in the right folder.503. Write the `SELECT` with explicit column lists (never `SELECT *` in marts).514. Add a `schema.yml` entry with tests (`not_null`, `unique` where applicable, `accepted_values` for known categories).5253**Phase C — Run and verify**54551. `dbt run --select <model>` in the venv on the VPS.562. `dbt test --select <model>`.573. Spot-check the resulting table in BigQuery (`bq query` or `SELECT * LIMIT 10`).5859**Phase D — Commit**60611. Push to the client repo.622. Update marker history.6364## References6566Conventions (complete):67- [`references/dbt-naming-conventions.md`](references/dbt-naming-conventions.md) — file/model/column naming, SQL style, tests pattern, canonical model shapes68- [`references/staging-vs-marts.md`](references/staging-vs-marts.md) — decision tree for which layer a model belongs in69- [`references/incremental-and-cost.md`](references/incremental-and-cost.md) — BigQuery cost control: when to go incremental, partition/cluster choice, `maximum_bytes_billed`, the GCP budget alert backstop, and the light cross-db convention (prefer dbt macros, flatten structs early — no `adapter.dispatch` framework)7071Templates (complete) — copy into the client's dbt project and fill the `<PLACEHOLDER>` markers:72- [`templates/staging.sql.template`](templates/staging.sql.template) — canonical staging model: `view` materialization, `source` + `renamed` CTEs, explicit casts, ingest-tool-agnostic load metadata (dlt's `_dlt_load_id` → `load_id`, join `_dlt_loads` for the timestamp; Airbyte legacy `_airbyte_extracted_at` → `loaded_at`), `where <pk> is not null`73- [`templates/marts.sql.template`](templates/marts.sql.template) — DIMENSION / small-report mart: `table` materialization, one CTE per `ref()` input, a `joined` CTE, explicit final select. Heavily commented on when to use `table` (dim) vs `incremental` (fact)74- [`templates/marts_incremental.sql.template`](templates/marts_incremental.sql.template) — FACT / event mart (BigQuery default): `incremental` + `insert_overwrite` + `partition_by` + `cluster_by` + `on_schema_change`, with the `is_incremental()` look-back guard75- [`templates/schema.yml.template`](templates/schema.yml.template) — model docs + tests (`not_null`/`unique` PK, `not_null` FK, `accepted_values` enum, optional monetary check) plus optional incremental-fact guards (partition `not_null`, recency, row-count)76- [`templates/sources.yml.template`](templates/sources.yml.template) — raw `sources:` declaration (database `<project>`, schema `raw_<source>`, freshness warn 26h / error 50h, `loaded_at_field`)