docs(course): каркас курса и переобвязка уроков 0–6 на путь import (#21)

Зачем: после редизайна пути менти курс ссылался на старый путь
generated-history-analytics/backfill и не проходился по новому стенду.

Что: в README курса — единый блок подготовки и канонического сброса
(make clean -> make up + ddl_init/world_init -> make superset-init),
таблица уроков дополнена лабами 07–08 («в работе»); LESSON_STANDARD и
уроки 0–6 ссылаются на канонический блок; урок 1 переведён на дозаливку
через world_next_day (кнопкой-анонсом, цена в минутах названа); урок 4 —
лесенка DAG-ов; урок 5 — словарь «база import / живой поток»; урок 6
обязателен; цифры старого мира помечены маркером «сверить-на-стенде».

Проверка: grep по generated-history-analytics/backfill в docs/course/
пуст; правки только в docs/course/; git diff --check чистый.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 13:22:41 +03:00
co-authored by Claude Opus 4.8
parent b57a4e2292
commit cc1cffe2f3
11 changed files with 142 additions and 171 deletions
+16 -14
View File
@@ -84,19 +84,12 @@ ODS мы пересобираем целиком, одной задачей Airf
## 2. Руки: смотрим базовый прогон
Поднимаем стенд и создаём стартовую историю. Это штатный путь курса: готовый источник
данных стенда пишет события в Kafka, ClickHouse читает их в STG, затем batch строит
ODS, DDS и DM. Файлы `data/*.jsonl` пока остаются только кладовкой значений для этого
источника, а не источником аналитического контура.
Подготовь стенд по
[канонической инструкции курса](../README.md#подготовка-и-канонический-сброс).
После успешного `world_init` эталонный мир уже прошёл путь Kafka → STG → ODS → DDS → DM.
```bash
make generated-history-analytics
make up
```
Команда прогоняет всю цепочку слоёв и прямо в консоли печатает то, что нам нужно сейчас, —
блок **«Статистика ODS»**. Это просто счётчики строк по всем восьми таблицам слоя (четыре
основных и четыре с ошибками). Пример формы вывода:
Ниже — форма блока **«Статистика ODS»**, который печатает `make transform`. Это просто
счётчики строк по всем восьми таблицам слоя (четыре основных и четыре с ошибками):
```
Статистика ODS:
@@ -111,12 +104,14 @@ make up
│ ods.geo_by_click_errors │ 0 │
└────────────────────────────┴──────┘
```
<!-- сверить-на-стенде -->
Прочитаем эту табличку — в ней три вещи, которые стоит заметить.
**Все четыре `*_errors` — по нулям.** Значит, стартовая история чистая: ни одна запись не дала
ошибки разбора, столбец `parse_errors` у всех пустой. Это нормально — данные стенда аккуратные.
Ошибки мы увидим в секции 4, когда сами их устроим.
<!-- сверить-на-стенде -->
**`browser` и `location` идут в одном зерне события.** Сколько событий пришло, столько строк
и ожидаем увидеть после типизации, если ключи валидны.
@@ -254,6 +249,7 @@ ORDER BY (click_id)
её через `toFloat64OrNull` — «привести к дробному числу». Заменим тип на целочисленный —
`toInt64OrNull`, «привести к целому». Для строки `"50.82709"` целого числа не получится
(там точка, дробная часть), и функция вернёт `NULL`. То есть широта просто исчезнет.
<!-- сверить-на-стенде -->
Из секции 3 помним: разбор продублирован, поэтому правок будет **две** — в обоих `INSERT`
блока `GEO EVENTS`. Открой `sql/ods/20_stg_to_ods.sql`, найди оба вхождения и в каждом замени
@@ -293,6 +289,7 @@ LIMIT 4;
│ 9ffd819b-... │ ᴺᵁᴸᴸ │ 85.37752 │ ['bad_geo_latitude'] │
└──────────────┴──────────────┴───────────────┴──────────────────────┘
```
<!-- сверить-на-стенде -->
Вот теперь видно всё разом — и DQ-split, и «двойной учёт» из секции 3 вживую. Строки с валидным
`click_id` остались в основной таблице с пометкой `bad_geo_latitude`. И те же записи попали в
@@ -307,6 +304,7 @@ LIMIT 4;
> это молча срезало миллисекунды, и время по всему стенду уехало в `1970-01-21`. Ничто на это
> не указывало — поймали только прогоном на стенде. Мораль урока: тип выбирают осознанно, даже
> когда функция «не падает».
<!-- сверить-на-стенде -->
**Верни как было.** Откати обе правки — верни `toFloat64OrNull` в оба места. Если запутался,
проще одной командой откатить весь файл к версии из репозитория:
@@ -316,8 +314,10 @@ git checkout -- sql/ods/20_stg_to_ods.sql
make transform
```
После этого `geo_by_click_errors` снова `0`, широта на месте. А если стенд совсем «поплыл» —
всегда есть полный чистый прогон: `make generated-history-analytics && make up`.
После этого `geo_by_click_errors` снова `0`, широта на месте. А если стенд совсем
«поплыл», пройди
[канонический сброс](../README.md#подготовка-и-канонический-сброс).
<!-- сверить-на-стенде -->
---
@@ -329,6 +329,7 @@ make transform
| почему `device`/`geo` меньше событий | запрос `uniqExact(click_id)` по `stg.geo_raw` | число различных `click_id` совпадает с `ods.geo_by_click` |
| правка из секции 4 | блок «Статистика ODS» | `ods.geo_by_click_errors` прыгнул с `0` на ненулевое число |
| правка из секции 4 | `SELECT geo_latitude, parse_errors FROM ods.geo_by_click` | широта `NULL`, в `parse_errors``bad_geo_latitude` |
<!-- сверить-на-стенде -->
---
@@ -339,6 +340,7 @@ make transform
- скрин блока «Статистика ODS», где после правки `ods.geo_by_click_errors` ушёл с `0`
на ненулевое число;
- либо выборка из `ods.geo_by_click` с пустой широтой и меткой `bad_geo_latitude` рядом.
<!-- сверить-на-стенде -->
И проверь себя на словах — примерно эти вопросы всплывут на еженедельном созвоне: