/ eu-air-traffic / dbt / models / intermediate / int_flight_details.sql
swadhinbiswas Make delays real: normalise the copy, widen AirLabs, derive from times · 9/14/2026
46d0e2d
91 lines · 3.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
{{ config(materialized='ephemeral', tags=['intermediate']) }}

with flights as (
    select * from {{ ref('stg_fact_flights') }}
),

airports as (
    select * from {{ ref('stg_dim_airport') }}
),

airlines as (
    select * from {{ ref('stg_dim_airline') }}
),

enriched as (
    select
        f.*,
        dep.name as departure_airport_name,
        dep.iata_code as departure_iata,
        dep.latitude_deg as departure_lat,
        dep.longitude_deg as departure_lon,
        dep.iso_country as departure_country,
        arr.name as arrival_airport_name,
        arr.iata_code as arrival_iata,
        arr.latitude_deg as arrival_lat,
        arr.longitude_deg as arrival_lon,
        arr.iso_country as arrival_country,
        al.airline_name,
        epoch(f.actual_arrival - f.actual_departure) / 60 as flight_duration_minutes,
        case
            when f.delay_minutes is null then null
            when f.delay_minutes > {{ var('delay_threshold_minutes') }} then true
            else false
        end as is_delayed,
        case
            when f.delay_minutes is null then null
            when f.delay_minutes <= {{ var('on_time_threshold_minutes') }} then true
            else false
        end as is_on_time,
        -- AirLabs can report delay 0 while the actual arrival slipped; where a
        -- row carries both actual and scheduled times, the difference is the
        -- real delay. Rows without a schedule stay NULL (unknown, not on time).
        coalesce(
            case
                when f.actual_arrival is not null and f.scheduled_arrival is not null
                then greatest(0, cast(epoch(f.actual_arrival - f.scheduled_arrival) / 60 as double))
            end,
            f.delay_minutes
        ) as delay_minutes,
        coalesce(f.scheduled_departure, f.actual_departure) as reference_departure,
        cast(coalesce(f.scheduled_departure, f.actual_departure) as date) as flight_date,
        hour(coalesce(f.scheduled_departure, f.actual_departure)) as departure_hour,
        dayofweek(coalesce(f.scheduled_departure, f.actual_departure)) as departure_day_of_week
    from flights f
    left join airports dep on f.departure_icao = dep.airport_icao
    left join airports arr on f.arrival_icao = arr.airport_icao
    left join airlines al on f.airline_icao = al.airline_icao
),

keyed as (
    -- One real flight can appear twice: an OpenSky movement (actual times) and
    -- an AirLabs schedule (planned times + delay). Key them to the same real
    -- flight so every count and average sees it once.
    select
        *,
        upper(trim(coalesce(callsign, flight_id)))
            || '|' || coalesce(cast(flight_date as varchar), flight_id)
            || '|' || coalesce(departure_icao, arrival_icao, '') as flight_key
    from enriched
),

deduped as (
    select * exclude (rank_in_key)
    from (
        select
            *,
            row_number() over (
                partition by flight_key
                order by
                    (data_source = 'airlabs') desc,
                    (delay_minutes is not null) desc,
                    actual_arrival desc nulls last,
                    flight_id
            ) as rank_in_key
        from keyed
    )
    where rank_in_key = 1
)

select * from deduped