One page: KPI cards, on-time and cancellation rate by month across the full range, carrier comparison, and delay minutes by cause.
The page carries a note explaining that on-time performance *improved* through mid-2020 while cancellations spiked to 41.5%. Both are real — domestic volume fell below 30% of normal, so the flights that did operate met empty airports and uncongested airspace. April 2020 records 95.1% on-time and 41.5% cancelled in the same month: the best punctuality and the worst reliability in the dataset. Retaining cancelled flights is what makes that visible.




A medallion lakehouse in Microsoft Fabric over US Bureau of Transportation Statistics flight data: January 2020 to June 2026, 78 monthly files, 41.8 million flight records, roughly 20 GB of source CSV.
Landing holds raw CSV. Bronze preserves the source contract with an explicit schema and audit columns. Silver types and cleans, reducing 109 source fields to 56 and merging on a business key. Gold is a star schema — two facts, five dimensions. A Direct Lake semantic model sits on the gold tables only.
Layer separation is by table-name prefix within a single lakehouse. Three separate lakehouses would mean cross-lakehouse references in every notebook for no architectural gain at this scale.
Orchestration is two Data Factory pipelines: one that processes a single period through four notebook activities, and a backfill that invokes it sequentially per period. Activities are chained on success, not on completion — on-completion would let a failed landing step run bronze anyway, which is how partial data gets written and reported as a success. All four activities share a Spark session tag so the pipeline runs as one Spark application rather than four, which is necessary on F4 capacity and removes three session startups from every run.
Why this dataset. Volume justifies the architecture at roughly 600,000 flights per month. BTS publishes monthly, so the pipeline processes real new periods rather than a simulated drip-feed. And the period covered contains a natural stress test: domestic flights fell from 648,229 in March 2020 to 180,617 in May, with a 41.5% cancellation rate in April.
string and as int in another, and the append then fails partway through a backfill. The schema is generated once from a reference month, hardened, and cached to a 20 KB JSON file rather than making two passes over 260 MB of CSV._phantom and dropping it by name avoids Spark flagging every row as containing discarded data — a false "corrupted records" warning on 600,000 rows trains you to ignore warnings. It is also faster: 13 seconds against 24.ActualElapsedTime is gate-to-gate in local clock time at each end, so pure arithmetic yields origin-local time while BTS reports destination-local. The hybrid resolves it without requiring an airport-to-timezone reference dataset.= returns null when either side is null, so a row with a null key column would never match itself and would insert a duplicate on every run.monotonically_increasing_id() is not stable across reruns, and a proper key-assignment step adds pipeline machinery this project does not need.dim_airport grew from 367 to 387 and dim_carrier from 17 to 18 as periods outside the development slice introduced airports and carriers that had not appeared before.Every layer boundary has a check that fails the run rather than warning. When the pipeline runs unattended, a bad month must fail the run rather than quietly write wrong data.
validate_timestamps() runs two independent checks: that the derived clock matches the BTS reported time of day, and that the arrival date is the flight date or the day after. The 2400 bug passed the first at 100% and was caught only by the second. One passing check is not validation.
left_anti joins on every load and raises on failure.fact_flight — no drift through the chain.Three defaults that cost time and are not obvious from the error messages.
TooManyRequestsForCapacity. Session tags are the mechanism, but they are ignored for pipelines unless the setting is enabled. The equivalent toggle for interactive notebooks is on by default, which makes the asymmetry easy to miss.