fix(generator): правки по двум линиям ревью — сторож состава и честные обещания
- Зачем:
- линия постановки: тест инвариантов обещал ловить дрейф колонок, но
переименование Referer или перенос колонки в другую группу проходили
все проверки; линия стандартов: докстринг говорил о contract-тесте
как о существующем и не нёс следа сверки API через Context7.
- Что:
- тест состава по разделу 1.2 мастер-спеки: группа, имя и тип всех 47
колонок записаны независимо от контракта, поэтому молчаливое
переименование или перестановка краснеют — проверено правкой
Referer → Referrer.
- контракт: contract-тест переведён в будущее время со ссылкой на
спеку; записана сверка записи типов ClickHouse (Context7 и запрос
к узлу стенда 26.3.17.56 — параметры входят в имя типа целиком).
- описание выгрузки самодостаточнее: расшифрованы коды
DeviceCategory, домен LastTrafficSource честно назван неполным,
«идентификатор» сведён к «id» ради одного слова на одну вещь.
- schema_doc: убраны неиспользуемые параметры render и main,
row → table_row; тест строки сверяет свойство, а не форму.
- Проверка:
- make test (249 тестов), make lint;
- make docs, затем git diff --exit-code docs/ — пусто.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,15 +4,19 @@
|
||||
1.1–1.2) и здесь не переоткрываются — модуль записывает их машинно-читаемо.
|
||||
Контракт принадлежит генератору и кормит трёх потребителей: сам генератор,
|
||||
его валидацию и «описание выгрузки» в доках (`schema_doc`). Хранилище
|
||||
строится по описанию, а не по модулю; границу сторожит contract-тест,
|
||||
сверяющий `system.columns` поднятого стенда с этим контрактом.
|
||||
строится по описанию, а не по модулю; границу будет сторожить contract-тест,
|
||||
сверяющий `system.columns` поднятого стенда с этим контрактом, — он придёт
|
||||
вместе с типизированным ODS (спека генератора, раздел 3).
|
||||
|
||||
Что несёт описатель колонки:
|
||||
|
||||
- `name` — имя источника, как в облачной выгрузке Метрики; сырой слой хранит
|
||||
его без изменений.
|
||||
- `clickhouse_type` — тип в хранилище ровно в той записи, в какой его вернёт
|
||||
`system.columns`.
|
||||
`system.columns`: параметры входят в имя типа целиком, без сокращений
|
||||
(`LowCardinality(String)`, `Array(Float64)`). Сверено 2026-08-01 —
|
||||
по документации ClickHouse через Context7 и запросом к узлу стенда
|
||||
(26.3.17.56); от этой записи зависит будущий contract-тест.
|
||||
- `numpy_dtype` — чем колонка представлена внутри генератора; у массивов это
|
||||
тип элемента. Строки живут в `object`-массивах: numpy-строки фиксированной
|
||||
длины стенду ничего не дают.
|
||||
@@ -199,7 +203,7 @@ COLUMNS: tuple[Column, ...] = (
|
||||
numpy_dtype="object",
|
||||
dds_name="last_traffic_source",
|
||||
group=ColumnGroup.PAGE,
|
||||
comment="последний источник трафика: direct, organic, ad, referral",
|
||||
comment="последний источник трафика: organic, direct, ad и подобные",
|
||||
),
|
||||
Column(
|
||||
name="HasGCLID",
|
||||
@@ -215,7 +219,7 @@ COLUMNS: tuple[Column, ...] = (
|
||||
numpy_dtype="uint64",
|
||||
dds_name="yclid",
|
||||
group=ColumnGroup.PAGE,
|
||||
comment="идентификатор клика Яндекс Директа; 0 — метки не было",
|
||||
comment="id клика Яндекс Директа; без метки — 0",
|
||||
),
|
||||
Column(
|
||||
name="Browser",
|
||||
@@ -263,8 +267,8 @@ COLUMNS: tuple[Column, ...] = (
|
||||
numpy_dtype="uint8",
|
||||
dds_name="device_category",
|
||||
group=ColumnGroup.CLIENT,
|
||||
comment="тип устройства кодами 1–4, как у Метрики; у неё это строка —"
|
||||
" отступление стенда",
|
||||
comment="тип устройства кодами Метрики: 1 — десктоп, 2 — телефон,"
|
||||
" 3 — планшет, 4 — телевизор; у Метрики это строка, у нас число",
|
||||
),
|
||||
Column(
|
||||
name="MobilePhoneModel",
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from collections.abc import Sequence
|
||||
from itertools import groupby
|
||||
from pathlib import Path
|
||||
|
||||
@@ -44,18 +43,18 @@ TABLE_HEADER = (
|
||||
)
|
||||
|
||||
|
||||
def render(columns: Sequence[Column] = COLUMNS) -> str:
|
||||
def render() -> str:
|
||||
"""Собирает документ целиком: преамбула и таблица колонок по группам."""
|
||||
lines = PREAMBLE.format(count=len(columns)).splitlines()
|
||||
numbers = iter(range(1, len(columns) + 1))
|
||||
for group, columns_of_group in groupby(columns, key=lambda column: column.group):
|
||||
lines = PREAMBLE.format(count=len(COLUMNS)).splitlines()
|
||||
numbers = iter(range(1, len(COLUMNS) + 1))
|
||||
for group, columns_of_group in groupby(COLUMNS, key=lambda column: column.group):
|
||||
lines += ["", f"## {group.value}", "", *TABLE_HEADER]
|
||||
lines += [row(next(numbers), column) for column in columns_of_group]
|
||||
lines += [table_row(next(numbers), column) for column in columns_of_group]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def row(number: int, column: Column) -> str:
|
||||
"""Строка таблицы; номер — место колонки в порядке выгрузки."""
|
||||
def table_row(number: int, column: Column) -> str:
|
||||
"""Строка таблицы колонок; номер — место колонки в порядке выгрузки."""
|
||||
cells = (
|
||||
str(number),
|
||||
f"`{column.name}`",
|
||||
@@ -67,12 +66,12 @@ def row(number: int, column: Column) -> str:
|
||||
return "| " + " | ".join(cells) + " |"
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> None:
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Собирает описание выгрузки из контракта схемы события."
|
||||
)
|
||||
parser.add_argument("output", type=Path, help="путь к файлу описания")
|
||||
output = parser.parse_args(argv).output
|
||||
output = parser.parse_args().output
|
||||
output.write_text(render(), encoding="utf-8")
|
||||
print(f"Описание выгрузки собрано: {output}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user