6.9 KiB
6.9 KiB
ТЗ: Обогащение 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 в запрос:
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 полёта:
"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
Эффективная длительность:
"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 в колонке Рейс
<!-- если sched_source == 'fr24' -->
<span class="src-badge src-fr24" title="Данные из FR24 API">FR</span>
<!-- если sched_source == 'yandex' -->
<!-- не показываем ничего, это норма -->
CSS для src-badge:
.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 в колонке Трек
// ✈ = ссылка на FR24
// 🛰 = RTL-SDR трек есть в mart
const rtlBadge = f.track_source === 'rtlsdr'
? `<span title="${f.track_points} точек RTL-SDR" style="color:#3fb950;cursor:default">🛰</span>`
: '';
Изменения в schedule.js
renderTable() — новые колонки
// Длительность
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 ? `<br><small style="color:#6e7681">${esc(f.registration)}</small>` : "");
// Source badge
const srcBadge = f.sched_source === 'fr24'
? `<span class="src-badge src-fr24" title="Источник: FR24">FR</span>` : "";
// Track column
const trackCol = [
f.fr24_id ? `<a href="https://www.flightradar24.com/data/flights/${f.fr24_id}" target="_blank" class="track-link" title="Трек FR24">✈</a>` : "",
f.track_source === 'rtlsdr' ? `<span title="${f.track_points||0} точек RTL-SDR" style="color:#3fb950">🛰</span>` : "",
f.track_source === 'fa' ? `<span title="${f.track_points||0} точек FA" style="color:#d29922">FA</span>` : "",
].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