Files
wiki/tasks/flightradar24/docs/PHASE2_STEP1_EXTERNAL_DATA.md
2026-04-20 12:50:01 +03:00

295 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Фаза 2, Шаг 1: Внешние источники данных — FR24 API + онлайн табло
## Статус
🔲 Не начат
## Цель
Развернуть отдельный контейнер для сбора данных из внешних источников:
1. **FR24 API** — исторические и live треки (как в прототипе noisemap)
2. **Онлайн табло** — расписание рейсов по аэропортам Москвы (SVO, DME, VKO, ZIA)
Данные сохранять в PostgreSQL в отдельной схеме `fr24_ext` — не пересекаться со схемой `fr24` (RTL-SDR ingest).
---
## Бизнес-требования
### BR-1: Сбор данных онлайн табло (приоритет)
- **Аэропорты:** SVO (Шереметьево), DME (Домодедово), VKO (Внуково), ZIA (Жуковский)
- **Режим:** T-1 (загрузка на следующий день за предыдущие сутки)
- **Данные:** номер рейса, авиакомпания, направление (прилёт/вылет), запланированное время, фактическое время (с учётом задержек/отмен), статус, ICAO24 борта (если доступен)
- **Источники:**
- Яндекс.Расписания API (основной) — расписание, статусы, маршруты
- OpenSky Network API (дополнительный) — фактические времена, ICAO24 бортов
- **Глубина хранения:** 3 года (параметр, изменяемый)
- **Старт загрузки:** с 01.04.2026
- **Догрузка:** возможность загружать старые периоды с учётом rate limits
### BR-2: UI для просмотра табло
- **Расположение:** в существующем фронтенде (`http://192.168.2.67:8080/schedule`)
- **Формат:** таблица с фильтрами
- **Фильтры:**
- Дата (диапазон)
- Тип (прилёт/вылет/все)
- Аэропорт (SVO/DME/VKO/ZIA/все)
- Номер рейса (поиск)
- Часовой интервал (например, 06:00-12:00)
- **Экспорт:** CSV
- **Адаптация:** мобильная версия обязательна
### BR-3: Сбор данных FR24 API (отложен до Шага 2)
- Загрузка треков рейсов над Московской областью через FR24 API
- Покрытие: bbox ~54.557.0°N, 35.540.5°E
- Стратегия: будет определена в Шаге 2 (витрина данных)
- На Шаге 1 — только табло, FR24 треки позже
### BR-4: Хранение
- Схема `fr24_ext` в существующей PostgreSQL БД
- Не влиять на схему `fr24` (RTL-SDR данные)
- Retention: табло — 3 года (параметр)
---
## Технические требования
### Контейнер `fr24-schedule`
- Python 3.11-slim
- Отдельный сервис в docker-compose.yml
- Переменные окружения: YANDEX_RASP_API_KEY, OPENSKY_USERNAME, OPENSKY_PASSWORD (опционально)
- Два независимых воркера: yandex_worker и opensky_worker
- Логирование в stdout (Docker logs)
### Схема БД `fr24_ext`
```sql
-- Табло аэропортов (объединённые данные из источников)
fr24_ext.schedule (
schedule_id BIGSERIAL PRIMARY KEY,
flight_date DATE NOT NULL,
airport_iata CHAR(3) NOT NULL, -- SVO, DME, VKO, ZIA
direction VARCHAR(10) NOT NULL, -- 'arrival' | 'departure'
flight_number VARCHAR(10) NOT NULL,
airline_iata CHAR(2),
airline_name VARCHAR(100),
origin_iata CHAR(3),
destination_iata CHAR(3),
aircraft_type VARCHAR(10),
scheduled_at TIMESTAMPTZ NOT NULL,
estimated_at TIMESTAMPTZ,
actual_at TIMESTAMPTZ,
status VARCHAR(20), -- 'scheduled', 'delayed', 'cancelled', 'departed', 'arrived'
icao24 CHAR(6), -- из OpenSky, если доступен
source VARCHAR(20) NOT NULL, -- 'yandex' | 'opensky' | 'merged'
fetched_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE(flight_number, airport_iata, scheduled_at, direction)
);
CREATE INDEX idx_schedule_date ON fr24_ext.schedule(flight_date);
CREATE INDEX idx_schedule_airport ON fr24_ext.schedule(airport_iata);
CREATE INDEX idx_schedule_flight ON fr24_ext.schedule(flight_number);
CREATE INDEX idx_schedule_time ON fr24_ext.schedule(scheduled_at);
-- Состояние загрузки (курсор для догрузки)
fr24_ext.load_state (
state_key VARCHAR(50) PRIMARY KEY,
state_value JSONB NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Пример state_value для догрузки:
-- {"last_loaded_date": "2026-04-01", "airports": ["SVO", "DME", "VKO", "ZIA"]}
```
### Воркеры
**yandex_worker.py:**
- Загрузка расписания через Яндекс.Расписания API
- Endpoint: `https://api.rasp.yandex.net/v3.0/schedule/`
- Параметры: `station={код станции}&date={YYYY-MM-DD}`
- Коды станций: SVO=s9600213, DME=s9600366, VKO=s9600215, ZIA=s9881291
- Upsert в `fr24_ext.schedule` по `(flight_number, airport_iata, scheduled_at, direction)`
- Rate limit: ~100 запросов/мин (не документирован, консервативная оценка)
**opensky_worker.py:**
- Загрузка arrivals/departures через OpenSky Network API
- Endpoint: `https://opensky-network.org/api/flights/arrival` и `/departure`
- Параметры: `airport={ICAO}&begin={unix_ts}&end={unix_ts}`
- ICAO коды: SVO=UUEE, DME=UUDD, VKO=UUWW, ZIA=UUBW
- Обогащение существующих записей в `fr24_ext.schedule` (добавление icao24)
- Rate limit: 4000 запросов/день для зарегистрированных, 400/день для анонимов
- Батчинг: 1 день = 1 запрос на аэропорт × 2 направления = 8 запросов/день
### Режимы работы
**T-1 режим (основной):**
- Запуск: ежедневно в 02:00 UTC (05:00 MSK)
- Загружает данные за вчерашний день (T-1)
- Последовательность: сначала Яндекс (расписание), потом OpenSky (обогащение icao24)
**Догрузка исторических данных:**
- Скрипт `backfill.py` с параметрами `--start-date 2026-04-01 --end-date 2026-04-19`
- Учитывает rate limits: пауза между запросами, батчинг
- Сохраняет прогресс в `fr24_ext.load_state`
- При ошибке — возобновление с последней успешной даты
---
## ТЗ для Dev-агента
### Файлы для создания
```
tasks/flightradar24/ingest/schedule/
Dockerfile
main.py # точка входа, запускает оба воркера + cron для T-1
yandex_worker.py # загрузка из Яндекс.Расписания
opensky_worker.py # загрузка из OpenSky Network
backfill.py # скрипт догрузки исторических данных
requirements.txt
tasks/flightradar24/db/init/003_schema_ext.sql # DDL схемы fr24_ext
tasks/flightradar24/frontend/schedule.html # UI для просмотра табло
tasks/flightradar24/frontend/schedule.js # логика фильтров и таблицы
```
### Обновить
- `tasks/flightradar24/compose/docker-compose.yml` — добавить сервис `schedule`
- `tasks/flightradar24/frontend/main.py` — добавить endpoints:
- `GET /schedule` — HTML страница
- `GET /api/schedule/data` — JSON данные с фильтрами
- `GET /api/schedule/export` — CSV экспорт
### API endpoints (новые)
```
GET /api/schedule/data
Query params:
- date_from: YYYY-MM-DD
- date_to: YYYY-MM-DD
- airport: SVO|DME|VKO|ZIA|all (default: all)
- direction: arrival|departure|all (default: all)
- flight_number: string (поиск, опционально)
- time_from: HH:MM (опционально)
- time_to: HH:MM (опционально)
- limit: int (default: 100)
- offset: int (default: 0)
Response:
{
"total": 1234,
"flights": [
{
"flight_number": "SU1234",
"airline": "Аэрофлот",
"airport": "SVO",
"direction": "departure",
"origin": "SVO",
"destination": "LED",
"scheduled_at": "2026-04-19T10:30:00Z",
"actual_at": "2026-04-19T10:45:00Z",
"delay_min": 15,
"status": "departed",
"icao24": "151ABC"
},
...
]
}
GET /api/schedule/export
Query params: те же что у /data
Response: CSV файл
```
### UI требования
**Макет:**
- Шапка с фильтрами (компактная, сворачиваемая на мобиле)
- Таблица с пагинацией (100 строк/страница)
- Кнопка экспорта CSV
**Колонки таблицы:**
- Дата
- Рейс (номер)
- Авиакомпания
- Аэропорт
- Направление (иконка ↑ вылет / ↓ прилёт)
- Маршрут (откуда → куда)
- Запланировано
- Фактически
- Задержка (мин)
- Статус (цветовая индикация)
**Мобильная адаптация:**
- Фильтры в выдвижной панели
- Таблица → карточки на экранах <768px
- Свайп для прокрутки таблицы на планшетах
**Цветовая индикация статусов:**
- `scheduled` — серый
- `departed` / `arrived` — зелёный
- `delayed` — жёлтый
- `cancelled` — красный
---
## Тест-кейсы
### TC-1: Яндекс.Расписания подключение
- [ ] Контейнер стартует без ошибок
- [ ] Яндекс API возвращает данные для SVO за вчерашний день
- [ ] Данные сохраняются в `fr24_ext.schedule`
- [ ] Дубликаты не создаются (upsert работает)
### TC-2: OpenSky Network подключение
- [ ] OpenSky API возвращает arrivals/departures для UUEE (SVO)
- [ ] icao24 добавляется к существующим записям в `fr24_ext.schedule`
- [ ] Rate limit соблюдается (не более 4000 запросов/день)
### TC-3: T-1 режим
- [ ] Ежедневный запуск в 02:00 UTC работает
- [ ] Загружаются данные за вчерашний день (T-1)
- [ ] Все 4 аэропорта обрабатываются
### TC-4: Догрузка исторических данных
- [ ] `backfill.py --start-date 2026-04-01 --end-date 2026-04-19` завершается успешно
- [ ] Прогресс сохраняется в `fr24_ext.load_state`
- [ ] При прерывании — возобновление с последней успешной даты
- [ ] Rate limits соблюдаются (паузы между запросами)
### TC-5: UI табло
- [ ] Страница `/schedule` открывается
- [ ] Фильтры работают (дата, аэропорт, направление, номер рейса, время)
- [ ] Пагинация работает (100 строк/страница)
- [ ] Экспорт CSV работает
- [ ] Мобильная версия адаптирована (карточки вместо таблицы на <768px)
### TC-6: Изоляция схем
- [ ] Запросы к `fr24_ext` не влияют на `fr24`
- [ ] При падении schedule контейнера остальные работают
### TC-7: Retention
- [ ] Данные старше 3 лет удаляются автоматически (cron job)
---
## Зависимости
- Существующая PostgreSQL (fr24-postgres)
- YANDEX_RASP_API_KEY в ~/.openclaw/.env
- OPENSKY_USERNAME, OPENSKY_PASSWORD в ~/.openclaw/.env (опционально, для повышенного лимита)
## Оценка объёма
### Данные
- ~800 рейсов/день (4 аэропорта × ~200 рейсов)
- 3 года = ~876K записей
- Размер записи: ~200 байт
- Объём БД: ~175 MB за 3 года
### Догрузка с 01.04.2026
- 20 дней × 4 аэропорта = 80 дней данных
- Яндекс: 80 запросов (1 запрос/аэропорт/день)
- OpenSky: 160 запросов (2 направления × 4 аэропорта × 20 дней)
- Укладывается в лимиты при батчинге
### Rate limits стратегия
- Яндекс: пауза 1 сек между запросами (консервативно)
- OpenSky: пауза 30 сек между запросами (4000/день = ~1 запрос/22 сек)
- Догрузка 20 дней: ~1.5 часа при последовательной обработке