docs(labs): добавлены три расследования расхождений выручки
Зачем: дать практику сверки событий покупки с заказами магазина. Что: задания о потерях событий, повторах покупок и расхождении состава корзин. Проверка: запросы под analyst на ClickHouse 26.3.17.56; счетчики, суммы и точный разбор JSON сошлись. make lint и независимая техническая проверка пройдены.
This commit is contained in:
1 parent
ddb186f841
commit
2502277af1
3 files changed
+214
No files matched your search
@@ -0,0 +1,65 @@
|
|||||||
|
# Почему повтор покупки завышает выручку
|
||||||
|
|
||||||
|
**Чему учит:** отличать повторную доставку строки от повторного события об
|
||||||
|
одной покупке и выбирать ключ, по которому считать деньги.
|
||||||
|
|
||||||
|
Факультатив доступен после ступени 1. Работайте в DBeaver пользователем
|
||||||
|
`analyst` по [инструкции](../../../README.md#подключиться-из-dbeaver).
|
||||||
|
Дождитесь завершения `etl_pipeline`; оставьте `world_live` на паузе и
|
||||||
|
дождитесь уже начатых прогонов, чтобы данные не менялись между запросами.
|
||||||
|
|
||||||
|
## Посчитайте клиентскую сумму двумя способами
|
||||||
|
|
||||||
|
Возьмите один закрытый день: его дата должна быть не позднее
|
||||||
|
`max(snapshot_date) - 6` из `ods.order_v`. Во всех запросах к `dds.event_v`
|
||||||
|
отбирайте этот `event_date` и `event_type = 'purchase'`.
|
||||||
|
|
||||||
|
Номер заказа находится в `purchase_id[1]`, заявленная сумма — в
|
||||||
|
`purchase_revenue[1]`. По формату источника у события покупки один номер заказа.
|
||||||
|
|
||||||
|
Сначала посчитайте число событий и сумму всех заявленных сумм. Затем найдите
|
||||||
|
номера заказов, которые встречаются несколько раз. Если таких нет, выберите
|
||||||
|
другой закрытый день.
|
||||||
|
|
||||||
|
Для двух повторившихся покупок выпишите `event_id`, `client_id`, `event_time`,
|
||||||
|
номер заказа и сумму. Сопоставьте число событий и заказов в `dds.order_v`.
|
||||||
|
|
||||||
|
Теперь рассчитайте клиентскую сумму, учитывая каждый номер заказа один раз.
|
||||||
|
До выбора одной суммы на заказ проверьте, что во всех его событиях она
|
||||||
|
одинакова. Объясните, почему удалять одинаковые денежные значения вместо
|
||||||
|
повторных номеров заказов было бы ошибкой.
|
||||||
|
|
||||||
|
## Объясните разницу
|
||||||
|
|
||||||
|
Для каждого повторившегося заказа определите число лишних событий и их
|
||||||
|
вклад в завышение. Сложите эти вклады и сверьте с разностью двух расчетов.
|
||||||
|
Равенство должно выполняться до копейки.
|
||||||
|
|
||||||
|
Слой ODS убирает повторную доставку одного события по его идентификатору;
|
||||||
|
`dds.event_v` читает уже это состояние. Объясните по своим строкам, почему
|
||||||
|
повтор покупки сохранился. Какие поля совпадают и какие различаются?
|
||||||
|
Какой ключ отвечает на вопрос «это то же событие», а какой — «это тот же заказ»?
|
||||||
|
|
||||||
|
После собственного расчета найдите эти заказы в `dm.purchase_vs_orders_v`.
|
||||||
|
Если у повторившегося заказа класс отличается от `duplicate_event`, проверьте
|
||||||
|
его статус и [порядок присвоения классов](../../architecture/dm.md#dmpurchase_vs_orders_v).
|
||||||
|
Класс витрины не заменяет поиск всех повторов в событиях.
|
||||||
|
|
||||||
|
## Сравните с выручкой магазина
|
||||||
|
|
||||||
|
Сложите `revenue` из `dm.revenue_daily_v` за тот же `report_date` по всем
|
||||||
|
категориям. Эта витрина считает стоимость позиций оплаченных заказов,
|
||||||
|
без скидки и доставки.
|
||||||
|
|
||||||
|
Сравните результат с исправленной клиентской суммой. Совпадение не требуется:
|
||||||
|
удаление повторов устраняет одну причину расхождения. Найдите хотя бы один
|
||||||
|
заказ, который объясняет, почему деньги трекера и магазина все еще могут
|
||||||
|
различаться. Для этого сопоставьте события, заказ и готовую сверку.
|
||||||
|
|
||||||
|
## Что должно получиться
|
||||||
|
|
||||||
|
- [ ] сохранены две клиентские суммы и строки повторившихся покупок;
|
||||||
|
- [ ] разность сумм полностью объяснена вкладом лишних событий;
|
||||||
|
- [ ] выбор ключа для устранения повторов подтвержден данными;
|
||||||
|
- [ ] вы можете объяснить, почему устранение повторов не завершает сверку
|
||||||
|
выручки с магазином.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# Куда пропали покупки
|
||||||
|
|
||||||
|
**Чему учит:** обнаружить потерю событий при сверке двух источников и
|
||||||
|
обосновать, каким данным можно доверять при подсчете покупок.
|
||||||
|
|
||||||
|
Факультатив доступен после ступени 1. В DBeaver подключитесь пользователем
|
||||||
|
`analyst` по [инструкции](../../../README.md#подключиться-из-dbeaver).
|
||||||
|
Дождитесь завершения `etl_pipeline`; на время расследования оставьте
|
||||||
|
`world_live` на паузе и дождитесь уже начатых прогонов.
|
||||||
|
|
||||||
|
## Выберите день
|
||||||
|
|
||||||
|
Магазин зарегистрировал заказы, но часть событий покупки не дошла до трекера.
|
||||||
|
Найдите след этой потери сначала в событиях сайта, затем в заказах.
|
||||||
|
|
||||||
|
Возьмите день, для которого уже получен последний слепок семидневного окна.
|
||||||
|
Запрос показывает самую позднюю подходящую дату:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT max(snapshot_date) - 6 AS last_closed_day
|
||||||
|
FROM ods.order_v;
|
||||||
|
```
|
||||||
|
|
||||||
|
Используйте выбранный день во всех запросах. События отбирайте по
|
||||||
|
`dds.event_v.event_date`, заказы — по `dds.order_v.order_date`.
|
||||||
|
Сравнивайте все заказы дня, включая отмененные: сейчас вопрос в том,
|
||||||
|
зарегистрировал ли магазин заказ, а не в том, был ли он оплачен.
|
||||||
|
|
||||||
|
## Найдите разрыв в событиях
|
||||||
|
|
||||||
|
Посчитайте просмотры страницы подтверждения и события покупки:
|
||||||
|
|
||||||
|
- у просмотра `event_type = 'pageview'`, а `url` заканчивается на `/confirmation`;
|
||||||
|
- у покупки `event_type = 'purchase'`.
|
||||||
|
|
||||||
|
Запишите оба числа и разность. Какую потерю она позволяет заподозрить?
|
||||||
|
Достаточно ли этих двух чисел, чтобы назвать пропавшие покупки?
|
||||||
|
|
||||||
|
## Сверьте с магазином
|
||||||
|
|
||||||
|
В событии номер заказа находится в `purchase_id[1]`, в заказах — в `order_id`.
|
||||||
|
Соберите список заказов выбранного дня, для которых нет события покупки.
|
||||||
|
Несколько событий с одним номером заказа должны оставаться одной покупкой.
|
||||||
|
|
||||||
|
Проверьте два равенства:
|
||||||
|
|
||||||
|
- число заказов магазина равно числу разных заказов из событий плюс число
|
||||||
|
заказов без события;
|
||||||
|
- разность просмотров подтверждения и событий покупки равна числу заказов
|
||||||
|
без события.
|
||||||
|
|
||||||
|
Первое равенство проверяет полноту сопоставления источников. Второе относится
|
||||||
|
к устройству этого стенда. Найдите покупку с повторным событием и посмотрите
|
||||||
|
соседние события того же `client_id` по времени: что произошло со страницей
|
||||||
|
подтверждения? Объясните, почему повтор не скрыл разрыв воронки.
|
||||||
|
|
||||||
|
Если в выбранном дне повторов нет, возьмите другой закрытый день.
|
||||||
|
Для оценки доли потерь используйте число заказов магазина и объясните,
|
||||||
|
почему число просмотров подтверждения может дать другую долю.
|
||||||
|
|
||||||
|
## Сопоставьте вывод с готовой сверкой
|
||||||
|
|
||||||
|
Найдите свои заказы без события в `dm.purchase_vs_orders_v` по `order_id`.
|
||||||
|
Сравните их `status` и `mismatch_class`. Витрина присваивает заказу один класс,
|
||||||
|
хотя причин расхождения может быть несколько. По
|
||||||
|
[правилу классификации](../../architecture/dm.md#dmpurchase_vs_orders_v)
|
||||||
|
объясните, почему потеря события не всегда попадает в `lost_event`.
|
||||||
|
|
||||||
|
Возьмите один заказ из своего списка и сохраните два запроса: первый находит
|
||||||
|
его в магазине, второй показывает отсутствие покупки в трекере. Сформулируйте,
|
||||||
|
какой источник нужен для отчета о поведении посетителей, а какой — для денег.
|
||||||
|
|
||||||
|
## Что должно получиться
|
||||||
|
|
||||||
|
- [ ] сохранены день, счетчики воронки и список заказов без события;
|
||||||
|
- [ ] оба равенства сходятся, влияние повторного просмотра объяснено;
|
||||||
|
- [ ] доля потерь посчитана с обоснованным знаменателем;
|
||||||
|
- [ ] один случай подтвержден запросами к обоим источникам;
|
||||||
|
- [ ] вы различаете отсутствие события и класс строки в готовой сверке.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# Какая позиция объясняет разницу в сумме
|
||||||
|
|
||||||
|
**Чему учит:** перейти от расхождения суммы заказа к конкретному товару и
|
||||||
|
проверить объяснение с точностью до копейки.
|
||||||
|
|
||||||
|
Факультатив доступен после ступени 1. В DBeaver используйте пользователя
|
||||||
|
`analyst` из [инструкции подключения](../../../README.md#подключиться-из-dbeaver).
|
||||||
|
Дождитесь завершения `etl_pipeline`; оставьте `world_live` на паузе и
|
||||||
|
дождитесь уже начатых прогонов.
|
||||||
|
|
||||||
|
## Найдите заказ
|
||||||
|
|
||||||
|
В `dm.purchase_vs_orders_v` выберите один заказ с
|
||||||
|
`mismatch_class = 'amount_delta'`. Возьмите закрытый день: `order_day`
|
||||||
|
не позднее `max(snapshot_date) - 6` из `ods.order_v`. Сохраните `order_id`,
|
||||||
|
`order_day`, `declared_revenue` и `items_total`.
|
||||||
|
|
||||||
|
Найдите этот заказ в `dds.order_v`. Сопоставьте `items_total`, `discount`,
|
||||||
|
`delivery` и `total`. Как связаны эти суммы? Объясните, почему клиентскую
|
||||||
|
`declared_revenue` нужно сравнивать с `items_total`, чтобы искать разницу
|
||||||
|
именно в составе корзины.
|
||||||
|
|
||||||
|
## Разберите позиции с двух сторон
|
||||||
|
|
||||||
|
Найдите событие покупки в `ods.event_v`: `purchaseID[1]` совпадает с номером
|
||||||
|
заказа, `EventDate` — с выбранным днем, `EventType = 'purchase'`.
|
||||||
|
Откройте поле `ecommerce`. Внутри JSON массив `purchase.products` содержит
|
||||||
|
позиции клиента с полями `id`, `price` и `quantity`.
|
||||||
|
|
||||||
|
Разверните этот массив в строки и получите артикул, точную цену, количество
|
||||||
|
и стоимость каждой позиции. Для разбора пригодятся `JSONExtractArrayRaw`
|
||||||
|
и `JSONExtract`, для разворачивания массива — `ARRAY JOIN`.
|
||||||
|
Деньги извлекайте как `Decimal` с двумя знаками после запятой.
|
||||||
|
|
||||||
|
С другой стороны используйте массивы `item_sku`, `item_price` и
|
||||||
|
`item_quantity` заказа в `dds.order_v`. Элементы с одним индексом относятся
|
||||||
|
к одной позиции; при разворачивании сохраняйте эту связь.
|
||||||
|
|
||||||
|
Сопоставьте позиции по артикулу так, чтобы остались видны товары,
|
||||||
|
присутствующие только с одной стороны. Покажите, какие позиции совпали,
|
||||||
|
а какая объясняет разницу. В результате нужны строки товаров, а не только
|
||||||
|
две итоговые суммы.
|
||||||
|
|
||||||
|
## Проверьте объяснение
|
||||||
|
|
||||||
|
Подтвердите три равенства:
|
||||||
|
|
||||||
|
- сумма стоимостей клиентских позиций равна `declared_revenue`;
|
||||||
|
- сумма стоимостей позиций магазина равна `items_total`;
|
||||||
|
- стоимость отсутствующей у магазина позиции равна
|
||||||
|
`declared_revenue - items_total`.
|
||||||
|
|
||||||
|
Затем сравните точные цены из JSON с готовым массивом `productPrice`
|
||||||
|
события. По [формату выгрузки](../../formats/clickstream-event.md) цены в нем
|
||||||
|
округлены до целых рублей. Покажите на своей корзине, что потеряется при
|
||||||
|
расчете по этому массиву. Если все цены выбранной корзины целые, возьмите
|
||||||
|
еще один заказ с расхождением и дробной ценой.
|
||||||
|
|
||||||
|
В коротком объяснении назовите заказ, товар, количество, цену и разницу.
|
||||||
|
Отделите то, что подтверждают данные, от причины, которую по ним установить
|
||||||
|
нельзя: отсутствие позиции в заказе видно, а причину ее удаления источник
|
||||||
|
отдельным полем не сообщает.
|
||||||
|
|
||||||
|
## Что должно получиться
|
||||||
|
|
||||||
|
- [ ] позиции клиента и магазина сопоставлены по артикулам;
|
||||||
|
- [ ] все три равенства сходятся до копейки;
|
||||||
|
- [ ] найден пример потери точности в готовом массиве цен;
|
||||||
|
- [ ] расхождение объяснено конкретной позицией, а скидка и доставка
|
||||||
|
отделены от сравнения корзин.
|
||||||
Reference in new issue
Block a user