# dev-2026-04-21-schedule-ui.md **Дата:** 2026-04-21 **Статус:** DONE **ТЗ:** TZ-schedule-ui-enrichment.md --- ## Задача Обогащение UI расписания данными из `flight_actual` и `mart.flights`: - Новые колонки: ВПП, тип ВС, регистрация, длительность, взлёт/посадка факт - Source badge для FR24-источников - Track badge для RTL-SDR/FA треков - Убрать дублирующийся столбец Трек и столбец Статус --- ## Выполненные изменения ### `main.py` — функция `schedule_data()` 1. **SQL переписан** с bare `FROM fr24_ext.schedule` на `FROM fr24_ext.schedule s` с двумя LEFT JOIN: - `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` 2. **Добавлены новые SELECT поля:** - `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` - `mf.aircraft_type` - `mf.track_source`, `mf.track_points` - `s.source AS sched_source` 3. **Фикс ambiguity**: `_schedule_where()` генерирует WHERE без table-alias. После JOIN столбцы `flight_date` и `flight_number` становятся неоднозначными (есть в `flight_actual` и `mart.flights`). Решение: в `schedule_data()` применяется `re.sub()` для подстановки `s.` prefix перед этими колонками в safe_where строке. 4. **Новые поля в JSON-ответе:** - `runway_takeoff`, `runway_landed` - `actual_distance` - `flight_time_min` (из fa_flight_time) - `registration` - `aircraft_type` - `track_source`, `track_points` - `sched_source` - `duration_eff` = `fa_flight_time or duration_min` --- ### `schedule.html` — полная перепись **Было:** 12 колонок, Трек дублировался в col 2 и col 12, был столбец Статус **Стало:** 13 колонок согласно ТЗ: | # | Колонка | Данные | |---|---------|--------| | 1 | Дата | flight_date | | 2 | Рейс | flight_number + cat badge + source badge | | 3 | Авиакомпания | airline + registration (мелко) | | 4 | ↑↓ | direction icon | | 5 | Маршрут | thread_title или origin → destination | | 6 | Аэропорт | airport_iata | | 7 | По расп. | scheduled_at (MSK) | | 8 | Взлёт факт | actual_takeoff (MSK) + delay badge | | 9 | Посадка факт | actual_landed (MSK) + delay badge | | 10 | Длит. | duration_eff в формате "2ч 35м" | | 11 | ВПП | runway_takeoff → runway_landed | | 12 | ВС | aircraft_type | | 13 | Трек | ✈ FR24 + 🛰 RTL-SDR + FA badge | **Добавлен CSS:** - `.src-badge` + `.src-fr24` для badge источника данных --- ### `schedule.js` — полная перепись **Новые функции:** - `fmtDuration(min)` — форматирует минуты как "2ч 35м" или "45м" **`renderTable()` переписана:** - 13 колонок вместо 12 - `actTakeoffCell`: actual_takeoff + delayCell(delay_takeoff_min) - `actLandedCell`: actual_landed + delayCell(delay_landed_min) - `airlineCell`: airline + `registration` под ней - `srcBadge`: показывается если `sched_source === 'fr24'` - `trackCol`: ✈ (если fr24_id) + 🛰 (если track_source=rtlsdr) + FA (если track_source=fa) - `runway`: `runway_takeoff → runway_landed` или "—" - `acType`: aircraft_type - `durationCell`: fmtDuration(duration_eff) **`renderCards()` обновлена** — добавлены строки: - Взлёт факт + задержка - Посадка факт + задержка - Длительность - ВПП - ВС / Борт (aircraft_type / registration) - Трек (✈ FR24, 🛰 RTL-SDR, FA) **`delayCell()`** — убран возврат "—" для null (теперь возвращает пустую строку, чтобы не засорять ячейки когда задержки нет) **colspan** везде обновлён с 12 на 13 (setLoading, showError, empty state) --- ## Деплой Файлы нужно скопировать на 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` ---