refactor(generator): нормализованное имя вместо имени в DDS, тесты на имена сняты

- Зачем:
  - контракт вёл себя как хозяин чужого слоя: поле называлось dds_name, в
    описании стоял столбец «Имя в DDS», а два теста прибивали имена
    гвоздями. Спека же задала вид имени (snake_case), а не список: имена
    атрибутов складывает модель данных DDS, и решать это не трекеру.
- Что:
  - поле контракта и столбец описания стали нормализованным именем: имя
    источника в нашем стиле. В описании и в докстринге сказано прямо, что
    слой DDS называет атрибуты по своей модели.
  - сняты оба теста на имена — копия имён DDS и конспект состава по
    мастер-спеке. Они не проверяли верность имени, только неизменность, а
    неизменность и так сторожит пересборка описания: молчаливой правки
    контракта не бывает, она всплывает диффом документа.
  - остались проверки формы: 47 колонок, уникальность, стили имён,
    заполненность, согласие типов numpy и ClickHouse, порядок групп.
  - спека генератора (раздел 3) и CONTEXT.md согласованы тем же
    коммитом: уточнение внесено как расхождение, найденное при исполнении.
- Проверка:
  - make test (248 тестов), make lint, make config-test;
  - make docs, затем git diff --exit-code docs/ — пусто.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 23:01:04 +03:00
co-authored by Claude Opus 5
parent aabd339a26
commit 24c8dd9b98
7 changed files with 95 additions and 199 deletions
+53 -51
View File
@@ -20,10 +20,12 @@
- `numpy_dtype` — чем колонка представлена внутри генератора; у массивов это
тип элемента. Строки живут в `object`-массивах: numpy-строки фиксированной
длины стенду ничего не дают.
- `dds_name` — наше snake_case-имя, под которым колонка появится в DDS.
Перевод механический, акроним идёт одним куском (`utm_source`, `has_gclid`,
- `normalized_name` — то же имя в нашем стиле: snake_case, механический
перевод имени источника, акроним одним куском (`utm_source`, `has_gclid`,
`ip_address`); единственное исключение — `client_timezone`: «timezone»
пишем одним словом.
пишем одним словом. Это имя источника, приведённое к нашему стилю, а не
имя атрибута в модели данных: слой DDS складывает свою модель и называет
атрибуты по ней — нормализованное имя ему отправная точка, не обязанность.
- `group` — раздел описания выгрузки; колонки одной группы идут подряд.
- `comment` — строка описания для менти, попадает в документ как есть.
@@ -54,7 +56,7 @@ class Column:
name: str
clickhouse_type: str
numpy_dtype: str
dds_name: str
normalized_name: str
group: ColumnGroup
comment: str
@@ -64,7 +66,7 @@ COLUMNS: tuple[Column, ...] = (
name="WatchID",
clickhouse_type="UInt64",
numpy_dtype="uint64",
dds_name="watch_id",
normalized_name="watch_id",
group=ColumnGroup.IDENTIFIERS,
comment="id события — хита; держится ниже 2^53, выше числа в JSON округляются",
),
@@ -72,7 +74,7 @@ COLUMNS: tuple[Column, ...] = (
name="VisitID",
clickhouse_type="UInt64",
numpy_dtype="uint64",
dds_name="visit_id",
normalized_name="visit_id",
group=ColumnGroup.IDENTIFIERS,
comment="id визита от генератора — эталон лабы: собери сессии сам и сравни",
),
@@ -80,7 +82,7 @@ COLUMNS: tuple[Column, ...] = (
name="ClientID",
clickhouse_type="UInt64",
numpy_dtype="uint64",
dds_name="client_id",
normalized_name="client_id",
group=ColumnGroup.IDENTIFIERS,
comment="анонимный id браузера — кука; по хешу от неё таблица шардируется",
),
@@ -88,7 +90,7 @@ COLUMNS: tuple[Column, ...] = (
name="CounterID",
clickhouse_type="UInt32",
numpy_dtype="uint32",
dds_name="counter_id",
normalized_name="counter_id",
group=ColumnGroup.IDENTIFIERS,
comment="id счётчика: на стенде константа, сайт один",
),
@@ -96,7 +98,7 @@ COLUMNS: tuple[Column, ...] = (
name="EventDate",
clickhouse_type="Date",
numpy_dtype="datetime64[D]",
dds_name="event_date",
normalized_name="event_date",
group=ColumnGroup.IDENTIFIERS,
comment="дата события; по ней режется партиция",
),
@@ -104,7 +106,7 @@ COLUMNS: tuple[Column, ...] = (
name="UTCEventTime",
clickhouse_type="DateTime",
numpy_dtype="datetime64[s]",
dds_name="utc_event_time",
normalized_name="utc_event_time",
group=ColumnGroup.IDENTIFIERS,
comment="время события в UTC — единственная метка времени, как у Метрики",
),
@@ -112,7 +114,7 @@ COLUMNS: tuple[Column, ...] = (
name="ClientTimeZone",
clickhouse_type="Int16",
numpy_dtype="int16",
dds_name="client_timezone",
normalized_name="client_timezone",
group=ColumnGroup.IDENTIFIERS,
comment="смещение часового пояса клиента от UTC, в минутах",
),
@@ -120,7 +122,7 @@ COLUMNS: tuple[Column, ...] = (
name="EventType",
clickhouse_type="LowCardinality(String)",
numpy_dtype="object",
dds_name="event_type",
normalized_name="event_type",
group=ColumnGroup.IDENTIFIERS,
comment="тип события: pageview, add_to_cart, purchase — добавка стенда,"
" у Метрики такого поля нет",
@@ -129,7 +131,7 @@ COLUMNS: tuple[Column, ...] = (
name="Sign",
clickhouse_type="Int8",
numpy_dtype="int8",
dds_name="sign",
normalized_name="sign",
group=ColumnGroup.IDENTIFIERS,
comment="всегда 1: колонка формата, исправлений записей генератор не шлёт",
),
@@ -137,7 +139,7 @@ COLUMNS: tuple[Column, ...] = (
name="URL",
clickhouse_type="String",
numpy_dtype="object",
dds_name="url",
normalized_name="url",
group=ColumnGroup.PAGE,
comment="адрес страницы события",
),
@@ -145,7 +147,7 @@ COLUMNS: tuple[Column, ...] = (
name="Referer",
clickhouse_type="String",
numpy_dtype="object",
dds_name="referer",
normalized_name="referer",
group=ColumnGroup.PAGE,
comment="адрес, с которого посетитель пришёл на страницу",
),
@@ -153,7 +155,7 @@ COLUMNS: tuple[Column, ...] = (
name="Title",
clickhouse_type="String",
numpy_dtype="object",
dds_name="title",
normalized_name="title",
group=ColumnGroup.PAGE,
comment="заголовок страницы",
),
@@ -161,7 +163,7 @@ COLUMNS: tuple[Column, ...] = (
name="UTMSource",
clickhouse_type="String",
numpy_dtype="object",
dds_name="utm_source",
normalized_name="utm_source",
group=ColumnGroup.PAGE,
comment="метка utm_source: площадка перехода",
),
@@ -169,7 +171,7 @@ COLUMNS: tuple[Column, ...] = (
name="UTMMedium",
clickhouse_type="String",
numpy_dtype="object",
dds_name="utm_medium",
normalized_name="utm_medium",
group=ColumnGroup.PAGE,
comment="метка utm_medium: тип трафика",
),
@@ -177,7 +179,7 @@ COLUMNS: tuple[Column, ...] = (
name="UTMCampaign",
clickhouse_type="String",
numpy_dtype="object",
dds_name="utm_campaign",
normalized_name="utm_campaign",
group=ColumnGroup.PAGE,
comment="метка utm_campaign: рекламная кампания",
),
@@ -185,7 +187,7 @@ COLUMNS: tuple[Column, ...] = (
name="UTMContent",
clickhouse_type="String",
numpy_dtype="object",
dds_name="utm_content",
normalized_name="utm_content",
group=ColumnGroup.PAGE,
comment="метка utm_content: что различает объявления одной кампании",
),
@@ -193,7 +195,7 @@ COLUMNS: tuple[Column, ...] = (
name="UTMTerm",
clickhouse_type="String",
numpy_dtype="object",
dds_name="utm_term",
normalized_name="utm_term",
group=ColumnGroup.PAGE,
comment="метка utm_term: ключевое слово перехода",
),
@@ -201,7 +203,7 @@ COLUMNS: tuple[Column, ...] = (
name="LastTrafficSource",
clickhouse_type="String",
numpy_dtype="object",
dds_name="last_traffic_source",
normalized_name="last_traffic_source",
group=ColumnGroup.PAGE,
comment="последний источник трафика: organic, direct, ad и подобные",
),
@@ -209,7 +211,7 @@ COLUMNS: tuple[Column, ...] = (
name="HasGCLID",
clickhouse_type="UInt8",
numpy_dtype="uint8",
dds_name="has_gclid",
normalized_name="has_gclid",
group=ColumnGroup.PAGE,
comment="1, если в адресе была метка Google Ads",
),
@@ -217,7 +219,7 @@ COLUMNS: tuple[Column, ...] = (
name="YCLID",
clickhouse_type="UInt64",
numpy_dtype="uint64",
dds_name="yclid",
normalized_name="yclid",
group=ColumnGroup.PAGE,
comment="id клика Яндекс Директа; без метки — 0",
),
@@ -225,7 +227,7 @@ COLUMNS: tuple[Column, ...] = (
name="Browser",
clickhouse_type="String",
numpy_dtype="object",
dds_name="browser",
normalized_name="browser",
group=ColumnGroup.CLIENT,
comment="браузер посетителя",
),
@@ -233,7 +235,7 @@ COLUMNS: tuple[Column, ...] = (
name="BrowserMajorVersion",
clickhouse_type="UInt16",
numpy_dtype="uint16",
dds_name="browser_major_version",
normalized_name="browser_major_version",
group=ColumnGroup.CLIENT,
comment="старшая версия браузера",
),
@@ -241,7 +243,7 @@ COLUMNS: tuple[Column, ...] = (
name="BrowserLanguage",
clickhouse_type="String",
numpy_dtype="object",
dds_name="browser_language",
normalized_name="browser_language",
group=ColumnGroup.CLIENT,
comment="язык браузера",
),
@@ -249,7 +251,7 @@ COLUMNS: tuple[Column, ...] = (
name="OperatingSystem",
clickhouse_type="String",
numpy_dtype="object",
dds_name="operating_system",
normalized_name="operating_system",
group=ColumnGroup.CLIENT,
comment="операционная система с версией",
),
@@ -257,7 +259,7 @@ COLUMNS: tuple[Column, ...] = (
name="OperatingSystemRoot",
clickhouse_type="String",
numpy_dtype="object",
dds_name="operating_system_root",
normalized_name="operating_system_root",
group=ColumnGroup.CLIENT,
comment="семейство операционной системы, без версии",
),
@@ -265,7 +267,7 @@ COLUMNS: tuple[Column, ...] = (
name="DeviceCategory",
clickhouse_type="UInt8",
numpy_dtype="uint8",
dds_name="device_category",
normalized_name="device_category",
group=ColumnGroup.CLIENT,
comment="тип устройства кодами Метрики: 1 — десктоп, 2 — телефон,"
" 3 — планшет, 4 — телевизор; у Метрики это строка, у нас число",
@@ -274,7 +276,7 @@ COLUMNS: tuple[Column, ...] = (
name="MobilePhoneModel",
clickhouse_type="String",
numpy_dtype="object",
dds_name="mobile_phone_model",
normalized_name="mobile_phone_model",
group=ColumnGroup.CLIENT,
comment="модель телефона; на десктопе пусто",
),
@@ -282,7 +284,7 @@ COLUMNS: tuple[Column, ...] = (
name="ScreenWidth",
clickhouse_type="UInt16",
numpy_dtype="uint16",
dds_name="screen_width",
normalized_name="screen_width",
group=ColumnGroup.CLIENT,
comment="ширина экрана в пикселях",
),
@@ -290,7 +292,7 @@ COLUMNS: tuple[Column, ...] = (
name="ScreenHeight",
clickhouse_type="UInt16",
numpy_dtype="uint16",
dds_name="screen_height",
normalized_name="screen_height",
group=ColumnGroup.CLIENT,
comment="высота экрана в пикселях",
),
@@ -298,7 +300,7 @@ COLUMNS: tuple[Column, ...] = (
name="IPAddress",
clickhouse_type="String",
numpy_dtype="object",
dds_name="ip_address",
normalized_name="ip_address",
group=ColumnGroup.CLIENT,
comment="IP-адрес посетителя",
),
@@ -306,7 +308,7 @@ COLUMNS: tuple[Column, ...] = (
name="RegionCountry",
clickhouse_type="String",
numpy_dtype="object",
dds_name="region_country",
normalized_name="region_country",
group=ColumnGroup.CLIENT,
comment="страна кодом ISO",
),
@@ -314,7 +316,7 @@ COLUMNS: tuple[Column, ...] = (
name="RegionCity",
clickhouse_type="String",
numpy_dtype="object",
dds_name="region_city",
normalized_name="region_city",
group=ColumnGroup.CLIENT,
comment="город, название по-английски",
),
@@ -322,7 +324,7 @@ COLUMNS: tuple[Column, ...] = (
name="RegionCountryID",
clickhouse_type="UInt32",
numpy_dtype="uint32",
dds_name="region_country_id",
normalized_name="region_country_id",
group=ColumnGroup.CLIENT,
comment="числовой id страны в справочнике регионов Яндекса",
),
@@ -330,7 +332,7 @@ COLUMNS: tuple[Column, ...] = (
name="RegionCityID",
clickhouse_type="UInt32",
numpy_dtype="uint32",
dds_name="region_city_id",
normalized_name="region_city_id",
group=ColumnGroup.CLIENT,
comment="числовой id города в том же справочнике",
),
@@ -338,7 +340,7 @@ COLUMNS: tuple[Column, ...] = (
name="GoalsReached",
clickhouse_type="Array(UInt32)",
numpy_dtype="uint32",
dds_name="goals_reached",
normalized_name="goals_reached",
group=ColumnGroup.PARAMS,
comment="id достигнутых целей; на стенде их две — корзина и покупка",
),
@@ -346,7 +348,7 @@ COLUMNS: tuple[Column, ...] = (
name="ParsedParamsKey1",
clickhouse_type="Array(String)",
numpy_dtype="object",
dds_name="parsed_params_key1",
normalized_name="parsed_params_key1",
group=ColumnGroup.PARAMS,
comment="свои параметры сайта, один уровень — например вариант A/B-теста",
),
@@ -354,7 +356,7 @@ COLUMNS: tuple[Column, ...] = (
name="purchaseID",
clickhouse_type="Array(String)",
numpy_dtype="object",
dds_name="purchase_id",
normalized_name="purchase_id",
group=ColumnGroup.ECOMMERCE,
comment="номер заказа; у события purchase — один элемент",
),
@@ -362,7 +364,7 @@ COLUMNS: tuple[Column, ...] = (
name="purchaseRevenue",
clickhouse_type="Array(Float64)",
numpy_dtype="float64",
dds_name="purchase_revenue",
normalized_name="purchase_revenue",
group=ColumnGroup.ECOMMERCE,
comment="выручка заказа глазами клиента; Float64, как у Метрики —"
" на этом держится урок о расхождениях с бэкендом",
@@ -371,7 +373,7 @@ COLUMNS: tuple[Column, ...] = (
name="purchaseCurrency",
clickhouse_type="Array(String)",
numpy_dtype="object",
dds_name="purchase_currency",
normalized_name="purchase_currency",
group=ColumnGroup.ECOMMERCE,
comment="валюта заказа",
),
@@ -379,7 +381,7 @@ COLUMNS: tuple[Column, ...] = (
name="purchaseCoupon",
clickhouse_type="Array(String)",
numpy_dtype="object",
dds_name="purchase_coupon",
normalized_name="purchase_coupon",
group=ColumnGroup.ECOMMERCE,
comment="купон заказа, если был применён",
),
@@ -387,7 +389,7 @@ COLUMNS: tuple[Column, ...] = (
name="productID",
clickhouse_type="Array(String)",
numpy_dtype="object",
dds_name="product_id",
normalized_name="product_id",
group=ColumnGroup.ECOMMERCE,
comment="id товаров события",
),
@@ -395,7 +397,7 @@ COLUMNS: tuple[Column, ...] = (
name="productName",
clickhouse_type="Array(String)",
numpy_dtype="object",
dds_name="product_name",
normalized_name="product_name",
group=ColumnGroup.ECOMMERCE,
comment="названия тех же товаров",
),
@@ -403,7 +405,7 @@ COLUMNS: tuple[Column, ...] = (
name="productCategory",
clickhouse_type="Array(String)",
numpy_dtype="object",
dds_name="product_category",
normalized_name="product_category",
group=ColumnGroup.ECOMMERCE,
comment="категории тех же товаров",
),
@@ -411,7 +413,7 @@ COLUMNS: tuple[Column, ...] = (
name="productPrice",
clickhouse_type="Array(Int64)",
numpy_dtype="int64",
dds_name="product_price",
normalized_name="product_price",
group=ColumnGroup.ECOMMERCE,
comment="цена за штуку целым числом: деньги генератор считает целыми",
),
@@ -419,7 +421,7 @@ COLUMNS: tuple[Column, ...] = (
name="productQuantity",
clickhouse_type="Array(UInt64)",
numpy_dtype="uint64",
dds_name="product_quantity",
normalized_name="product_quantity",
group=ColumnGroup.ECOMMERCE,
comment="количество штук каждого товара",
),
@@ -427,7 +429,7 @@ COLUMNS: tuple[Column, ...] = (
name="productEventType",
clickhouse_type="Array(String)",
numpy_dtype="object",
dds_name="product_event_type",
normalized_name="product_event_type",
group=ColumnGroup.ECOMMERCE,
comment="действие с товаром: стенд шлёт add и purchase, полный"
" словарь Метрики (detail, remove, impressions) не берём",
@@ -436,7 +438,7 @@ COLUMNS: tuple[Column, ...] = (
name="ecommerce",
clickhouse_type="String",
numpy_dtype="object",
dds_name="ecommerce",
normalized_name="ecommerce",
group=ColumnGroup.ECOMMERCE,
comment="сырой JSON события, как отдаёт Метрика — материал лабы"
" про разбор JSON внутри колонки",
@@ -26,10 +26,12 @@ JSON-поле `ecommerce`. Отдельной сущности «визит» в
собирают на стороне хранилища, а `VisitID` дан как эталон для самопроверки.
Имена и типы колонок — стороны источника. Хранилище принимает их как есть и
нормализует у себя: своё snake_case-имя каждой колонки ждёт в столбце «Имя в
DDS». Столбец «Тип numpy» показывает, чем колонка представлена внутри
генератора; у массивов это тип элемента. Номер — место колонки в выгрузке:
порядок задан контрактом.
нормализует у себя: то же имя в нашем стиле ждёт в столбце «Нормализованное
имя». Это имя источника, приведённое к snake_case, а не имя атрибута в
модели данных: слой DDS складывает свою модель и называет атрибуты по ней.
Столбец «Тип numpy» показывает, чем колонка представлена внутри генератора;
у массивов это тип элемента. Номер — место колонки в выгрузке: порядок задан
контрактом.
Колонки группы «Ecommerce» заполнены только у торговых событий:
`add_to_cart` несёт один товар, `purchase` — состав заказа и блок
@@ -38,7 +40,7 @@ DDS». Столбец «Тип numpy» показывает, чем колонк
Всего колонок: {count}."""
TABLE_HEADER = (
"| № | Колонка | Тип ClickHouse | Тип numpy | Имя в DDS | Комментарий |",
"| № | Колонка | Тип ClickHouse | Тип numpy | Нормализованное имя | Комментарий |",
"|---|---|---|---|---|---|",
)
@@ -60,7 +62,7 @@ def table_row(number: int, column: Column) -> str:
f"`{column.name}`",
f"`{column.clickhouse_type}`",
f"`{column.numpy_dtype}`",
f"`{column.dds_name}`",
f"`{column.normalized_name}`",
column.comment,
)
return "| " + " | ".join(cells) + " |"