Data platform engineering — worked demo

An end-to-end data product built the way I build them in production: ingestion from messy sources, entity resolution, a provenance-tracked warehouse layer, quality gates, and an application on top. The domain below is synthetic airport slot allocation — a structural stand-in for the regulated-asset platform I run day to day.

PythonSQLite FastAPIETL / ELT Entity resolutionData quality Chart.jsAWS EC2

01What this demonstrates

Four things that are hard to show on a CV, shown here instead.

–
panel observations

Long-format, one row per entity × market × period × metric × source.

–
allocation rounds

Auction outcomes with per-window pricing and mechanism metadata.

–
leases tracked

Holdings with terms and expiry, joined back to the round that created them.

–
sources reconciled

Overlapping sources kept separate, never blended.

02Live demo — market dashboard

Reads a generated JSON extract; no backend. Two independent sources report some of the same metrics and are plotted side by side — a deliberate design choice explained in ADR-002.

Metric trend

–

Slot share by carrier

Share of leased slots at the selected airport

Clearing price per slot

Completed rounds, by year

Lease expiry profile

Slots coming up for renewal

Carrier league table

CarrierGroupSlots ShareRevenueEBITDA margin Load factorAnnual fee

03Pipeline architecture

The demo pipeline in etl/pipeline_demo.py is a reduced version of one I operate daily. Every property below is deliberate and exercised by the code.

Sources API · files · feeds Change detect manifest diff fingerprint fetch only deltas Resolve alias matching tiered confidence N:1 guard Stage → swap derive in staging atomic promote readers never split Quality gate 6 assertions fail = degraded non-zero exit Product + freshness run lock · idempotent upserts · provenance manifest · fail-safe exit every step writes rows-in / rows-out so any figure traces back to a run

Run it yourself

python data/build_demo_db.py
python etl/pipeline_demo.py --dry-run
python etl/pipeline_demo.py
python etl/pipeline_demo.py --simulate-outage
python etl/news_updater.py

Seeded and deterministic — the same inputs produce byte-identical output.

Fail-safe behaviour

The property I care most about. With --simulate-outage:

[DEGR] change_detect  upstream unreachable;
                      last-good data retained
source degraded — exiting cleanly

It does not half-write, does not truncate, and does not report success. A pipeline that fails loudly and leaves yesterday's good data intact beats one that silently publishes a partial today.

04Entity resolution

Reconciling third-party names to an internal master is the unglamorous work that decides whether every downstream number is right. The demo warehouse ships deliberately messy aliases.

Tiered matching

TierRuleAction
1 exactcanonical / legal / known alias, same marketauto-link
2 fuzzynormalised score ≥ threshold, same marketlink + log for review
3 unmatchedno confident matchkept unlinked, never guessed

Normalisation strips legal suffixes and market qualifiers — but never to an empty string, because for some carriers the industry word is the brand.

The guard that matters

If two source entities both match one internal entity in the same market, linking both silently merges two companies' histories into a single series. The result looks plausible and is wrong.

collision: ARL / "Aurora Air"
  keep    Aurora Air      (1.00)
  demote  Aurora Airways  (0.86)  → unlinked

Genuine mergers are handled by an explicit successor rule, never by name similarity.

05Slot plan

The holdings view. Time of day runs left to right, and every block is capacity a carrier actually holds in that window. Unallocated capacity is drawn rather than omitted, because a gap in the plan is information: it is where the next award has room.

Hovering a block gives the carrier, the window and the holding. The same shape works for any divisible, time- or position-indexed resource sold in blocks.

06Allocation register

Every award, with the detail folded away. Click a row to open it. Summary tables that hide their provenance are how a wrong number survives review, so the expansion carries the assumptions, the source reference and the arithmetic rather than a prettier restatement of the row.

AwardAirport YearMechanismSlots Price / slotProceeds

Rows are keyboard reachable and the expanded panel is announced, so this is not a mouse-only affordance.

07Scheduled external feed

Two halves of the same idea. The scheduled job polls with ETag revalidation and writes a snapshot; the cards below are fetched live in your browser right now, and fall back to that snapshot if the request fails. Source is Wikipedia’s public REST API, openly licensed and deliberately unrelated to any client domain.

Status: idle

ETag / If-None-Match revalidation means an unchanged poll costs one 304 and no parsing. The live fetch degrades the same way the pipeline does: if it cannot reach the API it serves the last good snapshot and says so in the status line, rather than rendering an empty section and letting you assume there was no news. Extracts from Wikipedia, CC BY-SA 4.0, linked to source.

08Other systems I've built

Descriptions only — these are client systems, so no code, data, schema or identifiers are reproduced.

Regulated-asset intelligence platform

FastAPI · SQLite · Jinja2 · Gunicorn · Apache · EC2

Subscription web platform serving structured market data to enterprise users. ~34k LOC Python, 40+ routes, two databases with a one-way additive sync, 15 SQL views.

full ownershipmulti-tenant auth Excel export

Daily ingestion pipeline

Python · cron · run-locked · idempotent

Change-detecting daily refresh across a commercial API and 19 public regulator sources, with per-source caching, selective detail fetch, downstream sync, freshness snapshots and provenance manifests.

fail-saferesumable provenance

Multi-source warehouse layer

Long-format panel · provenance-tracked

Consolidated seven commercial and public sources into ~470k observations spanning 300+ entities and 100+ markets, including forecast series extracted from 51 unstructured PDF reports.

entity resolutionPDF extraction schema migration

Natural-language analytics assistant

LLM · RAG · FTS5 · evaluation harness

NL→SQL over a 20-table schema with retrieval across a full-text corpus, a curated exemplar library, and a regression harness of 87 cases with automated scoring and an error-pattern taxonomy.

eval-drivenretrieval guardrails

Auction simulation platforms

PHP/MySQL, later Python/FastAPI

Simulators across five national markets implementing ascending-clock, sealed-bid, hybrid and combinatorial formats — activity rules, eligibility points, bid increments, winner determination.

mechanism designreal-time rounds admin tooling

Package optimiser

Combinatorial allocation

Winner-determination and assignment-stage optimiser producing per-participant distributions across candidate packages, with audit output for every round.

optimisationauditability

09How this demo is sanitised

Nothing here derives from client work.
  • The domain is airport slot allocation — chosen as a structural analogue, not a disguise for the real one.
  • All data is generated by build_demo_db.py from a seeded RNG. No real figures, entities or geographies.
  • Table and column names are original to this demo. No production schema, identifier or naming convention is reproduced.
  • Carriers, airports and groups are invented and do not correspond to real organisations.
  • Client systems in §06 are described at the level of engineering approach only — no code, data or schema.
  • The external feed is Wikipedia's public API under CC BY-SA.