# Фаза 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.5–57.0°N, 35.5–40.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-агента **📋 Детальная спецификация:** `docs/PHASE2_STEP1_DETAILED_SPEC.md` Детальная спецификация содержит: - Архитектуру контейнера и структуру процессов - Полный код модулей с обработкой ошибок - Конфигурацию и переменные окружения - Retry логику и rate limiting - Docker setup и docker-compose интеграцию - Retention и автоочистку ### Файлы для создания ``` 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 часа при последовательной обработке