Перейти к основному содержимому

0063. Аналитика кампаний автобиддера: join с ad_campaigns и кампании без статистики

Копия ADR из репозитория: backend/docs/adr/0063-autobidder-campaign-analytics-join.md. Канонический текст — в репозитории; портал обновляется командой npm run sync:adr.

  • Статус: accepted
  • Дата: 2026-09-28
  • Связанные задачи: OP-69 (аналитика 5.4), OP-224 (управление кампаниями, ADR-0054), OP-68 (бюджеты)

Контекст и проблема​

GET /autobidder/analytics/campaigns агрегировал только ad_campaign_stats и отдавал advertId + метрики. UI списка кампаний читает именно этот эндпоинт и был вынужден показывать заглушку «Кампания {id}», autoEnabled: false и склеивать стратегии на клиенте. Нужно отдавать данные кампании (name, status, autoEnabled, strategyId, dailyBudget, syncedAt) и решить, что делать с кампаниями, у которых нет статистики за период.

Решение​

  1. Объединение множеств «кампании кабинета» ∪ «статистика за период»:
    • кампания со статистикой — метрики + поля кампании;
    • статистика без записи в ad_campaigns (ещё не синхронизирована) — метрики, поля кампании null;
    • кампания без статистики за период — нулевые метрики (drr/cpo = null), чтобы продавец видел и кампании без показов.
  2. Исключения из п. «без статистики»: удалённые кампании (status = -1) не добавляются; при фильтре nmId не добавляются (нет данных, что артикул в кампании есть).
  3. dailyBudget — из ad_campaign_budgets.daily_limit_rub (мягко удалённые лимиты не учитываются), null — лимита нет.
  4. Кеш: кешируется только агрегат статистики (как раньше). Данные кампаний и бюджетов читаются на каждый запрос двумя запросами по индексу кабинета — иначе PATCH /autobidder/campaigns/:id был бы невидим до истечения TTL.
  5. Сортировка/пагинация: расход по убыванию, затем advertId по возрастанию (числовой). Кампании без расхода — в хвосте. Пагинация in-memory поверх объединённого списка; meta.total включает кампании без статистики.

Последствия​

  • FE может убрать client-side merge (withStrategyBindings) и заглушки имён.
  • meta.total вырос на число кампаний без статистики; первая страница по-прежнему — самые «дорогие» кампании.
  • Мерж выполняется в памяти: число кампаний кабинета — сотни, не тысячи; при росте — перенести в SQL FULL OUTER JOIN с LIMIT/OFFSET.
  • Тай-брейк сортировки сменился со строкового на числовой advertId.