docs(ingestion): упрощены объяснения источников и приема данных

Зачем: помочь начинающему инженеру проследить событие и версию заказа
от генератора до хранилища, включая ошибки и повторные поступления.

Что: обновлены вводная генератора, справочники приема и модели заказа,
разделы хранилища и комментарии SQL и дага. Сохранены технические условия;
исполняемый код, схемы и поведение стенда не изменены.

Проверка: make lint, git diff --check, 142 локальные ссылки и справка CLI.
SQL без комментариев и Python AST без строк документации совпали
с исходными. Генерация данных и сброс состояния не выполнялись.

Refs #250
This commit is contained in:
ddadmin committed 2026-09-13 16:32:45 -04:00
1 parent 83434102d5
commit 75f28fe99f
13 files changed
+663 -974

No files matched your search

+78 -81
View File
@@ -1,98 +1,95 @@
# Генератор кликстрима
# Генератор событий и заказов
Клиентская сторона стенда: отсюда берётся поток событий — широкое событие по
образцу облачной выгрузки Яндекс Метрики. Устройство и принятые решения —
спека [«Генератор (этап 2)»](../docs/specs/2026-08-01-generator.md).
Генератор создает данные двух источников интернет-магазина. Счетчик сайта
отправляет в топик Kafka `hits` события: просмотр страницы, добавление товара
в корзину и покупку. Серверная часть магазина выгружает в `orders` дневной
слепок — состояние заказов, оформленных за последние семь модельных дней.
Оба источника работают; хранилище принимает события непрерывно, заказы —
по команде Airflow.
События уже есть и уже уезжают: день-функция отдаёт по паре (зерно, D)
упорядоченный поток трёх видов — просмотр страницы, корзина, покупка, —
канонический сериализатор превращает его в JSON, а проигрыватель гонит в файл
или в Kafka. Клиентская сторона на этом целая. Второй источник строится:
день отдаёт и заказы бэкенда — проекцию своих покупок с деньгами магазина, —
а судьба заказа и отправка слепка идут следующими тикетами.
Событие описывает отдельное действие посетителя. Заказ после оформления
может измениться, например перейти в статус `paid` или `cancelled`.
Поэтому один `order_id` встречается в нескольких слепках. Часть заказов
впервые появляется с задержкой в один или два дня; конец семидневного окна
от этого не сдвигается. Правила изменений и задержек — в
[описании судьбы заказа](../docs/architecture/orders/fate.md).
Слепок снимается на конец модельного дня и отправляется на следующий день.
Команда `snapshot --day 1` отправляет слепок дня 0, а `snapshot --day 0`
не отправляет ничего: до начала модели заказов нет. События и слепки
отправляются отдельными запусками генератора.
## Как это работает
Корневое зерно ветвится на подпотоки, из них складываются когорты, аудитория
дня, визиты, строки событий и заказы. План поднимает состав мира по требованию,
а день-функция проживает один день без накопленного состояния. Код этой
цепочки, ее схема и запуск D0 собраны в
[путеводителе](../docs/architecture/generator/README.md).
Зерно генерации задает случайные выборы: какие посетители придут,
какие страницы откроют и что купят. При неизменных коде, каталоге
и настройках одинаковые зерно и номер дня дают одинаковые данные.
День можно рассчитать без генерации всех предыдущих дней.
Сначала рассчитываются посещения и покупки. Из покупки получаются событие
`purchase` со стороны сайта и заказ со стороны магазина. Номер связывает
их: `purchaseID` в событии соответствует `order_id` заказа. Суммы могут
различаться: магазин учитывает скидку, доставку и удаленные позиции.
Деньги генератор считает целыми копейками; в JSON заказа это строки
в рублях с двумя знаками после точки, например `"1289.99"`.
Готовые записи превращаются в JSON и отправляются в файл или Kafka:
одна запись занимает одну строку файла или одно сообщение. Событие содержит
47 полей по [описанию выгрузки](../docs/formats/clickstream-event.md),
запись заказа — поля [формата слепка](../docs/architecture/orders/snapshot.md#запись-на-проводе).
Подробное устройство генератора разобрано в
[техническом справочнике](../docs/architecture/generator/README.md).
## Что где лежит
- `src/clickstream_generator/world.py` — конфигурация мира: все его числа
одним местом. Правка любого — смена мира; крутить их и предлагается.
- `src/clickstream_generator/seeds.py` — иерархия зёрен: кто из какого
подпотока берёт случайность. На ней держится весь детерминизм.
- `src/clickstream_generator/plan.py` — план состава: кто есть в мире в
день D. Когорты, приток, двухкуковые пары, паспорта кук, человек за кукой
и счётчики — до генерации событий.
- `src/clickstream_generator/weights.py` — выбор по целым весам: один приём
на весь генератор, чтобы дисциплина целочисленной случайности не жила
копиями.
- `src/clickstream_generator/reference.py` — справочники: устройства,
города, источники трафика, карта сайта. Таблицы-литералы: доля живёт в
строке, которой принадлежит.
- `src/clickstream_generator/catalog.py` — каталог товаров из
`data/catalog/products.csv`, общего у генератора и словаря ClickHouse.
- `src/clickstream_generator/day.py` — день-функция: визиты, страницы,
атрибуция, устройство и гео. Там же правила резки визитов и шов, на
который садятся торговые события; прожитый день отдаёт обе половины —
поток событий и заказы.
- `src/clickstream_generator/commerce.py` — торговые события: что легло в
корзину, что из этого куплено, деньги, номера заказов и сырой `ecommerce`.
Своя случайность, поэтому правка торговли трафик не двигает.
- `src/clickstream_generator/orders.py` — заказы бэкенда: вторая проекция
покупки. Личность покупателя и деньги магазина — скидка по промокоду,
доставка, итог; своя случайность, ветвящаяся по дню рождения заказа.
- `src/clickstream_generator/ids.py` — номера событий: неповторяющиеся и
ниже 2^53. Обещание одно на обе половины дня, поэтому и живёт отдельно.
- `src/clickstream_generator/serialize.py` — канонический сериализатор:
единственное место, где событие целиком превращается в JSON. Порядок ключей,
все 47 колонок всегда и форма на проводе — ISO-8601.
- `src/clickstream_generator/sinks.py` — приёмники: файл (одно событие — одна
строка) и Kafka (одно событие — одно сообщение). Про содержимое они не
знают; там же довод, почему у сообщения нет ключа.
- `src/clickstream_generator/player.py` — проигрыватель: гонит дни в приёмник
пачкой или с темпом живого дня. Состояния не хранит.
- `src/clickstream_generator/cli.py` — интерфейс запуска: параметры
аргументами или окружением, логи в стандартный вывод, итог кодом возврата.
- `src/clickstream_generator/schema.py` — контракт схемы: чистые данные о
колонках выгрузки. Собственность генератора; из него выводятся сам
генератор, его валидация и описание выгрузки в доках.
- `src/clickstream_generator/schema_doc.py` — сборка «описания выгрузки»
([`docs/formats/clickstream-event.md`](../docs/formats/clickstream-event.md))
из контракта. Документ руками не правят — пересобирают.
- `src/clickstream_generator/inventory.py` — сборка паспорта мира
([`data/world-inventory.json`](../data/world-inventory.json)): шапка паспорта
и хеши восьми дней, которыми наполняется стенд, плюс счётчики классов
расхождений по заказам для дней с закрытым окном. Руками не правят —
пересобирают целью `make inventory`.
- `tests/` — инварианты контракта, свежесть описания и обещания мира:
чистота от зерна, приток, гарантия двухкуковых пар, форма суточной волны
и сборка визитов по задокументированным правилам. Там же побайтовое
обещание, доведённое до диска: два прогона дня в файл дают тот же файл, а
строк в нём ровно столько, сколько событий.
Все перечисленные модули находятся в `src/clickstream_generator/`.
| Модули | Назначение |
|---|---|
| `world.py`, `seeds.py`, `plan.py` | Настройки мира, случайные выборы и план посещений |
| `reference.py`, `catalog.py` | Устройства, города, источники трафика и каталог товаров |
| `day.py`, `commerce.py` | Визиты, события сайта и покупки |
| `orders.py` | Состояние заказов на дату слепка, изменения и задержки |
| `serialize.py`, `player.py`, `sinks.py` | Запись JSON и отправка в файл или Kafka |
| `cli.py` | Команды и параметры запуска |
| `schema.py`, `schema_doc.py` | Описания колонок события и сборка документа выгрузки |
| `inventory.py` | Сборка паспорта мира с контрольными суммами и счетчиками |
Тесты в `tests/` проверяют формат, правила модели и повторяемость данных.
Описание выгрузки и [паспорт мира](../data/world-inventory.json) собираются
из кода; вручную их не редактируют.
## Команды
Проиграть день — раздел [«Как запустить
генератор»](../docs/architecture/world.md#как-запустить-генератор)
справочника мира и `--help`.
Для запуска из каталога `generator/` используйте
`uv run python -m clickstream_generator <команда>`. Справку можно открыть
через `--help`, в том числе после имени команды.
Из этого каталога — цели генератора живут здесь, а не в корне:
| Команда | Что отправляет | Особые параметры |
|---|---|---|
| `batch` | События дня без пауз | `--limit N` ограничивает число событий за весь запуск |
| `live` | События постепенно, с ускорением | `--speed X`, по умолчанию 60: сутки занимают около 24 минут |
| `snapshot` | Слепок заказов предыдущего дня | `--day D` задает день отправки |
Для всех команд нужны номер дня `--day` и приемник: `--file` либо
`--brokers` вместе с `--topic`. Общие параметры `--seed` и `--days`
задают зерно и число последовательных дней. Параметры можно передать
и через окружение; имена и примеры собраны в
[справочнике запуска](../docs/architecture/world.md#как-запустить-генератор).
Прямой запуск генератора не продвигает сохраненную позицию времени стенда —
ей управляют [даги модельного времени](../docs/handbook/airflow-dags.md#даги-мира).
Из каталога `generator/` доступны:
- `make test` — тесты генератора;
- `make lint` — ruff: проверка и формат;
- `make typecheck` — ty: проверка типов;
- `make demo` — пройти стадии канонического D0 на одном экране;
- `make lint` — проверка кода и форматирования через ruff;
- `make typecheck` — проверка типов через ty;
- `make demo` — показать стадии расчета первого дня, D0;
- `make docs` — пересобрать описание выгрузки;
- `make inventory` — пересобрать паспорт мира.
В корне репозитория `make lint` тоже есть, но он про код стенда — даги и
Superset. Одноимённые цели за разными дверями охватывают разное:
[карта проверок](../docs/architecture/testing.md).
Python и зависимости — через `uv`, версии закреплены в `uv.lock`: на этом
держится обещание побайтовой воспроизводимости (спека, раздел 2).
В корне `make lint` проверяет даги, код Superset и сборку карты архитектуры.
Корневые `make generate-batch` и `make generate-live` запускают события
через контейнер генератора. Состав проверок — в
[карте проверок](../docs/architecture/testing.md).
Python и зависимости запускаются через `uv`; версии закреплены в `uv.lock`.