Files
wiki/tasks/flightradar24/reports/TZ-schedule-ui-enrichment.md
2026-04-22 00:50:01 +03:00

200 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ТЗ: Обогащение 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
<!-- если sched_source == 'fr24' -->
<span class="src-badge src-fr24" title="Данные из FR24 API">FR</span>
<!-- если sched_source == 'yandex' -->
<!-- не показываем ничего, это норма -->
```
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'
? `<span title="${f.track_points} точек RTL-SDR" style="color:#3fb950;cursor:default">🛰</span>`
: '';
```
---
## Изменения в `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 ? `<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