Course contents36 lessons

Crash course / Modelling the warehouse

One metric, one definition

The reason three dashboards show three different revenue numbers is never a bug. It is three people who each wrote their own definition, and all of them were reasonable.

Lesson 12 of 36 · 4 min read ·Analytics engineering

Every organisation past about thirty people has this meeting. Finance says revenue was 4.1M. The product dashboard says 4.4M. The sales team's spreadsheet says 3.9M. Everybody's SQL is correct. Everybody's number is defensible. The meeting takes ninety minutes and resolves nothing, and next quarter it happens again.

This is not a data quality problem. It is a definition problem, and it is organisational as much as technical.

Why the numbers differ#

For one metric — revenue — here are the choices each person silently made:

  • Gross or net of discounts?
  • Before or after refunds, and refunds attributed to the original order date or the refund date?
  • Booked, invoiced, or recognised?
  • Which currency, converted at which date's rate?
  • Including or excluding internal test accounts?
  • Orders placed, or orders shipped?
  • Which timezone defines a "day"?

Seven binary-ish choices is 128 defensible numbers. Nobody was wrong. Nobody wrote it down.

Define it once, in code#

The fix is to make the definition a single artefact that every consumer reads, rather than something re-implemented per dashboard. The mechanics vary by stack — dbt metrics, a semantic layer, or simply a well-governed table — but the principle does not: there is exactly one place where "revenue" is defined, and everything else references it.

yaml
# A metric definition worth the name.
metrics:
  - name: net_revenue
    label: Net revenue
    description: >
      Order-line revenue after discounts and after refunds, attributed to the
      ORIGINAL order date. Excludes internal test accounts (customer_id
      starting 'TEST'). Non-USD converted at the daily ECB rate on the order
      date. Restated for up to 7 days as late refunds land.
    model: fct_order_lines
    calculation: sum(revenue_usd) - sum(refund_usd)
    time_grain: day
    time_column: ordered_at
    dimensions: [country, channel, category, customer_segment]
    owner: analytics-eng@example.com
    not_this: >
      This is NOT recognised revenue. Finance's recognised revenue defers
      subscription amounts across the term; see metric `recognised_revenue`.

The not_this field is the one people skip and the one that prevents the most damage. A definition that says what a metric is not stops it being used for the thing it superficially resembles.

The three-layer discipline#

Layer one: staging. One model per source table. Renamed, typed, cleaned. No joins, no business logic. If the source calls it cust_ctry_cd, staging calls it customer_country_code and that is the entire contribution.

Layer two: marts. The business concepts. fct_order_lines, dim_customers, fct_subscriptions. Joined, tested, documented, with a stated grain. This is where the definitions live.

Layer three: metrics. Named aggregations over the marts, with the documentation above.

Dashboards and notebooks read layer three, sometimes layer two, and never layer one. When someone bypasses this to write a bespoke query against raw tables — and someone will, usually under deadline — that number is now unowned and will show up in a meeting six months later as a discrepancy.

The certification ladder#

Not everything can be governed, and pretending otherwise produces a bureaucracy people route around. Grade instead:

  • Certified. Owned, tested, documented, definition reviewed. Appears in board decks. Changes require a note to consumers. Perhaps fifteen metrics.
  • Supported. Built by the data team, tested, but not board-grade. The long tail of operational reporting.
  • Ad hoc. Somebody's query. Perfectly legitimate and explicitly not guaranteed. Labelled as such, so nobody mistakes it for the first category.

Making the third tier legitimate is what makes the ladder work. If the only sanctioned path is a two-week governance process, people will build shadow spreadsheets, and you will lose visibility entirely.

Changing a definition#

You will need to. Do it like a schema migration, not an edit:

  1. Announce before changing. Say what changes, why, and by roughly how much.
  2. Publish both for a transition window — net_revenue and net_revenue_v2 side by side, with the delta explained.
  3. Restate history so trend charts do not have a cliff on the cutover date, or if you cannot, mark the discontinuity on every chart.
  4. Record it in a changelog with a date. When someone finds an old deck that disagrees, the changelog is the answer.

The five-minute test#

For any metric on any dashboard, can someone who is not you answer these in under five minutes, without asking a person?

  1. What exactly does this count, and what does it exclude?
  2. Which timestamp puts a row in a given day?
  3. Who owns it?
  4. When did it last change, and how?
  5. Is it certified?

If not, the metric is a rumour with a chart attached. That is not a moral failing — it is just work that has not been done yet.