Skills in action / Lakehouse
INDEPENDENT CONSULTANT · DATA GOVERNANCE, DATA QUALITY & MDM
A working pipeline, built to show what governance looks like inside the data itself
I took the open data of the Belgian statistical office (Statbel) and built, end to end, the physical layer that a data catalog needs: the files are loaded, cleaned and typed; every table and every column is described from the official dictionaries; keys are tested before they are declared; quality is measured and reported; and the result is published to BigQuery and harvested by OpenMetadata, next to a business glossary. Nothing is hidden: what the data does not say, the pipeline says it does not say.
This is a demonstration and training project, not a production system. It runs on Databricks Free Edition and stays within the BigQuery free tier.
It is the physical layer of my data catalog training: Data Owners and Data Stewards learn to link real columns to business terms. For that exercise they need a catalog that looks like the real thing, with real names, real ambiguity and real quality problems, and without confidential data. It is also the kind of foundation I put in place for clients: the same questions of ownership, definitions, keys, quality and access, answered in code that anyone can read.
| Layer | What it holds | Rule |
|---|---|---|
| Landing | The Statbel files, in a Unity Catalog volume | Stored unchanged; a UTF-8 copy when the source is Windows-1252 |
| Bronze | One Delta table per source file | Data as read, all columns as text, plus _source_file, _source_url, _ingested_at |
| Silver | Typed, documented tables | Statbel names kept; typing by column prefix; unknown markers become NULL; primary key only if tested unique |
| Metadata | The Statbel DCAT catalog and its field dictionaries, as tables | Source of every column description |
| Quality | run_report, in Databricks and BigQuery | One row per check and per run |
| BigQuery | The silver tables, published | Descriptions, labels, keys and geography; written by a service account with rights on one dataset only |
| Practice | How it is done |
|---|---|
| Configuration, not code | Every table is an entry of one configuration file (sources, filters, types, keys, dictionary). There is no table-specific code. |
| Documentation from the source | Descriptions come from the Statbel dictionaries; a gap is written as a gap (Description missing in the Statbel dictionary), never hidden. |
| Keys that are tested | A primary key is declared only if it is unique and never NULL. Foreign keys are declared after all loads. Orphan rows are counted, never dropped. |
| Quality report | Row counts, duplicate and NULL keys, null rates, distinct codes, values that could not be typed, values marked "not available". |
| Lineage | Bronze keeps the source file, URL and load time; Delta history and Unity Catalog lineage do the rest. |
| Licence and attribution | Every table description ends with the Statbel licence line; labels carry source, licence, domain, period and layer. |
| Least privilege | No secret in git. The BigQuery key lives in a Databricks secret scope; OpenMetadata reads through its own read-only identities. |
The checks found facts about the real files that a Data Steward has to know, document and decide on. None of them is a mistake of Statbel. Here are a few of the fifteen the pipeline records.
Files announced as UTF-8 are Windows-1252 further down: Irène and Irêne both became Ir�ne, creating 375 false duplicate keys.
The natural key of the family-name table is not unique in the source, so the pipeline refuses to declare it.
Merged municipalities stay in the files with an end date: a code alone is not unique, and one decision (valid today) had to be written down.
Old years use municipality codes erased by mergers: thousands of orphan rows, counted and kept.
One municipality code appears under several column names, and dictionaries use other names than the files.
A column named DT_HOUR holds an hour, not a date: a type override in the configuration.
Both, for different jobs. The glossary says what the data means and who approved it; the platform enforces who may use it. I connected both on purpose, to compare them on the same tables.
| Need | OpenMetadata | Unity Catalog |
|---|---|---|
| Business domain with an owner | Domains | No domain object (tags) |
| Glossary, subject area, term, characteristic | Glossaries and term hierarchy | — |
| Definitions, synonyms, related terms | Yes | — |
| Approval workflow, reviewers | Yes | — |
| Link between a column and a term | Glossary term on the column | Only a free tag |
| Access control, column masks, row filters | Described | Enforced |
| Lineage, audit, usage | Harvested | Native (system tables) |
The most useful part of a project like this is what does not work the first time. A selection, from the connection of the catalogs.
| Symptom | Cause | Fix |
|---|---|---|
access_denied: Scopes 'all-apis' are not assigned | The OpenMetadata Unity Catalog connector needs an OAuth secret with the all APIs scope | New secret with that scope; the identity itself stays read-only (no admin or workspace entitlement) |
does not have USE SCHEMA | Rights were granted to the wrong service principal ID | Check the identity before debugging the rights; fix the configuration and rerun the grants notebook |
| 31 warnings "dataset not found in location US" | The BigQuery connection used the US location for usage and taxonomy | Both set to EU; the missing primary and foreign keys then appeared |
| 15 errors about the storage view | The storage statistics view was not enabled in the Google Cloud project | Enabled once at project level (it can take up to a day to apply) |
| Two foreign keys refused by BigQuery | BigQuery rejects self-referencing keys (the two hierarchies) | Declared in Unity Catalog only: 17 foreign keys there, 15 in BigQuery |
| No lineage or usage from Unity Catalog | Free Edition does not give access to the system tables | A known limit, kept visible instead of hidden |
| An HTML page instead of the file | The Statbel server refuses clients announcing Python-urllib | Browser-like User-Agent; HTML responses are refused by the pipeline |
Notebook 07 turns the Statbel DCAT catalog into one self-contained web page: search datasets, files, keywords and field names, filter by theme and format, and read the description of every field, with the metadata in French, Dutch, English or German. The interface of the page is in French.
Open the explorer (new tab, 1.9 MB) The business glossary built on the same data
Everything is public: github.com/OlivierBouchezDataGovernance/statbel-lakehouse. Notebooks 00 to 07 run in order on a free Databricks workspace; the README and the install guide explain the Google Cloud and OpenMetadata side. Two short extracts show the style.
# config/datasets.yml: nothing else is needed to load, document and publish this table - table: pop_structure domain: pop title: Population by municipality, nationality, marital status, age and sex period: "2026-01-01" sources: - url: https://statbel.fgov.be/.../TF_SOC_POP_STRUCT_2026.zip dictionary: https://statbel.fgov.be/.../Columns%20description.xlsx dictionary_aliases: # the dictionary names the column differently CD_REFNIS: CD_MUNTY_REFNIS primary_key: [CD_REFNIS, CD_SEX, CD_NATLTY, CD_CIV_STS, CD_AGE] foreign_keys: - {column: CD_REFNIS, references: ref_municipality, referenced_column: CD_REFNIS}
# Statbel naming convention, used as the default typing rule CD_ code -> string # codes keep their leading zeros TX_ label -> string MS_ measure -> bigint, or double if decimals are found DT_ date -> date # dd/MM/yyyy or yyyy-MM-dd FL_ flag -> boolean LVL_ level -> int XY_ coord. -> double
system tables, so there is no automatic lineage or usage; the lineage across the two engines (Databricks to BigQuery) is not detected automatically either.Documented, tested and measurable data is what makes a catalog useful, and what AI needs to be trustworthy. If you want the same foundations in your context, or a training for your Data Owners and Stewards, get in touch.