diff --git a/tasks/flightradar24/analysis/adsb_quality_metrics.sql b/tasks/flightradar24/analysis/adsb_quality_metrics.sql new file mode 100644 index 0000000..334ce2a --- /dev/null +++ b/tasks/flightradar24/analysis/adsb_quality_metrics.sql @@ -0,0 +1,178 @@ +-- ============================================================ +-- ADS-B Track Quality Metrics +-- Замени: 56.121, 37.216 — координаты антенны +-- Замени: 150 — радиус ЗУП в км (начни с 150) +-- ============================================================ + + +-- ============================================================ +-- 0. Вспомогательная функция: расстояние от точки до антенны (км) +-- Используется PostGIS ST_Distance с geography +-- ============================================================ + + +-- ============================================================ +-- 1. ГИСТОГРАММА РАССТОЯНИЙ — для определения ЗУП +-- Показывает плотность точек по кольцам от антенны +-- ============================================================ +WITH antenna AS ( + SELECT ST_SetSRID(ST_MakePoint(37.216, 56.121), 4326)::geography AS pt +), +point_distances AS ( + SELECT + tp.track_id, + ST_Distance(tp.geom::geography, a.pt) / 1000 AS dist_km + FROM fr24.track_points tp, antenna a + WHERE tp.observed_at >= NOW() - INTERVAL '7 days' +) +SELECT + (dist_km / 10)::int * 10 AS dist_bucket_km, -- кольца по 10 км + COUNT(*) AS point_count, + COUNT(DISTINCT track_id) AS track_count +FROM point_distances +GROUP BY dist_bucket_km +ORDER BY dist_bucket_km; + + +-- ============================================================ +-- 2. МЕТРИКИ ПО ТРЕКАМ (M1–M4) за последние 7 дней +-- Только треки с точками в ЗУП +-- ============================================================ +WITH antenna AS ( + SELECT ST_SetSRID(ST_MakePoint(37.216, 56.121), 4326)::geography AS pt +), +-- Точки с расстоянием от антенны +pts AS ( + SELECT + tp.track_id, + tp.track_point_id, + tp.observed_at, + tp.geom, + tp.ground_speed_kt, + tp.heading_deg, + ST_Distance(tp.geom::geography, a.pt) / 1000 AS dist_km + FROM fr24.track_points tp, antenna a + WHERE tp.observed_at >= NOW() - INTERVAL '7 days' +), +-- Только точки в ЗУП +zup_pts AS ( + SELECT * FROM pts WHERE dist_km <= 150 +), +-- Последовательные пары точек для M1/M2/скорость +pairs AS ( + SELECT + track_id, + observed_at, + dist_km, + ground_speed_kt, + -- расстояние до следующей точки (м) + ST_Distance( + geom::geography, + LEAD(geom) OVER (PARTITION BY track_id ORDER BY observed_at)::geography + ) AS step_dist_m, + -- временной интервал до следующей точки (сек) + EXTRACT(EPOCH FROM ( + LEAD(observed_at) OVER (PARTITION BY track_id ORDER BY observed_at) - observed_at + )) AS step_dt_sec + FROM zup_pts +), +-- Производная скорость между точками +pairs_with_speed AS ( + SELECT *, + CASE WHEN step_dt_sec > 0 THEN step_dist_m / step_dt_sec ELSE NULL END AS computed_speed_ms + FROM pairs + WHERE step_dist_m IS NOT NULL AND step_dt_sec IS NOT NULL +), +-- Агрегация по треку +track_metrics AS ( + SELECT + track_id, + -- M3: кол-во точек в ЗУП + COUNT(*) AS n_zup, + -- M1: медианное расстояние между точками + PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY step_dist_m) AS median_step_m, + MAX(step_dist_m) AS max_step_m, + -- M2: медианный интервал между точками + PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY step_dt_sec) AS median_dt_sec, + MAX(step_dt_sec) AS max_gap_sec, + -- gap rate: доля интервалов > 30 сек + ROUND( + SUM(CASE WHEN step_dt_sec > 30 THEN 1 ELSE 0 END)::numeric + / NULLIF(COUNT(*), 0), 3 + ) AS gap_rate, + -- M4: outlier ratio — вычисленная скорость > 350 м/с (Mach 1) + ROUND( + SUM(CASE WHEN computed_speed_ms > 350 THEN 1 ELSE 0 END)::numeric + / NULLIF(COUNT(*), 0), 3 + ) AS outlier_ratio, + MIN(dist_km) AS min_dist_km, + MAX(dist_km) AS max_dist_km + FROM pairs_with_speed + GROUP BY track_id + HAVING COUNT(*) >= 5 -- минимальный значимый трек +), +-- ============================================================ +-- 3. TRACK QUALITY SCORE (TQS) +-- TQS = 0.3*(1-gap_rate) + 0.3*(1-outlier_ratio) +-- + 0.2*min(n_zup/20, 1) + 0.2*(coverage_ratio ≈ 1 - gap_rate) +-- ============================================================ +tqs AS ( + SELECT + track_id, + n_zup, + median_step_m, + median_dt_sec, + max_gap_sec, + gap_rate, + outlier_ratio, + min_dist_km, + max_dist_km, + ROUND( + 0.3 * (1 - COALESCE(gap_rate, 0)) + + 0.3 * (1 - COALESCE(outlier_ratio, 0)) + + 0.2 * LEAST(n_zup::numeric / 20, 1) + + 0.2 * (1 - COALESCE(gap_rate, 0)) -- coverage ≈ 1 - gap_rate + , 3) AS tqs + FROM track_metrics +) +SELECT * FROM tqs +ORDER BY tqs DESC; + + +-- ============================================================ +-- 4. RECEPTION HEALTH INDEX (RHI) — итоговый по дням +-- Мониторинг деградации антенны/приёмника во времени +-- ============================================================ +WITH antenna AS ( + SELECT ST_SetSRID(ST_MakePoint(37.216, 56.121), 4326)::geography AS pt +), +daily AS ( + SELECT + DATE(tp.observed_at) AS day, + COUNT(DISTINCT tp.track_id) AS tracks_total, + COUNT(*) AS points_total, + -- точки в ЗУП + COUNT(*) FILTER ( + WHERE ST_Distance(tp.geom::geography, a.pt) / 1000 <= 150 + ) AS points_in_zup, + COUNT(DISTINCT tp.track_id) FILTER ( + WHERE ST_Distance(tp.geom::geography, a.pt) / 1000 <= 150 + ) AS tracks_in_zup + FROM fr24.track_points tp, antenna a + WHERE tp.observed_at >= NOW() - INTERVAL '14 days' + GROUP BY DATE(tp.observed_at) +) +SELECT + day, + tracks_total, + tracks_in_zup, + points_total, + points_in_zup, + ROUND(points_in_zup::numeric / NULLIF(points_total, 0), 3) AS zup_ratio, + -- RHI: нормализуем tracks_in_zup на медиану по периоду + ROUND( + tracks_in_zup::numeric + / NULLIF(AVG(tracks_in_zup) OVER (), 0) + , 3) AS rhi +FROM daily +ORDER BY day;