The story
A Databricks pipeline that checks every record before it's used, keeps a history of every change, and has a dashboard showing how healthy the data is.
Making re-runs safe: change data feed plus SCD Type 2 history means a repeated or backfilled batch could silently duplicate history, so every MERGE is guarded by a _batch_id and runs on the same code path as normal loads.
How it’s built
Inside Lakehouse-Ledger
System architecture
- US EIA API
- → Landing volume (API client)
- US EIA API
- Landing volume
- → Bronze: raw tables (Auto Loader)
- Landing volume
- Bronze: raw tables
- → DLT expectations
- Bronze: raw tables
- DLT expectations
- → Silver: clean tables (pass)
- → Quarantine tables (fail)
- DLT expectations
- Silver: clean tables
- → SCD2 dimension via MERGE (change feed)
- → Gold KPI tables (aggregate)
- Quarantine tables
- → gold.dq_metrics
- Silver: clean tables
- SCD2 dimension via MERGE
- → Gold KPI tables (timeline join)
- SCD2 dimension via MERGE
- Gold KPI tables
- → gold.dq_metrics (observability log)
- → Databricks SQL dashboard
- Gold KPI tables
- gold.dq_metrics
- → Databricks SQL dashboard
- gold.dq_metrics
- Databricks SQL dashboard
Engineering decisions
Decision 01
Rules as code, in one place
Declarative DLT Expectations replace scattered if-checks. Every quality rule is visible, versioned and enforced on every row before it reaches Silver.
Decision 02
Quarantine, don't drop
Rows that fail a rule go to quarantine tables with the reason attached. Bad data is kept for inspection instead of vanishing.
Decision 03
Change capture from the source of truth
Delta Change Data Feed drives CDC and SCD Type 2 history instead of diffing full snapshots, so history is exact and cheap to compute.
Decision 04
Safe to run twice
Every MERGE is guarded by a _batch_id, so any run can be repeated or backfilled on the same code path without duplicating history.
Decision 05
Quality you can see
A dq_metrics Gold table feeds a live dashboard, and Unity Catalog grants are scoped per layer, so trust in the data is measured, not assumed.