# ТЗ: Дополнение расписания из FR24 flight-summary/full **Дата:** 2026-04-21 **Статус:** READY FOR DEV **Приоритет:** Высокий --- ## Контекст FR24 `/api/flight-summary/full` уже используется в `fr24_worker.py` и возвращает **все реальные рейсы** через московские аэропорты за сутки (до 20 000 записей). Текущее поведение: - `enrich_schedule()` — только **UPDATE** существующих строк в `fr24_ext.schedule` - Рейсы из `flight_actual`, которых нет в Яндекс.Расписании, **теряются** Проблема: Яндекс не покрывает 100% рейсов (чартеры, грузовые, технические, задержанные). FR24 имеет полные данные. Нужно использовать `flight_actual` как дополнительный источник расписания. --- ## Что нужно сделать ### 1. Функция `supplement_schedule()` в `fr24_worker.py` ```python def supplement_schedule(conn, target_date: date) -> int: """ Insert into fr24_ext.schedule flights from flight_actual that have no matching schedule record. Source: fr24_ext.flight_actual Target: fr24_ext.schedule (source='fr24') Returns: number of rows inserted """ ``` **Логика:** 1. Найти все рейсы в `flight_actual` за дату, у которых нет соответствия в `schedule` 2. Определить direction: если `origin_icao` в московских аэропортах → departure, иначе arrival 3. INSERT в `schedule` с `source='fr24'`, `status='actual'` **SQL-скелет для поиска незаматченных:** ```sql SELECT fa.* FROM fr24_ext.flight_actual fa WHERE fa.flight_date = %(date)s AND NOT EXISTS ( SELECT 1 FROM fr24_ext.schedule s WHERE UPPER(REPLACE(s.flight_number,' ','')) = UPPER(REPLACE(fa.flight,' ','')) AND s.flight_date = fa.flight_date ) AND fa.flight IS NOT NULL AND fa.flight != '' ``` **Поля для INSERT в schedule:** ``` flight_date = target_date airport_iata = ICAO_TO_IATA[origin_icao или dest_icao] (московский аэропорт) direction = 'departure' если origin_icao московский, иначе 'arrival' flight_number = fa.flight (нормализовать: убрать лишние пробелы) airline_iata = первые 2 символа IATA (если можно определить) origin_iata = ICAO_TO_IATA.get(fa.origin_icao) destination_iata = ICAO_TO_IATA.get(fa.dest_icao) aircraft_type = NULL (нет в flight_actual) scheduled_at = fa.datetime_takeoff (или datetime_landed для arrival) actual_takeoff = fa.datetime_takeoff actual_landed = fa.datetime_landed status = 'actual' source = 'fr24' fr24_id = fa.fr24_id ``` **ON CONFLICT:** `(flight_number, airport_iata, scheduled_at, direction)` — уже есть. ### 2. Также исправить баги из ревью #### Баг A: ZBAD/PKX не в словаре ICAO_TO_IATA Файл: `build_mart.py` Добавить в словарь `ICAO_TO_IATA`: ```python "ZBAD": "PKX", # Beijing Daxing "RKSI": "ICN", # Seoul Incheon (уже есть, проверить) ``` #### Баг B: Soft match для FA/FR24 когда destination не совпадает Файл: `build_mart.py`, функции `find_fa_track()` и `find_fr24_track()` Текущая логика: ```python if origin_iata and not destination_iata: # только если dest == NULL # soft match by origin only ``` Нужно изменить на: ```python # Если полный матч не нашёл → fallback на origin only if origin_iata: for row in rows: orig_iata = ICAO_TO_IATA.get(row[2]) if orig_iata == origin_iata: return (row[0], row[1]) ``` (убрать условие `not destination_iata`) ### 3. Вызов `supplement_schedule()` в `run()` fr24_worker.py После `enrich_schedule()`: ```python # 4. Supplement schedule with flights from FR24 not in Yandex try: supplemented = supplement_schedule(conn, target_date) conn.commit() stats["schedule_supplemented"] = supplemented log.info("FR24 worker: supplemented %d new schedule rows", supplemented) except Exception as e: conn.rollback() log.error("FR24 worker: supplement_schedule failed: %s", e) stats["errors"] += 1 ``` --- ## Что НЕ нужно делать - НЕ менять DDL/схему БД (колонки `fr24_id`, `actual_takeoff`, `actual_landed` в schedule уже есть или добавить как nullable) - НЕ трогать RTL-SDR матч — он работает корректно - НЕ запускать backfill — только текущая логика для новых дат --- ## Проверка результата 1. Запустить build для 19.04 или 20.04 (данные уже есть в `flight_actual`) 2. Проверить: ```sql SELECT source, count(*) FROM fr24_ext.schedule WHERE flight_date = '2026-04-19' GROUP BY source; -- Ожидаемо: yandex=~1570, fr24=N (новые) SELECT count(*) FROM fr24_ext.schedule s JOIN fr24_ext.flight_actual fa ON UPPER(REPLACE(fa.flight,' ','')) = UPPER(REPLACE(s.flight_number,' ','')) WHERE s.flight_date = '2026-04-19' AND s.source = 'fr24'; ``` 3. Перезапустить mart build для даты — посмотреть увеличение `schedule_flights` и `with_track` --- ## Файлы для изменения | Файл | Изменение | |------|-----------| | `ingest/tracks_fr24/fr24_worker.py` | Добавить `supplement_schedule()`, вызвать в `run()` | | `ingest/mart/build_mart.py` | Добавить `ZBAD→PKX` в словарь, исправить soft match | --- ## Путь к проекту на VM `/home/fr24/fr24/ingest/` Деплой: `docker cp fr24-tracks-fr24:/app/ && docker restart fr24-tracks-fr24`