From wire
Proactive skill for building dbt Semantic Layer with MetricFlow. Auto-activates when working with semantic models, metrics, entities, or dimensions in dbt. Covers both latest (dbt Core 1.12+/Fusion) and legacy (1.6-1.11) spec formats. Distinct from LookML — this skill covers MetricFlow/dbt Semantic Layer only.
npx claudepluginhub rittmananalytics/wire-plugin --plugin wireThis skill uses the workspace's default tool permissions.
This skill guides the creation and maintenance of dbt Semantic Layer artifacts — semantic models, entities, dimensions, measures, and metrics — using MetricFlow. It provides conventions, validation workflows, and reference material for building a consistent, queryable metrics layer on top of dbt models.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
This skill guides the creation and maintenance of dbt Semantic Layer artifacts — semantic models, entities, dimensions, measures, and metrics — using MetricFlow. It provides conventions, validation workflows, and reference material for building a consistent, queryable metrics layer on top of dbt models.
Important distinction: This skill covers the MetricFlow / dbt Semantic Layer only. It does NOT cover LookML, Looker explores, or Looker dashboards. For Looker-related work, defer to the lookml-content-authoring skill. While Wire's /wire:semantic_layer-generate command produces LookML semantic layer artifacts for Looker, this skill focuses on the dbt-native Semantic Layer powered by MetricFlow.
This skill should activate when users:
*_semantic_model.yml or *_metrics.yml filesKeywords to watch for:
Activate BEFORE creating or modifying semantic layer YAML when:
semantic_models: or metrics: keys in YAML filessem_*, *_semantic_model.yml)Example internal triggers:
measures: or entities: -> Validate against conventionsDo not activate this skill when:
.lkml extension) -> defer to lookml-content-authoring/wire:semantic_layer-generate command (that produces LookML)Before creating any semantic layer artifacts, determine which spec version to use:
Decision Tree:
Check for existing semantic layer files in the project:
semantic_models: YAML exists with metrics: defined inline -> Latest spec is already in usemetrics: YAML files exist with type: simple / type: derived -> Legacy spec is already in useCheck dbt version:
When in doubt: Use the Latest spec for new projects. It is simpler, has fewer files, and is the direction dbt is moving.
Semantic models are the foundation of the dbt Semantic Layer. Each semantic model maps to exactly one dbt model (a table or view in the warehouse) and defines the entities, dimensions, and measures available from that model.
Key properties:
name: Unique identifier for the semantic modeldescription: Human-readable descriptionmodel: Reference to the underlying dbt model (e.g., ref('fct_orders'))defaults.agg_time_dimension: The default time dimension for time-based queriesprimary_entity: (Latest spec) The primary entity for this semantic modelentities: List of join keysdimensions: List of categorical and time attributesmeasures: List of aggregationsOne semantic model per mart/fact table. Do not create multiple semantic models pointing to the same dbt model.
Entities define the join keys that connect semantic models to each other. They are the backbone of the semantic graph.
Entity types:
| Type | Description | Usage |
|---|---|---|
primary | The grain of the table. One per semantic model. | The unique identifier for each row (e.g., order_id in fct_orders) |
unique | A column with unique values but not the grain | Rare; used when a non-PK column is guaranteed unique |
foreign | A reference to another semantic model's primary entity | Join keys (e.g., customer_id in fct_orders referencing dim_customers) |
natural | A business key that may not be unique | Used for loose joins where duplicates are acceptable |
Example:
entities:
- name: order
type: primary
expr: order_id
- name: customer
type: foreign
expr: customer_id
- name: product
type: foreign
expr: product_id
Rules:
primary or unique entityname (not expr) of the target semantic model's primary entitycustomer), not the column name (e.g., customer_id)Dimensions are the attributes you group by or filter on when querying metrics.
Dimension types:
| Type | Description | Required Properties |
|---|---|---|
categorical | Text, boolean, or numeric categories | name, type: categorical, optional expr |
time | Date or timestamp columns | name, type: time, type_params.time_granularity |
Time dimension requirements:
type_params.time_granularity (e.g., day, week, month)agg_time_dimension in defaults must reference a defined time dimensionExample:
dimensions:
- name: order_date
type: time
type_params:
time_granularity: day
expr: order_date
- name: order_status
type: categorical
expr: order_status
- name: is_completed
type: categorical
expr: "CASE WHEN order_status = 'completed' THEN TRUE ELSE FALSE END"
Rules:
expr when the dimension name differs from the column name, or for computed dimensionsday, week, month, quarter, yearis_ or has_ prefix per RA conventionsMeasures are the aggregations that form the building blocks of metrics. A measure is NOT a metric — it is an intermediate aggregation that metrics reference.
Measure types:
| Type | Description | Example |
|---|---|---|
sum | Sum of a numeric column | Total revenue |
count | Count of rows | Number of orders |
count_distinct | Count of unique values | Number of unique customers |
avg | Average of a numeric column | Average order value |
min | Minimum value | Earliest order date |
max | Maximum value | Latest order date |
sum_boolean | Count of TRUE values | Number of completed orders |
percentile | Percentile calculation | Median order value |
median | Median (shortcut for 50th percentile) | Median order value |
Example:
measures:
- name: order_count
type: count
expr: "1"
- name: total_revenue
type: sum
expr: order_total
- name: unique_customers
type: count_distinct
expr: customer_id
- name: avg_order_value
type: avg
expr: order_total
- name: completed_orders
type: sum_boolean
expr: is_completed
Rules:
count measures typically use expr: "1" to count rows (or omit expr to count non-null values of a column)sum_boolean requires a boolean expressionfilter property (see Metric Filters below)Metrics are the user-facing calculations built on top of measures. There are five metric types.
A simple metric wraps a single measure with an optional filter. This is the most common metric type.
Latest spec:
metrics:
- name: count_orders
description: "Total number of orders"
type: simple
type_params:
measure: order_count
filter: |
{{ Dimension('order__order_status') }} = 'completed'
Legacy spec:
metrics:
- name: count_orders
description: "Total number of orders"
type: simple
type_params:
measure:
name: order_count
filter: |
{{ Dimension('order__order_status') }} = 'completed'
Derived metrics perform mathematical operations on other metrics. Use when you need calculations that span multiple measures.
metrics:
- name: avg_order_value
description: "Average revenue per order"
type: derived
type_params:
expr: total_revenue / count_orders
metrics:
- name: total_revenue
- name: count_orders
Rules:
expr field uses metric names as variables+, -, *, /Cumulative metrics calculate running totals over time windows.
metrics:
- name: cumulative_revenue
description: "Running total of revenue"
type: cumulative
type_params:
measure: total_revenue
window: 7 # Rolling 7-day window
grain_to_date: month # Or: MTD cumulative
Options:
window: Rolling window in days (e.g., 7 for 7-day rolling)grain_to_date: Resets at the start of the specified grain (month, quarter, year)window and grain_to_date simultaneouslyRatio metrics divide one metric by another. Use for rates, percentages, and proportions.
metrics:
- name: order_completion_rate
description: "Percentage of orders that are completed"
type: ratio
type_params:
numerator: completed_orders_count
denominator: count_orders
Rules:
numerator and denominator must reference existing metricsConversion metrics measure the rate at which a base event leads to a conversion event within a time window.
metrics:
- name: visit_to_purchase_rate
description: "Rate at which website visits convert to purchases"
type: conversion
type_params:
entity: customer
calculation: conversions # or: conversion_rate
base_measure: visit_count
conversion_measure: purchase_count
window: 7
Properties:
entity: The entity to track conversions for (e.g., customer, user)calculation: conversions (raw count) or conversion_rate (percentage)base_measure: The starting event measureconversion_measure: The target event measurewindow: Number of days for the conversion windowconstant_properties: Optional list of properties that must match between base and conversion eventsFilters use Jinja templating to reference dimensions and time dimensions from the semantic graph.
Syntax:
filter: |
{{ Dimension('entity__dimension_name') }} = 'value'
Filter functions:
| Function | Usage |
|---|---|
{{ Dimension('entity__dim') }} | Reference a categorical dimension |
{{ TimeDimension('entity__time_dim', 'grain') }} | Reference a time dimension at a grain |
{{ Metric('metric_name') }} | Reference another metric (in derived metrics only) |
{{ Entity('entity_name') }} | Reference an entity |
Examples:
# Simple equality
filter: |
{{ Dimension('order__order_status') }} = 'completed'
# Date range
filter: |
{{ TimeDimension('order__order_date', 'day') }} >= '2024-01-01'
# Multiple conditions
filter: |
{{ Dimension('order__order_status') }} = 'completed'
AND {{ Dimension('order__region') }} = 'EMEA'
Rules:
name, not the column name__) to separate entity from dimensionAND, OR, NOT, IN, BETWEEN)The latest spec simplifies the YAML structure:
type_params for measures (direct reference, not nested name)primary_entity as a top-level propertyFile structure:
models/
marts/
sem_orders.yml # Semantic model + metrics together
sem_customers.yml # Semantic model + metrics together
Example (latest):
semantic_models:
- name: sem_orders
description: "Order semantic model"
model: ref('fct_orders')
primary_entity: order
defaults:
agg_time_dimension: order_date
entities:
- name: order
type: primary
expr: order_id
- name: customer
type: foreign
expr: customer_id
dimensions:
- name: order_date
type: time
type_params:
time_granularity: day
- name: order_status
type: categorical
measures:
- name: order_count
type: count
expr: "1"
- name: total_revenue
type: sum
expr: order_total
metrics:
- name: count_orders
type: simple
type_params:
measure: order_count
- name: sum_revenue
type: simple
type_params:
measure: total_revenue
The legacy spec uses a more verbose structure:
name propertyprimary_entity shorthand — use entities list onlyFile structure:
models/
marts/
semantic_models/
sem_orders.yml # Semantic model only
sem_customers.yml # Semantic model only
metrics/
order_metrics.yml # Metrics only
customer_metrics.yml # Metrics only
utilities/
metricflow_time_spine.sql # Required time spine model
Example (legacy):
# sem_orders.yml
semantic_models:
- name: sem_orders
description: "Order semantic model"
model: ref('fct_orders')
defaults:
agg_time_dimension: order_date
entities:
- name: order
type: primary
expr: order_id
- name: customer
type: foreign
expr: customer_id
dimensions:
- name: order_date
type: time
type_params:
time_granularity: day
- name: order_status
type: categorical
measures:
- name: order_count
type: count
expr: "1"
- name: total_revenue
type: sum
expr: order_total
# order_metrics.yml
metrics:
- name: count_orders
type: simple
type_params:
measure:
name: order_count
- name: sum_revenue
type: simple
type_params:
measure:
name: total_revenue
Time spine (legacy only):
-- metricflow_time_spine.sql
{{ config(materialized='table') }}
WITH date_spine AS (
{{ dbt_utils.date_spine(
datepart="day",
start_date="cast('2020-01-01' as date)",
end_date="cast('2030-12-31' as date)"
) }}
)
SELECT
date_day as ds
FROM date_spine
Always validate semantic layer artifacts before considering them complete.
Validation workflow:
Parse the project:
dbt parse
This checks YAML syntax and schema validation. Fix any parse errors before proceeding.
Validate semantic layer configs:
dbt sl validate
mf validate-configs
This checks entity relationships, dimension references, measure references, and metric definitions.
Test with a query (optional but recommended):
# Latest / dbt Cloud
dbt sl query --metrics count_orders --group-by order__order_date
# Legacy / MetricFlow CLI
mf query --metrics count_orders --dimensions order__order_date
Common validation errors and fixes:
| Error | Cause | Fix |
|---|---|---|
Entity not found | Misspelled entity name in filter or join | Check entity name values across semantic models |
Measure not found | Metric references non-existent measure | Verify measure name in the semantic model |
Time dimension required | Cumulative metric without time dimension | Ensure defaults.agg_time_dimension is set |
Duplicate metric name | Same metric name in multiple files | Use unique names; prefer verb_noun pattern |
Invalid granularity | Time dimension missing type_params | Add type_params.time_granularity |
Use the appropriate entry point based on how the user approaches semantic layer work.
The user has a business question and needs metrics to answer it.
Workflow:
sum_mrr, count_active_subscriptions)mrr_amount as sum, active_subscription_count as count)subscription_start_date, plan_type)fct_subscriptions)This is the recommended entry point for consulting engagements. It ensures metrics are driven by business needs, not technical convenience.
The user has a dbt model and wants to add semantic layer support.
Workflow:
The user wants to explore what's possible.
Workflow:
models/ directory for mart/fact/dimension modelssem_{entity} (e.g., sem_orders, sem_customers, sem_subscriptions)sem_{entity}.yml placed alongside the dbt model it referencesverb_noun (e.g., count_orders, sum_revenue, avg_order_value)count, sum, avg, min, max, rate, pct (percentage), cumulativetotal or amount without contextNaming examples:
| Good | Bad | Why |
|---|---|---|
count_orders | orders | Missing verb; ambiguous |
sum_revenue | total_revenue_sum | Verb should come first |
avg_order_value | aov | Abbreviations are unclear |
rate_order_completion | completion_rate | Verb-first is more consistent |
cumulative_revenue_mtd | mtd_rev | Be explicit about the aggregation type |
{aggregation_descriptor} — concise name reflecting the aggregationverb_noun patternorder_count, total_revenue, unique_customers, completed_ordersorder, customer, product)expr for that)fct_orders has customer as a foreign entity, dim_customers must have customer as its primary entity)Latest spec:
models/
marts/
fct_orders.sql
sem_orders.yml # Semantic model + metrics
dim_customers.sql
sem_customers.yml # Semantic model + metrics
Legacy spec:
models/
marts/
fct_orders.sql
dim_customers.sql
semantic_models/
sem_orders.yml
sem_customers.yml
metrics/
order_metrics.yml
customer_metrics.yml
/wire:semantic_layer-generate command serve different purposes:
/wire:semantic_layer-generate: Produces LookML semantic layer artifacts for Looker, using Wire's design artifacts as input/wire:data_model-generate)| Mistake | Impact | Fix |
|---|---|---|
| Defining metrics without a primary entity | Validation fails; MetricFlow cannot determine grain | Every semantic model must have a primary or unique entity |
| Using column names as entity names | Joins fail across semantic models | Use business concept names; map to columns with expr |
Missing type_params.time_granularity on time dimensions | Validation error | Always specify granularity for time dimensions |
| Creating multiple semantic models for one dbt model | Ambiguous metric resolution | One semantic model per dbt model |
| Using legacy spec syntax with latest dbt version | Unexpected behavior or deprecation warnings | Check spec version with decision tree in Section 0 |
| Forgetting the time spine model (legacy spec) | Cumulative and time-based metrics fail | Create metricflow_time_spine.sql for legacy projects |
| Filter paths with wrong entity name | Filter silently ignored or error | Use entity name, not column name, in filter paths |
| Defining a metric directly on a column | Metrics reference measures, not columns | Create a measure first, then a metric on that measure |
| Circular derived metric references | Infinite loop in compilation | Derived metrics can only reference simple, cumulative, or ratio metrics |
When working with user-provided YAML, SQL, or schema files:
This skill is adapted from the building-dbt-semantic-layer skill in the dbt-labs/dbt-agent-skills repository, modified for Rittman Analytics conventions, BigQuery-first development, and integration with the Wire Framework delivery lifecycle.