diff --git a/docs/labs/README.md b/docs/labs/README.md index cf593f1..bc9d870 100644 --- a/docs/labs/README.md +++ b/docs/labs/README.md @@ -124,8 +124,19 @@ DM](../architecture/dm.md). ### Почему выручка не сходится -Открывается после ступени 1. Расхождения между источниками, повторы -покупок и день, после которого отчет можно считать окончательным. +Открывается после ступени 1. Четыре расследования: + +- [Куда пропали покупки](optional/missing-purchases.md): найти заказы без + события в трекере и объяснить разрыв воронки. +- [Почему повтор покупки завышает выручку](optional/duplicate-purchases.md): + посчитать деньги по номеру заказа и объяснить вклад повторов. +- [Какая позиция объясняет разницу в сумме](optional/order-amount-difference.md): + сопоставить корзины и найти причину расхождения до копейки. +- [Почему вчерашняя выручка меняется сегодня](optional/revenue-over-time.md): + проследить опоздания и отмены, затем определить момент закрытия дня. + +Первые три работы используют готовые данные. Четвертая продвигает мир через +Airflow; для нее вы сохраняете состояния заказов после каждого модельного дня. ### Тонкий анализ данных diff --git a/docs/labs/optional/revenue-over-time.md b/docs/labs/optional/revenue-over-time.md new file mode 100644 index 0000000..a11d017 --- /dev/null +++ b/docs/labs/optional/revenue-over-time.md @@ -0,0 +1,131 @@ +# Почему вчерашняя выручка меняется сегодня + +**Чему учит:** объяснять изменения уже рассчитанного отчета поступлением +заказов и сменой их статусов, определять момент закрытия дня и границы +доступной истории. + +Факультатив доступен после ступени 1. Понадобятся Airflow и DBeaver с +подключением `analyst` по [инструкции](../../../README.md#подключиться-из-dbeaver). +Вы будете наблюдать один день оформления заказов, продвигая мир по одному +модельному дню. Новые данные останутся в стенде. + +## Подготовьте первое наблюдение + +Поставьте `world_live` на паузу. Дождитесь завершения начатых прогонов +генерации и обработки, затем запустите `etl_pipeline` и дождитесь успеха. +Так исходное наблюдение будет снято после обновления витрин. + +Узнайте последний день событий и дату последнего полученного слепка: + +```sql +SELECT + (SELECT max(event_date) FROM dds.event_v) AS event_day, + (SELECT max(snapshot_date) FROM ods.order_v) AS snapshot_day; +``` + +День `event_day` обозначим **D**. Наблюдайте именно его до конца работы. +Дата слепка, по которому сделано очередное наблюдение, будет **S**. +Это модельные даты магазина. Календарная дата запуска Airflow день D не задает. + +Слепок снимается к концу суток и доставляется следующим прогоном. Поэтому +покупки последнего сыгранного дня уже видны в событиях, а их заказов в +хранилище еще нет. Проверьте это для D в `dds.order_v` и +`dm.purchase_vs_orders_v`. + +## Подготовьте запросы и сохраняйте результаты + +Напишите запросы для следующих наблюдений: + +| Что сохранить | Откуда взять | +|---|---| +| Дату полученного слепка S | `max(snapshot_date)` из `ods.order_v` | +| Номер, статус и `items_total` каждого заказа дня D | `dds.order_v`, отбор по `order_date` | +| Число заказов в каждом статусе и сумму `items_total` только у `paid` | те же заказы дня D | +| Выручку дня D | сумма `revenue` из `dm.revenue_daily_v`, отбор по `report_date` | +| Номера покупок, еще ожидающих заказ | строки `awaiting_order` из `dm.purchase_vs_orders_v`, отбор по `order_day` | + +Выручку складывайте по всем категориям. В этой витрине она означает стоимость +позиций оплаченных заказов, без скидки и доставки, поэтому должна сходиться +с суммой `items_total` у заказов в статусе `paid`. + +Сохраняйте результаты запросов отдельными файлами, например выгрузками CSV +из DBeaver. Подписывайте D и S. До поступления первого слепка список заказов +пуст, а выручка равна нулю; сохраните и это наблюдение. + +Публичное представление заказа показывает текущее состояние, а сырые слепки +хранятся ограниченное время. Сохраненные выгрузки — история для расследования. + +## Продвигайте мир по одному дню + +Для каждого следующего наблюдения: + +1. Запустите `world_next_day` с `days = 1` и дождитесь успеха. +2. Запустите `etl_pipeline` и дождитесь успеха: `world_next_day` сам витрины + не обновляет. +3. Повторите свои запросы и сохраните новый комплект результатов с датой S. + +Во всех запросах остается прежний день D. За один запуск проигрывайте один +день, чтобы сохранить промежуточные состояния. Пакетный `world_next_day` +отправляет события без пауз; ждать 24 минуты живого дня здесь не нужно. + +Продолжайте до слепка **D + 7** включительно. У заказов дня D окно состоит +из семи дней, от D до D + 6. Слепок D + 6 содержит окончательное состояние, +а еще один шаг позволит проверить, что оно сохранилось за пределами окна. +От исходного наблюдения потребуется восемь однодневных запусков. + +## Объясните изменения отчета + +После каждого шага сравнивайте заказы по `order_id`, а не по положению строк +в выгрузке. Найдите: + +- заказы, отсутствовавшие в первом слепке D, но появившиеся в следующем; +- переходы из `created` в `paid`; +- отмены заказов, для которых раньше был виден статус `paid`. + +Первый слепок, полученный на следующий день, — обычная доставка заказов. +Опоздавшими здесь считаются те, которых не было уже в этом слепке. +Покажите на нескольких номерах, как они исчезают из `awaiting_order`. + +Для двух соседних наблюдений разложите изменение выручки по заказам. +Вклад заказа — его `items_total`, если статус `paid`, и ноль в остальных +случаях, включая отсутствие заказа. Разность вкладов объясняет, сколько +каждый заказ добавил к отчету или убрал из него. Сумма этих изменений должна +совпасть с изменением `revenue` до копейки. + +Запишите даты, между которыми выручка выросла или уменьшилась. Отдельный +отмененный заказ мог убрать деньги, даже если итог дня вырос: покажите это +по его вкладу. Одна общая сумма скрывает встречные изменения. + +## Что известно об отменах + +Возьмите отмененный заказ, у которого в вашей истории был статус `paid`. +Укажите два наблюдения, доказывающих, что он был оплачен, а затем отменен. + +Для отмененного заказа без наблюдаемой оплаты сформулируйте предел вывода. +Дневные слепки могли не застать промежуточное состояние: оплата и отмена +могли произойти между ними. Поэтому отсутствие `paid` в выгрузках не +доказывает, что оплаты не было. Не заменяйте недостающие данные догадкой. + +## Когда день закрыт + +Сравните сохраненные состояния при S = D + 6 и S = D + 7: состав заказов, +их статусы, суммы и выручку. Объясните, почему отчет за D теперь окончательный. +Основание — получен и обработан последний слепок окна изменяемости; +одно совпадение сумм в соседних наблюдениях этого не доказывает. + +После собственных выводов сверьтесь с описанием +[слепка и его доставки](../../architecture/orders/snapshot.md#слепок-и-его-доставка) +и [границ истории отмен](../../architecture/orders/fate.md#судьба-заказа). + +## Что должно получиться + +- [ ] сохранена последовательность наблюдений одного дня D с датами слепков S; +- [ ] найдены опоздавшие заказы и изменения статусов; +- [ ] изменение выручки между двумя наблюдениями объяснено вкладами заказов; +- [ ] оплата одной из последующих отмен подтверждена двумя наблюдениями; +- [ ] вы различаете доказанную оплату и отсутствие сведений о ней; +- [ ] состояние дня за пределами окна совпало с окончательным, а момент + закрытия обоснован правилом доставки слепков. + +После работы оставьте `world_live` на паузе либо верните автоматическое +продвижение, если пользовались им до наблюдения. diff --git a/docs/specs/2026-09-01-labs-program.md b/docs/specs/2026-09-01-labs-program.md index e90985c..2796b44 100644 --- a/docs/specs/2026-09-01-labs-program.md +++ b/docs/specs/2026-09-01-labs-program.md @@ -161,34 +161,21 @@ изменение выручки и история отмен используют одну историю заказов, поэтому для них выбрана общая работа вместо трех отдельных. -- **Куда пропали покупки (#186).** Учащийся сравнивает подтверждения и события - покупки, находит заказы без события и объясняет разрыв воронки конкретными - заказами. -- **Почему повтор покупки завышает выручку (#187).** Движок убирает технический - дубль строки, а повтор одной покупки приходится находить по деловому ключу. - Учащийся сравнивает сумму событий с суммой по уникальным покупкам и - подтверждает, что разница равна вкладу повторов. Другие причины расхождения - с выручкой магазина при этом остаются. -- **Какая позиция объясняет разницу в сумме.** Учащийся сопоставляет корзину - из сырого `ecommerce` с позициями заказа, находит удаленный товар и объясняет - разницу до копейки. Для точных цен нужен исходный JSON: в готовых массивах - событий цены округлены до рублей. -- **Почему вчерашняя выручка меняется сегодня (#188).** Учащийся наблюдает один - день по мере поступления слепков: появляются опоздавшие заказы, меняются - статусы и выручка. По сохраненным состояниям объясняет причины изменений, - определяет момент закрытия дня и отделяет доказанную оплату отмененного - заказа от случаев, в которых историю оплаты восстановить нельзя. +- **[Куда пропали покупки](../labs/optional/missing-purchases.md) (#186).** + Учащийся объясняет разрыв воронки заказами, для которых нет события покупки. +- **[Почему повтор покупки завышает выручку](../labs/optional/duplicate-purchases.md) (#187).** + Разность клиентских сумм объясняется повторными событиями одной покупки. +- **[Какая позиция объясняет разницу в сумме](../labs/optional/order-amount-difference.md).** + Сравнение позиций из исходного JSON и заказа объясняет расхождение до копейки. +- **[Почему вчерашняя выручка меняется сегодня](../labs/optional/revenue-over-time.md) (#188).** + История одного дня связывает опоздания, оплату и отмены с изменением выручки, + позволяет определить момент закрытия дня и границу знания об оплате. -В четвертой лабораторной учащийся сам собирает историю одного дня оформления -заказов. Перед наблюдением он ставит `world_live` на паузу и дожидается -завершения начатых прогонов. Затем продвигает -мир по одному модельному дню через `world_next_day` и после каждого запуска -выполняет `etl_pipeline`. Только после завершения конвейера сохраняет дату -слепка, состояния отдельных заказов и выручку выбранного дня. Эти результаты -позволяют сравнить наблюдения даже после удаления сырых слепков. Готовые -слепки и сочетание готового примера с собственным наблюдением отклонены: -учащийся может получить нужную историю на стенде и сам увидеть, как новые -данные меняют уже рассчитанный отчет. +В четвертой лабораторной выбрано собственное наблюдение: учащийся продвигает +мир по одному дню через Airflow и сохраняет результаты после обновления витрин. +Готовые слепки и сочетание готового примера с собственным наблюдением +отклонены: нужную историю можно получить на стенде и самому увидеть, +как новые данные меняют уже рассчитанный отчет. День считается закрытым после получения и обработки последнего слепка семидневного окна изменяемости; этот слепок доставляется следующим модельным