Case study · Data engineering & semantic layer

One data mart replaced eight source systems and reconciles to finance to the dollar.

One row per day, 136 documented metrics, refreshed every morning, read by 40 downstream projects. Built solo in five months. It also caught three broken data feeds before they reached a deck.

When2026 · ~5 months, alongside delivery work
RoleSole designer & builder
Stackdbt · BigQuery · Python · scheduled queries
Scale8 systems · 19 source tables · 9 staging models · 136 daily metrics · 40 consumers

The problem

Every ecommerce question started with the same join across eight systems and 19 source tables, about 200 lines of SQL, re-derived slightly differently each time. The same metric was defined differently from dashboard to dashboard. The tiebreaker was a 40-tab workbook, 112 columns wide, that one person updated by hand every day. Meetings opened with an argument about whose number was right and rarely got past it.

The build

One governed layer under every number
Sources → typed & tested staging → one master table → everything downstream
SOURCES STAGING MART CONSUMERS ERP sales & store master Web analytics (feed A) Web analytics (feed B) Behavioral sessions Media-spend platform Search-ads API Chat & call center Store foot traffic Finance plan (seeds) 9 staging models one per source · typed & tested every row joined to the fiscal calendar, so no source can silently drop a day quality report + drift gate staleness alerts on feeds The master mart one row per day, 136 metrics sales · traffic · spend · funnel forecast · contact center · stores definitions documented in-place Weekly exec deck10 modules, one command 10 dashboardspublished to a gated hub 3 forecast enginesweekly · monthly · traffic Ad-hoc research40 downstream projects Refreshes on schedule every morning before the business opens · rewrites a trailing 7-day window, so late-arriving data heals itself
What the shape buys you: nothing downstream touches a raw source. A dashboard that used to start with 200 lines of bespoke SQL now starts with select from the mart, so the definitions are the same every time.
Before and after
What changed for the people who ask questions of the data
BeforeAfter
Sources joined per report8 systems, 19 tables, across warehouse projects1 table
SQL to start a new dashboard200+ lines, re-derived each timeselect from the mart
Definition of a metricVaried by dashboardOne, documented in the warehouse
Referee when numbers disagreedA 40-tab workbook updated by hand dailyReconciliation to finance, checked weekly
Ecommerce sales vs. financeOverstated by about $6M a quarterTo the dollar on completed weeks
Late-arriving dataManual re-pulls7-day self-healing window, every morning
Lineage documentationHand-drawn, drifted for 4 months unnoticedGenerated from the models, build fails on drift
Prior-year comparisonsA 364-day offset, wrong in 53-week yearsA mapped prior-year date, 114 prior-year columns

What it showed

The payoff was not consistency. It was detection. With every source side by side in one table, a broken feed cannot hide. Three catches in one summer:

The documentation needed a gate too. The hand-drawn lineage diagram drifted for four months, three sources and ten columns behind the models, before anyone noticed. It is generated from the models now, and the build fails if they diverge.

Not everything reconciles, and the mart says so. Two instruments for store intent disagree by roughly a quarter and are published as two independent instruments, not rescaled to agree.

A semantic layer isn't where the numbers agree. It's where the broken feeds get caught before they reach a deck.

What changed

Technical notes, for the people who will ask

Modeling. dbt over BigQuery: 19 source tables from 8 systems, one staging model per source (typed, deduplicated, calendar-joined), one wide daily mart of 136 documented columns plus a prior-year view. LEFT JOINs from the calendar spine so no source can drop a row.

Operations. Warehouse-scheduled queries every morning, staging then mart, DELETE+INSERT over a trailing 7-day window, so runs are idempotent and late data self-heals. API ingestion for search ads runs outside dbt on a scheduler with retries. Metric documentation is pushed into the warehouse schema so definitions travel with the table.

Quality. A check script and an HTML quality report per run, staleness alerts on feeds, and a lineage generator with a drift gate: undocumented model, orphan source, mart column outside a documented section, or a hard-coded table name fails the build.

Fiscal-calendar correctness. Year-over-year comparisons use a mapped prior-year date rather than a naive 364-day offset; the retail calendar's 53-week years and moving holidays break the naive version in ways that surface at the worst time.

Resilience. Overlapping sources are kept side by side (two web-analytics feeds, spend from platform and API) with blended columns that prefer the freshest complete source. That redundancy is what makes the feed-failure catches possible.

Next: the dashboard & reporting suite that reads from this layer, the organic decline, or back to all work.

Structure, counts, and anecdotes are real; dollar figures are rounded or perturbed and source names generalized. The reconciliations and the catches are real.