350 lines
13 KiB
Markdown
350 lines
13 KiB
Markdown
# Фаза 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/` или переписать
|