← всі звіти · project_tabletki_integration.md

name: Tabletki.ua Integration description: Pipeline для регулярного pull продажів і UTM-звітів з Tabletki.ua Producer API в BigQuery ad-analytics-hub.tabletki. Backfill готовий, cron активний (вт 08:00 Київ), Looker views в комплекті. type: project originSessionId: c120c8e1-db10-4b48-a8c7-53b2e93524a5

Tabletki.ua → BigQuery Integration

Статус: 🟢 backfill виконаний (Бер 2 — Кві 26, 8 тижнів Mon-Sun), Python-loader працює, systemd timer активний з 30.04.2026.

Що робить

Раз на тиждень (вівторок 08:00 Київ) забирає попередній повний тиждень Mon-Sun з двох звітів Producer API:

Idempotent upsert: DELETE rows for (date_from, date_to) → INSERT нових. Repeat-safe.

Архітектура

Tabletki.ua Producer API (cab-producer.tabletki.ua)
    │ Bearer JWT (clientId=270, login=APIadsanalyzer)
    │ ▼ /api/ReportExecution/report?reportType=JSON&reportId={uuid}
    │ ▼ body: {dateFrom, dateTo}  ← НЕ передавати periodType (HTTP 500)
    │
    ├─ Sales (≈390 SKU rows / week)
    │       ▼ tabletki_loader.py --mode sales
    │       ▼ flatten 20 fields per docs handoff
    │       ▼ BQ: tabletki_sales (DATE+DATE+SkuId-based natural key)
    │
    └─ UTM (≈10-20 campaign rows / week, after ChildArray flatten)
            ▼ tabletki_loader.py --mode utm
            ▼ ChildArray (JSON string) → 1 row per (campaign,source,medium)
            ▼ BQ: tabletki_utm (DATE+DATE+utm_campaign+source+medium)

Схеми BigQuery

ad-analytics-hub.tabletki.tabletki_sales (20 полів):

ad-analytics-hub.tabletki.tabletki_utm (10 полів):

Looker Studio views (4 шт., створені Desktop'ом раніше): v_delta_sales_daily, v_delta_sales_weekly, v_delta_sales_trend, v_market_share.

Файли на VPS

Регламент (schedule / SLA)

Коли: щовівторка 08:00 за Києвом.

Чому вівторок: дані Tabletki за неділю стабілізуються з лагом ~24-48 год (внутрішня зміна замовлень/повернень). Понеділок ще «брудний», вівторок — уже чистий тиждень Mon-Sun.

Що пулиться щотижня: попередній повний Mon-Sun тиждень (8 днів тому → 2 дні тому). Один прохід = 2 API-запити (sales + UTM) + 4 BQ-запити (delete×2 + load×2). Тривалість: ~5-15 секунд залежно від відповіді API.

SLA на дані: доступні в BQ через ~30 секунд після успішного запуску.

Ручний refill: якщо вівторок-запуск пропущено (наприклад VPS down) — Persistent=true у timer-юніті означає systemd сам наздожене при наступному boot. Або вручну:

GOOGLE_APPLICATION_CREDENTIALS=/srv/passepartout/google/ad-pipeline-worker-sa.json \
  /srv/tools/tabletki/tabletki_loader.py --mode auto-week

Backfill ширшого діапазону: --mode both --from YYYY-MM-DD --to YYYY-MM-DD — loader сам розіб'є на тижні Mon-Sun, чанками. Рекомендовано не більше 12 тижнів за раз (~5 хв роботи).

Cron / systemd (заплановано до активації)

Як споживачі читають дані

  1. Looker Studio dashboard (через Sergey's Google account) — користує v_delta_sales_daily/weekly/trend/market_share views. Авто-refresh.
  2. Прямі BQ-запити з PowerBI / DataStudio / Notion / Google Sheets connector. Будь-який інструмент який підключається до BQ через service account.
  3. Ad-hoc analytics з VPS — Sergey пише в Telegram «дай зріз X», VPS Claude робить SQL-запит до tabletki + ad_data, скидає табличкою в чат.

Приклади корисних запитів

-- Весь обсяг продажів Delta Medical за останні 8 тижнів
SELECT date_from, ROUND(SUM(revenue_uah), 0) AS uah, SUM(units_sold) AS units
FROM `ad-analytics-hub.tabletki.tabletki_sales`
WHERE manufacturer LIKE '%Дельта%' AND period_type = 'week'
GROUP BY 1 ORDER BY 1;

-- Топ-10 SKU за останній доступний тиждень
SELECT product_name, units_sold, revenue_uah
FROM `ad-analytics-hub.tabletki.tabletki_sales`
WHERE date_to = (SELECT MAX(date_to) FROM `ad-analytics-hub.tabletki.tabletki_sales`)
  AND manufacturer LIKE '%Дельта%'
ORDER BY revenue_uah DESC LIMIT 10;

-- UTM funnel за тиждень: clicks → conversions → revenue
SELECT utm_campaign, utm_source, utm_medium, clicks, conversions, conversion_value
FROM `ad-analytics-hub.tabletki.tabletki_utm`
WHERE date_from = '2026-04-20'
ORDER BY clicks DESC;

Failure modes + recovery

Симптом Причина Recovery
OnFailure алерт у Telegram будь-який не-нуль exit journalctl -u tabletki-weekly-sync.service -e -n 100 → побачити причину
401 Unauthorized токен прострочений (раз на рік) оновити токен на cab-producer.tabletki.ua → перезаписати /srv/passepartout/tabletki/api.token (chmod 600) → не треба перезавантажувати — loader читає при кожному запуску
400 validation, min_date API не пускає UTM раніше 2026-03-01 norm — loader просто пропускає такі тижні і йде далі
500 Internal Server Error передано periodType body field в loader НЕ передається, але якщо хтось доробить — пам'ятати
0 рядків API повертає діапазон у майбутньому або помилка dateFrom > dateTo перевірити cron OnCalendar
BQ Access Denied SA втратив роль gcloud projects add-iam-policy-binding ad-analytics-hub --member=serviceAccount:ad-pipeline-worker@... --role=roles/bigquery.dataEditor (потрібен власник проекту, бо ад-pipeline-worker SA living в іншому проекті)
Дублі рядків у BQ пропущено DELETE перед INSERT loader робить це автоматично, але якщо хтось через bq load --append — почистити вручну SQL DELETE

Origin

Відомі обмеження API

Режими loader'а

# Один тиждень (для cron — auto-detect Mon-Sun попереднього тижня)
GOOGLE_APPLICATION_CREDENTIALS=/srv/passepartout/google/ad-pipeline-worker-sa.json \
  /srv/tools/tabletki/tabletki_loader.py --mode auto-week

# Backfill range (chunk by Mon-Sun automatically)
... --mode both --from 2026-03-01 --to 2026-04-29

# Тільки sales або тільки utm
... --mode sales --from 2026-04-13 --to 2026-04-19
... --mode utm --from 2026-04-20 --to 2026-04-26

# Dry-run (без запису в BQ)
... --mode utm --from 2026-04-20 --to 2026-04-26 --dry-run

Поточний стан даних (2026-04-30)

Як перевіряти стан

GOOGLE_APPLICATION_CREDENTIALS=/srv/passepartout/google/ad-pipeline-worker-sa.json python3 -c "
from google.cloud import bigquery
client = bigquery.Client(project='ad-analytics-hub')
for tbl in ('tabletki_sales', 'tabletki_utm'):
    q = f'SELECT date_from, date_to, COUNT(*) cnt FROM \\\`ad-analytics-hub.tabletki.{tbl}\\\` GROUP BY 1,2 ORDER BY 1'
    print(tbl)
    for r in client.query(q).result():
        print(f'  {r.date_from} → {r.date_to}: {r.cnt}')
"

Open items

Origin

Контакти / акаунти