# Задание 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: хвост несобранных дней, донор, замена дневных партиций. ## Ключ шардирования Твое решение по [правилу донора](../architecture/storage.md), «Раскладка по шардам». Зерно здесь составное, из трех колонок, и решение «взять хеш от одной из них» надо защищать отдельно: назови, какая доля строк придется на самое частое значение выбранной колонки. ## Конфликт, ради которого витрина и написана Собери за один и тот же день две суммы: `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`.