# Как воспроизвести отчеты из raw data

Эта статья описывает, как построить в BI расчеты, близкие к отчетам Gravity Field, используя только CSV из экспорта сырых данных.

# Базовый staging

Перед расчетом отчетов загрузите CSV в staging-таблицу событий и приведите типы.

Минимальный набор нормализованных полей:

Поле в staging Источник Как использовать
event_time moscowTime Порядок событий и группировка по датам
event_date date(moscowTime) Дневные агрегаты
event_type_id type Фильтр типа события
uid uid Основная связка пользователя
session_id sessionId Связка внутри сессии
campaign_id campaignId Фильтр кампании
experience_id experienceId Фильтр experience
version_id versionId Фильтр версии теста
variation_id variationId Группировка A/B
strategy_id strategyId Группировка стратегий
event_name eventName Фильтр custom events
event_type eventType Фильтр purchase-v1
event_value_minor eventValue Деньги в minor units
event_value eventValue / 100 Деньги в валюте события
currency eventCurrency Валюта
transaction_id uniqueTransactionId Дедупликация заказов

Отдельно разверните eventCart в таблицу строк заказа:

Поле Описание
event_id eventId покупки
transaction_id uniqueTransactionId покупки
uid Пользователь покупки
session_id Сессия покупки
purchase_time moscowTime покупки
product_id Первый элемент tuple из eventCart
quantity Второй элемент tuple
item_price_minor Третий элемент tuple
line_revenue quantity * item_price_minor / 100

Для покупок используйте только события:

  • type = 1
  • eventType = 'purchase-v1'
  • непустой uniqueTransactionId

Если один и тот же uniqueTransactionId встречается несколько раз, оставьте первую покупку по времени.

# A/B report

# Настройки, которые нужно взять из платформы

Перед расчетом зафиксируйте:

  • campaignId, experienceId, versionId или список variationId
  • период отчета
  • stickiness: по uid или по sessionId
  • attribution window: session или N дней
  • trigger атрибуции: Variation Impression или Event is triggered
  • цель: purchase-v1, revenue или custom event
  • включено ли исключение outliers в платформенном отчете

# Алгоритм для purchase/revenue

  1. Отберите exposure-события:
    • type = 2
    • нужные campaignId, experienceId, versionId или variationId
    • непустой uid
    • период отчета
  2. Если trigger = Event is triggered, найдите trigger-событие после exposure:
    • тот же uid
    • если attribution by session, тот же sessionId
    • trigger_time >= exposure_time
    • eventName соответствует настройке цели-триггера
  3. Отберите покупки:
    • type = 1
    • eventType = 'purchase-v1'
    • дедупликация по uniqueTransactionId
  4. Привяжите покупки к exposure или trigger:
    • тот же uid
    • если attribution window = session, тот же sessionId
    • purchase_time >= attribution_start_time
    • для day window: purchase_time <= attribution_start_time + N days
  5. Для чистого сравнения исключите пользователей или сессии, где в рамках той же версии теста были разные variationId.
  6. Сгруппируйте результат по variationId.

SQL-псевдокод:

with exposures as (
  select
    uid,
    sessionId as session_id,
    moscowTime as exposure_time,
    campaignId as campaign_id,
    experienceId as experience_id,
    versionId as version_id,
    variationId as variation_id
  from raw_events
  where type = 2
    and campaignId = :campaign_id
    and experienceId = :experience_id
    and versionId = :version_id
),
purchases as (
  select *
  from (
    select
      uid,
      sessionId as session_id,
      moscowTime as purchase_time,
      eventId as event_id,
      uniqueTransactionId as transaction_id,
      eventValue / 100.0 as revenue,
      row_number() over (
        partition by uniqueTransactionId
        order by moscowTime
      ) as rn
    from raw_events
    where type = 1
      and eventType = 'purchase-v1'
      and uniqueTransactionId <> ''
  )
  where rn = 1
),
attributed as (
  select
    e.variation_id,
    p.uid,
    p.transaction_id,
    p.revenue
  from exposures e
  join purchases p
    on p.uid = e.uid
   and p.purchase_time >= e.exposure_time
   and p.purchase_time <= e.exposure_time + interval ':window_days day'
   -- для session attribution добавьте:
   -- and p.session_id = e.session_id
)
select
  variation_id,
  count(distinct uid) as users_with_purchase,
  count(distinct transaction_id) as purchases,
  sum(revenue) as revenue,
  sum(revenue) / nullif(count(distinct transaction_id), 0) as aov
from attributed
group by variation_id;

Если в платформенном отчете включено исключение outliers, raw BI-расчет без такой же обработки будет отличаться. Минимально зафиксируйте это как причину расхождения. Для более близкого расчета примените тот же подход: значения ниже 5-го перцентиля и выше 95-го перцентиля заменяются на консервативное значение на основе среднего/медианы периода.

# Strategies report

# Что можно воспроизвести из raw data

Из raw export можно посчитать близкие к платформе:

  • Impressions
  • Visible Impressions
  • Clicks
  • CTR
  • Direct Revenue по точному SKU
  • Direct Purchases по точному SKU
  • Direct Revenue per 1000
  • дневную динамику и разрез по strategyId

# Настройки, которые нужно взять из платформы

  • период отчета
  • attribution window: session, 1, 7, 14 или 30 дней
  • нужные strategyId, если считаете не все стратегии
  • валюта и source-фильтры, если они применяются в платформенном отчете

# Базовые метрики вовлечения

Считайте события по strategyId и дате события:

Метрика Расчет из raw data
Impressions count(*) where type = 4
Visible Impressions count(*) where type = 7
Clicks count(*) where type = 6
CTR Clicks / Visible Impressions
Visibility Visible Impressions / Impressions

# Direct Revenue и Direct Purchases

Платформенная логика direct attribution для точного SKU строится от Product Click к последующей покупке того же SKU.

Алгоритм:

  1. Отберите клики:
    • type = 6
    • непустой uid
    • непустой strategyId
    • разверните массив sku в отдельные строки clicked_sku
  2. Отберите покупки:
    • type = 1
    • eventType = 'purchase-v1'
    • дедупликация по uniqueTransactionId
    • разверните eventCart в строки заказа
  3. Привяжите purchase line к click:
    • тот же uid
    • purchase_time >= click_time
    • если attribution window = session, тот же sessionId
    • если attribution window = N days, purchase_time <= click_time + N days
    • cart.product_id = clicked_sku
  4. Для таблицы по стратегиям группируйте по strategyId и дате покупки.
  5. Для summary дедуплицируйте заказ так, чтобы один uniqueTransactionId не увеличивал итог несколько раз из-за нескольких стратегий.

SQL-псевдокод:

with product_clicks as (
  select
    uid,
    sessionId as session_id,
    moscowTime as click_time,
    strategyId as strategy_id,
    clicked_sku
  from raw_events
  cross join unnest(sku) as clicked_sku
  where type = 6
    and strategyId <> ''
),
purchase_lines as (
  select
    p.uid,
    p.sessionId as session_id,
    p.moscowTime as purchase_time,
    p.eventId as event_id,
    p.uniqueTransactionId as transaction_id,
    cart.product_id,
    cart.quantity,
    cart.item_price_minor,
    cart.quantity * cart.item_price_minor / 100.0 as line_revenue
  from deduplicated_purchases p
  cross join unnest_event_cart(p.eventCart) as cart
)
select
  c.strategy_id,
  date(l.purchase_time) as date,
  count(distinct l.transaction_id) as direct_purchases,
  sum(l.line_revenue) as direct_revenue
from product_clicks c
join purchase_lines l
  on l.uid = c.uid
 and l.product_id = c.clicked_sku
 and l.purchase_time >= c.click_time
 and l.purchase_time <= c.click_time + interval ':window_days day'
 -- для session attribution добавьте:
 -- and l.session_id = c.session_id
group by c.strategy_id, date(l.purchase_time);

Direct Revenue per 1000:

Direct Revenue per 1000 = Direct Revenue / (Visible Impressions / 1000)

CR click -> purchase:

CR click -> purchase = Direct Purchases / Clicks

# Почему сумма по стратегиям может быть больше summary

# FAQ по расхождениям

Почему BI-выручка отличается от платформы?
Проверьте timezone, период отчета, attribution window, дедупликацию по uniqueTransactionId, outliers в A/B-отчете и late events. Также проверьте, не нужна ли метрика, которая использует неэкспортируемые поля.

Что считать выручкой: gross, net, с НДС или без?
Gravity Field считает то значение, которое было отправлено в eventValue и eventCart.itemPrice. Платформа не выводит gross/net из каталога и не пересчитывает НДС или промо-скидки.

Учитываются ли возвраты и отмены заказов?
Автоматически нет. Если возвраты или отмены не передаются отдельными событиями/данными и не настроены в проекте, purchase-событие остается в расчете.

Почему Direct Revenue (All Variants) нельзя посчитать из raw CSV?
Для этой метрики нужен group_id кликнутого товара и купленных товаров. В текущем raw export этих полей нет.

Можно ли использовать eventId для дедупликации покупок?
Нет. Для заказов используйте uniqueTransactionId. eventId идентифицирует конкретное событие и может отличаться у повторно отправленных событий одного заказа.

Можно ли заменить Product Click событием add-to-cart?
Нет. Product Click (type = 6) — это engagement с рекомендательным товаром. Add-to-cart или другой сайт-клик является отдельным custom event и не равен клику по рекомендации.