# Фаза 2, Шаг 2: Загрузка треков + Витрина данных ## Статус 🔲 БТ готово, реализация не начата ## Цель Загрузить треки рейсов из двух внешних источников (FR24 API и FlightAware AeroAPI) в отдельные таблицы, затем объединить всё в витрину `fr24_mart` для шумовой карты. --- ## Архитектура источников ``` fr24_ext.schedule ← Яндекс.Расписания (готово ✅) fr24_ext.flight_tracks_fr24 ← FR24 API (Flight tracks) fr24_ext.flight_tracks_fa ← FlightAware AeroAPI (track endpoint) fr24.flights + fr24.positions ← RTL-SDR (готово ✅) ↓ fr24_mart.flights_unified fr24_mart.track_points fr24_mart.noise_grid ``` --- ## Источник 1: FR24 API — Flight Tracks ### Ключевые решения - **Endpoint:** `GET /api/flight-tracks?flight_id={fr24_id}` — полный трек рейса одним запросом - **Стоимость:** 40 кредитов/рейс ($0.012) - **Ключ:** `MVM0hi4S7RRh7Dm4EOl1ShpDPc8CrmITXT2LY5y4dd84a62a` (KEY2, подтверждён рабочим) - **Тариф:** Explorer (30K кредитов/мес, история 30 дней) - **Загрузка:** ТОЛЬКО по явной команде Славы (не автоматически) - **Период:** T-1 (вчерашний день) - **Scope:** рейсы аэропортов SVO/DME/VKO/ZIA (через `flight-summary/light` за день) - **Rate limit:** 10 запросов/мин (Explorer) ### Стратегия загрузки 1. `flight-summary/light` → список fr24_id за вчерашний день по 4 аэропортам (~1500 рейсов) 2. Для каждого fr24_id → `flight-tracks` → треки с точками 3. Сохранить в `fr24_ext.flight_tracks_fr24` + `fr24_ext.track_points_fr24` 4. Отметить загрузку в `fr24_ext.load_state` ### Что возвращает `flight-summary/light` ```json { "fr24_id": "3f509576", "flight": "SU208", "callsign": "AFL208", "type": "B77W", "reg": "RA-73140", "orig_icao": "UUEE", "dest_icao": "ZSPD", "datetime_takeoff": "2026-04-20T17:16:13Z", "datetime_landed": null, "flight_ended": true } ``` ### Что возвращает `flight-tracks` ```json [{ "fr24_id": "3f4f0101", "tracks": [{ "timestamp": "2026-04-20T10:07:14Z", "lat": 49.72, "lon": 43.57, "alt": 25975, "gspeed": 432, "vspeed": 1280, "track": 336, "squawk": "0054", "source": "MLAT" }, ...] }] ``` ~782 точки на рейс (интервал ~5-6 сек) ### Расчёт расхода кредитов - Summary: 1500 × 3 кредита = 4500 кредитов - Tracks: 1500 × 40 кредитов = 60 000 кредитов - **Итого за 1 день: ~64 500 кредитов** - Explorer (30K/мес): хватит на ~0.5 дня → нужно пополнять кредиты перед каждым запуском - При пополнении до Essential ($90/мес, 333K): ~5 дней истории/мес ### Схема БД `fr24_ext` ```sql CREATE TABLE fr24_ext.flight_tracks_fr24 ( id BIGSERIAL PRIMARY KEY, fr24_id VARCHAR(20) NOT NULL UNIQUE, flight_number VARCHAR(20), callsign VARCHAR(20), aircraft_type VARCHAR(10), registration VARCHAR(15), origin_icao VARCHAR(5), destination_icao VARCHAR(5), actual_takeoff TIMESTAMPTZ, actual_landed TIMESTAMPTZ, flight_date DATE NOT NULL, fetched_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE fr24_ext.track_points_fr24 ( id BIGSERIAL PRIMARY KEY, track_id BIGINT REFERENCES fr24_ext.flight_tracks_fr24(id), observed_at TIMESTAMPTZ NOT NULL, lat DOUBLE PRECISION NOT NULL, lon DOUBLE PRECISION NOT NULL, altitude_ft INTEGER, gspeed_kt INTEGER, vspeed_fpm INTEGER, heading SMALLINT, squawk VARCHAR(5), source VARCHAR(10) ); CREATE INDEX ON fr24_ext.track_points_fr24 (track_id, observed_at); ``` --- ## Источник 2: FlightAware AeroAPI — Треки ### Ключевые решения - **Endpoint:** `GET /aeroapi/flights/{fa_flight_id}/track` - **Ключ:** `7qMijd3b3gVudezng3eVhKtup8iKFr75` (подтверждён рабочим) - **Тариф:** Personal (500 запросов/мес бесплатно) - **Использование:** треки рейсов, для которых нет трека в FR24 или нужна верификация - **История:** до января 2011 - **Загрузка:** только по команде (как и FR24) ### Стратегия загрузки 1. `GET /aeroapi/flights/{ident}` → получить fa_flight_id для рейса 2. `GET /aeroapi/flights/{fa_flight_id}/track` → полный трек 3. Сохранить в `fr24_ext.flight_tracks_fa` + `fr24_ext.track_points_fa` ### Что возвращает track endpoint ```json { "actual_distance": 948, "positions": [{ "altitude": 222, // сотни футов "groundspeed": 382, "heading": 315, "latitude": 54.75, "longitude": 37.55, "timestamp": "2026-04-20T11:00:14Z", "update_type": "M" // M=ADS-B, D=dead reckoning }, ...] } ``` ~400 точек на рейс (интервал ~30-60 сек) ### Бонус — фактические времена из `/flights/{ident}` FlightAware возвращает полный набор фактических времён: - `actual_off` — фактический взлёт (wheels off) - `actual_on` — фактическая посадка (wheels on) - `departure_delay` — задержка вылета (сек) - `arrival_delay` — задержка прилёта (сек) Эти данные можно сохранять в `fr24_ext.schedule` (UPDATE) для обогащения табло. ### Расчёт лимитов - Personal: 500 запросов/мес - На каждый рейс нужно 2 запроса (flights + track) = 250 рейсов/мес бесплатно - При T-1 загрузке: 250 рейсов/мес ÷ 22 рабочих дня ≈ **11 рейсов/день** на free tier - Для полного покрытия нужен Standard тариф (уточнить цену в portal) ### Схема БД `fr24_ext` ```sql CREATE TABLE fr24_ext.flight_tracks_fa ( id BIGSERIAL PRIMARY KEY, fa_flight_id VARCHAR(50) NOT NULL UNIQUE, ident_iata VARCHAR(10), ident_icao VARCHAR(10), registration VARCHAR(15), aircraft_type VARCHAR(10), origin_icao VARCHAR(5), destination_icao VARCHAR(5), actual_off TIMESTAMPTZ, actual_on TIMESTAMPTZ, departure_delay INTEGER, -- секунды arrival_delay INTEGER, -- секунды actual_distance INTEGER, -- nautical miles flight_date DATE NOT NULL, fetched_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE fr24_ext.track_points_fa ( id BIGSERIAL PRIMARY KEY, track_id BIGINT REFERENCES fr24_ext.flight_tracks_fa(id), observed_at TIMESTAMPTZ NOT NULL, lat DOUBLE PRECISION NOT NULL, lon DOUBLE PRECISION NOT NULL, altitude_ft INTEGER, -- сотни футов × 100 gspeed_kt INTEGER, heading SMALLINT, update_type VARCHAR(5) -- M=ADS-B, D=dead reckoning ); CREATE INDEX ON fr24_ext.track_points_fa (track_id, observed_at); ``` --- ## Витрина `fr24_mart` ### Цель Единая модель для шумовой карты — объединяет все источники, рассчитывает шум по сетке. ### Приоритет источников для трека `RTL-SDR` > `FR24 API` > `FlightAware` > нет данных RTL-SDR точнее (5-сек интервал, локальный приём), FR24 детальнее FlightAware (5 vs 30 сек). ### Схема `fr24_mart` ```sql -- Единая таблица рейсов CREATE TABLE fr24_mart.flights ( id BIGSERIAL PRIMARY KEY, flight_number VARCHAR(20), callsign VARCHAR(20), icao24 CHAR(6), airline_iata VARCHAR(5), origin_iata VARCHAR(5), destination_iata VARCHAR(5), aircraft_type VARCHAR(50), flight_date DATE NOT NULL, scheduled_dep TIMESTAMPTZ, actual_dep TIMESTAMPTZ, actual_arr TIMESTAMPTZ, duration_min INTEGER, -- источники has_schedule BOOLEAN DEFAULT false, has_rtlsdr BOOLEAN DEFAULT false, has_fr24 BOOLEAN DEFAULT false, has_fa BOOLEAN DEFAULT false, track_source VARCHAR(10), -- 'rtlsdr'|'fr24'|'fa'|null track_points INTEGER, -- ключи источников schedule_id BIGINT, fr24_track_id BIGINT, fa_track_id BIGINT, rtlsdr_flight_id BIGINT, updated_at TIMESTAMPTZ DEFAULT now() ); CREATE UNIQUE INDEX ON fr24_mart.flights (flight_date, callsign); -- Единые точки трека (лучший источник) CREATE TABLE fr24_mart.track_points ( id BIGSERIAL PRIMARY KEY, flight_id BIGINT REFERENCES fr24_mart.flights(id), observed_at TIMESTAMPTZ NOT NULL, lat DOUBLE PRECISION NOT NULL, lon DOUBLE PRECISION NOT NULL, altitude_m INTEGER, speed_kt INTEGER, heading SMALLINT, source VARCHAR(10) ); CREATE INDEX ON fr24_mart.track_points (flight_id, observed_at); CREATE INDEX ON fr24_mart.track_points (lat, lon); -- Шумовая сетка (агрегат по 0.01°) CREATE TABLE fr24_mart.noise_grid ( id BIGSERIAL PRIMARY KEY, grid_lat NUMERIC(7,4) NOT NULL, grid_lon NUMERIC(7,4) NOT NULL, period_date DATE NOT NULL, flight_count INTEGER DEFAULT 0, noise_score FLOAT DEFAULT 0, avg_altitude_m FLOAT, updated_at TIMESTAMPTZ DEFAULT now(), UNIQUE (grid_lat, grid_lon, period_date) ); CREATE INDEX ON fr24_mart.noise_grid (period_date); -- Метрики покрытия источников CREATE TABLE fr24_mart.source_coverage ( coverage_date DATE PRIMARY KEY, total_schedule INTEGER DEFAULT 0, with_rtlsdr INTEGER DEFAULT 0, with_fr24 INTEGER DEFAULT 0, with_fa INTEGER DEFAULT 0, schedule_only INTEGER DEFAULT 0, rtlsdr_pct FLOAT, fr24_pct FLOAT, fa_pct FLOAT, updated_at TIMESTAMPTZ DEFAULT now() ); ``` ### Обновление витрины - Контейнер `fr24-mart` (отдельный) - Cron: ежечасно (инкрементальное обновление) - Полный rebuild по флагу --- ## UI статистики — страница `/data-sources` ### Блоки страницы **1. Покрытие источников** (за выбранный период, по дням) - Яндекс.Расписания: N рейсов - FR24 треки: N рейсов, % от табло - FlightAware: N рейсов, % от табло - RTL-SDR: N рейсов, % от табло - График: stacked bar по дням **2. Качество данных** - % рейсов с маршрутом (origin/destination) - % рейсов с треком - % рейсов с фактическими временами - % рейсов с типом самолёта - Медиана точек в треке по источнику **3. Расход кредитов FR24** - Потрачено кредитов на загрузку (из load_state) - Среднее кредитов/день **4. Топ авиакомпаний** (по количеству рейсов) **5. Топ маршрутов** (откуда/куда) **6. Загрузка аэропортов** (SVO/DME/VKO/ZIA по часам) --- ## Контейнеры | Контейнер | Назначение | Порт | |---|---|---| | `fr24-schedule` | Яндекс.Расписания (готов ✅) | 8000 | | `fr24-tracks-fr24` | Загрузчик FR24 треков | 8001 | | `fr24-tracks-fa` | Загрузчик FlightAware треков | 8002 | | `fr24-mart` | Обновление витрины | — | --- ## Зависимости и порядок реализации 1. **DDL** — схемы `fr24_ext` (доп. таблицы) + `fr24_mart` 2. **fr24-tracks-fr24** — воркер FR24 API (ручной запуск) 3. **fr24-tracks-fa** — воркер FlightAware API (ручной запуск) 4. **fr24-mart** — воркер витрины (автоматический, каждый час) 5. **UI `/data-sources`** — страница статистики --- ## Открытые вопросы - [ ] Тариф FlightAware Standard — уточнить цену за track запрос - [ ] Глубина исторической загрузки FR24 — сколько дней заливать при первом запуске - [ ] Шумовая модель — использовать прототип из `tasks/flightradar24/prototype/` или переписать