Интерактивная карта архитектуры хранилища — один самодостаточный HTML-файл docs/handbook/architecture-map.html. Читатель открывает его двойным щелчком
из любой копии репозитория, без сети и сервера, и видит путь данных через
слои: топики Kafka → STG → ODS → DDS → DM, сбоку зона dic. Щелчок по узлу
показывает карточку объекта: что это, какой поверхностью его читать, из чего
он физически состоит, какими ключами соединяется с соседями, кто и в каком
ритме его наполняет, ссылки в справочник и DDL. Щелчок по стрелке показывает,
кто переносит данные между узлами и каким SQL.
Охват первой версии — только слои хранилища и путь данных через них
(резолюция #208). Потоки Airflow как второй слой карты, читатели DM
(Superset, Grafana) и генератор в эту версию не входят.
Урок карты: у одного смысла в каждом слое свое имя и своя поверхность, а
данные между слоями переносят три разных механизма — матвью непрерывно, даг
пачкой, представление без копии. Глава «Хранилище и его слои» это
рассказывает, карта дает это увидеть и потрогать.
Файлы
Файл
Что это
docs/handbook/architecture-map.html
Готовая карта. Рендер, не правится руками; строка-комментарий в начале файла говорит об этом и называет команду сборки
Исходник формы: разметка, стили, скрипт; данные подставляются в отмеченное место
docs/handbook/architecture-map/render.py
Сборка: читает map.yaml, вкладывает его в шаблон как JSON, пишет HTML. Флаг --check сравнивает свежий рендер с файлом в репозитории и падает при расхождении
docs/handbook/architecture-map/README.md
Пять-десять строк: что где лежит, как пересобрать, правило «правится исходник, файл пересобирается»
render.py — скрипт uv с inline-метаданными (# /// script), единственная
зависимость — pyyaml. Запуск: uv run docs/handbook/architecture-map/render.py.
Рендер детерминирован: тот же map.yaml и template.html дают байт в байт тот
же HTML (без времени сборки, без случайных id; ключи JSON — в порядке map.yaml, не сортируются; ensure_ascii=False). Порядок полей карточки
задает шаблон, а не порядок ключей в данных.
Объем: render.py — не длиннее 80 строк; template.html — не длиннее 450
строк; готовый HTML — не тяжелее 80 КБ. Если форма и бюджет спорят,
побеждает форма: превышение назвать в отчете с причиной.
Содержание: слои, узлы, стрелки
Слои — колонки слева направо. У каждой колонки заголовок и подпись-вопрос из
главы о хранилище:
Слой
Подпись
Kafka
откуда приходит
STG
как поступило
ODS
язык источника
DDS
язык бизнеса
DM
данные для дашбордов
Зона dic — не колонка, а отдельный узел под колонками DDS и DM с подписью
«сбоку цепочки: справочники».
Узел — один логический объект; его физические формы (_kafka, _rep, _dist, _mv, _stage_rep, _stage_dist, _v; таблицы ошибок *_errors_*
— отдельные узлы, у них свои формы) живут внутри карточки, а не отдельными
узлами. Имя узла на карте — имя, которым читатель его читает: поверхность _v там, где представление есть; где его нет (STG, таблицы ошибок) — _dist;
для словаря — dic.products, для Kafka — имя топика.
Состав узлов (проверить по DDL в sql/ddl/ и карте таблиц docs/architecture/storage.md#карта-таблиц; расхождение — исправить у себя и
назвать в отчете):
Слой
Узел
Потоки
Kafka
топик hits
hits
Kafka
топик orders
orders
STG
stg.hits_raw_dist
hits
STG
stg.orders_raw_dist
orders
ODS
ods.event_v
hits
ODS
ods.event_errors_dist
hits
ODS
ods.order_v
orders
ODS
ods.order_errors_dist
orders
DDS
dds.event_v
hits
DDS
dds.session_v
hits
DDS
dds.order_v
orders
DDS
dds.identity_map_v
hits, orders
DM
dm.daily_traffic_v
hits
DM
dm.revenue_daily_v
orders
DM
dm.purchase_vs_orders_v
hits, orders
DM
dm.dq_summary_v
hits, orders
dic
dic.products
orders
Стрелка — перенос данных из узла в узел. У каждой стрелки есть механизм, и
механизмов ровно три; они же — легенда карты:
Механизм
Линия
Ритм
матвью
сплошная
непрерывно, по мере прихода сообщений
даг Airflow
штриховая
пачкой: раз в модельные сутки (orders_ingest) или на каждом прогоне конвейера (dds_transform, dm_transform, dq_check)
представление
пунктир
без копии: данные читаются на месте
Состав стрелок — гипотеза постановки; источники каждой проверить по SQL в sql/stg/, sql/ods/, sql/dds/, sql/dm/ и по дагам в dags/ (sql/dq/
стрелок не дает, см. ниже).
Стрелка, которой SQL не подтверждает, убирается; стрелка, которую SQL
показывает, а таблица нет, добавляется; и то и другое — в отчет.
Откуда
Куда
Механизм
Кто именно
hits
stg.hits_raw_dist
матвью
чтец stg.hits_raw_kafka, матвью stg.hits_raw_mv
stg.hits_raw_dist
ods.event_v
матвью
ods.event_mv
stg.hits_raw_dist
ods.event_errors_dist
матвью
ods.event_errors_mv
orders
stg.orders_raw_dist
даг
orders_ingest, задача pull_batch, чтец stg.orders_raw_kafka, sql/stg/orders_raw_load.sql
stg.orders_raw_dist
ods.order_v
даг
orders_ingest, задача parse_batch, sql/ods/order_parse.sql
stg.orders_raw_dist
ods.order_errors_dist
даг
то же
ods.event_v
dds.event_v
представление
sql/ddl/50-dds-views.sql
dds.event_v
dds.session_v
даг
dds_transform, sql/dds/session_*.sql
ods.order_v
dds.order_v
даг
dds_transform, sql/dds/order_*.sql
dds.event_v
dds.identity_map_v
даг
dds_transform, sql/dds/identity_map_rebuild.sql
dds.order_v
dds.identity_map_v
даг
то же
dds.session_v
dm.daily_traffic_v
даг
dm_transform, sql/dm/daily_traffic_rebuild.sql
dds.identity_map_v
dm.daily_traffic_v
даг
то же: известные пользователи по client_id
dds.order_v
dm.revenue_daily_v
даг
dm_transform, sql/dm/revenue_daily_*.sql
dic.products
dm.revenue_daily_v
даг
то же: категория товара — dictGet по item_sku заказа, ключ словаря sku
dds.event_v
dm.purchase_vs_orders_v
даг
dm_transform, sql/dm/purchase_vs_orders_*.sql
dds.order_v
dm.purchase_vs_orders_v
даг
то же
У dm.dq_summary_v стрелок нет. Ее наполняет даг dq_check, но он не
переносит данные, а сравнивает: витрины DM (в том числе те, что строятся на
уроках и в DDL стенда отсутствуют) с DDS, ODS, словарем и паспортом мира.
Рисовать это стрелками — значит утопить карту в линиях ради одного узла.
Узел остается в колонке DM, а его карточка в поле «Кто наполняет» объясняет
это своими словами и перечисляет пять проверок, которые запускает dags/dq_check.py, с именами их SQL из sql/dq/.
У dic.products входящих стрелок тоже нет: словарь читает CSV-файл
(dic.products_file), это не путь данных через слои. Карточка называет
источник в поле «Из чего состоит».
Карточка узла
Поля, в этом порядке; пустое поле не показывается:
Имя — заголовок карточки; под ним слой и потоки.
Что это — одна-две фразы. Основа — колонка «Что это» карты таблиц и
главы путеводителя; не пересказ справочника.
Как читать — какой поверхностью читать и почему (_v — публичный
договор; _dist у STG и ошибок — потому что представления там нет).
Из чего состоит — список физических объектов с одной фразой на каждый: stg.hits_raw_kafka — чтец топика; stg.hits_raw_rep — реплицируемая
таблица на шарде; и так далее. Имена — точно как в DDL.
Ключи — колонки, которыми узел соединяется с соседями, с указанием, как
тот же смысл называется у соседа. Основа — таблица «Сквозные ключи» главы о
хранилище; имена колонок сверить с DDL.
Кто наполняет и как часто — механизм и ритм из таблицы стрелок; для
DDS и DM — правило обновления: замена партиции дня, полный пересчет новой
версией, FINAL в представлении.
Ссылки — «Справочник» (раздел docs/architecture/, для DDS и DM —
файл объекта в docs/architecture/dds/ или раздел dm.md) и «DDL»
(файл в sql/ddl/). Ссылки абсолютные, на ветку main Gitea: https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/<путь>#<якорь>.
Карта открывается из любого места, относительные ссылки там не живут.
Якорь — фактический id заголовка на странице Gitea, а не слаг по
догадке: у заголовков с обратными кавычками он другой (dm.md
дает dmrevenue_daily_v, не dm-revenue_daily_v). Снимать так: curl -sS <url файла> | grep -oE 'id="user-content-[^"]*"'.
Обязательные поля: у каждого узла — «Что это», «Как читать», «Кто наполняет
и как часто», «Ссылки»; «Из чего состоит» — у всех узлов, кроме топиков
Kafka (у dds.event_v поле состоит из одной фразы: только представление над ods.event_v, своей таблицы нет); «Ключи» — у всех узлов ODS,
DDS, DM и у словаря, кроме таблиц ошибок и dm.dq_summary_v. В «Ссылках» у
узлов хранилища обе ссылки — справочник и DDL; у топиков Kafka вместо DDL —
описание формата (docs/formats/clickstream-event.md для hits, docs/architecture/orders/snapshot.md для orders).
Карточка стрелки: заголовок «откуда → куда», механизм, ритм, кто именно
(даг и задача или матвью), ссылка на SQL и на раздел справочника
(docs/architecture/etl.md, docs/architecture/orders/ingestion.md, docs/architecture/dds/*.md, docs/architecture/dm.md).
Каждый факт карточек берется из DDL, SQL, справочников docs/architecture/, CONTEXT.md или глав путеводителя. Ничего не додумывается. Нашли расхождение
справочника с кодом — карточка следует коду, расхождение идет в отчет.
Взаимодействие
Щелчок по узлу — карточка в боковой панели справа; на карте узел
выделен, его входящий путь (все узлы и стрелки, из которых он собран, до
Kafka) и исходящий путь (все, что из него получается, до DM) подсвечены,
остальное приглушено. Повторный щелчок или Esc снимает выделение. У узлов
без стрелок (dic.products на входе, dm.dq_summary_v целиком) путь
пуст — это не ошибка, подсвечивается только сам узел.
Щелчок по стрелке — карточка стрелки в той же панели; стрелка и ее два
узла выделены.
Наведение — узел и его прямые соседи подсвечиваются слабее, чем при
щелчке; уходит вместе с курсором.
Переключатель потока над картой: «Все», «События», «Заказы». Узлы и
стрелки не выбранного потока приглушаются, но остаются на месте: карта не
перестраивается.
Легенда трех механизмов — всегда видна, под переключателем.
Пока ничего не выбрано, панель показывает вводку в пять-семь строк: что
это за карта, как ею пользоваться, ссылка на главу «Хранилище и его слои»
(абсолютная, на Gitea).
Раскладку считает скрипт в браузере из данных: колонка — по слою, строка —
по полю order узла внутри слоя, зона dic — под колонками DDS и DM.
Стрелки между колонками — кривые Безье от правого края узла к левому краю
следующего. Стрелки внутри одной колонки (три в DDS) выходят из правого
края источника, изгибаются в промежуток между колонками и входят в правый
край цели; порядок узлов в DDS — dds.event_v, dds.session_v, dds.order_v, dds.identity_map_v, чтобы источник всегда стоял выше цели.
Стрелка из dic выходит из верхнего края словаря. Карта — один SVG с viewBox, тянется
по ширине окна, при узком окне появляется горизонтальная прокрутка. Ни
масштабирования колесом, ни перетаскивания: карта помещается на экран.
Цвета: серые узлы и линии, один акцент для выделения, вторым тоном —
приглушение; потоки различаются не цветом, а фильтром. Шрифт системный.
Все тексты интерфейса — по-русски, обращение на «вы», тире «—» и
кавычки-елочки, без буквы «ё». Никаких внешних запросов: ни шрифтов, ни
скриптов, ни картинок по сети; файл работает по file://.
Где карта упоминается
docs/handbook/README.md: после раздела «Как читать» — раздел «Карта
хранилища» в четыре-пять строк: что это, ссылка на architecture-map.html,
совет открыть рядом с главой о хранилище. Gitea показывает файл как текст,
а не как страницу, поэтому раздел говорит прямо: откройте файл из своей
копии репозитория или скачайте его кнопкой «Raw» и откройте в браузере.
Остальной текст не меняется.
docs/handbook/storage-layers.md, раздел «Куда дальше»: одна фраза со
ссылкой на карту и той же оговоркой в двух словах — открывать из своей
копии репозитория. Остальной текст главы не меняется.
Makefile, цель lint: третья строка uv run docs/handbook/architecture-map/render.py --check, а в две строки ruff добавляется путь docs/handbook/architecture-map. Комментарий над
целью дополняется одной фразой: почему проверка рендера живет здесь
(быстрая, стенд не нужен, ловит протухший рендер).
docs/architecture/testing.md: там, где описано, что утверждает make lint, — одна фраза про проверку рендера карты; длительность цели
и дата замера, которые там записаны, обновляются по фактическому прогону.
Больше ничего.
AGENTS.md, раздел «Структура», абзац про docs/handbook/: после фразы
про индекс папки вставить ровно это предложение: «Исходники интерактивной
карты лежат подпапкой architecture-map/, а ее рендер — файлом architecture-map.html рядом с главами.» Больше ничего.
render.py объявляет requires-python = ">=3.13": так target-version
корневого ruff.toml для него верен, и сам ruff.toml не трогается.
Критерии приемки
uv run docs/handbook/architecture-map/render.py собирает docs/handbook/architecture-map.html; повторный запуск не меняет файл; --check зеленый на свежем рендере и красный, если в HTML изменить
один символ.
HTML открывается из file:// без сети: ни одного внешнего src, href на стили и скрипты, @import, url() на сетевые адреса.
Ссылки на Gitea в карточках — обычные <a target="_blank">.
Все узлы и стрелки из таблиц (с поправками, названными в отчете) на
месте; каждый узел открывает карточку со всеми непустыми полями; каждая
стрелка открывает свою карточку.
Выделение пути, наведение, переключатель потока, Esc, легенда и вводка
работают так, как описано в «Взаимодействии». Браузера у исполнителя нет:
он проверяет скрипт чтением и, где возможно, прогоном чистых функций
раскладки и достижимости через node -e; проверка в браузере — за
оркестратором на приемке, исполнитель называет в отчете, что проверить
щелчками.
Каждое имя объекта, колонки, дага, задачи и SQL-файла в map.yaml
существует в коде под этим именем.
Каждая ссылка карточек ведет на существующий файл в main
(curl -sI отвечает 200) и на фактический id заголовка (снят командой
из раздела «Карточка узла», не угадан).
Объемы в пределах раздела «Файлы».
make lint зеленый; ruff в него теперь проверяет и render.py.
docs/handbook/README.md, storage-layers.md, Makefile, testing.md изменены только в названных местах.
В прозе карточек, интерфейса и README.md карты нет «ё», слов «менти»
и «оператор», олицетворений («дашборд спрашивает», «заказ ездит»);
обращение на «вы».
Никаких других файлов, кроме перечисленных в разделах «Файлы» и «Где
карта упоминается», не создано и не изменено.
Границы
Справочники docs/architecture/ (кроме названных правок в testing.md), CONTEXT.md, корневой README.md, ruff.toml, AGENTS.md (кроме одного
названного предложения), DDL, SQL, даги и код генератора не правятся. Расхождение справочника с кодом — в отчет, не в правку.
Никаких JS-библиотек и сборщиков: ни mermaid, ни d3, ни npm. Скрипт — на
чистом JS, стили — на чистом CSS, оба внутри шаблона.
Никаких коммитов и веток: правки остаются в рабочем дереве.
Никаких изменений вне репозитория.
Сначала прочитать
docs/handbook/storage-layers.md — глава, которую карта дополняет;
таблица «Сквозные ключи» и подписи слоев берутся отсюда.
sql/stg/, sql/ods/, sql/dds/, sql/dm/, sql/dq/ — источники
каждой стрелки.
dags/ — какая задача какого дага исполняет какой SQL; имена дагов и
задач — отсюда.
docs/architecture/etl.md, docs/architecture/orders/ingestion.md, docs/architecture/dds/README.md, docs/architecture/dm.md — ритмы и
правила обновления для карточек.
docs/handbook/README.md — куда встает раздел о карте.
AGENTS.md, раздел «Язык».
Команды проверки
uv run docs/handbook/architecture-map/render.py
uv run docs/handbook/architecture-map/render.py --check
make lint
grep -nE 'https?://' docs/handbook/architecture-map.html | grep -v -e git.dementev.space -e www.w3.org
grep -n 'ё' docs/handbook/architecture-map/map.yaml docs/handbook/architecture-map/template.html docs/handbook/architecture-map/README.md
Последние две команды должны молчать. Стенд для задачи не нужен.
Part of #206
## Цель
Интерактивная карта архитектуры хранилища — один самодостаточный HTML-файл
`docs/handbook/architecture-map.html`. Читатель открывает его двойным щелчком
из любой копии репозитория, без сети и сервера, и видит путь данных через
слои: топики Kafka → STG → ODS → DDS → DM, сбоку зона `dic`. Щелчок по узлу
показывает карточку объекта: что это, какой поверхностью его читать, из чего
он физически состоит, какими ключами соединяется с соседями, кто и в каком
ритме его наполняет, ссылки в справочник и DDL. Щелчок по стрелке показывает,
кто переносит данные между узлами и каким SQL.
Охват первой версии — только слои хранилища и путь данных через них
(резолюция #208). Потоки Airflow как второй слой карты, читатели DM
(Superset, Grafana) и генератор в эту версию не входят.
Урок карты: у одного смысла в каждом слое свое имя и своя поверхность, а
данные между слоями переносят три разных механизма — матвью непрерывно, даг
пачкой, представление без копии. Глава «Хранилище и его слои» это
рассказывает, карта дает это увидеть и потрогать.
## Файлы
| Файл | Что это |
|---|---|
| `docs/handbook/architecture-map.html` | Готовая карта. Рендер, не правится руками; строка-комментарий в начале файла говорит об этом и называет команду сборки |
| `docs/handbook/architecture-map/map.yaml` | Исходник содержания: слои, узлы, стрелки, карточки |
| `docs/handbook/architecture-map/template.html` | Исходник формы: разметка, стили, скрипт; данные подставляются в отмеченное место |
| `docs/handbook/architecture-map/render.py` | Сборка: читает `map.yaml`, вкладывает его в шаблон как JSON, пишет HTML. Флаг `--check` сравнивает свежий рендер с файлом в репозитории и падает при расхождении |
| `docs/handbook/architecture-map/README.md` | Пять-десять строк: что где лежит, как пересобрать, правило «правится исходник, файл пересобирается» |
`render.py` — скрипт `uv` с inline-метаданными (`# /// script`), единственная
зависимость — `pyyaml`. Запуск: `uv run docs/handbook/architecture-map/render.py`.
Рендер детерминирован: тот же `map.yaml` и `template.html` дают байт в байт тот
же HTML (без времени сборки, без случайных id; ключи JSON — в порядке
`map.yaml`, не сортируются; `ensure_ascii=False`). Порядок полей карточки
задает шаблон, а не порядок ключей в данных.
Объем: `render.py` — не длиннее 80 строк; `template.html` — не длиннее 450
строк; готовый HTML — не тяжелее 80 КБ. Если форма и бюджет спорят,
побеждает форма: превышение назвать в отчете с причиной.
## Содержание: слои, узлы, стрелки
Слои — колонки слева направо. У каждой колонки заголовок и подпись-вопрос из
главы о хранилище:
| Слой | Подпись |
|---|---|
| Kafka | откуда приходит |
| STG | как поступило |
| ODS | язык источника |
| DDS | язык бизнеса |
| DM | данные для дашбордов |
Зона `dic` — не колонка, а отдельный узел под колонками DDS и DM с подписью
«сбоку цепочки: справочники».
Узел — один логический объект; его физические формы (`_kafka`, `_rep`,
`_dist`, `_mv`, `_stage_rep`, `_stage_dist`, `_v`; таблицы ошибок `*_errors_*`
— отдельные узлы, у них свои формы) живут внутри карточки, а не отдельными
узлами. Имя узла на карте — имя, которым читатель его читает: поверхность
`_v` там, где представление есть; где его нет (STG, таблицы ошибок) — `_dist`;
для словаря — `dic.products`, для Kafka — имя топика.
Состав узлов (проверить по DDL в `sql/ddl/` и карте таблиц
`docs/architecture/storage.md#карта-таблиц`; расхождение — исправить у себя и
назвать в отчете):
| Слой | Узел | Потоки |
|---|---|---|
| Kafka | топик `hits` | hits |
| Kafka | топик `orders` | orders |
| STG | `stg.hits_raw_dist` | hits |
| STG | `stg.orders_raw_dist` | orders |
| ODS | `ods.event_v` | hits |
| ODS | `ods.event_errors_dist` | hits |
| ODS | `ods.order_v` | orders |
| ODS | `ods.order_errors_dist` | orders |
| DDS | `dds.event_v` | hits |
| DDS | `dds.session_v` | hits |
| DDS | `dds.order_v` | orders |
| DDS | `dds.identity_map_v` | hits, orders |
| DM | `dm.daily_traffic_v` | hits |
| DM | `dm.revenue_daily_v` | orders |
| DM | `dm.purchase_vs_orders_v` | hits, orders |
| DM | `dm.dq_summary_v` | hits, orders |
| dic | `dic.products` | orders |
Стрелка — перенос данных из узла в узел. У каждой стрелки есть механизм, и
механизмов ровно три; они же — легенда карты:
| Механизм | Линия | Ритм |
|---|---|---|
| матвью | сплошная | непрерывно, по мере прихода сообщений |
| даг Airflow | штриховая | пачкой: раз в модельные сутки (`orders_ingest`) или на каждом прогоне конвейера (`dds_transform`, `dm_transform`, `dq_check`) |
| представление | пунктир | без копии: данные читаются на месте |
Состав стрелок — гипотеза постановки; источники каждой проверить по SQL в
`sql/stg/`, `sql/ods/`, `sql/dds/`, `sql/dm/` и по дагам в `dags/` (`sql/dq/`
стрелок не дает, см. ниже).
Стрелка, которой SQL не подтверждает, убирается; стрелка, которую SQL
показывает, а таблица нет, добавляется; и то и другое — в отчет.
| Откуда | Куда | Механизм | Кто именно |
|---|---|---|---|
| `hits` | `stg.hits_raw_dist` | матвью | чтец `stg.hits_raw_kafka`, матвью `stg.hits_raw_mv` |
| `stg.hits_raw_dist` | `ods.event_v` | матвью | `ods.event_mv` |
| `stg.hits_raw_dist` | `ods.event_errors_dist` | матвью | `ods.event_errors_mv` |
| `orders` | `stg.orders_raw_dist` | даг | `orders_ingest`, задача `pull_batch`, чтец `stg.orders_raw_kafka`, `sql/stg/orders_raw_load.sql` |
| `stg.orders_raw_dist` | `ods.order_v` | даг | `orders_ingest`, задача `parse_batch`, `sql/ods/order_parse.sql` |
| `stg.orders_raw_dist` | `ods.order_errors_dist` | даг | то же |
| `ods.event_v` | `dds.event_v` | представление | `sql/ddl/50-dds-views.sql` |
| `dds.event_v` | `dds.session_v` | даг | `dds_transform`, `sql/dds/session_*.sql` |
| `ods.order_v` | `dds.order_v` | даг | `dds_transform`, `sql/dds/order_*.sql` |
| `dds.event_v` | `dds.identity_map_v` | даг | `dds_transform`, `sql/dds/identity_map_rebuild.sql` |
| `dds.order_v` | `dds.identity_map_v` | даг | то же |
| `dds.session_v` | `dm.daily_traffic_v` | даг | `dm_transform`, `sql/dm/daily_traffic_rebuild.sql` |
| `dds.identity_map_v` | `dm.daily_traffic_v` | даг | то же: известные пользователи по `client_id` |
| `dds.order_v` | `dm.revenue_daily_v` | даг | `dm_transform`, `sql/dm/revenue_daily_*.sql` |
| `dic.products` | `dm.revenue_daily_v` | даг | то же: категория товара — `dictGet` по `item_sku` заказа, ключ словаря `sku` |
| `dds.event_v` | `dm.purchase_vs_orders_v` | даг | `dm_transform`, `sql/dm/purchase_vs_orders_*.sql` |
| `dds.order_v` | `dm.purchase_vs_orders_v` | даг | то же |
У `dm.dq_summary_v` стрелок нет. Ее наполняет даг `dq_check`, но он не
переносит данные, а сравнивает: витрины DM (в том числе те, что строятся на
уроках и в DDL стенда отсутствуют) с DDS, ODS, словарем и паспортом мира.
Рисовать это стрелками — значит утопить карту в линиях ради одного узла.
Узел остается в колонке DM, а его карточка в поле «Кто наполняет» объясняет
это своими словами и перечисляет пять проверок, которые запускает
`dags/dq_check.py`, с именами их SQL из `sql/dq/`.
У `dic.products` входящих стрелок тоже нет: словарь читает CSV-файл
(`dic.products_file`), это не путь данных через слои. Карточка называет
источник в поле «Из чего состоит».
## Карточка узла
Поля, в этом порядке; пустое поле не показывается:
1. **Имя** — заголовок карточки; под ним слой и потоки.
2. **Что это** — одна-две фразы. Основа — колонка «Что это» карты таблиц и
главы путеводителя; не пересказ справочника.
3. **Как читать** — какой поверхностью читать и почему (`_v` — публичный
договор; `_dist` у STG и ошибок — потому что представления там нет).
4. **Из чего состоит** — список физических объектов с одной фразой на каждый:
`stg.hits_raw_kafka` — чтец топика; `stg.hits_raw_rep` — реплицируемая
таблица на шарде; и так далее. Имена — точно как в DDL.
5. **Ключи** — колонки, которыми узел соединяется с соседями, с указанием, как
тот же смысл называется у соседа. Основа — таблица «Сквозные ключи» главы о
хранилище; имена колонок сверить с DDL.
6. **Кто наполняет и как часто** — механизм и ритм из таблицы стрелок; для
DDS и DM — правило обновления: замена партиции дня, полный пересчет новой
версией, `FINAL` в представлении.
7. **Ссылки** — «Справочник» (раздел `docs/architecture/`, для DDS и DM —
файл объекта в `docs/architecture/dds/` или раздел `dm.md`) и «DDL»
(файл в `sql/ddl/`). Ссылки абсолютные, на ветку `main` Gitea:
`https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/<путь>#<якорь>`.
Карта открывается из любого места, относительные ссылки там не живут.
Якорь — фактический id заголовка на странице Gitea, а не слаг по
догадке: у заголовков с обратными кавычками он другой (`dm.md`
дает `dmrevenue_daily_v`, не `dm-revenue_daily_v`). Снимать так:
`curl -sS <url файла> | grep -oE 'id="user-content-[^"]*"'`.
Обязательные поля: у каждого узла — «Что это», «Как читать», «Кто наполняет
и как часто», «Ссылки»; «Из чего состоит» — у всех узлов, кроме топиков
Kafka (у `dds.event_v` поле состоит из одной фразы: только представление над
`ods.event_v`, своей таблицы нет); «Ключи» — у всех узлов ODS,
DDS, DM и у словаря, кроме таблиц ошибок и `dm.dq_summary_v`. В «Ссылках» у
узлов хранилища обе ссылки — справочник и DDL; у топиков Kafka вместо DDL —
описание формата (`docs/formats/clickstream-event.md` для `hits`,
`docs/architecture/orders/snapshot.md` для `orders`).
Карточка стрелки: заголовок «откуда → куда», механизм, ритм, кто именно
(даг и задача или матвью), ссылка на SQL и на раздел справочника
(`docs/architecture/etl.md`, `docs/architecture/orders/ingestion.md`,
`docs/architecture/dds/*.md`, `docs/architecture/dm.md`).
Каждый факт карточек берется из DDL, SQL, справочников `docs/architecture/`,
`CONTEXT.md` или глав путеводителя. Ничего не додумывается. Нашли расхождение
справочника с кодом — карточка следует коду, расхождение идет в отчет.
## Взаимодействие
- **Щелчок по узлу** — карточка в боковой панели справа; на карте узел
выделен, его входящий путь (все узлы и стрелки, из которых он собран, до
Kafka) и исходящий путь (все, что из него получается, до DM) подсвечены,
остальное приглушено. Повторный щелчок или Esc снимает выделение. У узлов
без стрелок (`dic.products` на входе, `dm.dq_summary_v` целиком) путь
пуст — это не ошибка, подсвечивается только сам узел.
- **Щелчок по стрелке** — карточка стрелки в той же панели; стрелка и ее два
узла выделены.
- **Наведение** — узел и его прямые соседи подсвечиваются слабее, чем при
щелчке; уходит вместе с курсором.
- **Переключатель потока** над картой: «Все», «События», «Заказы». Узлы и
стрелки не выбранного потока приглушаются, но остаются на месте: карта не
перестраивается.
- **Легенда** трех механизмов — всегда видна, под переключателем.
- Пока ничего не выбрано, панель показывает вводку в пять-семь строк: что
это за карта, как ею пользоваться, ссылка на главу «Хранилище и его слои»
(абсолютная, на Gitea).
Раскладку считает скрипт в браузере из данных: колонка — по слою, строка —
по полю `order` узла внутри слоя, зона `dic` — под колонками DDS и DM.
Стрелки между колонками — кривые Безье от правого края узла к левому краю
следующего. Стрелки внутри одной колонки (три в DDS) выходят из правого
края источника, изгибаются в промежуток между колонками и входят в правый
край цели; порядок узлов в DDS — `dds.event_v`, `dds.session_v`,
`dds.order_v`, `dds.identity_map_v`, чтобы источник всегда стоял выше цели.
Стрелка из `dic` выходит из верхнего края словаря. Карта — один SVG с `viewBox`, тянется
по ширине окна, при узком окне появляется горизонтальная прокрутка. Ни
масштабирования колесом, ни перетаскивания: карта помещается на экран.
Цвета: серые узлы и линии, один акцент для выделения, вторым тоном —
приглушение; потоки различаются не цветом, а фильтром. Шрифт системный.
Все тексты интерфейса — по-русски, обращение на «вы», тире «—» и
кавычки-елочки, без буквы «ё». Никаких внешних запросов: ни шрифтов, ни
скриптов, ни картинок по сети; файл работает по `file://`.
## Где карта упоминается
- `docs/handbook/README.md`: после раздела «Как читать» — раздел «Карта
хранилища» в четыре-пять строк: что это, ссылка на `architecture-map.html`,
совет открыть рядом с главой о хранилище. Gitea показывает файл как текст,
а не как страницу, поэтому раздел говорит прямо: откройте файл из своей
копии репозитория или скачайте его кнопкой «Raw» и откройте в браузере.
Остальной текст не меняется.
- `docs/handbook/storage-layers.md`, раздел «Куда дальше»: одна фраза со
ссылкой на карту и той же оговоркой в двух словах — открывать из своей
копии репозитория. Остальной текст главы не меняется.
- `Makefile`, цель `lint`: третья строка
`uv run docs/handbook/architecture-map/render.py --check`, а в две строки
`ruff` добавляется путь `docs/handbook/architecture-map`. Комментарий над
целью дополняется одной фразой: почему проверка рендера живет здесь
(быстрая, стенд не нужен, ловит протухший рендер).
- `docs/architecture/testing.md`: там, где описано, что утверждает
`make lint`, — одна фраза про проверку рендера карты; длительность цели
и дата замера, которые там записаны, обновляются по фактическому прогону.
Больше ничего.
- `AGENTS.md`, раздел «Структура», абзац про `docs/handbook/`: после фразы
про индекс папки вставить ровно это предложение: «Исходники интерактивной
карты лежат подпапкой `architecture-map/`, а ее рендер — файлом
`architecture-map.html` рядом с главами.» Больше ничего.
- `render.py` объявляет `requires-python = ">=3.13"`: так `target-version`
корневого `ruff.toml` для него верен, и сам `ruff.toml` не трогается.
## Критерии приемки
- [ ] `uv run docs/handbook/architecture-map/render.py` собирает
`docs/handbook/architecture-map.html`; повторный запуск не меняет файл;
`--check` зеленый на свежем рендере и красный, если в HTML изменить
один символ.
- [ ] HTML открывается из `file://` без сети: ни одного внешнего `src`,
`href` на стили и скрипты, `@import`, `url()` на сетевые адреса.
Ссылки на Gitea в карточках — обычные `<a target="_blank">`.
- [ ] Все узлы и стрелки из таблиц (с поправками, названными в отчете) на
месте; каждый узел открывает карточку со всеми непустыми полями; каждая
стрелка открывает свою карточку.
- [ ] Выделение пути, наведение, переключатель потока, Esc, легенда и вводка
работают так, как описано в «Взаимодействии». Браузера у исполнителя нет:
он проверяет скрипт чтением и, где возможно, прогоном чистых функций
раскладки и достижимости через `node -e`; проверка в браузере — за
оркестратором на приемке, исполнитель называет в отчете, что проверить
щелчками.
- [ ] Каждое имя объекта, колонки, дага, задачи и SQL-файла в `map.yaml`
существует в коде под этим именем.
- [ ] Каждая ссылка карточек ведет на существующий файл в `main`
(`curl -sI` отвечает 200) и на фактический id заголовка (снят командой
из раздела «Карточка узла», не угадан).
- [ ] Объемы в пределах раздела «Файлы».
- [ ] `make lint` зеленый; `ruff` в него теперь проверяет и `render.py`.
- [ ] `docs/handbook/README.md`, `storage-layers.md`, `Makefile`,
`testing.md` изменены только в названных местах.
- [ ] В прозе карточек, интерфейса и `README.md` карты нет «ё», слов «менти»
и «оператор», олицетворений («дашборд спрашивает», «заказ ездит»);
обращение на «вы».
- [ ] Никаких других файлов, кроме перечисленных в разделах «Файлы» и «Где
карта упоминается», не создано и не изменено.
## Границы
- Справочники `docs/architecture/` (кроме названных правок в `testing.md`),
`CONTEXT.md`, корневой `README.md`, `ruff.toml`, `AGENTS.md` (кроме одного
названного предложения), DDL, SQL, даги и код генератора не правятся. Расхождение справочника с кодом — в отчет, не в правку.
- Никаких JS-библиотек и сборщиков: ни mermaid, ни d3, ни npm. Скрипт — на
чистом JS, стили — на чистом CSS, оба внутри шаблона.
- Никаких коммитов и веток: правки остаются в рабочем дереве.
- Никаких изменений вне репозитория.
## Сначала прочитать
1. `docs/handbook/storage-layers.md` — глава, которую карта дополняет;
таблица «Сквозные ключи» и подписи слоев берутся отсюда.
2. `docs/architecture/storage.md` — разделы «Имена объектов», «Служебные
колонки», «Актуальные события», «Версии заказов», «Справочники», «Карта
таблиц».
3. `sql/ddl/*.sql` — имена и устройство всех объектов.
4. `sql/stg/`, `sql/ods/`, `sql/dds/`, `sql/dm/`, `sql/dq/` — источники
каждой стрелки.
5. `dags/` — какая задача какого дага исполняет какой SQL; имена дагов и
задач — отсюда.
6. `docs/architecture/etl.md`, `docs/architecture/orders/ingestion.md`,
`docs/architecture/dds/README.md`, `docs/architecture/dm.md` — ритмы и
правила обновления для карточек.
7. `docs/handbook/README.md` — куда встает раздел о карте.
8. `AGENTS.md`, раздел «Язык».
## Команды проверки
```bash
uv run docs/handbook/architecture-map/render.py
uv run docs/handbook/architecture-map/render.py --check
make lint
grep -nE 'https?://' docs/handbook/architecture-map.html | grep -v -e git.dementev.space -e www.w3.org
grep -n 'ё' docs/handbook/architecture-map/map.yaml docs/handbook/architecture-map/template.html docs/handbook/architecture-map/README.md
```
Последние две команды должны молчать. Стенд для задачи не нужен.
Что получилось.docs/handbook/architecture-map.html — один самодостаточный файл: пять колонок Kafka → STG → ODS → DDS → DM, зона dic сбоку, 17 узлов и 17 стрелок. Щелчок по узлу открывает карточку (что это, как читать, из чего состоит, ключи, кто наполняет, ссылки в справочник и DDL на Gitea) и подсвечивает входящий и исходящий путь; щелчок по стрелке — механизм, ритм, даг и задача, SQL. Переключатель потока приглушает чужую часть карты, легенда трех механизмов всегда на виду. Исходники — docs/handbook/architecture-map/ (map.yaml, template.html, render.py, README.md); make lint проверяет ruff для сборщика и свежесть рендера.
Решения по ходу.
У dm.dq_summary_v стрелок нет: dq_check сравнивает, а не переносит; карточка объясняет это и называет пять проверок. У dic.products входа нет — словарь читает CSV.
Стрелки внутри колонки DDS идут петлями через промежуток между колонками; кривая из словаря проведена так, чтобы не проходить под узлами.
Якоря ссылок — фактические id заголовков Gitea (у заголовков с обратными кавычками они не совпадают со слагом); ё в якорях закодирована.
Карта тянется по ширине окна; уже 1530 px панель карточки уходит под карту, чтобы колонка DM не обрезалась.
Gitea показывает HTML как текст: README путеводителя и глава о хранилище говорят открывать файл из своей копии репозитория.
Шаблон 453 строки при бюджете 450 — форма важнее бюджета.
Расхождения справочника с кодом (не правились):storage.md во вводке называет две физические модели DDS этапа 4 и не упоминает карту идентичностей; описание 55-dm-tables.sql там же не упоминает dm.dq_summary (карта таблиц ниже его содержит).
Конвейер. Постановка — Fable, ревью постановки — Opus (18 находок, все внесены). Реализация — Codex (Sol), саморевью 7/7. Два слепых ревью Opus: уместность и вкус (11 находок), код (10); два круга починки, все закрыты; от оркестратора — проверка в headless Chromium (карта не помещалась в окно, совет читать Kafka-таблицу напрямую). Вычитка прозы — Sol: 36 находок, 34 внесены.
Дальше по карте: ТЗ ступеней 2–7 в docs/labs/ — следующий тикет открывается при старте работы.
Сделано, PR #223 (сливается с `Closes #222`).
**Что получилось.** `docs/handbook/architecture-map.html` — один самодостаточный файл: пять колонок Kafka → STG → ODS → DDS → DM, зона `dic` сбоку, 17 узлов и 17 стрелок. Щелчок по узлу открывает карточку (что это, как читать, из чего состоит, ключи, кто наполняет, ссылки в справочник и DDL на Gitea) и подсвечивает входящий и исходящий путь; щелчок по стрелке — механизм, ритм, даг и задача, SQL. Переключатель потока приглушает чужую часть карты, легенда трех механизмов всегда на виду. Исходники — `docs/handbook/architecture-map/` (`map.yaml`, `template.html`, `render.py`, `README.md`); `make lint` проверяет ruff для сборщика и свежесть рендера.
**Решения по ходу.**
- У `dm.dq_summary_v` стрелок нет: `dq_check` сравнивает, а не переносит; карточка объясняет это и называет пять проверок. У `dic.products` входа нет — словарь читает CSV.
- Стрелки внутри колонки DDS идут петлями через промежуток между колонками; кривая из словаря проведена так, чтобы не проходить под узлами.
- Якоря ссылок — фактические id заголовков Gitea (у заголовков с обратными кавычками они не совпадают со слагом); `ё` в якорях закодирована.
- Карта тянется по ширине окна; уже 1530 px панель карточки уходит под карту, чтобы колонка DM не обрезалась.
- Gitea показывает HTML как текст: README путеводителя и глава о хранилище говорят открывать файл из своей копии репозитория.
- Шаблон 453 строки при бюджете 450 — форма важнее бюджета.
**Расхождения справочника с кодом (не правились):** `storage.md` во вводке называет две физические модели DDS этапа 4 и не упоминает карту идентичностей; описание `55-dm-tables.sql` там же не упоминает `dm.dq_summary` (карта таблиц ниже его содержит).
**Конвейер.** Постановка — Fable, ревью постановки — Opus (18 находок, все внесены). Реализация — Codex (Sol), саморевью 7/7. Два слепых ревью Opus: уместность и вкус (11 находок), код (10); два круга починки, все закрыты; от оркестратора — проверка в headless Chromium (карта не помещалась в окно, совет читать Kafka-таблицу напрямую). Вычитка прозы — Sol: 36 находок, 34 внесены.
**Дальше по карте:** ТЗ ступеней 2–7 в `docs/labs/` — следующий тикет открывается при старте работы.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of #206
Цель
Интерактивная карта архитектуры хранилища — один самодостаточный HTML-файл
docs/handbook/architecture-map.html. Читатель открывает его двойным щелчкомиз любой копии репозитория, без сети и сервера, и видит путь данных через
слои: топики Kafka → STG → ODS → DDS → DM, сбоку зона
dic. Щелчок по узлупоказывает карточку объекта: что это, какой поверхностью его читать, из чего
он физически состоит, какими ключами соединяется с соседями, кто и в каком
ритме его наполняет, ссылки в справочник и DDL. Щелчок по стрелке показывает,
кто переносит данные между узлами и каким SQL.
Охват первой версии — только слои хранилища и путь данных через них
(резолюция #208). Потоки Airflow как второй слой карты, читатели DM
(Superset, Grafana) и генератор в эту версию не входят.
Урок карты: у одного смысла в каждом слое свое имя и своя поверхность, а
данные между слоями переносят три разных механизма — матвью непрерывно, даг
пачкой, представление без копии. Глава «Хранилище и его слои» это
рассказывает, карта дает это увидеть и потрогать.
Файлы
docs/handbook/architecture-map.htmldocs/handbook/architecture-map/map.yamldocs/handbook/architecture-map/template.htmldocs/handbook/architecture-map/render.pymap.yaml, вкладывает его в шаблон как JSON, пишет HTML. Флаг--checkсравнивает свежий рендер с файлом в репозитории и падает при расхожденииdocs/handbook/architecture-map/README.mdrender.py— скриптuvс inline-метаданными (# /// script), единственнаязависимость —
pyyaml. Запуск:uv run docs/handbook/architecture-map/render.py.Рендер детерминирован: тот же
map.yamlиtemplate.htmlдают байт в байт тотже HTML (без времени сборки, без случайных id; ключи JSON — в порядке
map.yaml, не сортируются;ensure_ascii=False). Порядок полей карточкизадает шаблон, а не порядок ключей в данных.
Объем:
render.py— не длиннее 80 строк;template.html— не длиннее 450строк; готовый HTML — не тяжелее 80 КБ. Если форма и бюджет спорят,
побеждает форма: превышение назвать в отчете с причиной.
Содержание: слои, узлы, стрелки
Слои — колонки слева направо. У каждой колонки заголовок и подпись-вопрос из
главы о хранилище:
Зона
dic— не колонка, а отдельный узел под колонками DDS и DM с подписью«сбоку цепочки: справочники».
Узел — один логический объект; его физические формы (
_kafka,_rep,_dist,_mv,_stage_rep,_stage_dist,_v; таблицы ошибок*_errors_*— отдельные узлы, у них свои формы) живут внутри карточки, а не отдельными
узлами. Имя узла на карте — имя, которым читатель его читает: поверхность
_vтам, где представление есть; где его нет (STG, таблицы ошибок) —_dist;для словаря —
dic.products, для Kafka — имя топика.Состав узлов (проверить по DDL в
sql/ddl/и карте таблицdocs/architecture/storage.md#карта-таблиц; расхождение — исправить у себя иназвать в отчете):
hitsordersstg.hits_raw_diststg.orders_raw_distods.event_vods.event_errors_distods.order_vods.order_errors_distdds.event_vdds.session_vdds.order_vdds.identity_map_vdm.daily_traffic_vdm.revenue_daily_vdm.purchase_vs_orders_vdm.dq_summary_vdic.productsСтрелка — перенос данных из узла в узел. У каждой стрелки есть механизм, и
механизмов ровно три; они же — легенда карты:
orders_ingest) или на каждом прогоне конвейера (dds_transform,dm_transform,dq_check)Состав стрелок — гипотеза постановки; источники каждой проверить по SQL в
sql/stg/,sql/ods/,sql/dds/,sql/dm/и по дагам вdags/(sql/dq/стрелок не дает, см. ниже).
Стрелка, которой SQL не подтверждает, убирается; стрелка, которую SQL
показывает, а таблица нет, добавляется; и то и другое — в отчет.
hitsstg.hits_raw_diststg.hits_raw_kafka, матвьюstg.hits_raw_mvstg.hits_raw_distods.event_vods.event_mvstg.hits_raw_distods.event_errors_distods.event_errors_mvordersstg.orders_raw_distorders_ingest, задачаpull_batch, чтецstg.orders_raw_kafka,sql/stg/orders_raw_load.sqlstg.orders_raw_distods.order_vorders_ingest, задачаparse_batch,sql/ods/order_parse.sqlstg.orders_raw_distods.order_errors_distods.event_vdds.event_vsql/ddl/50-dds-views.sqldds.event_vdds.session_vdds_transform,sql/dds/session_*.sqlods.order_vdds.order_vdds_transform,sql/dds/order_*.sqldds.event_vdds.identity_map_vdds_transform,sql/dds/identity_map_rebuild.sqldds.order_vdds.identity_map_vdds.session_vdm.daily_traffic_vdm_transform,sql/dm/daily_traffic_rebuild.sqldds.identity_map_vdm.daily_traffic_vclient_iddds.order_vdm.revenue_daily_vdm_transform,sql/dm/revenue_daily_*.sqldic.productsdm.revenue_daily_vdictGetпоitem_skuзаказа, ключ словаряskudds.event_vdm.purchase_vs_orders_vdm_transform,sql/dm/purchase_vs_orders_*.sqldds.order_vdm.purchase_vs_orders_vУ
dm.dq_summary_vстрелок нет. Ее наполняет дагdq_check, но он непереносит данные, а сравнивает: витрины DM (в том числе те, что строятся на
уроках и в DDL стенда отсутствуют) с DDS, ODS, словарем и паспортом мира.
Рисовать это стрелками — значит утопить карту в линиях ради одного узла.
Узел остается в колонке DM, а его карточка в поле «Кто наполняет» объясняет
это своими словами и перечисляет пять проверок, которые запускает
dags/dq_check.py, с именами их SQL изsql/dq/.У
dic.productsвходящих стрелок тоже нет: словарь читает CSV-файл(
dic.products_file), это не путь данных через слои. Карточка называетисточник в поле «Из чего состоит».
Карточка узла
Поля, в этом порядке; пустое поле не показывается:
главы путеводителя; не пересказ справочника.
_v— публичныйдоговор;
_distу STG и ошибок — потому что представления там нет).stg.hits_raw_kafka— чтец топика;stg.hits_raw_rep— реплицируемаятаблица на шарде; и так далее. Имена — точно как в DDL.
тот же смысл называется у соседа. Основа — таблица «Сквозные ключи» главы о
хранилище; имена колонок сверить с DDL.
DDS и DM — правило обновления: замена партиции дня, полный пересчет новой
версией,
FINALв представлении.docs/architecture/, для DDS и DM —файл объекта в
docs/architecture/dds/или разделdm.md) и «DDL»(файл в
sql/ddl/). Ссылки абсолютные, на веткуmainGitea:https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/<путь>#<якорь>.Карта открывается из любого места, относительные ссылки там не живут.
Якорь — фактический id заголовка на странице Gitea, а не слаг по
догадке: у заголовков с обратными кавычками он другой (
dm.mdдает
dmrevenue_daily_v, неdm-revenue_daily_v). Снимать так:curl -sS <url файла> | grep -oE 'id="user-content-[^"]*"'.Обязательные поля: у каждого узла — «Что это», «Как читать», «Кто наполняет
и как часто», «Ссылки»; «Из чего состоит» — у всех узлов, кроме топиков
Kafka (у
dds.event_vполе состоит из одной фразы: только представление надods.event_v, своей таблицы нет); «Ключи» — у всех узлов ODS,DDS, DM и у словаря, кроме таблиц ошибок и
dm.dq_summary_v. В «Ссылках» уузлов хранилища обе ссылки — справочник и DDL; у топиков Kafka вместо DDL —
описание формата (
docs/formats/clickstream-event.mdдляhits,docs/architecture/orders/snapshot.mdдляorders).Карточка стрелки: заголовок «откуда → куда», механизм, ритм, кто именно
(даг и задача или матвью), ссылка на SQL и на раздел справочника
(
docs/architecture/etl.md,docs/architecture/orders/ingestion.md,docs/architecture/dds/*.md,docs/architecture/dm.md).Каждый факт карточек берется из DDL, SQL, справочников
docs/architecture/,CONTEXT.mdили глав путеводителя. Ничего не додумывается. Нашли расхождениесправочника с кодом — карточка следует коду, расхождение идет в отчет.
Взаимодействие
выделен, его входящий путь (все узлы и стрелки, из которых он собран, до
Kafka) и исходящий путь (все, что из него получается, до DM) подсвечены,
остальное приглушено. Повторный щелчок или Esc снимает выделение. У узлов
без стрелок (
dic.productsна входе,dm.dq_summary_vцеликом) путьпуст — это не ошибка, подсвечивается только сам узел.
узла выделены.
щелчке; уходит вместе с курсором.
стрелки не выбранного потока приглушаются, но остаются на месте: карта не
перестраивается.
это за карта, как ею пользоваться, ссылка на главу «Хранилище и его слои»
(абсолютная, на Gitea).
Раскладку считает скрипт в браузере из данных: колонка — по слою, строка —
по полю
orderузла внутри слоя, зонаdic— под колонками DDS и DM.Стрелки между колонками — кривые Безье от правого края узла к левому краю
следующего. Стрелки внутри одной колонки (три в DDS) выходят из правого
края источника, изгибаются в промежуток между колонками и входят в правый
край цели; порядок узлов в DDS —
dds.event_v,dds.session_v,dds.order_v,dds.identity_map_v, чтобы источник всегда стоял выше цели.Стрелка из
dicвыходит из верхнего края словаря. Карта — один SVG сviewBox, тянетсяпо ширине окна, при узком окне появляется горизонтальная прокрутка. Ни
масштабирования колесом, ни перетаскивания: карта помещается на экран.
Цвета: серые узлы и линии, один акцент для выделения, вторым тоном —
приглушение; потоки различаются не цветом, а фильтром. Шрифт системный.
Все тексты интерфейса — по-русски, обращение на «вы», тире «—» и
кавычки-елочки, без буквы «ё». Никаких внешних запросов: ни шрифтов, ни
скриптов, ни картинок по сети; файл работает по
file://.Где карта упоминается
docs/handbook/README.md: после раздела «Как читать» — раздел «Картахранилища» в четыре-пять строк: что это, ссылка на
architecture-map.html,совет открыть рядом с главой о хранилище. Gitea показывает файл как текст,
а не как страницу, поэтому раздел говорит прямо: откройте файл из своей
копии репозитория или скачайте его кнопкой «Raw» и откройте в браузере.
Остальной текст не меняется.
docs/handbook/storage-layers.md, раздел «Куда дальше»: одна фраза соссылкой на карту и той же оговоркой в двух словах — открывать из своей
копии репозитория. Остальной текст главы не меняется.
Makefile, цельlint: третья строкаuv run docs/handbook/architecture-map/render.py --check, а в две строкиruffдобавляется путьdocs/handbook/architecture-map. Комментарий надцелью дополняется одной фразой: почему проверка рендера живет здесь
(быстрая, стенд не нужен, ловит протухший рендер).
docs/architecture/testing.md: там, где описано, что утверждаетmake lint, — одна фраза про проверку рендера карты; длительность целии дата замера, которые там записаны, обновляются по фактическому прогону.
Больше ничего.
AGENTS.md, раздел «Структура», абзац проdocs/handbook/: после фразыпро индекс папки вставить ровно это предложение: «Исходники интерактивной
карты лежат подпапкой
architecture-map/, а ее рендер — файломarchitecture-map.htmlрядом с главами.» Больше ничего.render.pyобъявляетrequires-python = ">=3.13": такtarget-versionкорневого
ruff.tomlдля него верен, и самruff.tomlне трогается.Критерии приемки
uv run docs/handbook/architecture-map/render.pyсобираетdocs/handbook/architecture-map.html; повторный запуск не меняет файл;--checkзеленый на свежем рендере и красный, если в HTML изменитьодин символ.
file://без сети: ни одного внешнегоsrc,hrefна стили и скрипты,@import,url()на сетевые адреса.Ссылки на Gitea в карточках — обычные
<a target="_blank">.месте; каждый узел открывает карточку со всеми непустыми полями; каждая
стрелка открывает свою карточку.
работают так, как описано в «Взаимодействии». Браузера у исполнителя нет:
он проверяет скрипт чтением и, где возможно, прогоном чистых функций
раскладки и достижимости через
node -e; проверка в браузере — заоркестратором на приемке, исполнитель называет в отчете, что проверить
щелчками.
map.yamlсуществует в коде под этим именем.
main(
curl -sIотвечает 200) и на фактический id заголовка (снят командойиз раздела «Карточка узла», не угадан).
make lintзеленый;ruffв него теперь проверяет иrender.py.docs/handbook/README.md,storage-layers.md,Makefile,testing.mdизменены только в названных местах.README.mdкарты нет «ё», слов «менти»и «оператор», олицетворений («дашборд спрашивает», «заказ ездит»);
обращение на «вы».
карта упоминается», не создано и не изменено.
Границы
docs/architecture/(кроме названных правок вtesting.md),CONTEXT.md, корневойREADME.md,ruff.toml,AGENTS.md(кроме одногоназванного предложения), DDL, SQL, даги и код генератора не правятся. Расхождение справочника с кодом — в отчет, не в правку.
чистом JS, стили — на чистом CSS, оба внутри шаблона.
Сначала прочитать
docs/handbook/storage-layers.md— глава, которую карта дополняет;таблица «Сквозные ключи» и подписи слоев берутся отсюда.
docs/architecture/storage.md— разделы «Имена объектов», «Служебныеколонки», «Актуальные события», «Версии заказов», «Справочники», «Карта
таблиц».
sql/ddl/*.sql— имена и устройство всех объектов.sql/stg/,sql/ods/,sql/dds/,sql/dm/,sql/dq/— источникикаждой стрелки.
dags/— какая задача какого дага исполняет какой SQL; имена дагов изадач — отсюда.
docs/architecture/etl.md,docs/architecture/orders/ingestion.md,docs/architecture/dds/README.md,docs/architecture/dm.md— ритмы иправила обновления для карточек.
docs/handbook/README.md— куда встает раздел о карте.AGENTS.md, раздел «Язык».Команды проверки
Последние две команды должны молчать. Стенд для задачи не нужен.
Сделано, PR #223 (сливается с
Closes #222).Что получилось.
docs/handbook/architecture-map.html— один самодостаточный файл: пять колонок Kafka → STG → ODS → DDS → DM, зонаdicсбоку, 17 узлов и 17 стрелок. Щелчок по узлу открывает карточку (что это, как читать, из чего состоит, ключи, кто наполняет, ссылки в справочник и DDL на Gitea) и подсвечивает входящий и исходящий путь; щелчок по стрелке — механизм, ритм, даг и задача, SQL. Переключатель потока приглушает чужую часть карты, легенда трех механизмов всегда на виду. Исходники —docs/handbook/architecture-map/(map.yaml,template.html,render.py,README.md);make lintпроверяет ruff для сборщика и свежесть рендера.Решения по ходу.
dm.dq_summary_vстрелок нет:dq_checkсравнивает, а не переносит; карточка объясняет это и называет пять проверок. Уdic.productsвхода нет — словарь читает CSV.ёв якорях закодирована.Расхождения справочника с кодом (не правились):
storage.mdво вводке называет две физические модели DDS этапа 4 и не упоминает карту идентичностей; описание55-dm-tables.sqlтам же не упоминаетdm.dq_summary(карта таблиц ниже его содержит).Конвейер. Постановка — Fable, ревью постановки — Opus (18 находок, все внесены). Реализация — Codex (Sol), саморевью 7/7. Два слепых ревью Opus: уместность и вкус (11 находок), код (10); два круга починки, все закрыты; от оркестратора — проверка в headless Chromium (карта не помещалась в окно, совет читать Kafka-таблицу напрямую). Вычитка прозы — Sol: 36 находок, 34 внесены.
Дальше по карте: ТЗ ступеней 2–7 в
docs/labs/— следующий тикет открывается при старте работы.