Reporting layer

The largest single layer in this project by model count, and the layer BI actually queries directly. This is where business, inventory, and stock live as domains in their own right, distinct from the crm/paid_media/web_analytics split used everywhere upstream.

By domain

DomainFocus
businessThe largest domain at this layer — article/audience engagement, campaign attribution, revenue forecasting
stockDealer analysis, Metabase feeds, lead spread
inventoryDealer/CFS/agency daily and monthly snapshots
crmRevenue and lead-allocation reporting — see CRM domain
paid_mediapaid_media__spend_daily_rpt — the only paid-media report at this layer

business: not just rollups — real forecasting logic

The name suggests simple aggregation, but at least one model does genuine time-series forecasting. business__daily_decay_rpt projects the rest of the current month's active-user count from how prior months typically trend:

  1. Computes a decay curve — for each day-of-month, the average ratio of that day's active users to the previous day's, across the trailing 6 months.
  2. Computes an average daily pattern — for each day-of-month, the average actual value across the same 6-month window.
  3. For any date in the current month that hasn't happened yet, forecasts it as avg_daily_last_6_months × avg_decay_last_6_months for that day-of-month.
  4. Unions historical actuals, month-to-date figures, and the forward-looking forecast into one output, tagged by metric (Active Users, Month to Date, MTD Decay, Daily, Daily Growth, Daily Decay, Forecast, Avg Decay) with an explicit metric_order for display sequencing.

This is a real statistical projection, built entirely in SQL window functions — not something to assume is "just a report" from the name. Other business models cover article engagement by author, campaign/channel daily attribution, landing-page overview by country and listing type, and audience-revenue summaries by listing type — the domain spans everything from editorial content performance to paid-campaign ROI, unified only by "the business wants to see this," not by a single data source.

business__campaign_performance_rpt (date × campaign grain) draws entirely from crm__revenue_daily_rpt — see CRM domain for the gross_leads/chargeable_leads definitions it depends on. Its own header comment excludes spend deliberately, pointing to business__campaign_daily_rpt instead for margin/ROI — a stated scope boundary, not a gap.

inventory and stock: two domains covering similar ground, differently

Both report on dealer stock, but from different angles. inventory is built around CFS (Cars For Sale) snapshotsinventory__cfs_daily_rpt, inventory__cfs_dealer_page_daily_rpt, inventory__cfs_region_daily_rpt, and monthly equivalents of each — plus dealer/agency daily summaries and an inventory__inventory_master_sheet_rpt that reads Stock's fact marts directly (see below). stock is built around dealer analysis and Metabase-facing feedsstock__dealer_analysis_brand_model_rpt, stock__dealer_brand_health_rpt, stock__metabase_stock_rpt, stock__metabase_dealer_rpt, stock__leads_rpt (one row per lead_id, combining lead/allocation/dealer/stock data), and stock__ga4_dealermarketplace_rpt (one row per month × page path × stock_external_identifier — see GA4 & web analytics for the join underneath it).

If you're looking for a specific dealer/stock metric and don't find it in one domain, check the other — the split is by reporting angle, not by an obvious ownership boundary.

Reporting doesn't always go through marts

Several inventory/stock reporting models — inventory__inventory_master_sheet_rpt, stock__metabase_stock_rpt, stock__leads_rpt, stock__ga4_dealermarketplace_rpt among them — query the stock source directly (STOCK_FACT_MART/STOCK_LIFECYCLE_FACT_MART from VEHICLE_STOCK_DB.VEHICLE_STOCK_SCHEMA) rather than passing through this project's own staging → intermediate → marts chain first. This is a deliberate break from the 5-layer flow the rest of the project follows — worth knowing if you're tracing a reporting model's lineage and it doesn't lead where the medallion diagram suggests it should. See Raw sources & permissions for how that source is declared, including a stale copy-pasted description worth not trusting.

Test coverage across the whole project

Schema .yml tests exist across all layers, using dbt_utils and dbt_expectations for generic column tests (type checks, bounds checks, expression_is_true). Severity is overwhelmingly warn, with only a couple of error-severity tests anywhere in the project — both non-negativity checks on cost columns (google_ads__campaign_performance_report_stg, google_ads__account_performance_report_stg). store_failures isn't configured anywhere in the project. This matches the severity: warn-dominant posture already found in both Stock and Redbook.

Custom test coverage is thin relative to the project's size: only one singular test exists across the whole project, tests/unit/google_ads/test_google_ads__campaign_stg__cost_without_impressions.sql — a warn-severity check that cost shouldn't appear on rows with zero impressions. No other platform has any unit-style test at all, despite the tests/unit/ directory structure implying a broader intended pattern.

See also

Esc