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