docs(glossary): четыре термина переименованы, world.md и stand.md вычитаны
- Зачем:
- справочники читает человек со стороны, а «пульт мира», «опись мира»,
«работник» и «сырьё» звучали шуткой или калькой времён разработки
(тикет #214, выбор владельца).
- Что:
- CONTEXT.md: статьи «Паспорт мира», «Даги модельного времени»,
«Шаговый даг», «Сырые данные», старые слова в строках «Избегать»;
пометки в ADR 0005, 0008, 0009 без переписывания решений.
- замены по README, справочникам, путеводителю, лабам, спекам,
комментариям дагов и SQL, тегу дага и панели Grafana; имена в коде
не тронуты.
- world.md и stand.md: 35 находок вычитки внесены, регистр выровнен.
- Проверка:
- make config-test; make lint в корне и в generator/; make test в
generator/ (437 passed); скрипт ссылок из #212 пуст; grep старых
слов вне ADR, research и CONTEXT.md пуст.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QBCtU38qSHg5DvgtnKXcW
This commit is contained in:
1 parent
86ad8afaf2
commit
59d8c83b17
54 files changed
+794
-776
No files matched your search
+3
-3
@@ -64,8 +64,8 @@
|
||||
- `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)): паспорт мира
|
||||
- `src/clickstream_generator/inventory.py` — сборка паспорта мира
|
||||
([`data/world-inventory.json`](../data/world-inventory.json)): шапка паспорта
|
||||
и хеши восьми дней, которыми наполняется стенд, плюс счётчики классов
|
||||
расхождений по заказам для дней с закрытым окном. Руками не правят —
|
||||
пересобирают целью `make inventory`.
|
||||
@@ -88,7 +88,7 @@
|
||||
- `make typecheck` — ty: проверка типов;
|
||||
- `make demo` — пройти стадии канонического D0 на одном экране;
|
||||
- `make docs` — пересобрать описание выгрузки;
|
||||
- `make inventory` — пересобрать опись мира.
|
||||
- `make inventory` — пересобрать паспорт мира.
|
||||
|
||||
В корне репозитория `make lint` тоже есть, но он про код стенда — даги и
|
||||
Superset. Одноимённые цели за разными дверями охватывают разное:
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
"""Опись мира: чем стенд наполняется при подъёме и каким это обязано выйти.
|
||||
"""Паспорт мира: чем стенд наполняется при подъёме и каким это обязано выйти.
|
||||
|
||||
Мир — чистая функция зерна (спека генератора, раздел 2), поэтому в git лежит не
|
||||
он сам, а опись: паспорт мира, число событий и хеш байтов каждого дня, счётчики
|
||||
классов расхождений по заказам. Сам мир пересчитывается когда угодно, а опись
|
||||
он сам, а паспорт: шапка, число событий и хеш байтов каждого дня, счётчики
|
||||
классов расхождений по заказам. Сам мир пересчитывается когда угодно, а паспорт
|
||||
отвечает на единственный вопрос — **тот ли это мир, что был вчера**. Разошлись
|
||||
хеши — мир уехал, и дальше уже неважно, чего от него ждали проверки.
|
||||
|
||||
Дней в описи восемь: столько заливается в стенд при `make up`. Понедельник по
|
||||
Дней в паспорте восемь: столько заливается в стенд при `make up`. Понедельник по
|
||||
понедельник — полная неделя с выходными и первый замкнутый цикл окна K = 7.
|
||||
Это и есть эталонный мир: обещание в четырнадцать дней снято на этапе 7 —
|
||||
у хвоста не нашлось читателя. Понадобится длиннее — опись продолжит этот
|
||||
у хвоста не нашлось читателя. Понадобится длиннее — паспорт продолжит этот
|
||||
же файл, а не заведет второй.
|
||||
|
||||
**Сторожат мир хеши, а не паспорт.** Паспорт отвечает на другой вопрос — «чем
|
||||
@@ -30,9 +30,9 @@
|
||||
Слепок заказов — второй артефакт мира, и хешами дней он не покрыт ни при каком
|
||||
раскладе подпотоков, поэтому у каждого отправленного слепка своя строка. Их на
|
||||
один меньше, чем дней: прогон дня 0 отправлять ещё нечего. Счёта строк у
|
||||
слепка нет — опись описывает мир, а не доставку.
|
||||
слепка нет — паспорт описывает мир, а не доставку.
|
||||
|
||||
Собирается опись из каталога генератора целью `make inventory`, а свежесть её
|
||||
Собирается паспорт из каталога генератора целью `make inventory`, а его свежесть
|
||||
сторожит тест — как и у «описания выгрузки».
|
||||
"""
|
||||
|
||||
@@ -57,7 +57,7 @@ STARTING_DAYS = 8
|
||||
|
||||
|
||||
def build() -> dict[str, Any]:
|
||||
"""Опись целиком: паспорт мира, строка на день и строка на слепок.
|
||||
"""Паспорт целиком: шапка, строка на день и строка на слепок.
|
||||
|
||||
Дни играются по одному и отпускаются: заказы дня остаются, потому что из
|
||||
них собираются слепки, а полсотни тысяч событий восьми дней сразу в память
|
||||
@@ -83,12 +83,12 @@ def build() -> dict[str, Any]:
|
||||
|
||||
|
||||
def render() -> str:
|
||||
"""Опись текстом файла: отступы в два пробела, кириллица как есть."""
|
||||
"""Паспорт текстом файла: отступы в два пробела, кириллица как есть."""
|
||||
return json.dumps(build(), ensure_ascii=False, indent=2) + "\n"
|
||||
|
||||
|
||||
def _day(number: int, today: day_module.Day) -> dict[str, Any]:
|
||||
"""Строка описи: номер дня, его дата, число событий и хеш байтов.
|
||||
"""Строка паспорта: номер дня, его дата, число событий и хеш байтов.
|
||||
|
||||
Дата считается от D0 арифметикой, а не берётся из событий: ось модельного
|
||||
времени так и определена (`world.ORIGIN`), и по этой же дате счётчики
|
||||
@@ -105,7 +105,7 @@ def _day(number: int, today: day_module.Day) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _snapshot(number: int, orders: list[orders_module.Orders]) -> dict[str, Any]:
|
||||
"""Строка описи слепка: какой день снят, его дата и хеш отправленных байтов.
|
||||
"""Строка паспорта слепка: какой день снят, его дата и хеш отправленных байтов.
|
||||
|
||||
Байты те же, что уезжают в топик `orders`, с переводом строки после
|
||||
каждого заказа: пересъёмка слепка обязана дать их снова.
|
||||
@@ -169,12 +169,12 @@ def _digest(payload: bytes) -> str:
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Собирает опись мира: паспорт, счётчики и хеши дней."
|
||||
description="Собирает паспорт мира: шапку, счётчики и хеши дней."
|
||||
)
|
||||
parser.add_argument("output", type=Path, help="путь к файлу описи")
|
||||
parser.add_argument("output", type=Path, help="путь к файлу паспорта")
|
||||
output = parser.parse_args().output
|
||||
output.write_text(render(), encoding="utf-8")
|
||||
print(f"Опись мира собрана: {output}")
|
||||
print(f"Паспорт мира собран: {output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
Тайминги печатаются раздельно — генерация и доставка, как требует спека
|
||||
(раздел 5): это разные машины разной природы, и сложенные в одно число они
|
||||
перестают что-либо говорить. Сериализация считается частью генерации: она
|
||||
рождает те самые байты, которые сторожит опись.
|
||||
рождает те самые байты, которые сторожит паспорт.
|
||||
|
||||
**Слепки заказов идут третьим ходом того же проигрывателя** и живут по правилу
|
||||
сдвига: прогон дня D отправляет слепок дня D−1 — ночная выгрузка бэкенда за
|
||||
|
||||
@@ -25,8 +25,8 @@ from enum import IntEnum
|
||||
|
||||
import numpy as np
|
||||
|
||||
# Каноническое зерно эталонного мира — константа репозитория; опись хранит
|
||||
# его в паспорте мира. Свои зёрна менти крутит без гарантий описи.
|
||||
# Каноническое зерно эталонного мира — константа репозитория; паспорт хранит
|
||||
# его в своей шапке. Свои зёрна менти крутит без гарантий паспорта.
|
||||
CANONICAL_SEED = 20260601
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
строгий приём хранилища сверяет набор ключей (ADR 0005).
|
||||
- **Даты и время — ISO-8601** (спека, раздел 4): `EventDate` уезжает как
|
||||
`2026-06-01`, `UTCEventTime` — как `2026-06-01T12:34:56Z`. Довод — читаемость
|
||||
сырья: менти открывает колонку `raw` обычным клиентом и разбирает событие
|
||||
сырых данных: менти открывает колонку `raw` обычным клиентом и разбирает событие
|
||||
глазами, а число эпохи этот урок убивает.
|
||||
- **Одно событие — один документ JSON**, без перевода строки внутри: приёмник
|
||||
сам решает, чем их разделить.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
Модуль — приглашение крутить: поменяйте число, пересоберите снимок и
|
||||
посмотрите, что стало с данными. Правка любой константы здесь — смена мира,
|
||||
поэтому чек описи честно покраснеет: опись сторожит только канонический мир,
|
||||
свои миры менти собирает без её гарантий (спека генератора, раздел 9).
|
||||
поэтому чек паспорта честно покраснеет: паспорт сторожит только канонический мир,
|
||||
свои миры менти собирает без его гарантий (спека генератора, раздел 9).
|
||||
|
||||
Числа решены спекой и связаны между собой; связки сторожат тесты
|
||||
`test_world.py`, чтобы правка одного числа не рассыпала вывод соседнего.
|
||||
@@ -28,7 +28,7 @@ COUNTER_TIMEZONE_MINUTES = 240 # Europe/Samara
|
||||
# D0 — первый день оси модельного времени, понедельник. Реальный календарь в
|
||||
# модели не участвует: дата нужна лишь затем, чтобы дни оси легли в
|
||||
# `EventDate`/`UTCEventTime` конкретными числами. От даты запуска мир не
|
||||
# зависит — иначе опись перестала бы быть воспроизводимой.
|
||||
# зависит — иначе паспорт перестал бы быть воспроизводимым.
|
||||
ORIGIN = date(2026, 6, 1)
|
||||
|
||||
# Окно изменяемости заказа, модельные дни: слепок бэкенда несёт заказы,
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
|
||||
def test_demo_runs_and_shows_the_whole_chain() -> None:
|
||||
"""Сторожит команду и стадии, а числа мира оставляет описи."""
|
||||
"""Сторожит команду и стадии, а проверку чисел мира оставляет паспорту."""
|
||||
finished = subprocess.run(
|
||||
["make", "--silent", "demo"],
|
||||
check=False,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Проверка описи мира: та ли она, что собирается из кода сегодня.
|
||||
"""Проверка паспорта мира: совпадает ли он с тем, что собирается из кода сегодня.
|
||||
|
||||
Опись собирается из кода, значит разойтись они могут только одним способом —
|
||||
код правили, опись не пересобрали. Ровно это здесь и сторожится, тем же
|
||||
Паспорт собирается из кода, значит разойтись они могут только одним способом —
|
||||
код правили, паспорт не пересобрали. Ровно это здесь и сторожится, тем же
|
||||
способом, что свежесть «описания выгрузки».
|
||||
|
||||
Проверка дорогая — она пересчитывает восемь модельных дней целиком, и это
|
||||
@@ -23,24 +23,24 @@ from clickstream_generator.seeds import CANONICAL_SEED
|
||||
|
||||
INVENTORY_PATH = Path(__file__).resolve().parents[2] / "data" / "world-inventory.json"
|
||||
|
||||
# Прогон, слепок которого сверяется с описью байт в байт. День любой из
|
||||
# Прогон, слепок которого сверяется с паспортом байт в байт. День любой из
|
||||
# отправляемых; этот дешевле прочих — его окно короче.
|
||||
RUN_DAY = 2
|
||||
|
||||
# День любой: хеш дня не зависит ни от соседей, ни от края описи.
|
||||
# День любой: хеш дня не зависит ни от соседей, ни от края паспорта.
|
||||
REPLAY_DAY = 4
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def inventory() -> dict:
|
||||
"""Опись, собранная из кода: мир пересчитывается один раз на весь модуль."""
|
||||
"""Паспорт, собранный из кода: мир пересчитывается один раз на весь модуль."""
|
||||
return build()
|
||||
|
||||
|
||||
def test_inventory_is_up_to_date(inventory: dict):
|
||||
stored = json.loads(INVENTORY_PATH.read_text(encoding="utf-8"))
|
||||
assert stored == inventory, (
|
||||
"опись мира отстала от кода — пересоберите: make inventory."
|
||||
"паспорт мира отстал от кода — пересоберите: make inventory."
|
||||
" Разошлись хеши дней и каталога — правили data/catalog/products.csv;"
|
||||
" разошлись только дни — правили генератор"
|
||||
)
|
||||
@@ -68,7 +68,7 @@ def test_the_inventory_holds_the_hash_of_every_sent_snapshot(inventory: dict, tm
|
||||
"""У каждого отправленного слепка — своя строка с хешем его байтов.
|
||||
|
||||
Байты слепка не покрыты хешами дней ни при каком раскладе подпотоков: это
|
||||
второй артефакт мира, и побайтовое обещание сторожит опись. Слепков на день
|
||||
второй артефакт мира, и побайтовое обещание сторожит паспорт. Слепков на день
|
||||
меньше, чем дней: прогон дня 0 не отправляет ничего.
|
||||
|
||||
Хеш сверяется с настоящей выгрузкой, а не с самим собой: в файл уезжают те
|
||||
@@ -86,7 +86,7 @@ def test_the_inventory_holds_the_hash_of_every_sent_snapshot(inventory: dict, tm
|
||||
|
||||
|
||||
def test_a_replayed_day_matches_its_inventory_hash(tmp_path):
|
||||
"""Опись сверяется с байтами, вышедшими из CLI, а не с собранной тут же:
|
||||
"""Паспорт сверяется с байтами, вышедшими из CLI, а не с собранной тут же:
|
||||
пересчет сравнивался бы сам с собой.
|
||||
"""
|
||||
stored = json.loads(INVENTORY_PATH.read_text(encoding="utf-8"))
|
||||
|
||||
@@ -28,7 +28,7 @@ def test_every_event_carries_every_column(events):
|
||||
|
||||
«Пусто» по контракту — пустое значение, а не отсутствие ключа: пропавший
|
||||
ключ уводит событие в брак целиком (ADR 0005). Порядок ключей — часть
|
||||
канона: от него зависят байты, а значит и хеши описи.
|
||||
канона: от него зависят байты, а значит и хеши паспорта.
|
||||
"""
|
||||
names = [column.name for column in schema.COLUMNS]
|
||||
for event in events:
|
||||
|
||||
@@ -167,7 +167,7 @@ def test_the_moments_carry_real_milliseconds(days: list[day_module.Day]):
|
||||
def test_the_rows_go_in_the_order_of_birth(days: list[day_module.Day]):
|
||||
"""Порядок строк слепка — порядок рождения заказов, он же рост номера.
|
||||
|
||||
Хешу слепка в описи нужен именно названный порядок: детерминизм даёт его
|
||||
Хешу слепка в паспорте нужен именно названный порядок: детерминизм даёт его
|
||||
даром, но обещание побайтового повтора держится на нём, а не на удаче.
|
||||
"""
|
||||
numbers = [record["order_id"] for record in snapshot_records(days, FULL_WINDOW_DAY)]
|
||||
|
||||
Reference in new issue
Block a user