Files
wiki/tasks/flightradar24/reports/TZ-fr24-worker-pagination-fix.md
2026-04-22 09:00:01 +03:00

161 lines
5.0 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.
# ТЗ: Фикс пагинации fr24_worker — commit per page + дедупликация
**Дата:** 2026-04-22
**Статус:** READY FOR DEV
**Приоритет:** Критический (из-за бага слиты 44K кредитов за ночь)
---
## Суть проблемы
`fetch_flight_summaries()` накапливает **все** страницы в памяти, потом `run()` вставляет всё разом.
При ошибке на любой странице — ничего не коммитится, все данные теряются.
Дополнительно: `both:SVO,both:DME,both:VKO,both:ZIA` возвращает дубли —
рейс SVO→DME попадает в выборку и как рейс SVO, и как рейс DME.
Итого: ~3 500 уникальных рейсов → 14 660 записей (×4 дублирование).
---
## Изменения в `fetch_flight_summaries()` → убрать, заменить на генератор
Файл: `ingest/tracks_fr24/fr24_worker.py`
### Было
```python
def fetch_flight_summaries(target_date: date) -> List[Dict]:
all_items = []
offset = 0
while True:
data = _get(...)
items = data.get("data", ...)
all_items.extend(items)
if len(items) < PAGE: break
offset += PAGE
return all_items
```
### Стало — генератор страниц
```python
def iter_flight_summary_pages(target_date: date):
"""Yield one page (list of flights) at a time. Stops on 402/empty."""
PAGE = 20
airports_param = _build_airports_param()
offset = 0
seen_fr24_ids = set() # дедупликация между страницами
while True:
try:
data = _get("/api/flight-summary/full", params={
"flight_datetime_from": f"{target_date}T00:00:00",
"flight_datetime_to": f"{target_date}T23:59:59",
"airports": airports_param,
"limit": PAGE,
"offset": offset,
})
except Exception as e:
log.error("fetch page offset=%d failed: %s", offset, e)
break
items = data.get("data", data) if isinstance(data, dict) else data
if not items or not isinstance(items, list):
break
# Дедупликация по fr24_id
unique = [x for x in items if x.get("fr24_id") not in seen_fr24_ids]
seen_fr24_ids.update(x["fr24_id"] for x in items if x.get("fr24_id"))
yield unique
if len(items) < PAGE:
break
offset += PAGE
```
---
## Изменения в `run()` — commit после каждой страницы
### Было
```python
summaries = fetch_flight_summaries(target_date)
stats["flights_found"] = len(summaries)
for item in summaries:
...upsert...
conn.commit()
```
### Стало
```python
for page in iter_flight_summary_pages(target_date):
stats["flights_found"] += len(page)
for item in page:
fr24_id = item.get("fr24_id")
if not fr24_id:
continue
try:
actual_id = upsert_flight_actual(conn, item, target_date)
if actual_id:
stats["flights_upserted"] += 1
...FETCH_TRACKS логика...
except Exception as e:
conn.rollback()
stats["errors"] += 1
log.error("FR24: error processing %s: %s", fr24_id, e)
# Коммит после каждой страницы
try:
conn.commit()
log.debug("Committed page, total so far: %d", stats["flights_upserted"])
except Exception as e:
conn.rollback()
log.error("Commit failed: %s", e)
stats["errors"] += 1
```
---
## Дополнительно — лимит страниц
Добавить защитный лимит:
```python
MAX_PAGES = int(os.getenv("FR24_MAX_PAGES", "200")) # 200 × 20 = 4000 рейсов max
```
В генераторе добавить счётчик:
```python
page_num = 0
while True:
...
page_num += 1
if page_num >= MAX_PAGES:
log.warning("Reached MAX_PAGES=%d, stopping pagination", MAX_PAGES)
break
```
В `config.py` добавить:
```python
MAX_PAGES: int = int(os.getenv("FR24_MAX_PAGES", "200"))
```
---
## Проверка
После деплоя запустить:
```
POST http://fr24-vm:8001/run?date=2026-04-19
```
(дата когда кредиты были) — проверить что `flight_actual` заполняется постепенно по страницам.
В БД сразу должны появляться строки, не ждать конца всей загрузки.
---
## Файлы для изменения
- `ingest/tracks_fr24/fr24_worker.py` — главные изменения
- `ingest/tracks_fr24/config.py` — добавить `MAX_PAGES`
**Деплой:** только `docker cp` (не rebuild), потом проверить синтаксис.