5.0 KiB
ТЗ: Фикс пагинации 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
Было
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
Стало — генератор страниц
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 после каждой страницы
Было
summaries = fetch_flight_summaries(target_date)
stats["flights_found"] = len(summaries)
for item in summaries:
...upsert...
conn.commit()
Стало
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
Дополнительно — лимит страниц
Добавить защитный лимит:
MAX_PAGES = int(os.getenv("FR24_MAX_PAGES", "200")) # 200 × 20 = 4000 рейсов max
В генераторе добавить счётчик:
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 добавить:
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), потом проверить синтаксис.