# ТЗ: Обогащение UI расписания данными FR24 **Дата:** 2026-04-21 **Статус:** READY FOR DEV **Файлы:** `frontend/main.py`, `frontend/static/schedule.html`, `frontend/static/schedule.js` --- ## Что сейчас есть и что не так Таблица `/schedule` уже получает часть FR24-полей (actual_takeoff, delay, fr24_id, flight_category), но отображение плохое: - Трек-ссылка ✈ дублируется в столбцах 2 и 12 - `duration_min` есть в API, но не отображается в таблице - Runway, дистанция, тип ВС, регистрация — не показываются - Колонка "Фактически" показывает `actual_at` из Яндекса (часто null), а не FR24 actual_takeoff - Нет признака источника данных (Яндекс vs FR24) --- ## Изменения в `main.py` ### 1. Обогатить `/api/schedule/data` данными из `flight_actual` Добавить LEFT JOIN на `fr24_ext.flight_actual` в запрос: ```sql SELECT s.flight_number, s.airline_name, s.airport_iata, s.direction, s.origin_iata, s.destination_iata, s.scheduled_at, s.actual_at, s.status, s.flight_date, s.duration_min, s.thread_title, s.actual_takeoff, s.actual_landed, s.delay_takeoff_min, s.delay_landed_min, s.fr24_id, s.flight_category, s.source AS sched_source, -- из flight_actual: fa.runway_takeoff, fa.runway_landed, fa.actual_distance, fa.flight_time AS fa_flight_time, fa.reg AS registration, fa.operated_as, fa.category AS fa_category, -- из mart.flights (тип ВС): mf.aircraft_type, mf.track_source, mf.track_points FROM fr24_ext.schedule s LEFT JOIN fr24_ext.flight_actual fa ON fa.fr24_id = s.fr24_id AND s.fr24_id IS NOT NULL LEFT JOIN fr24_mart.flights mf ON mf.flight_number = s.flight_number AND mf.flight_date = s.flight_date WHERE {where} ORDER BY s.scheduled_at DESC LIMIT %s OFFSET %s ``` ### 2. Добавить новые поля в JSON-ответ В `schedule_data()` добавить в dict полёта: ```python "runway_takeoff": r.get("runway_takeoff"), "runway_landed": r.get("runway_landed"), "actual_distance": r.get("actual_distance"), # км "flight_time_min": r.get("fa_flight_time"), # минуты из FA "registration": r.get("registration"), "aircraft_type": r.get("aircraft_type"), "track_source": r.get("track_source"), # rtlsdr/fr24/fa/null "track_points": r.get("track_points"), "sched_source": r.get("sched_source"), # yandex/fr24 ``` Эффективная длительность: ```python "duration_eff": r.get("fa_flight_time") or r.get("duration_min"), ``` --- ## Изменения в `schedule.html` ### Новые колонки таблицы (вместо текущих 12) | # | Колонка | Данные | |---|---------|--------| | 1 | Дата | flight_date | | 2 | Рейс | flight_number + cat badge + source badge | | 3 | Авиакомпания | airline + registration (мелко) | | 4 | ↑↓ | direction | | 5 | Маршрут | thread_title или origin → destination | | 6 | Аэропорт | airport_iata | | 7 | По расп. | scheduled_at (MSK) | | 8 | Взлёт факт | actual_takeoff (MSK) + delay badge | | 9 | Посадка факт | actual_landed (MSK) | | 10 | Длит. | duration_eff в формате "2ч 35м" | | 11 | ВПП | runway_takeoff → runway_landed | | 12 | ВС | aircraft_type | | 13 | Трек | ✈ ссылка на FR24 + 🛰 если есть RTL-SDR трек | **Убрать:** дублирующий столбец Трек (#12 старый), убрать Статус (всегда scheduled). ### Source badge в колонке Рейс ```html FR ``` CSS для src-badge: ```css .src-badge { border-radius:3px; font-size:9px; font-weight:700; padding:1px 4px; margin-left:3px; } .src-fr24 { background:#0d2d5e; color:#58a6ff; } ``` ### Track source badge в колонке Трек ```js // ✈ = ссылка на FR24 // 🛰 = RTL-SDR трек есть в mart const rtlBadge = f.track_source === 'rtlsdr' ? `🛰` : ''; ``` --- ## Изменения в `schedule.js` ### `renderTable()` — новые колонки ```js // Длительность function fmtDuration(min) { if (!min) return "—"; const h = Math.floor(min / 60); const m = min % 60; return h > 0 ? `${h}ч ${m}м` : `${m}м`; } // ВПП const runway = (f.runway_takeoff || f.runway_landed) ? `${f.runway_takeoff || "?"}→${f.runway_landed || "?"}` : "—"; // Actual takeoff с задержкой const actTakeoffCell = f.actual_takeoff ? `${fmtTime(f.actual_takeoff)} ${delayCell(f.delay_takeoff_min)}` : "—"; // Actual landed const actLandedCell = f.actual_landed ? fmtTime(f.actual_landed) : "—"; // Регистрация под авиакомпанией const airlineCell = `${esc(f.airline || "—")}` + (f.registration ? `
${esc(f.registration)}` : ""); // Source badge const srcBadge = f.sched_source === 'fr24' ? `FR` : ""; // Track column const trackCol = [ f.fr24_id ? `` : "", f.track_source === 'rtlsdr' ? `🛰` : "", f.track_source === 'fa' ? `FA` : "", ].filter(Boolean).join(" "); ``` ### `renderCards()` — добавить новые поля В мобильных карточках добавить строки: - Взлёт факт + задержка - Посадка факт - Длительность - ВПП - Тип ВС / Регистрация - Трек (FR24 + RTL-SDR badge) --- ## Деплой Файлы на VM лежат в контейнере `fr24-api`, монтируются из: - `/home/fr24/projects/fr24/frontend/main.py` - `/home/fr24/projects/fr24/frontend/static/schedule.html` - `/home/fr24/projects/fr24/frontend/static/schedule.js` После изменений: `docker restart fr24-api` --- ## НЕ делать - Не трогать другие страницы (index.html, monitoring.html, data_sources) - Не менять схему БД - Не трогать main.py кроме функции `schedule_data()` и добавления SQL JOIN