From 882fc0fae2121eb8ac21705349db0be3cdbdc720 Mon Sep 17 00:00:00 2001 From: Stream Date: Mon, 20 Apr 2026 12:50:01 +0300 Subject: [PATCH] auto-sync: 2026-04-20 12:50:01 --- .../docs/PHASE2_STEP1_EXTERNAL_DATA.md | 290 ++++++++++++++---- 1 file changed, 232 insertions(+), 58 deletions(-) diff --git a/tasks/flightradar24/docs/PHASE2_STEP1_EXTERNAL_DATA.md b/tasks/flightradar24/docs/PHASE2_STEP1_EXTERNAL_DATA.md index 908e78c..712f730 100644 --- a/tasks/flightradar24/docs/PHASE2_STEP1_EXTERNAL_DATA.md +++ b/tasks/flightradar24/docs/PHASE2_STEP1_EXTERNAL_DATA.md @@ -14,107 +14,281 @@ ## Бизнес-требования -### BR-1: Сбор данных FR24 API -- Загружать треки рейсов над Московской областью через FR24 API -- Покрытие: bbox ~54.5–57.0°N, 35.5–40.5°E (Московская область) -- Исторические данные: последние 7 дней (при наличии кредитов) -- Live данные: обновление каждые 5 минут -- Хранить: icao24, callsign, координаты трека, высота, скорость, время +### BR-1: Сбор данных онлайн табло (приоритет) +- **Аэропорты:** SVO (Шереметьево), DME (Домодедово), VKO (Внуково), ZIA (Жуковский) +- **Режим:** T-1 (загрузка на следующий день за предыдущие сутки) +- **Данные:** номер рейса, авиакомпания, направление (прилёт/вылет), запланированное время, фактическое время (с учётом задержек/отмен), статус, ICAO24 борта (если доступен) +- **Источники:** + - Яндекс.Расписания API (основной) — расписание, статусы, маршруты + - OpenSky Network API (дополнительный) — фактические времена, ICAO24 бортов +- **Глубина хранения:** 3 года (параметр, изменяемый) +- **Старт загрузки:** с 01.04.2026 +- **Догрузка:** возможность загружать старые периоды с учётом rate limits -### BR-2: Сбор данных онлайн табло -- Аэропорты: SVO (Шереметьево), DME (Домодедово), VKO (Внуково), ZIA (Жуковский) -- Данные: номер рейса, авиакомпания, направление, время вылета/прилёта, статус -- Источник: Яндекс.Расписания API или открытые табло аэропортов -- Обновление: каждые 15 минут -- Хранить историю за 30 дней +### BR-2: UI для просмотра табло +- **Расположение:** в существующем фронтенде (`http://192.168.2.67:8080/schedule`) +- **Формат:** таблица с фильтрами +- **Фильтры:** + - Дата (диапазон) + - Тип (прилёт/вылет/все) + - Аэропорт (SVO/DME/VKO/ZIA/все) + - Номер рейса (поиск) + - Часовой интервал (например, 06:00-12:00) +- **Экспорт:** CSV +- **Адаптация:** мобильная версия обязательна -### BR-3: Хранение +### 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: треки FR24 — 30 дней, табло — 30 дней +- Retention: табло — 3 года (параметр) --- ## Технические требования -### Контейнер `fr24-external` +### Контейнер `fr24-schedule` - Python 3.11-slim - Отдельный сервис в docker-compose.yml -- Переменные окружения: FR24_API_KEY, YANDEX_RASP_API_KEY -- Два независимых воркера: fr24_worker и rasp_worker -- Логирование в /var/log/fr24/external.log +- Переменные окружения: YANDEX_RASP_API_KEY, OPENSKY_USERNAME, OPENSKY_PASSWORD (опционально) +- Два независимых воркера: yandex_worker и opensky_worker +- Логирование в stdout (Docker logs) ### Схема БД `fr24_ext` + ```sql --- Рейсы из FR24 API -fr24_ext.flights_ext ( - flight_ext_id, source, fr24_id, icao24, callsign, - origin_iata, destination_iata, aircraft_type, - fetched_at, flight_date -) - --- Треки из FR24 API -fr24_ext.track_points_ext ( - point_id, flight_ext_id, observed_at, - lat, lon, altitude_ft, speed_kt, heading_deg -) - --- Табло аэропортов +-- Табло аэропортов (объединённые данные из источников) fr24_ext.schedule ( - schedule_id, airport_iata, direction, - flight_number, airline_iata, aircraft_type, - scheduled_at, estimated_at, actual_at, - status, fetched_at -) + 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/external/ +tasks/flightradar24/ingest/schedule/ Dockerfile - main.py # точка входа, запускает оба воркера - fr24_worker.py # загрузка из FR24 API - rasp_worker.py # загрузка из Яндекс.Расписания + 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` — добавить сервис `external` +- `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: FR24 API подключение +### TC-1: Яндекс.Расписания подключение - [ ] Контейнер стартует без ошибок -- [ ] FR24 API возвращает данные для bbox Москвы -- [ ] Треки сохраняются в `fr24_ext.track_points_ext` +- [ ] Яндекс API возвращает данные для SVO за вчерашний день +- [ ] Данные сохраняются в `fr24_ext.schedule` +- [ ] Дубликаты не создаются (upsert работает) -### TC-2: Табло аэропортов -- [ ] Данные по SVO загружаются корректно -- [ ] Статусы рейсов обновляются при повторном запросе -- [ ] Дубликаты не создаются (upsert по flight_number + scheduled_at) +### TC-2: OpenSky Network подключение +- [ ] OpenSky API возвращает arrivals/departures для UUEE (SVO) +- [ ] icao24 добавляется к существующим записям в `fr24_ext.schedule` +- [ ] Rate limit соблюдается (не более 4000 запросов/день) -### TC-3: Изоляция схем +### 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` -- [ ] При падении external контейнера остальные работают +- [ ] При падении schedule контейнера остальные работают -### TC-4: Retention -- [ ] Данные старше 30 дней удаляются автоматически +### TC-7: Retention +- [ ] Данные старше 3 лет удаляются автоматически (cron job) --- ## Зависимости - Существующая PostgreSQL (fr24-postgres) -- FR24_API_KEY в ~/.openclaw/.env -- YANDEX_RASP_API_KEY в ~/.openclaw/.env (или альтернативный источник табло) +- YANDEX_RASP_API_KEY в ~/.openclaw/.env +- OPENSKY_USERNAME, OPENSKY_PASSWORD в ~/.openclaw/.env (опционально, для повышенного лимита) ## Оценка объёма -- ~500 рейсов/день над Москвой из FR24 -- ~800 записей/день в табло (4 аэропорта × ~200 рейсов) -- Объём БД: ~50 MB/месяц + +### Данные +- ~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 часа при последовательной обработке