docs(orders): уточнён формат слепка и временные поля

- Зачем:
  - аудит источника нельзя смешивать с бизнес-временем и `_load_ts` ClickHouse.
- Что:
  - зафиксированы JSON-контракт слепка, порядок ключей и намеренное различие `Array(Float64)` и `Decimal`.
  - разведены бизнес-время, аудит источника и загрузка; уточнены слой ODS, `snapshot_date` и технический ключ партиции.
  - добавлены термины словаря и исследование точного миллисекундного формата с проверками ClickHouse.
- Проверка:
  - выполнен `git diff --cached --check`.
This commit is contained in:
2026-08-16 17:56:46 +03:00
parent c17c5ef995
commit 8d54a3caba
4 changed files with 271 additions and 24 deletions
+25 -7
View File
@@ -190,17 +190,34 @@ Ecommerce (заполнены только у торговых событий):
выручка дня D «дышит» K дней, потом замерзает. Боевой аналог окна есть и у
трекеров: лог Метрики «доформировывается» ещё около трёх дней.
- Запись слепка — состояние заказа на момент выгрузки, «родной» экспорт
бэкенда в snake_case:
бэкенда в snake_case. Таблица задаёт тип после разбора в ODS:
| Поле | Тип | Комментарий |
| Поле | Тип в ODS | Комментарий |
|---|---|---|
| `order_id` | String | номер заказа; равен клиентскому `purchaseID` |
| `user_id` | UInt64 | пользователь магазина — мост к склейке |
| `status` | String | `created``paid``cancelled` |
| `created_at`, `updated_at` | DateTime | |
| `created_at`, `updated_at` | DateTime64(3, 'UTC') | аудит строки в БД источника: создание и последнее изменение; не бизнес-время покупки и не время загрузки в ClickHouse |
| `items_total`, `discount`, `delivery`, `total` | Decimal(18,2) | деньги бэкенда — в Decimal |
| `items` | String | позиции вложенным JSON: `[{sku, qty, price}]` |
| `snapshot_date` | Date | дата слепка (день выгрузки) |
| `items` | String | сырой текст массива позиций `[{sku, qty, price}]`, извлечённый из внешнего JSON |
| `snapshot_date` | Date | завершившийся модельный день, состояние которого снято на исходящей границе суток |
На проводе один заказ — один документ JSON. Деньги, включая `items[].price`,
передаются строками с ровно двумя знаками после точки; `created_at` и
`updated_at` — строками RFC 3339 в UTC с обязательными миллисекундами
(`YYYY-MM-DDTHH:mm:ss.SSSZ`); `snapshot_date` — строкой `YYYY-MM-DD`;
`items` — обычным массивом JSON, не строкой с JSON внутри. Порядок внешних
ключей совпадает с порядком полей в таблице контракта, у позиции — `sku`,
`qty`, `price`: так байты воспроизводимы без сортировки ключей. Разница с
кликстримом намеренна: там значения `purchaseRevenue` приезжают JSON-числами
и разбираются как `Array(Float64)`, а бэкенд передаёт деньги строками для
точного `Decimal`. Это показывает расхождение представлений денег в двух
источниках.
Запись собирает явная функция существующего канонического сериализатора:
один словарь с вложенным списком и один `orjson.dumps`, без универсального
слоя кодеков.
Обоснование и проверка разбора — в
[исследовании формата](../research/2026-08-16-order-snapshot-wire-format.md).
- Приём идемпотентный, но дедуп расщеплён на два слоя:
- `ods.order_snapshot` — партиция по `snapshot_date`, **без дедупа**,
@@ -208,7 +225,8 @@ Ecommerce (заполнены только у торговых событий):
партиции дня слепка, а не ReplacingMergeTree.
- Дедуп до последней версии — **argMax** в трансформации при сборке
`dds.order`. `dds.order` — единственная дедуплицированная таблица:
партиция по дню заказа (`toDate(created_at)`),
партиция по дню создания строки источника (`toDate(created_at)`) — это
стабильный технический ключ, а не бизнес-день покупки,
ReplacingMergeTree(`updated_at`), `ORDER BY order_id` — заказ всегда
лежит в одной партиции, дедуп работает.
@@ -394,7 +412,7 @@ README.
| ODS | `ods.order_snapshot` (+`_errors`) | слепки заказов как приехали, партиция по `snapshot_date`, без дедупа |
| DDS | `dds.session` | сборка сессий из событий (наследник `dds.click`) |
| DDS | `dds.event_v` | представление над `ods.event`: snake_case-имена, расшифровка кодов `DeviceCategory`; витрины DM читают его, а не ODS напрямую |
| DDS | `dds.order` | единственная дедуплицированная таблица заказа: партиция по дню заказа (`toDate(created_at)`), ReplacingMergeTree(`updated_at`), `ORDER BY order_id`, дедуп до последней версии — argMax в трансформации при сборке |
| DDS | `dds.order` | единственная дедуплицированная таблица заказа: партиция по техническому дню создания строки источника (`toDate(created_at)`), ReplacingMergeTree(`updated_at`), `ORDER BY order_id`, дедуп до последней версии — argMax в трансформации при сборке |
| DDS | `dds.identity_map` | карта кука↔пользователь |
| DDS | словарь `products` | каталог из CSV |
| DM | витрины `dm.*_v`, `dm.dq_summary` | см. ниже |