- Зачем:
- README и путеводитель обращаются к читателю на «вы», а ТЗ в docs/labs/
были на «ты»; после пересборки индекса смесь стала заметна.
- Что:
- во всех восьми файлах docs/labs/ местоимения, глаголы и повелительное
наклонение переведены во второе лицо множественного числа.
- названия ступеней в таблице README оставлены как в спеке.
- Проверка:
- grep по формам «ты/твой», 'ё' и 'менти' по docs/labs/ молчит;
git diff --check чистый; ревью диффа свежей сессией.
151 lines
11 KiB
Markdown
151 lines
11 KiB
Markdown
# Задание 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`.
|