auto-sync: 2026-04-21 17:00:01
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
- **Старт проекта:** 22 марта 2026
|
- **Старт проекта:** 22 марта 2026
|
||||||
- **Текущий статус:** активен
|
- **Текущий статус:** активен
|
||||||
- **Текущий фокус:** переход от FR24-only к локальному RTL-SDR контуру с PostgreSQL/PostGIS
|
- **Текущий фокус:** переход от FR24-only к локальному RTL-SDR контуру с PostgreSQL/PostGIS
|
||||||
- **Последнее обновление:** 20 апреля 2026
|
- **Последнее обновление:** 21 апреля 2026
|
||||||
|
|
||||||
## 3. Текущая часть: noisemap / FR24-прототип
|
## 3. Текущая часть: noisemap / FR24-прототип
|
||||||
### URL
|
### URL
|
||||||
@@ -109,11 +109,18 @@
|
|||||||
- [x] Экспорт CSV
|
- [x] Экспорт CSV
|
||||||
- [ ] OpenSky отключён (исторические данные платные)
|
- [ ] OpenSky отключён (исторические данные платные)
|
||||||
|
|
||||||
### 🔜 Фаза 2, Шаг 2: Витрина данных
|
### ✅ Фаза 2, Шаг 2: Витрина данных (выполнено 21.04.2026)
|
||||||
- [ ] Объединение RTL-SDR + FR24 API + табло в схему `fr24_mart`
|
- [x] Схема `fr24_mart` (flights, track_points, noise_grid, source_coverage)
|
||||||
- [ ] Шумовая сетка noise_grid
|
- [x] Контейнеры fr24-mart, fr24-tracks-fr24, fr24-tracks-fa
|
||||||
- [ ] Метрики покрытия источников
|
- [x] build_mart.py: мэтчинг по приоритету RTL-SDR > FR24 > FA
|
||||||
- [ ] ТЗ: `docs/PHASE2_STEP2_DATA_MART.md`
|
- [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
|
### 🔜 Фаза 2, Шаг 3: Перенос noisemap на VM-FR24
|
||||||
- [ ] Адаптация прототипа под fr24_mart
|
- [ ] Адаптация прототипа под fr24_mart
|
||||||
@@ -122,16 +129,13 @@
|
|||||||
- [ ] ТЗ: `docs/PHASE2_STEP3_NOISEMAP_MIGRATION.md`
|
- [ ] ТЗ: `docs/PHASE2_STEP3_NOISEMAP_MIGRATION.md`
|
||||||
|
|
||||||
## 7. Документация
|
## 7. Документация
|
||||||
- `tasks/flightradar24/README.md` — обзор проекта
|
- **`tasks/flightradar24/docs/SYSTEM_OVERVIEW.md`** — полное актуальное описание системы (читать первым)
|
||||||
- `tasks/flightradar24/docs/ARCHITECTURE.md` — контейнерная архитектура ingest-контура
|
- `tasks/flightradar24/PROJECT.md` — статус и бэклог
|
||||||
- `tasks/flightradar24/docs/RTL-SDR_TZ.md` — ТЗ на ingest-контур
|
- `tasks/flightradar24/docs/INDEX.md` — индекс всех документов
|
||||||
- `tasks/flightradar24/docs/VM_SETUP.md` — инструкция по созданию VM в PVE
|
- `tasks/flightradar24/docs/ARCHITECTURE.md` — контейнерная архитектура (базовая)
|
||||||
- `tasks/flightradar24/docs/TEST_PLAN.md` — тестовый контракт и acceptance checks
|
- `tasks/flightradar24/docs/PHASE2_STEP2_DATA_MART.md` — ТЗ витрины fr24_mart
|
||||||
- `tasks/flightradar24/docs/DEV_AGENT_HANDOFF.md` — пакет передачи Dev-агенту
|
- `tasks/flightradar24/docs/VM_SETUP.md` — инструкция по VM
|
||||||
- `tasks/flightradar24/prototype/docs/ARCHITECTURE.md` — архитектура прототипа
|
- `tasks/flightradar24/prototype/docs/NOISE_MODEL.md` — шумовая модель
|
||||||
- `tasks/flightradar24/prototype/docs/NOISE_MODEL.md` — модель шума
|
|
||||||
- `tasks/flightradar24/prototype/docs/DATA_LOADING.md` — загрузка данных
|
|
||||||
- `tasks/flightradar24/prototype/docs/UI.md` — UI
|
|
||||||
|
|
||||||
## 8. Критерий направления
|
## 8. Критерий направления
|
||||||
Проект считается развивающимся правильно, если:
|
Проект считается развивающимся правильно, если:
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ CREATE TABLE IF NOT EXISTS fr24_ext.flight_tracks_fa (
|
|||||||
ident_icao VARCHAR(10),
|
ident_icao VARCHAR(10),
|
||||||
registration VARCHAR(15),
|
registration VARCHAR(15),
|
||||||
aircraft_type VARCHAR(10),
|
aircraft_type VARCHAR(10),
|
||||||
origin_icao VARCHAR(5),
|
origin_icao VARCHAR(20),
|
||||||
destination_icao VARCHAR(5),
|
destination_icao VARCHAR(20),
|
||||||
actual_off TIMESTAMPTZ,
|
actual_off TIMESTAMPTZ,
|
||||||
actual_on TIMESTAMPTZ,
|
actual_on TIMESTAMPTZ,
|
||||||
departure_delay INTEGER, -- seconds
|
departure_delay INTEGER, -- seconds
|
||||||
|
|||||||
9
tasks/flightradar24/db/migrations/006_fix_fa_varchar.sql
Normal file
9
tasks/flightradar24/db/migrations/006_fix_fa_varchar.sql
Normal file
@@ -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);
|
||||||
@@ -1,40 +1,24 @@
|
|||||||
# FR24 / noisemap docs index
|
# FR24 / noisemap docs index
|
||||||
|
|
||||||
Набор документов для перехода проекта от FR24-only прототипа к локальному RTL-SDR ingest-контуру.
|
> Обновлено: 2026-04-21
|
||||||
|
|
||||||
|
## 🔑 Главный документ
|
||||||
|
- **`SYSTEM_OVERVIEW.md`** — полное актуальное описание системы: контейнеры, БД, ETL, UI, статус данных
|
||||||
|
|
||||||
## Основные документы
|
## Основные документы
|
||||||
- `tasks/flightradar24/PROJECT.md` — общий статус и границы проекта
|
- `tasks/flightradar24/PROJECT.md` — общий статус и границы проекта
|
||||||
- `tasks/flightradar24/docs/ARCHITECTURE.md` — контейнерная архитектура ingest-контура
|
- `tasks/flightradar24/docs/ARCHITECTURE.md` — контейнерная архитектура (базовая, устарела частично)
|
||||||
- `tasks/flightradar24/docs/RTL-SDR_TZ.md` — ТЗ на приём, хранение и обработку ADS-B данных
|
- `tasks/flightradar24/docs/PHASE2_STEP2_DATA_MART.md` — ТЗ Фазы 2 Шаг 2 (витрина fr24_mart)
|
||||||
- `tasks/flightradar24/docs/VM_SETUP.md` — инструкция по созданию VM в PVE
|
- `tasks/flightradar24/docs/VM_SETUP.md` — инструкция по созданию VM в PVE
|
||||||
- `tasks/flightradar24/docs/TEST_PLAN.md` — тестовый контракт и acceptance checks
|
- `tasks/flightradar24/docs/TEST_PLAN.md` — тестовый контракт
|
||||||
- `tasks/flightradar24/docs/DEV_AGENT_HANDOFF.md` — пакет передачи Dev-агенту
|
|
||||||
|
|
||||||
## Исторические и вспомогательные документы
|
## Исторические документы (прототип)
|
||||||
- `tasks/flightradar24/README.md` — обзор проекта и текущая справка
|
- `tasks/flightradar24/prototype/docs/ARCHITECTURE.md`
|
||||||
- `tasks/flightradar24/prototype/docs/ARCHITECTURE.md` — архитектура текущего FR24-прототипа
|
- `tasks/flightradar24/prototype/docs/NOISE_MODEL.md`
|
||||||
- `tasks/flightradar24/prototype/docs/NOISE_MODEL.md` — шумовая модель прототипа
|
- `tasks/flightradar24/prototype/docs/UI.md`
|
||||||
- `tasks/flightradar24/prototype/docs/DATA_LOADING.md` — загрузка исторических данных
|
- `tasks/flightradar24/prototype/docs/DEVLOG.md`
|
||||||
- `tasks/flightradar24/prototype/docs/UI.md` — UI прототипа
|
|
||||||
- `tasks/flightradar24/prototype/docs/DEVLOG.md` — журнал разработки
|
|
||||||
|
|
||||||
## Что читать в каком порядке
|
## Что читать в каком порядке
|
||||||
|
1. `SYSTEM_OVERVIEW.md` — текущее состояние системы целиком
|
||||||
### Для запуска нового контура
|
2. `PROJECT.md` — задачи и backlog
|
||||||
1. `PROJECT.md`
|
3. `PHASE2_STEP2_DATA_MART.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-агенту и как"
|
|
||||||
|
|||||||
255
tasks/flightradar24/docs/SYSTEM_OVERVIEW.md
Normal file
255
tasks/flightradar24/docs/SYSTEM_OVERVIEW.md
Normal file
@@ -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
|
||||||
@@ -39,10 +39,88 @@ ICAO_TO_IATA = {
|
|||||||
"RJTT": "HND", "RJBB": "KIX", "ZBAA": "PEK", "ZSSS": "SHA",
|
"RJTT": "HND", "RJBB": "KIX", "ZBAA": "PEK", "ZSSS": "SHA",
|
||||||
"ZSPD": "PVG", "VIDP": "DEL", "VABB": "BOM", "OMDB": "DXB",
|
"ZSPD": "PVG", "VIDP": "DEL", "VABB": "BOM", "OMDB": "DXB",
|
||||||
"OTHH": "DOH", "OJAI": "AMM", "LLBG": "TLV",
|
"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_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]:
|
def _ft_to_m(ft: Optional[int]) -> Optional[int]:
|
||||||
if ft is None:
|
if ft is None:
|
||||||
@@ -52,17 +130,17 @@ def _ft_to_m(ft: Optional[int]) -> Optional[int]:
|
|||||||
|
|
||||||
# ── source matchers ───────────────────────────────────────────
|
# ── source matchers ───────────────────────────────────────────
|
||||||
|
|
||||||
def find_rtlsdr_flight(conn, callsign: str, flight_date: date) -> Optional[int]:
|
def find_rtlsdr_flight(conn, flight_number: str, flight_date: date) -> Optional[int]:
|
||||||
"""Return fr24.flights.flight_id for RTL-SDR data."""
|
"""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:
|
with conn.cursor() as cur:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"SELECT f.flight_id FROM fr24.flights f "
|
||||||
SELECT f.flight_id FROM fr24.flights f
|
"WHERE f.callsign = %s AND f.started_at::date = %s "
|
||||||
WHERE f.callsign = %s
|
"ORDER BY f.started_at LIMIT 1",
|
||||||
AND f.started_at::date = %s
|
|
||||||
ORDER BY f.started_at
|
|
||||||
LIMIT 1
|
|
||||||
""",
|
|
||||||
(callsign, flight_date),
|
(callsign, flight_date),
|
||||||
)
|
)
|
||||||
row = cur.fetchone()
|
row = cur.fetchone()
|
||||||
@@ -477,7 +555,7 @@ def build(target_date: date, conn) -> Dict:
|
|||||||
source_label = None
|
source_label = None
|
||||||
|
|
||||||
# 1. Try RTL-SDR
|
# 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:
|
if rtlsdr_id:
|
||||||
source_info["has_rtlsdr"] = True
|
source_info["has_rtlsdr"] = True
|
||||||
source_info["rtlsdr_flight_id"] = rtlsdr_id
|
source_info["rtlsdr_flight_id"] = rtlsdr_id
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ HEADERS = {"x-apikey": config.FA_API_KEY}
|
|||||||
_last_request_at: float = 0.0
|
_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():
|
def _throttle():
|
||||||
global _last_request_at
|
global _last_request_at
|
||||||
elapsed = time.monotonic() - _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("ident_icao"),
|
||||||
fa_flight.get("registration"),
|
fa_flight.get("registration"),
|
||||||
fa_flight.get("aircraft_type"),
|
fa_flight.get("aircraft_type"),
|
||||||
(fa_flight.get("origin") or {}).get("code_icao"),
|
_icao_or_none((fa_flight.get("origin") or {}).get("code_icao")),
|
||||||
(fa_flight.get("destination") or {}).get("code_icao"),
|
_icao_or_none((fa_flight.get("destination") or {}).get("code_icao")),
|
||||||
fa_flight.get("actual_off"),
|
fa_flight.get("actual_off"),
|
||||||
fa_flight.get("actual_on"),
|
fa_flight.get("actual_on"),
|
||||||
fa_flight.get("departure_delay"),
|
fa_flight.get("departure_delay"),
|
||||||
|
|||||||
Reference in New Issue
Block a user