Files
clickstream-data-platform/docs/labs/utm-effectiveness.md
T
ddadmin cffe1aeb73 docs(labs): обращение «вы» во всех учебных заданиях
- Зачем:
  - README и путеводитель обращаются к читателю на «вы», а ТЗ в docs/labs/
    были на «ты»; после пересборки индекса смесь стала заметна.
- Что:
  - во всех восьми файлах docs/labs/ местоимения, глаголы и повелительное
    наклонение переведены во второе лицо множественного числа.
  - названия ступеней в таблице README оставлены как в спеке.
- Проверка:
  - grep по формам «ты/твой», 'ё' и 'менти' по docs/labs/ молчит;
    git diff --check чистый; ревью диффа свежей сессией.
2026-09-03 17:29:13 -04:00

11 KiB
Raw Blame History

Задание 5: dm.utm_effectiveness

Чему учит: приставка declared_ в имени колонки — предупреждение, а не украшение. Она говорит: это заявка клиента, а не деньги отчетности. Урок здесь дает конфликт: суммы не сойдутся, и раскапывать почему предстоит самостоятельно.

Правило стенда: поведение и атрибуцию считаем по трекеру, деньги — по бэкенду. Эта витрина — единственное разрешенное исключение: атрибуции по UTM у бэкенда нет, метки живут только в кликстриме. Раз считать иначе нечем, клиентская сумма берется, но называется своим именем.

Роль витрины

Эффективность рекламных каналов по дням: сколько трафика привел канал, сколько корзин и покупок в нем случилось и на какую сумму эти покупки, по словам клиента.

Атрибуция: по событию, а не по визиту

Метки едут на каждом событии, и витрина группирует события по их собственным меткам. Модели «первое касание» или «последнее касание» здесь нет: их построение — отдельная тема, а это витрина трафика по меткам.

dds.session_v при этом несет UTM первого события визита, и ответ «какой канал привел» из сессии и из этой витрины будет разным. Это не ошибка ни одной из сторон — это два разных вопроса, и в отчете их надо различать.

Прямой трафик из витрины не выбрасывается

Соблазн отфильтровать строки без меток велик: витрина же про рекламу. Отфильтровать нельзя по двум причинам. Первая деловая: прямой трафик — обычно самый крупный «канал» магазина, и отчет, где его нет, врет о доле рекламы. Вторая техническая: как только часть событий выпала, итог витрины перестал сходиться с итогом источника, и проверить сборку стало нечем.

Пустые метки остаются пустыми строками, как в источнике. Своего слова для «прямого захода» витрина не изобретает: чем меньше выдуманных значений, тем меньше расшифровывать читателю.

Зерно и форма

Зерно — день события × utm_source × utm_medium × utm_campaign. Зерно источника — событие, значит витрина материализуется: dm.utm_effectiveness_rep / _dist, донор _stage_rep / _stage_dist, публичное dm.utm_effectiveness_v. Ключ партиции — день.

Источник — dds.event_v, публичный договор события: витрина слоя DM читает договор DDS, а не соседнюю витрину.

Состав колонок

Колонка Тип Что это
report_date Date день события
utm_source LowCardinality(String) источник кампании
utm_medium LowCardinality(String) канал кампании
utm_campaign String имя кампании
visitors UInt64 число разных client_id
pageviews UInt64 просмотров страниц
add_to_carts UInt64 добавлений в корзину
purchases UInt64 покупок
declared_revenue Decimal(18, 2) выручка покупок глазами клиента
_load_id String запуск, собравший день
_load_ts DateTime64(3, 'UTC') когда собран

declared_revenue складывает purchase_revenue[1] событий purchase. Индекс не описка: блок purchase* едет параллельными массивами, и у события покупки в них ровно один элемент — контракт источника обещает один номер заказа и одну объявленную сумму на событие. Тем же индексом берет свою сторону стендовая сверка purchase_vs_orders.

Приводить тип не нужно: в договоре purchase_revenue уже Array(Decimal(18, 2)). Float64 эта сумма была только в выгрузке источника, и ODS перевел ее в точные деньги на приеме — сложение Decimal точное, и где стоит CAST, до или после sum, на результат не влияет. Витрина просто хранит то, что сложила, в Decimal(18, 2).

utm_campaign оставлена обычной строкой: имен кампаний со временем становится много, и словарь LowCardinality на растущем множестве перестает окупаться.

Ритм пересборки

Источник — события: день замерзает сразу. Объем прогона и механика те же, что в задании 2: хвост несобранных дней, донор, замена дневных партиций.

Ключ шардирования

Ваше решение по правилу донора, «Раскладка по шардам». Зерно здесь составное, из трех колонок, и решение «взять хеш от одной из них» надо защищать отдельно: назовите, какая доля строк придется на самое частое значение выбранной колонки.

Конфликт, ради которого витрина и написана

Соберите за один и тот же день две суммы: sum(declared_revenue) из своей витрины и sum(revenue) из dm.revenue_daily_v. Они не сойдутся, и это верное поведение обеих витрин.

Базы у двух сумм при этом сравнимые: revenue_daily складывает цену позиций на количество и ни скидки, ни доставки в себя не берет — ровно то, что видит клиент. Значит все расхождение до копейки объясняется тем, что случилось с заказом, а не тем, как считали деньги. Ищите ответы среди таких причин:

  • отмененный заказ из кликстрима никуда не девается: событие покупки случилось, а revenue_daily считает только оплаченные;
  • событие покупки может потеряться по дороге, а заказ при этом доехать — и наоборот, событие приехать дважды;
  • клиент назвал сумму, отличную от серверной суммы позиций: генератор делает это намеренно у одного-двух заказов из ста.

Как эти же расхождения выглядят на зерне заказа, показывает стендовая витрина dm.purchase_vs_orders_v с ее классами расхождений: это готовый ответ на вопрос «а сколько именно и из-за чего». Запишите свой вывод — в двух-трех фразах, своими словами.

Проверять сходимость этих двух сумм не нужно и нельзя: они и не должны сходиться.

Как проверить себя

Деловой ключ — тройка utm_source, utm_medium, utm_campaign внутри дня. Соберите прямой агрегат из dds.event_v за день, соедините с dm.utm_effectiveness_v полным внешним соединением и посчитайте строки, где ключа нет с одной из сторон или где расходится любая из пяти величин. Ноль — зеленый.

Две проверки покрупнее ловят самые частые ошибки:

  • сумма pageviews, add_to_carts и purchases за день равна числу событий соответствующего типа в dds.event_v за тот же день;
  • сумма declared_revenue за день равна сумме purchase_revenue[1] по всем событиям purchase этого дня.

Обе не сойдутся, если прямой трафик отфильтрован, и разойдутся ровно на его долю — по этому следу отфильтрованные строки и находят. visitors, как и в задании 3, по строкам не складывается: один посетитель приходит по разным меткам.

Приемка

  • пять объектов витрины созданы, донор объявлен через AS и шардируется ключом цели;
  • строки без UTM-меток в витрине остались;
  • declared_revenue хранится в Decimal(18, 2);
  • расхождение с dm.revenue_daily_v объяснено письменно;
  • задача utm_effectiveness в homework_check зеленая;
  • задача витрины добавлена в dm_transform.