From d2ac1ed3e5f15a3b1467e245fe5ba5ba22f83fd2 Mon Sep 17 00:00:00 2001 From: Stream Date: Mon, 20 Apr 2026 23:00:01 +0300 Subject: [PATCH] auto-sync: 2026-04-20 23:00:01 --- .../docs/PHASE2_STEP2_DATA_MART.md | 422 +++++++++++++----- 1 file changed, 317 insertions(+), 105 deletions(-) diff --git a/tasks/flightradar24/docs/PHASE2_STEP2_DATA_MART.md b/tasks/flightradar24/docs/PHASE2_STEP2_DATA_MART.md index 9ee4341..61bdae5 100644 --- a/tasks/flightradar24/docs/PHASE2_STEP2_DATA_MART.md +++ b/tasks/flightradar24/docs/PHASE2_STEP2_DATA_MART.md @@ -1,137 +1,349 @@ -# Фаза 2, Шаг 2: Витрина данных — объединение FR24 API + табло + RTL-SDR +# Фаза 2, Шаг 2: Загрузка треков + Витрина данных ## Статус -🔲 Не начат (зависит от Шага 1) +🔲 БТ готово, реализация не начата ## Цель -Спроектировать и реализовать витрину данных (data mart) которая объединяет три источника: -1. **FR24 API** — треки и рейсы из внешнего API (схема `fr24_ext`) -2. **Онлайн табло** — расписание и статусы рейсов (схема `fr24_ext`) -3. **RTL-SDR dongle** — локально принятые ADS-B треки (схема `fr24`) - -Витрина — это набор материализованных представлений и таблиц в схеме `fr24_mart`, которые склеивают данные из всех источников в единую модель для визуализации и анализа. +Загрузить треки рейсов из двух внешних источников (FR24 API и FlightAware AeroAPI) в отдельные таблицы, затем объединить всё в витрину `fr24_mart` для шумовой карты. --- -## Бизнес-требования +## Архитектура источников -### BR-1: Единая модель рейса -- Рейс идентифицируется по callsign + дата -- Один рейс может иметь данные из нескольких источников одновременно -- Приоритет данных: RTL-SDR (наиболее точный) > FR24 API > табло -- Витрина должна показывать из какого источника взяты данные - -### BR-2: Обогащение данных -- К треку из RTL-SDR добавлять данные из табло (авиакомпания, маршрут, тип ВС) -- К треку из FR24 API добавлять данные из табло (статус, фактическое время) -- Если рейс есть в табло но нет трека — показывать как "нет данных о треке" - -### BR-3: Покрытие источников -- Метрика: % рейсов из табло для которых есть трек (RTL-SDR или FR24) -- Метрика: % рейсов с треком из RTL-SDR vs только FR24 -- Метрика: среднее качество трека (кол-во точек, покрытие маршрута) - -### BR-4: Шумовая модель -- Витрина должна содержать поле `noise_score` для каждой точки трека -- Расчёт: на основе высоты, скорости, типа ВС (упрощённая модель из прототипа) -- Агрегация: суммарный шум по сетке 0.01° × 0.01° за период +``` +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 --- Единая таблица рейсов (объединение источников) -fr24_mart.flights_unified ( - unified_id, callsign, flight_date, - origin_iata, destination_iata, airline_iata, aircraft_type, - scheduled_dep, actual_dep, status, - has_rtlsdr_track BOOL, has_fr24_track BOOL, - rtlsdr_points INT, fr24_points INT, - source_priority TEXT, -- 'rtlsdr' | 'fr24' | 'schedule_only' - updated_at -) +-- Единая таблица рейсов +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); --- Единые точки трека (лучший доступный источник) -fr24_mart.track_points_unified ( - point_id, unified_id, observed_at, - lat, lon, altitude_m, speed_kt, heading_deg, - source TEXT, -- 'rtlsdr' | 'fr24' - noise_score FLOAT -) +-- Единые точки трека (лучший источник) +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); --- Шумовая сетка (агрегат) -fr24_mart.noise_grid ( - grid_id, grid_lat FLOAT, grid_lon FLOAT, - period_date DATE, - flight_count INT, total_noise_score FLOAT, - avg_altitude_m FLOAT, - updated_at -) +-- Шумовая сетка (агрегат по 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); --- Покрытие источников (метрики) -fr24_mart.source_coverage ( - coverage_date DATE, - total_schedule INT, - with_rtlsdr INT, with_fr24 INT, schedule_only INT, - rtlsdr_pct FLOAT, fr24_pct FLOAT -) +-- Метрики покрытия источников +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 внутри существующего -- Обновление каждые 10 минут -- Инкрементальное (только новые данные) +- Контейнер `fr24-mart` (отдельный) +- Cron: ежечасно (инкрементальное обновление) +- Полный rebuild по флагу --- -## ТЗ для Dev-агента +## UI статистики — страница `/data-sources` -### Файлы для создания -``` -tasks/flightradar24/ingest/mart/ - Dockerfile - main.py # планировщик обновления витрины - build_unified.py # логика объединения источников - noise_model.py # расчёт noise_score (перенос из прототипа) - requirements.txt -tasks/flightradar24/db/init/004_schema_mart.sql # DDL схемы fr24_mart -``` +### Блоки страницы -### Обновить -- `tasks/flightradar24/compose/docker-compose.yml` — добавить сервис `mart` +**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 по часам) --- -## Тест-кейсы +## Контейнеры -### TC-1: Объединение источников -- [ ] Рейс с треком из RTL-SDR и записью в табло — объединяется корректно -- [ ] Рейс только в табло — создаётся запись с `source_priority = 'schedule_only'` -- [ ] Рейс только в RTL-SDR (нет в табло) — создаётся с `source_priority = 'rtlsdr'` - -### TC-2: Приоритет источников -- [ ] При наличии RTL-SDR трека — используется он, не FR24 -- [ ] При отсутствии RTL-SDR — используется FR24 - -### TC-3: Шумовая модель -- [ ] noise_score рассчитывается для каждой точки трека -- [ ] noise_grid обновляется после добавления новых точек -- [ ] Агрегация по сетке 0.01° корректна - -### TC-4: Метрики покрытия -- [ ] source_coverage обновляется ежедневно -- [ ] % покрытия считается корректно - -### TC-5: Производительность -- [ ] Обновление витрины за 10 минут занимает < 60 сек -- [ ] Запрос noise_grid за 1 день < 500 мс +| Контейнер | Назначение | Порт | +|---|---|---| +| `fr24-schedule` | Яндекс.Расписания (готов ✅) | 8000 | +| `fr24-tracks-fr24` | Загрузчик FR24 треков | 8001 | +| `fr24-tracks-fa` | Загрузчик FlightAware треков | 8002 | +| `fr24-mart` | Обновление витрины | — | --- -## Зависимости -- Шаг 1 (схема fr24_ext должна быть заполнена) -- Схема fr24 (RTL-SDR данные) -- Модель шума из прототипа (tasks/flightradar24/prototype/docs/NOISE_MODEL.md) +## Зависимости и порядок реализации + +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/` или переписать