200 lines
6.9 KiB
Markdown
200 lines
6.9 KiB
Markdown
# ТЗ: Обогащение 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
|