Marts

Organised by domain rather than platform. This is the "current state" layer — where SCD2 history collapses to a single row per entity, and where a domain's raw fields turn into named dimensions and facts.

By domain

DomainShape
web_analyticsGA4-derived facts — traffic, engagement, stock-attributed page views. The largest domain at this layer, mirroring GA4's outsized share of staging.
crmDealer, subscription, lead, and billing dimensions/facts — see CRM domain for the full dim/fact breakdown
paid_mediaCampaign dimension (current-state, from the SCD2 chain) and daily/hourly spend facts

Two different ideas of what a "dimension" is

paid_media__campaigns_dim and crm__dealer_dim are both called dimensions, but they're built differently, and it's worth knowing which pattern a given mart follows before assuming its shape:

  • paid_media__campaigns_dim is an SCD2 current-state filter — literally select * from {{ ref('paid_media__campaign_scd_int') }} where is_latest = true. The "dimension" here is a snapshot of the SCD2 history table at a point in time, not a separately-modelled entity. paid_media__ad_groups_dim and paid_media__ads_dim follow the identical pattern against their own SCD2 intermediates.
  • crm__dealer_dim is a traditional Kimball-style split — the same intermediate model (crm__dealers_int) feeds both crm__dealer_dim (descriptive attributes: name, status, region, billing address) and crm__dealer_fact (measures only: annual revenue, employee count, expected listing). There's no SCD2 involved; this is a straight columnar split by kind. crm__products_dim/crm__products_fact and crm__subscription_dim/crm__subscription_fact follow the same split.

paid_media__campaign_daily_fact adds derived ratios (ctr, avg_cpc, cpa) via a shared safe_divide() macro on top of its intermediate counterpart — the kind of small cross-cutting utility worth knowing exists if you're adding a similar ratio elsewhere.

How data arrives in each mart

Every single mart in this project reads from exactly one intermediate model — there is no mart that joins two intermediates together, and no mart that reads from another mart. Cross-entity joining is deliberately pushed one layer later, into reporting.

Within that constraint, three distinct arrival patterns show up:

  • Straight pass-through. The overwhelming majority of marts — most of crm, most of web_analytics, and paid_media__accounts_dim — are a select (sometimes select *) straight off their intermediate, with no transformation at all.
  • SCD2 current-state filter. paid_media__campaigns_dim, paid_media__ad_groups_dim, and paid_media__ads_dim all follow the where is_latest = true pattern described above.
  • Real aggregation. The six web_analytics__inventorydashboard_cfs_*_fact models are the exception to "no transformation" — each aggregates the same shared intermediate with SUM(...) and a different GROUP BY, differentiated only by grain (daily / monthly / by-region / by-dealer-page) and, for the dealer-page variants, an added WHERE page_path LIKE '%/cars-for-sale/car%' filter.

One pattern that looks like it should exist but doesn't: paid_media__campaign_daily_fact is not a rollup of paid_media__campaign_hourly_fact. Despite both being campaign-grain spend facts, each reads its own separate intermediate model independently — they're parallel siblings, not a daily-from-hourly aggregation chain.

How marts are actually used downstream

Reporting fans out from marts very unevenly — a handful of marts are read by many reporting models across multiple domains, most are read by one or two, and a meaningful number aren't read by any reporting model at all.

Marts several different reporting models depend on:

MartRead by reporting models in
crm__dealer_diminventory and stock
web_analytics__audience_stock_cfs_daily_factinventory and stock
crm__lead_dim, crm__lead_allocation_dim, crm__lead_allocation_factbusiness, crm, inventory, and stock
web_analytics__audience_country_daily_factmostly the business predictor/drilldown family
web_analytics__channel_daily_fact, channel_session_daily_fact, site_section_daily_factmostly the business predictor/drilldown family

Marts with no reporting-layer consumer found anywhere in the project — these are the layer's most likely direct-BI-query surface, not dead weight, but that can't be confirmed from dbt lineage alone:

  • crm: crm__dealer_fact, crm__subscription_fact, crm__products_fact, crm__products_dim, crm__billing_cycle_dim
  • paid_media: paid_media__accounts_dim, paid_media__ad_groups_dim, paid_media__ads_dim, paid_media__account_daily_fact, paid_media__campaign_hourly_fact
  • web_analytics: web_analytics__audience_country_hourly_fact, web_analytics__audience_section_channel_daily_fact

A few reporting models skip the mart layer entirely, reading the raw intermediate instead of the mart built on top of it:

  • business__daily_decay_rpt and business__detailed_drilldown_hourly_rpt both read web_analytics__audience_country_hourly_int directly — bypassing web_analytics__audience_country_hourly_fact, which is exactly the orphaned hourly mart listed above.
  • stock__market_overview_monthly_rpt reads web_analytics__inventorydashboard_cfs_daily_int directly, bypassing its own mart.

(inventory__inventory_master_sheet_rpt also bypasses this project's marts chain, but by reading STOCK_FACT_MART from a different project entirely — see Reporting layer.)

See also

Esc