diff --git a/docs/formats/clickstream-event.md b/docs/formats/clickstream-event.md index a4ec787..05889c0 100644 --- a/docs/formats/clickstream-event.md +++ b/docs/formats/clickstream-event.md @@ -90,5 +90,5 @@ DDS». Столбец «Тип numpy» показывает, чем колонк | 43 | `productCategory` | `Array(String)` | `object` | `product_category` | категории тех же товаров | | 44 | `productPrice` | `Array(Int64)` | `int64` | `product_price` | цена за штуку целым числом: деньги генератор считает целыми | | 45 | `productQuantity` | `Array(UInt64)` | `uint64` | `product_quantity` | количество штук каждого товара | -| 46 | `productEventType` | `Array(String)` | `object` | `product_event_type` | действие с товаром: detail, add, remove, purchase | +| 46 | `productEventType` | `Array(String)` | `object` | `product_event_type` | действие с товаром: стенд шлёт add и purchase, полный словарь Метрики (detail, remove, impressions) не берём | | 47 | `ecommerce` | `String` | `object` | `ecommerce` | сырой JSON события, как отдаёт Метрика — материал лабы про разбор JSON внутри колонки | diff --git a/generator/src/clickstream_generator/schema.py b/generator/src/clickstream_generator/schema.py index eb7c2f0..6c33d59 100644 --- a/generator/src/clickstream_generator/schema.py +++ b/generator/src/clickstream_generator/schema.py @@ -429,7 +429,8 @@ COLUMNS: tuple[Column, ...] = ( numpy_dtype="object", dds_name="product_event_type", group=ColumnGroup.ECOMMERCE, - comment="действие с товаром: detail, add, remove, purchase", + comment="действие с товаром: стенд шлёт add и purchase, полный" + " словарь Метрики (detail, remove, impressions) не берём", ), Column( name="ecommerce", diff --git a/generator/tests/test_schema.py b/generator/tests/test_schema.py index afce036..030586c 100644 --- a/generator/tests/test_schema.py +++ b/generator/tests/test_schema.py @@ -87,6 +87,60 @@ NUMPY_BY_CLICKHOUSE_TYPE = { "DateTime": "datetime64[s]", } +# Имена для DDS — не производная от имён Метрики, а решение тикета #36: +# вывести их правилом нельзя (акронимы, «timezone» одним словом), поэтому +# сверять их не с чем, кроме такой же независимой записи. Без неё осмысленно +# неверное имя молча уезжает в опубликованное описание выгрузки. +EXPECTED_DDS_NAMES = { + "WatchID": "watch_id", + "VisitID": "visit_id", + "ClientID": "client_id", + "CounterID": "counter_id", + "EventDate": "event_date", + "UTCEventTime": "utc_event_time", + "ClientTimeZone": "client_timezone", + "EventType": "event_type", + "Sign": "sign", + "URL": "url", + "Referer": "referer", + "Title": "title", + "UTMSource": "utm_source", + "UTMMedium": "utm_medium", + "UTMCampaign": "utm_campaign", + "UTMContent": "utm_content", + "UTMTerm": "utm_term", + "LastTrafficSource": "last_traffic_source", + "HasGCLID": "has_gclid", + "YCLID": "yclid", + "Browser": "browser", + "BrowserMajorVersion": "browser_major_version", + "BrowserLanguage": "browser_language", + "OperatingSystem": "operating_system", + "OperatingSystemRoot": "operating_system_root", + "DeviceCategory": "device_category", + "MobilePhoneModel": "mobile_phone_model", + "ScreenWidth": "screen_width", + "ScreenHeight": "screen_height", + "IPAddress": "ip_address", + "RegionCountry": "region_country", + "RegionCity": "region_city", + "RegionCountryID": "region_country_id", + "RegionCityID": "region_city_id", + "GoalsReached": "goals_reached", + "ParsedParamsKey1": "parsed_params_key1", + "purchaseID": "purchase_id", + "purchaseRevenue": "purchase_revenue", + "purchaseCurrency": "purchase_currency", + "purchaseCoupon": "purchase_coupon", + "productID": "product_id", + "productName": "product_name", + "productCategory": "product_category", + "productPrice": "product_price", + "productQuantity": "product_quantity", + "productEventType": "product_event_type", + "ecommerce": "ecommerce", +} + METRICA_NAME = re.compile(r"^[A-Za-z][A-Za-z0-9]*$") DDS_NAME = re.compile(r"^[a-z][a-z0-9_]*$") ARRAY_TYPE = re.compile(r"^Array\((.+)\)$") @@ -124,6 +178,11 @@ def test_dds_names_are_unique(): assert len(set(names)) == len(names) +def test_dds_names_are_the_ones_we_chose(): + """Переименование колонки в DDS — решение, а не правка мимоходом.""" + assert {column.name: column.dds_name for column in COLUMNS} == EXPECTED_DDS_NAMES + + @pytest.mark.parametrize("column", COLUMNS, ids=lambda column: column.name) def test_attributes_are_filled(column: Column): assert column.name.strip() diff --git a/generator/tests/test_schema_doc.py b/generator/tests/test_schema_doc.py index 653c0db..9b154f8 100644 --- a/generator/tests/test_schema_doc.py +++ b/generator/tests/test_schema_doc.py @@ -16,7 +16,9 @@ from clickstream_generator.schema_doc import render REPO_ROOT = Path(__file__).resolve().parents[2] DOC_PATH = REPO_ROOT / "docs" / "formats" / "clickstream-event.md" -TABLE_ROW = re.compile(r"^\| \d+ \|", re.MULTILINE) +# Номер и имя колонки из строки таблицы: по ним сверяется не только состав +# документа, но и его порядок — по нему сторона хранилища выпишет колонки. +TABLE_ROW = re.compile(r"^\| (\d+) \| `([^`]+)` \|", re.MULTILINE) @pytest.fixture(scope="module") @@ -35,9 +37,11 @@ def test_every_column_has_a_row(rendered: str): assert len(TABLE_ROW.findall(rendered)) == len(COLUMNS) -def test_rows_are_numbered_in_contract_order(rendered: str): - numbers = [int(row.strip("| ")) for row in TABLE_ROW.findall(rendered)] - assert numbers == list(range(1, len(COLUMNS) + 1)) +def test_rows_follow_contract_order(rendered: str): + """Строки идут в порядке контракта, а не просто нумеруются с 1 по 47.""" + rows = [(int(number), name) for number, name in TABLE_ROW.findall(rendered)] + expected = [(number, column.name) for number, column in enumerate(COLUMNS, 1)] + assert rows == expected @pytest.mark.parametrize("column", COLUMNS, ids=lambda column: column.name)