From ee0b19f3116eb86fd3bde4e3481c2586ea4591e9 Mon Sep 17 00:00:00 2001 From: Stream Date: Tue, 21 Apr 2026 17:00:01 +0300 Subject: [PATCH] auto-sync: 2026-04-21 17:00:01 --- tasks/flightradar24/PROJECT.md | 36 +-- .../db/init/005_schema_tracks.sql | 4 +- .../db/migrations/006_fix_fa_varchar.sql | 9 + tasks/flightradar24/docs/INDEX.md | 46 ++-- tasks/flightradar24/docs/SYSTEM_OVERVIEW.md | 255 ++++++++++++++++++ tasks/flightradar24/ingest/mart/build_mart.py | 98 ++++++- .../ingest/tracks_fa/fa_worker.py | 12 +- 7 files changed, 399 insertions(+), 61 deletions(-) create mode 100644 tasks/flightradar24/db/migrations/006_fix_fa_varchar.sql create mode 100644 tasks/flightradar24/docs/SYSTEM_OVERVIEW.md diff --git a/tasks/flightradar24/PROJECT.md b/tasks/flightradar24/PROJECT.md index bc14d98..b817b40 100644 --- a/tasks/flightradar24/PROJECT.md +++ b/tasks/flightradar24/PROJECT.md @@ -11,7 +11,7 @@ - **Старт проекта:** 22 марта 2026 - **Текущий статус:** активен - **Текущий фокус:** переход от FR24-only к локальному RTL-SDR контуру с PostgreSQL/PostGIS -- **Последнее обновление:** 20 апреля 2026 +- **Последнее обновление:** 21 апреля 2026 ## 3. Текущая часть: noisemap / FR24-прототип ### URL @@ -109,11 +109,18 @@ - [x] Экспорт CSV - [ ] OpenSky отключён (исторические данные платные) -### 🔜 Фаза 2, Шаг 2: Витрина данных -- [ ] Объединение RTL-SDR + FR24 API + табло в схему `fr24_mart` -- [ ] Шумовая сетка noise_grid -- [ ] Метрики покрытия источников -- [ ] ТЗ: `docs/PHASE2_STEP2_DATA_MART.md` +### ✅ Фаза 2, Шаг 2: Витрина данных (выполнено 21.04.2026) +- [x] Схема `fr24_mart` (flights, track_points, noise_grid, source_coverage) +- [x] Контейнеры fr24-mart, fr24-tracks-fr24, fr24-tracks-fa +- [x] build_mart.py: мэтчинг по приоритету RTL-SDR > FR24 > FA +- [x] Шумовая сетка noise_grid (ячейки 0.01°×0.01°) +- [x] Метрики source_coverage +- [x] FR24 API: 1 трек загружен и проверен (FV6807) +- [x] FA API: 5 треков загружено (тест) +- [ ] ⚠️ Мэтчинг RTL-SDR/FA в витрине сломан (ICAO vs IATA) — в работе +- [ ] ⚠️ DDL fix: VARCHAR(5) в flight_tracks_fa — в работе +- [ ] ⚠️ Яндекс backfill 06–19.04 не завершён (rate limit) +- [ ] Bulk rebuild витрины за 02–19.04 ### 🔜 Фаза 2, Шаг 3: Перенос noisemap на VM-FR24 - [ ] Адаптация прототипа под fr24_mart @@ -122,16 +129,13 @@ - [ ] ТЗ: `docs/PHASE2_STEP3_NOISEMAP_MIGRATION.md` ## 7. Документация -- `tasks/flightradar24/README.md` — обзор проекта -- `tasks/flightradar24/docs/ARCHITECTURE.md` — контейнерная архитектура ingest-контура -- `tasks/flightradar24/docs/RTL-SDR_TZ.md` — ТЗ на ingest-контур -- `tasks/flightradar24/docs/VM_SETUP.md` — инструкция по созданию VM в PVE -- `tasks/flightradar24/docs/TEST_PLAN.md` — тестовый контракт и acceptance checks -- `tasks/flightradar24/docs/DEV_AGENT_HANDOFF.md` — пакет передачи Dev-агенту -- `tasks/flightradar24/prototype/docs/ARCHITECTURE.md` — архитектура прототипа -- `tasks/flightradar24/prototype/docs/NOISE_MODEL.md` — модель шума -- `tasks/flightradar24/prototype/docs/DATA_LOADING.md` — загрузка данных -- `tasks/flightradar24/prototype/docs/UI.md` — UI +- **`tasks/flightradar24/docs/SYSTEM_OVERVIEW.md`** — полное актуальное описание системы (читать первым) +- `tasks/flightradar24/PROJECT.md` — статус и бэклог +- `tasks/flightradar24/docs/INDEX.md` — индекс всех документов +- `tasks/flightradar24/docs/ARCHITECTURE.md` — контейнерная архитектура (базовая) +- `tasks/flightradar24/docs/PHASE2_STEP2_DATA_MART.md` — ТЗ витрины fr24_mart +- `tasks/flightradar24/docs/VM_SETUP.md` — инструкция по VM +- `tasks/flightradar24/prototype/docs/NOISE_MODEL.md` — шумовая модель ## 8. Критерий направления Проект считается развивающимся правильно, если: diff --git a/tasks/flightradar24/db/init/005_schema_tracks.sql b/tasks/flightradar24/db/init/005_schema_tracks.sql index db1c858..4da7838 100644 --- a/tasks/flightradar24/db/init/005_schema_tracks.sql +++ b/tasks/flightradar24/db/init/005_schema_tracks.sql @@ -45,8 +45,8 @@ CREATE TABLE IF NOT EXISTS fr24_ext.flight_tracks_fa ( ident_icao VARCHAR(10), registration VARCHAR(15), aircraft_type VARCHAR(10), - origin_icao VARCHAR(5), - destination_icao VARCHAR(5), + origin_icao VARCHAR(20), + destination_icao VARCHAR(20), actual_off TIMESTAMPTZ, actual_on TIMESTAMPTZ, departure_delay INTEGER, -- seconds diff --git a/tasks/flightradar24/db/migrations/006_fix_fa_varchar.sql b/tasks/flightradar24/db/migrations/006_fix_fa_varchar.sql new file mode 100644 index 0000000..7d2ddbd --- /dev/null +++ b/tasks/flightradar24/db/migrations/006_fix_fa_varchar.sql @@ -0,0 +1,9 @@ +-- Migration: widen origin_icao / destination_icao in flight_tracks_fa +-- Reason: FA API sometimes returns coordinates (e.g. 'L 55.61740 39.72253') +-- instead of ICAO codes; VARCHAR(5) is too short for such values. +-- fa_worker.py now filters these out via _icao_or_none(), but this migration +-- prevents errors on any already-stored bad data and makes the column resilient. + +ALTER TABLE fr24_ext.flight_tracks_fa + ALTER COLUMN origin_icao TYPE VARCHAR(20), + ALTER COLUMN destination_icao TYPE VARCHAR(20); diff --git a/tasks/flightradar24/docs/INDEX.md b/tasks/flightradar24/docs/INDEX.md index 8617945..44a1f4c 100644 --- a/tasks/flightradar24/docs/INDEX.md +++ b/tasks/flightradar24/docs/INDEX.md @@ -1,40 +1,24 @@ # FR24 / noisemap docs index -Набор документов для перехода проекта от FR24-only прототипа к локальному RTL-SDR ingest-контуру. +> Обновлено: 2026-04-21 + +## 🔑 Главный документ +- **`SYSTEM_OVERVIEW.md`** — полное актуальное описание системы: контейнеры, БД, ETL, UI, статус данных ## Основные документы - `tasks/flightradar24/PROJECT.md` — общий статус и границы проекта -- `tasks/flightradar24/docs/ARCHITECTURE.md` — контейнерная архитектура ingest-контура -- `tasks/flightradar24/docs/RTL-SDR_TZ.md` — ТЗ на приём, хранение и обработку ADS-B данных +- `tasks/flightradar24/docs/ARCHITECTURE.md` — контейнерная архитектура (базовая, устарела частично) +- `tasks/flightradar24/docs/PHASE2_STEP2_DATA_MART.md` — ТЗ Фазы 2 Шаг 2 (витрина fr24_mart) - `tasks/flightradar24/docs/VM_SETUP.md` — инструкция по созданию VM в PVE -- `tasks/flightradar24/docs/TEST_PLAN.md` — тестовый контракт и acceptance checks -- `tasks/flightradar24/docs/DEV_AGENT_HANDOFF.md` — пакет передачи Dev-агенту +- `tasks/flightradar24/docs/TEST_PLAN.md` — тестовый контракт -## Исторические и вспомогательные документы -- `tasks/flightradar24/README.md` — обзор проекта и текущая справка -- `tasks/flightradar24/prototype/docs/ARCHITECTURE.md` — архитектура текущего FR24-прототипа -- `tasks/flightradar24/prototype/docs/NOISE_MODEL.md` — шумовая модель прототипа -- `tasks/flightradar24/prototype/docs/DATA_LOADING.md` — загрузка исторических данных -- `tasks/flightradar24/prototype/docs/UI.md` — UI прототипа -- `tasks/flightradar24/prototype/docs/DEVLOG.md` — журнал разработки +## Исторические документы (прототип) +- `tasks/flightradar24/prototype/docs/ARCHITECTURE.md` +- `tasks/flightradar24/prototype/docs/NOISE_MODEL.md` +- `tasks/flightradar24/prototype/docs/UI.md` +- `tasks/flightradar24/prototype/docs/DEVLOG.md` ## Что читать в каком порядке - -### Для запуска нового контура -1. `PROJECT.md` -2. `ARCHITECTURE.md` -3. `RTL-SDR_TZ.md` -4. `VM_SETUP.md` -5. `DEV_AGENT_HANDOFF.md` - -### Для понимания старого прототипа -1. `tasks/flightradar24/README.md` -2. `prototype/docs/ARCHITECTURE.md` -3. `prototype/docs/NOISE_MODEL.md` - -## Принцип -- `PROJECT.md` отвечает на вопрос "что это за проект" -- `ARCHITECTURE.md` отвечает на вопрос "как это разложено по контейнерам" -- `RTL-SDR_TZ.md` отвечает на вопрос "что именно строим" -- `VM_SETUP.md` отвечает на вопрос "как поднять инфраструктуру" -- `DEV_AGENT_HANDOFF.md` отвечает на вопрос "что отдать Dev-агенту и как" +1. `SYSTEM_OVERVIEW.md` — текущее состояние системы целиком +2. `PROJECT.md` — задачи и backlog +3. `PHASE2_STEP2_DATA_MART.md` — если нужна витрина подробно diff --git a/tasks/flightradar24/docs/SYSTEM_OVERVIEW.md b/tasks/flightradar24/docs/SYSTEM_OVERVIEW.md new file mode 100644 index 0000000..6b56481 --- /dev/null +++ b/tasks/flightradar24/docs/SYSTEM_OVERVIEW.md @@ -0,0 +1,255 @@ +# FR24 Noisemap — Полное описание системы + +> Актуально на: 2026-04-21 + +## Назначение проекта + +Система сбора, хранения и визуализации данных о воздушном движении над московскими аэропортами (SVO, DME, VKO, ZIA) для построения **карты шумового загрязнения** от авиации. + +--- + +## Инфраструктура + +**VM:** `fr24@192.168.2.67` (VM-FR24 в домашней сети, доступ через jump-host vpn-srv) +**Доступ:** `ssh -i ha_ssh_key -J vpn@185.130.212.192:3322 fr24@192.168.2.67` +**Docker Compose:** `/home/fr24/projects/fr24/compose/docker-compose.yml` + +--- + +## Контейнеры (10 шт.) + +### Слой хранения + +| Контейнер | Порт | Назначение | +|---|---|---| +| `fr24-postgres` | 5432 | PostgreSQL 15 + PostGIS. Единая БД `fr24` со схемами: `fr24`, `fr24_ext`, `fr24_mart` | +| `fr24-backup` | — | Ежедневный pg_dump в `/backup/` | + +### Слой сбора RTL-SDR + +| Контейнер | Порт | Назначение | +|---|---|---| +| `fr24-capture` | — | Читает ADS-B поток с dump1090 (SBS port 30003). Пишет сырые пакеты в `fr24.raw_packets`. Retention 3 дня. | +| `fr24-preprocess` | — | Читает `fr24.raw_packets`, строит нормализованные `fr24.flights`, `fr24.tracks`, `fr24.track_points`. Поддерживает recovery после сбоя. | + +### Слой внешних треков + +| Контейнер | Порт | Назначение | +|---|---|---| +| `fr24-tracks-fr24` | 8001 | Загрузка треков из FR24 API (AeroAPI). `POST /run?date=YYYY-MM-DD` — вручную. Пишет в `fr24_ext.flight_tracks_fr24` + `fr24_ext.track_points_fr24`. Стоимость: ~40 кредитов/рейс. | +| `fr24-tracks-fa` | 8002 | Загрузка треков из FlightAware AeroAPI. `POST /run?date=YYYY-MM-DD` — вручную. Пишет в `fr24_ext.flight_tracks_fa` + `fr24_ext.track_points_fa`. Стоимость: $0.01/запрос. | + +### Слой расписания и витрины + +| Контейнер | Порт | Назначение | +|---|---|---| +| `fr24-schedule` | 8000 | Загрузка расписания из Яндекс Расписаний API. Запускается по cron T-1. Пишет в `fr24_ext.schedule`. Rate limit: ~100 запросов/час. | +| `fr24-mart` | 8003 | ETL-витрина. Объединяет расписание + треки из всех источников. Запускается по расписанию каждый час (T-1) и вручную `POST /run?date=YYYY-MM-DD`. Пишет в `fr24_mart.*`. | + +### Слой отдачи данных и мониторинга + +| Контейнер | Порт | Назначение | +|---|---|---| +| `fr24-api` | 8080 | REST API для фронтенда. Читает из `fr24`, `fr24_ext`, `fr24_mart`. Эндпоинты: расписание, мониторинг, data-sources. | +| `fr24-monitoring` | — | Мониторинг системы. Раз в минуту логирует: disk%, db_size, capture_lag, throughput, unprocessed. Алерты при деградации. | + +--- + +## Схема базы данных + +### Схема `fr24` — RTL-SDR данные (real-time) + +``` +fr24.raw_packets — сырые SBS-пакеты от dump1090 +fr24.aircraft — реестр воздушных судов (icao24, callsign, reg) +fr24.flights — рейсы (callsign, started_at, ended_at) +fr24.tracks — треки рейсов +fr24.track_points — точки трека (lat, lon, altitude_m, speed, heading) +fr24.processing_state — курсор обработки (для recovery) +fr24.noise_results — результаты расчёта шума (устаревшее, мигрировано в mart) +``` + +### Схема `fr24_ext` — внешние источники + +``` +fr24_ext.schedule — расписание (Яндекс). ~1400 рейсов/день на 4 аэропорта. + Ключевые поля: schedule_id, flight_date, airport_iata, + direction, flight_number, origin_iata, destination_iata, + scheduled_at, thread_title (маршрут из Яндекса) + +fr24_ext.flight_tracks_fr24 — заголовки треков FR24 API +fr24_ext.track_points_fr24 — точки треков FR24 (altitude_ft, gspeed_kt) + +fr24_ext.flight_tracks_fa — заголовки треков FlightAware +fr24_ext.track_points_fa — точки треков FA (altitude_ft, gspeed_kt) + +fr24_ext.load_state — состояние загрузок (для идемпотентности) +``` + +### Схема `fr24_mart` — финальная витрина + +``` +fr24_mart.flights — объединённые рейсы. Один рейс = одна строка. + Флаги: has_rtlsdr, has_fr24, has_fa + track_source: 'rtlsdr' | 'fr24' | 'fa' | null + Ссылки: fr24_track_id, fa_track_id, rtlsdr_flight_id + +fr24_mart.track_points — точки трека с noise_score (расчёт по шумовой модели) +fr24_mart.noise_grid — агрегат по ячейкам 0.01°×0.01° (~1 км). AVG(noise_score) +fr24_mart.source_coverage — покрытие по источникам (% рейсов с треком) +``` + +--- + +## ETL-процессы + +### 1. RTL-SDR pipeline (real-time) +``` +Антенна → dump1090 SBS:30003 → fr24-capture → fr24.raw_packets + → fr24-preprocess → fr24.flights/tracks/track_points +``` +- **Покрытие:** ~230 рейсов/день (~9.6% от расписания) +- **Задержка:** 1-2 сек +- **Throughput:** ~3000 пакетов/5 мин + +### 2. Schedule pipeline (ежедневно T-1) +``` +Яндекс Расписания API → fr24-schedule → fr24_ext.schedule +``` +- **4 аэропорта × 2 направления** = ~1400 рейсов/день +- **Rate limit:** ~100 запросов/час → ~30 запросов на один день +- **thread_title:** парсится из `thread.title` (маршрут "Москва → Санкт-Петербург") + +### 3. FR24 tracks pipeline (по запросу) +``` +FR24 AeroAPI → fr24-tracks-fr24 → fr24_ext.flight_tracks_fr24 + track_points_fr24 +``` +- Запуск: `POST http://fr24-tracks-fr24:8001/run?date=YYYY-MM-DD` +- **Лимит:** Explorer тариф, остаток ~50K кредитов (40 кредитов/рейс = ~1250 рейсов) +- **Хранится только по явной команде** (дорого) + +### 4. FlightAware pipeline (по запросу) +``` +FlightAware AeroAPI → fr24-tracks-fa → fr24_ext.flight_tracks_fa + track_points_fa +``` +- Запуск: `POST http://fr24-tracks-fa:8002/run?date=YYYY-MM-DD` +- **Стоимость:** $0.01/запрос, ~$28/день при полной загрузке +- Rate limit: 429 после ~5-10 быстрых запросов, Retry-After 60с + +### 5. Mart builder (каждый час автоматически, T-1) +``` +fr24_ext.schedule + fr24.flights + fr24_ext.flight_tracks_fr24 + fr24_ext.flight_tracks_fa + → build_mart.py → fr24_mart.flights + track_points + noise_grid + source_coverage +``` + +**Логика мэтчинга (приоритет RTL-SDR > FR24 > FA):** + +1. **RTL-SDR:** `flight_number` (IATA `'SU 1057'`) → конвертируется в ICAO callsign (`'AFL1057'`) → поиск в `fr24.flights.callsign` +2. **FR24:** числовой номер рейса (`6807`) + маршрут (ICAO аэропортов → IATA через словарь) → поиск в `fr24_ext.flight_tracks_fr24` +3. **FA:** числовой номер рейса + маршрут → поиск в `fr24_ext.flight_tracks_fa` + +**Проблема мэтчинга (⚠️ в работе):** +- RTL-SDR: требует словарь `AIRLINE_IATA_TO_ICAO` (AFL=SU, SDM=FV и т.д.) +- FA: хранит ICAO ident (`CSN342`), расписание IATA (`CZ 342`) → числовой матч +- Словарь аэропортов `ICAO_TO_IATA` неполный (~40 аэропортов) + +### 6. Шумовая модель +``` +track_points → altitude_to_noise_db(alt_ft, aircraft_type) → noise_score + → AGG по ячейкам 0.01° → noise_grid +``` + +**Зоны:** +- 0-2 км: Критический (>75 dB) +- 2-5 км: Высокий (65-75 dB) +- 5-7 км: Средний (55-65 dB) +- 7-9 км: Низкий (45-55 dB) +- >9 км: Фоновый (<45 dB) + +--- + +## UI (фронтенд) + +Фронтенд: Flask + Leaflet.js, доступен через nginx reverse proxy. + +### Страницы + +#### `/` — Карта шумового загрязнения +- **Тепловая карта** (Leaflet heatmap layer) по данным `fr24_mart.noise_grid` +- **Аэропорты** SVO/DME/VKO/ZIA маркерами +- **Зоны влияния** 2/5/7/9 км (цветные круги) +- **Выбор даты** — слайдер или датепикер +- **Легенда** шума + +#### `/schedule` — Расписание рейсов +- Таблица рейсов из `fr24_mart.flights` с маршрутами +- **Фильтры:** аэропорт, направление, дата +- Маршрут отображается из `thread_title` (Яндекс) — "Москва → Санкт-Петербург" +- **CSV экспорт** — выгрузка с городами вместо IATA-кодов +- Иконки источника трека (RTL/FR24/FA) + +#### `/monitoring` — Мониторинг системы +- Capture lag, throughput, unprocessed packets +- DB size, disk usage +- Статус контейнеров +- График активности за последние 24ч + +#### `/data-sources` — Покрытие источниками +- Статистика по `fr24_mart.source_coverage` +- % рейсов с треком по каждому источнику +- Топ авиакомпаний, топ маршрутов +- Загрузка аэропортов + +### API эндпоинты (fr24-api :8080) + +``` +GET /api/schedule/data?date_from=&date_to=&airport=&direction= +GET /api/monitoring +GET /api/data-sources/coverage +GET /api/data-sources/quality +GET /api/data-sources/top-airlines +GET /api/data-sources/top-routes +GET /api/data-sources/airport-load +``` + +--- + +## Текущий статус данных (на 21.04.2026) + +| Что | Количество | +|---|---| +| Расписание в fr24_ext | 01.04 – 19.04.2026 (~26K рейсов) | +| FR24 треки | 1 рейс (FV6807, тест) | +| FA треки | 5 рейсов (CSN, тест) | +| RTL-SDR рейсы | 19–21.04 (~730 рейсов) | +| Витрина fr24_mart | 01.04 только (1399 рейсов, 1 с треком) | +| Noise grid | 688 ячеек за 01.04 | + +--- + +## Известные ограничения и backlog + +| # | Проблема | Приоритет | +|---|---|---| +| 1 | Мэтчинг RTL-SDR: IATA→ICAO конвертация авиакомпаний не реализована | 🔴 Высокий | +| 2 | Мэтчинг FA: ICAO ident в БД vs IATA в расписании | 🔴 Высокий | +| 3 | DDL: `origin_icao`/`destination_icao` VARCHAR(5) в flight_tracks_fa | 🔴 Высокий | +| 4 | Словарь ICAO_TO_IATA аэропортов неполный (~40 из нужных 200+) | 🟡 Средний | +| 5 | Яндекс backfill 06–19.04 не завершён (rate limit 429) | 🟡 Средний | +| 6 | Витрина не содержит данных за 02–19.04 | 🟡 Средний | +| 7 | Duration рейсов: только 6.6% заполнено | 🟢 Низкий | + +--- + +## Ключи и конфиги + +| Сервис | Переменная | Где | +|---|---|---| +| FR24 API | `FR24_API_KEY` | `.env` на VM | +| FlightAware | `FLIGHTAWARE_API_KEY` | `.env` на VM | +| Яндекс Расписания | `YANDEX_API_KEY` | `.env` на VM | +| PostgreSQL | `POSTGRES_PASSWORD=change-me` | docker-compose | + +FR24 Explorer: остаток **~50 486 кредитов** из 120K (промо до 31.05.2026) +FlightAware: потрачено **$0.05** из pay-per-use +Яндекс: **rate limit** сбросится ~23:30 UTC 21.04 diff --git a/tasks/flightradar24/ingest/mart/build_mart.py b/tasks/flightradar24/ingest/mart/build_mart.py index 83acb00..d64cb6d 100644 --- a/tasks/flightradar24/ingest/mart/build_mart.py +++ b/tasks/flightradar24/ingest/mart/build_mart.py @@ -39,10 +39,88 @@ ICAO_TO_IATA = { "RJTT": "HND", "RJBB": "KIX", "ZBAA": "PEK", "ZSSS": "SHA", "ZSPD": "PVG", "VIDP": "DEL", "VABB": "BOM", "OMDB": "DXB", "OTHH": "DOH", "OJAI": "AMM", "LLBG": "TLV", + # Russian domestic + "URWW": "VOG", "UNKL": "KJA", "USCC": "CEK", "UWUU": "UFA", + "XWGS": "PEE", "UWKD": "KZN", "ULMM": "MMK", "ULWW": "PES", + "ULPB": "PES", "UUYY": "SYK", "USDD": "TOF", "UNOO": "OMS", + "UNBB": "BAX", "UNWW": "NOZ", "ULWC": "KEM", "UIII": "IKT", + "UITT": "UUS", "UIBB": "BQS", "UHPP": "PKC", "UHMD": "GDX", + "UHMM": "MMK", "UASK": "IGT", "UATT": "AKX", "UAOO": "KSN", + "UKDD": "DNK", "UKDE": "ZAP", + # Turkey + "LTBA": "ISL", "LTAI": "AYT", "LTBS": "DLM", "LTBJ": "ADB", + # Spain / Canaries / Portugal + "LEAL": "ALC", "LEPA": "PMI", "GCTS": "TFS", "GCRR": "ACE", + "GCFV": "FUE", "GCLA": "SPC", "LPFR": "FAO", + # Cyprus + "LCLK": "LCA", "LCPH": "PFO", + # Africa + "HECA": "CAI", "HTDA": "DAR", "HAAB": "ADD", + "HESH": "SSH", "HEGN": "HRG", + # Southeast Asia + "VDPP": "PNH", "VVNB": "HAN", "VVTS": "SGN", + "WSSS": "SIN", "VTBD": "DMK", "VTBS": "BKK", + # East Asia + "RJAA": "NRT", "RKSI": "ICN", + "ZBAD": "PKX", "ZGGG": "CAN", "ZHHH": "WUH", "ZWWW": "URC", + # Gulf + "OMAA": "AUH", "OERK": "RUH", "OEDF": "DMM", } IATA_TO_ICAO = {v: k for k, v in ICAO_TO_IATA.items()} +# IATA airline code → ICAO airline code (for RTL-SDR callsign conversion) +AIRLINE_IATA_TO_ICAO = { + "SU": "AFL", # Аэрофлот + "FV": "SDM", # Россия + "DP": "PBD", # Победа + "S7": "SBI", # S7 + "U6": "SVR", # Уральские авиалинии + "UT": "UTS", # UTair + "N4": "NWS", # Nordwind + "5N": "AUL", # Smartavia + "7K": "KYV", # Ямал + "6W": "TZA", # Saratov (исторический) + "ZX": "AZS", # Azimuth + "RT": "RLT", # РУСЛАЙН + "TK": "THY", # Turkish Airlines + "LH": "DLH", # Lufthansa + "AF": "AFR", # Air France + "BA": "BAW", # British Airways + "EK": "UAE", # Emirates + "QR": "QTR", # Qatar Airways + "SV": "SVA", # Saudia + "ET": "ETH", # Ethiopian + "FZ": "FDB", # flydubai + "CZ": "CSN", # China Southern + "CA": "CCA", # Air China + "MU": "CES", # China Eastern + "HU": "CHH", # Hainan Airlines + "9C": "CQH", # Spring Airlines + "MS": "MSR", # EgyptAir + "AT": "RAM", # Royal Air Maroc + "IR": "IRA", # Iran Air + "W5": "IRM", # Mahan Air + "KC": "KZR", # Air Astana + "HY": "UZB", # Uzbekistan Airways + "T5": "TUA", # Turkmenistan Airlines + "J2": "AHY", # Azerbaijan Airlines + "A9": "TGZ", # Georgian Airways + "QN": "RLU", # Royal Flight +} + + +def _flight_number_to_callsign(flight_number: str) -> Optional[str]: + """Convert 'SU 1057' (IATA) to 'AFL1057' (ICAO callsign) for RTL-SDR matching.""" + m = re.match(r'^([A-Z0-9]{1,3})\s*(\d+)$', flight_number.strip()) + if not m: + return None + iata_code, num = m.group(1), m.group(2) + icao_code = AIRLINE_IATA_TO_ICAO.get(iata_code) + if icao_code: + return f"{icao_code}{num}" + return None + def _ft_to_m(ft: Optional[int]) -> Optional[int]: if ft is None: @@ -52,17 +130,17 @@ def _ft_to_m(ft: Optional[int]) -> Optional[int]: # ── source matchers ─────────────────────────────────────────── -def find_rtlsdr_flight(conn, callsign: str, flight_date: date) -> Optional[int]: - """Return fr24.flights.flight_id for RTL-SDR data.""" +def find_rtlsdr_flight(conn, flight_number: str, flight_date: date) -> Optional[int]: + """Return fr24.flights.flight_id for RTL-SDR data. + Converts IATA flight_number (e.g. 'SU 1057') to ICAO callsign ('AFL1057').""" + callsign = _flight_number_to_callsign(flight_number) + if not callsign: + return None with conn.cursor() as cur: cur.execute( - """ - SELECT f.flight_id FROM fr24.flights f - WHERE f.callsign = %s - AND f.started_at::date = %s - ORDER BY f.started_at - LIMIT 1 - """, + "SELECT f.flight_id FROM fr24.flights f " + "WHERE f.callsign = %s AND f.started_at::date = %s " + "ORDER BY f.started_at LIMIT 1", (callsign, flight_date), ) row = cur.fetchone() @@ -477,7 +555,7 @@ def build(target_date: date, conn) -> Dict: source_label = None # 1. Try RTL-SDR - rtlsdr_id = find_rtlsdr_flight(conn, callsign, target_date) + rtlsdr_id = find_rtlsdr_flight(conn, flight_number, target_date) if rtlsdr_id: source_info["has_rtlsdr"] = True source_info["rtlsdr_flight_id"] = rtlsdr_id diff --git a/tasks/flightradar24/ingest/tracks_fa/fa_worker.py b/tasks/flightradar24/ingest/tracks_fa/fa_worker.py index d9517ee..374e8f1 100644 --- a/tasks/flightradar24/ingest/tracks_fa/fa_worker.py +++ b/tasks/flightradar24/ingest/tracks_fa/fa_worker.py @@ -23,6 +23,14 @@ HEADERS = {"x-apikey": config.FA_API_KEY} _last_request_at: float = 0.0 +def _icao_or_none(code: Optional[str]) -> Optional[str]: + """Return code only if it looks like a valid ICAO airport code (4 uppercase letters). + FA sometimes returns coordinates like 'L 55.61740 39.72253' instead of ICAO.""" + if code and len(code) == 4 and code.isalpha() and code.isupper(): + return code + return None + + def _throttle(): global _last_request_at elapsed = time.monotonic() - _last_request_at @@ -107,8 +115,8 @@ def upsert_flight(conn, fa_flight: Dict, target_date: date) -> Optional[int]: fa_flight.get("ident_icao"), fa_flight.get("registration"), fa_flight.get("aircraft_type"), - (fa_flight.get("origin") or {}).get("code_icao"), - (fa_flight.get("destination") or {}).get("code_icao"), + _icao_or_none((fa_flight.get("origin") or {}).get("code_icao")), + _icao_or_none((fa_flight.get("destination") or {}).get("code_icao")), fa_flight.get("actual_off"), fa_flight.get("actual_on"), fa_flight.get("departure_delay"),