dbt Models¶
This page documents the dbt transformation models in the XO-Data platform. dbt handles the BRONZE → SILVER → GOLD transformation pipeline after xo-foundry loads raw data.
dbt project location: apps/dbt/dbt_xo_models/
Model Lineage: Warby Parker¶
flowchart TD
B1[BRONZE\nGLADLY_CONTACT_TIMESTAMPS] --> S1[SILVER\ncontact_timestamps]
B2[BRONZE\nGLADLY_CONVERSATION_TIMESTAMPS] --> S2[SILVER\nconversation_timestamps]
B3[BRONZE\nGLADLY_WORK_SESSIONS] --> S3[SILVER\nwork_sessions]
B4[BRONZE\nGLADLY_AGENT_DURATIONS] --> S4[SILVER\nagent_durations]
SEED[SEED\nai_agents.csv] --> RPT
ROSTER[OPERATIONS.BAMBOOHR\nROSTER_WARBYPARKER\n⚠️ temporary bridge] --> FCT
S1 --> INT[GOLD int\nint_contacts_hold]
S1 --> FCT[GOLD fct\nfct_contacts]
INT --> FCT
S3 --> AGG[GOLD agg\nagg_agent_daily_contacts]
FCT --> AGG
AGG --> RPT[GOLD rpt\nrpt_email_daily\nview]
Temporary roster bridge
fct_contacts currently JOINs OPERATIONS.BAMBOOHR.ROSTER_WARBYPARKER. This is a temporary bridge while CORE_DB.SILVER.ROSTER_WARBYPARKER is being set up per ADR 010.
Silver Models¶
Silver models are incremental append-only — they accumulate history from Bronze tables that are cleared on each run (batch_replace strategy).
contact_timestamps¶
| Property | Value |
|---|---|
| Target | WBP_DB.SILVER.CONTACT_TIMESTAMPS |
| Source | WBP_DB.BRONZE.GLADLY_CONTACT_TIMESTAMPS |
| Grain | 1 row per contact timestamp event |
| Materialization | Incremental table |
| Unique key | RECORD_KEY |
| Columns | 36 Gladly API columns + 7 xo-foundry metadata + 2 dbt audit |
Preserves the complete history of contact-level events from the Gladly ContactTimestampsReport API.
conversation_timestamps¶
| Property | Value |
|---|---|
| Target | WBP_DB.SILVER.CONVERSATION_TIMESTAMPS |
| Source | WBP_DB.BRONZE.GLADLY_CONVERSATION_TIMESTAMPS |
| Grain | 1 row per conversation timestamp event |
| Materialization | Incremental table |
| Unique key | RECORD_KEY (composite of 10 columns) |
| Columns | 21 Gladly API columns + 7 metadata + 2 audit |
Preserves conversation-level event history from ConversationTimestampsReport.
work_sessions¶
| Property | Value |
|---|---|
| Target | WBP_DB.SILVER.WORK_SESSIONS |
| Source | WBP_DB.BRONZE.GLADLY_WORK_SESSIONS |
| Grain | 1 row per agent work session |
| Materialization | Incremental table |
| Unique key | RECORD_KEY (WORK_SESSION_ID) |
| Columns | 24 Gladly API columns + 7 metadata + 2 audit |
Preserves work session history from WorkSessionsReportV3. Filters out unrouted contacts (null WORK_SESSION_ID).
agent_durations¶
| Property | Value |
|---|---|
| Target | WBP_DB.SILVER.AGENT_DURATIONS |
| Source | WBP_DB.BRONZE.GLADLY_AGENT_DURATIONS |
| Grain | 1 row per agent status span |
| Materialization | Incremental table |
| Unique key | RECORD_KEY (composite of AGENT_ID + START_TIME + TYPE) |
| Columns | 9 Gladly API columns + 7 metadata + 2 audit |
Preserves agent duration history from AgentDurationsReportV2.
Gold Models¶
Gold models add business enrichment — roster JOINs, aggregations, and report-ready views.
int_contacts_hold (intermediate)¶
| Property | Value |
|---|---|
| Target | Ephemeral (materialized inline) |
| Source | contact_timestamps |
| Purpose | Hold duration calculation using LAG() window function |
| Grain | 1 row per contact with computed hold time |
Shared by fct_contacts and agg_agent_daily_contacts. Replaces OPERATIONS.STAGING.STAGE_WARBYPARKER_CONTACTS_HOLD.
fct_contacts¶
| Property | Value |
|---|---|
| Target | WBP_DB.GOLD.FCT_CONTACTS |
| Source | contact_timestamps, int_contacts_hold, OPERATIONS.BAMBOOHR.ROSTER_WARBYPARKER |
| Grain | 1 row per contact |
| Materialization | Table |
Master contacts fact table with roster enrichment. Tracks contact status, queue times, handle times, agent attribution, and hold durations.
Replaces: OPERATIONS.AGGREGATE.WARBYPARKER_CONTACTS_MODEL
agg_agent_daily_contacts¶
| Property | Value |
|---|---|
| Target | WBP_DB.GOLD.AGG_AGENT_DAILY_CONTACTS |
| Source | work_sessions, fct_contacts |
| Grain | 1 row per agent × date × channel × direction × status × inbox |
| Materialization | Table |
Daily agent-level aggregation with roster enrichment and tenure bucketing. Reusable building block for multiple reports.
Replaces: OPERATIONS.AGGREGATE.WARBYPARKER_AGENT_DAILY_CONTACTS
rpt_email_daily¶
| Property | Value |
|---|---|
| Target | WBP_DB.GOLD.RPT_EMAIL_DAILY |
| Source | agg_agent_daily_contacts, ai_agents seed |
| Grain | 1 row per agent × date |
| Materialization | View (zero storage) |
| Tableau-ready | Yes |
Daily email performance report for AI-enabled agents. Tracks email volume, handle times, emails per hour, SLA compliance, and AI wave categorization (pre/post AI enablement date).
Replaces: OPERATIONS.AGGREGATE.WARBYPARKER_RPT_EMAIL_DAILY
Key columns:
| Column | Description |
|---|---|
DATE |
Date of agent email activity |
AGENT_NAME |
Agent name |
TOTAL_EMAILS |
Total email contacts handled |
AVG_HANDLE_TIME_MIN |
Average contact handle time (minutes) |
EMAILS_PER_HOUR |
Contacts fulfilled per active hour |
SLA_MET_PCT |
Percentage of emails meeting SLA |
WAVE_PERIOD |
Pre-Wave or Post-Wave relative to agent's AI enablement date |
Seeds¶
ai_agents¶
| Property | Value |
|---|---|
| Location | seeds/warbyparker/ai_agents.csv |
| Target | WBP_DB.GOLD.AI_AGENTS |
| Purpose | AI wave enablement dates per agent |
| Columns | AGENT_NAME, AI_ENABLED_DATE |
Used by rpt_email_daily to compute WAVE_PERIOD — whether a given date is before or after the agent's AI enablement date.
Environment Configuration¶
The dbt project switches between dev and prod via an environment variable:
# dbt_project.yml
vars:
environment: "{{ env_var('ENVIRONMENT', 'dev') }}"
models:
dbt_xo_models:
silver:
warbyparker:
+database: "{{ 'WBP_DB' if env_var('ENVIRONMENT', 'dev') == 'prod' else 'WBP_DB_DEV' }}"
gold:
warbyparker:
+database: "{{ 'WBP_DB' if env_var('ENVIRONMENT', 'dev') == 'prod' else 'WBP_DB_DEV' }}"
| Environment | Database | Set via |
|---|---|---|
dev (default) |
WBP_DB_DEV |
Default |
prod |
WBP_DB |
Airflow Variable ENVIRONMENT=prod |
Running dbt¶
# From apps/dbt/dbt_xo_models/
# Run all models
dbt run --select tag:warbyparker
# Run only Silver
dbt run --select silver.warbyparker.*
# Run only Gold
dbt run --select gold.warbyparker.*
# Run specific model + its downstream dependencies
dbt run --select fct_contacts+
# Seed AI agents lookup
dbt seed --select warbyparker.ai_agents
# Full refresh (rebuilds Silver from Bronze history)
dbt run --full-refresh --select tag:warbyparker
Full refresh safety
Because Bronze uses batch_replace (ADR 011), Bronze tables retain all historical data. Running dbt --full-refresh on Silver incremental models correctly rebuilds Silver from the complete Bronze history.
Airflow Integration¶
dbt runs are orchestrated by Astronomer Cosmos, integrated into the warbyparker_gladly_daily DAG:
warbyparker_gladly_daily
├── extract_contact_timestamps
├── extract_conversation_timestamps
├── extract_work_sessions
├── extract_agent_durations
├── [stage tasks]
├── [load to bronze tasks]
└── dbt_transform (DbtTaskGroup)
├── silver.contact_timestamps
├── silver.conversation_timestamps
├── silver.work_sessions
├── silver.agent_durations
├── gold.int_contacts_hold
├── gold.fct_contacts
├── gold.agg_agent_daily_contacts
└── gold.rpt_email_daily
DAG config: apps/airflow/xo-pipelines/dags/configs/warbyparker-gladly-daily.yaml
Related Documentation¶
- Medallion Layers — Layer architecture overview
- ELT Layer Architecture — Layer responsibilities
- Architecture Decisions — ADRs 008, 009, 010, 011