Files
clickstream-ch-kafka-supers…/docs/specs/2026-07-23-course-labs-redesign.md
T
ddadminandClaude Opus 4.8 b57a4e2292 docs(course): спека редизайна лаб курса под три режима менти
- Зачем:
  - курс целиком сидит на старом пути backfill и после редизайна
    пути менти (issue #9) не заработает; issue #7 требует постановки
- Что:
  - спека docs/specs/2026-07-23-course-labs-redesign.md: урок 6
    становится обязательным, две обязательные лабы 07 (next-day:
    инкремент и границы дня) и 08 (continue: живой поток и свежесть),
    канонический сброс через import, вердикты по урокам 0-6 и метадокам
  - handoff для продолжения работы в новой сессии
- Проверка:
  - адверсарное ревью спеки против кода и доков: 8 находок внесены
    решениями, повторная проверка ревьюером — все закрыты

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 12:57:57 +03:00

220 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Редизайн лаб курса под три режима менти
Статус: принято, в работе (ветка `feature/course-labs-redesign`).
Источник: issue #7; аудит курса против стенда и штурм педагогики лаб
с пользователем 2026-07-23. Опирается на
[спеку редизайна пути менти](./2026-07-19-mentee-path-redesign.md)
(реализована, issue #9 закрыт) и термины из `CONTEXT.md`
(три режима менти, эталонный мир, модельное время).
## Проблема
Курс в `docs/course/` (7 уроков + 4 метадокумента) целиком построен на
старом пути `make generated-history-analytics && make up`: ни один документ
не знает про `world_init`, `import`, эталонный мир и `make superset-init`.
После редизайна пути менти курс в текущем виде не заработает.
При этом педагогическое ядро уроков цело: SQL, DAG'и, мониторинг и
Superset актуальны — ломаются только блоки подготовки стенда и обвязка.
А два новых режима роста мира (`next-day`, `continue`) вообще не имеют
уроков, хотя спека пути менти прямо называет их «разные педагогики и
разные лабы».
## Цели
- Менти проходит курс по новому пути: `make up` → Airflow UI
(`ddl_init``world_init` с пустой формой) → `make superset-init`.
- Оба режима роста мира получают по обязательному уроку-лабе с
собственным учебным паттерном.
- Числа в уроках совпадают у всех менти число-в-число (следствие
`import` эталонного мира) и сверены с живым стендом.
- Смешанных состояний не бывает: вся перестройка — один PR.
## Целевая модель
### Маршрут курса
Маршрут остаётся линейным, все уроки — обязательные:
- **Урок 6 (Superset) становится обязательным.** Его опциональность
родом из PRD («делаем, если останется ресурс») — экономия ресурса на
написание, а урок давно написан; причина истекла. Ожидания
работодателей к дата-инженеру теперь включают витринно-BI-слой.
- **Две новые лабы после урока 6, обе обязательные:**
`07_lab_next_day.md`, затем `08_lab_continue.md`. Порядок осознанный:
next-day детерминирована (числа сойдутся у всех — можно печатать
точные ожидания), continue — живая и недетерминированная, «второй шаг
свободы».
- Обе лабы строятся по шаблону `LESSON_STANDARD.md` (шапка + 6 секций).
### Новая семантика сброса
Для лаб «верни как было» — это не откат правки в git: мир вырос.
Канонический сброс — тот же путь, что и первый вход, и описывается он
в одном месте (README курса), уроки ссылаются:
1. `make clean` (сносит и данные, и Superset — это надо назвать явно);
2. `make up` → Airflow UI: `ddl_init``world_init` с пустой формой;
3. `make superset-init`.
Идём UI-путём менти, а не мейнтейнерским `make startup-history-import`.
Старая команда сброса `make generated-history-analytics && make up`
уходит из учебных текстов (остаётся мейнтейнеру и CI).
### Лаба 07 — next-day
Паттерн одной фразой: **пакетный инкремент дня и его границы**
(прод-аналог — «ночью приехал вчерашний день»).
- **Руки:** триггер беспараметрного `world_next_day` → день 4 в
витринах; сверка чисел manifest (точные ожидаемые значения);
`make generated-history-chain-check` на стыке; день-к-дню на дашборде.
- **Центральное открытие — переходящие визиты:** менти SQL-запросом
находит визиты, чьи события лежат по обе стороны полуночи, и осознаёт,
почему суточная нарезка режет живые сессии — откуда берутся проверки
стыков, late data и пересчёт вчерашнего хвоста. Этого нет ни в одном
уроке 0–6. Важно для текста лабы: запрос идёт в `dds.event` напрямую
(`GROUP BY click_id` + `HAVING toDate(min(event_ts)) <>
toDate(max(event_ts))`) — витрина `dm.v_session_overview` не годится,
она группирует по `event_date` и режет переходящий визит на две
строки. Это новый приём, который лаба сама и учит (уроки давали
argMax/JOIN, но не агрегацию визита целиком). Годится любой стык дней:
стыки 1→2 и 2→3 внутри эталонного мира есть точно; живы ли визиты на
стыке 3→4 (заморозка мира могла закрыть открытые сессии) — проверяется
на стенде при реализации. Здесь же — короткая врезка про соответствие
терминов: «визит» из manifest = `click_id` в SQL (в схеме нет таблицы
«визитов», есть `dds.click`).
- **Управляемая правка:** включить расписание `world_next_day` в
Airflow UI → день 5 приезжает сам → выключить обратно. Спека пути
менти прямо проектировала paused-расписание под этот шаг лабы; заодно
менти трогает paused/`catchup`.
- **Цена роста — коротким наблюдением:** в длительностях задач Airflow
генерация дня ~постоянна (инкрементальные счётчики manifest, issue #5),
а `full_refresh` ETL растёт с миром — осознанный долг, ссылка на #8.
- **Самопроверка — крючок про идемпотентность:** «триггерни дважды —
что будет? почему прод-джобы за день устроены иначе?».
- Эталонный путь: `airflow/dags/world_next_day_dag.py`.
### Лаба 08 — continue
Паттерн одной фразой: **живой поток и свежесть данных**.
- **Ядро — потоковый приём ≠ потоковая обработка:** Kafka и STG
пополняются сами (MV урока 1), витрины DM стоят до прогона
`etl_pipeline`; запустил — догнали и снова отстают. Свежесть данных
как явное понятие: «какую свежесть обещаем потребителю?».
- **Руки:** `make generator-continue` → мониторинг урока 5 оживает
(target генератора в UP, алерт `Kafka No Messages Produced` гаснет,
events/min шевелится, офсеты урока 0 растут) → наблюдение расслоения
свежести → догон через `etl_pipeline`.
- **Управляемая правка — останови поток и продолжи:**
`make generator-down` → алерт срабатывает, потребитель дочитывает лаг
до нуля; `make generator-continue` → мир продолжается с места
остановки (популяция и модельные часы пережили рестарт). Это суть
режима continue и рабочий паттерн устойчивости. По шаблону
LESSON_STANDARD лаба всё равно завершается явным шагом «верни как
было» — канонический сброс (см. выше): мир после continue
недетерминирован, и к следующему прохождению стенд возвращается к
эталону.
- **Финальное наблюдение — «мир расходится»:** после continue числа
менти перестают совпадать с эталонными, и это правильно; возвраты
пользователей вживую разводят `users < sessions` (на статике было
невозможно — см. `CONTEXT.md`).
- **Модельное время — только врезкой:** «стенд ускорен в 60 раз, чтобы
сутки потока уложились в ~полчаса; "сейчас" дашборда может обгонять
настенные часы», ручка `GEN_MODEL_TIME_SPEED` — одной строкой.
Это механизм тренажёра, не рабочий паттерн — секционного веса не даём.
Висячую ссылку `CONTEXT.md` про «урок 7» поправить на эту врезку.
Симметрия курса: 07 — про границы времени в пакетном мире, 08 — про
свежесть в потоковом; обе лабы растят один и тот же импортированный мир
двумя способами.
### Существующие уроки и метадокументы
По вердиктам аудита:
- **Все уроки 0–6:** блок подготовки в §2 заменить на новый путь
(import); сам блок вынести в одно каноническое место (README курса),
уроки ссылаются на него.
- **Все цифры уроков 0–6 недействительны.** Старые значения считались
на старом мире (другой профиль, другой объём); с переходом на
эталонный мир каждое цитируемое число каждого урока пересчитывается
заново. Это полноправный этап работы, а не финальная галочка — даже в
уроках, где правки текста минимальны.
- **Урок 1:** управляемая правка «добавь колонку и получи свежие
сообщения» переводится с перегенерации backfill на дозаливку через
`world_next_day` (детерминированно). Известная цена: прогон DAG'а
занимает минуты — урок называет её честно; сам DAG подаётся кнопкой-
анонсом без разбора («подробно — в лабе 07»), чтобы не красть у лабы
её материал.
- **Урок 4:** добавить лесенку `ddl_init``world_init`
`world_next_day` как контекст оркестрации (менти уже прошёл её руками).
- **Урок 5:** словарь «backfill-only стенд» заменить на «база import /
живой поток»; сценарий алерта `Kafka No Messages Produced` переписать
в этих терминах.
- **Урок 6:** снять пометку «опционально»; цифры сверить с эталонным
миром.
- **README курса:** переписать блоки подготовки и «Проверка чистого
маршрута»; таблицу уроков дополнить лабами.
- **PRD:** не переписывать — одна датированная поправка по его же
конвенции (три режима менти, урок 6 обязателен, лабы 07–08).
- **LEARNING_PLAN:** переписать маршрут и статус, сохранить таблицу
аудита эталонных путей; дополнить её `world_next_day_dag.py`.
- **LESSON_STANDARD:** заменить команду сброса на import-путь.
## Чего здесь не делаем
- Не трогаем код стенда: DAG'и, SQL, генератор, инфраструктура — вне
скоупа; работа только с документами курса, README курса и `CONTEXT.md`
(одна ссылка).
- Не чиним рост стоимости `full_refresh` (issue #8) — в лабе 07 он
только показывается.
- Не пишем урок про модельное время — понижено до врезки в лабе 08.
- Не переносим приватные менторские материалы — граница из README курса
остаётся.
- Не переделываем эталонный код уроков 0–6 сверх замены обвязки: аудит
подтвердил, что ядро актуально.
## Проверка
- Чистый стенд: менти проходит весь курс 0–8 по текстам уроков, ни разу
не встретив `generated-history-analytics` и `backfill`.
- Каждая цитируемая цифра уроков и лаб сверена с живым стендом после
`import` эталонного мира (обязательный финальный шаг реализации).
- Лаба 07: после `world_next_day` числа manifest совпадают с
напечатанными в лабе; `make generated-history-chain-check` зелёный;
SQL-запрос из лабы находит хотя бы один переходящий визит (на любом
стыке дней).
- Детерминизм инкремента подтверждён до печати чисел: два независимых
прогона `world_next_day` от свежего import дают одинаковые числа
manifest и контрольную сумму.
- Лаба 08: сценарий стоп/продолжение проходит без потерь (лаг стекает к
нулю, после продолжения числа согласованы с manifest).
- Все внутренние ссылки курса живы; `make test` / `make lint` зелёные
(доки код не трогают — проверка от регрессий по касанию).
## Решения и отклонённые варианты
- **Лабы — секциями внутри уроков 4/5** — отклонено: уроки распухают,
лабы теряют самостоятельность; выбраны отдельные файлы.
- **Урок 6 оставить опциональным, финал лабы 07 — только SQL** —
отклонено: причина опциональности истекла, рынок ждёт BI-навыков;
урок 6 становится обязательным, лабы опираются на дашборд.
- **Модельное время как секция или отдельный урок** — отклонено:
механизм тренажёра, не рабочий паттерн; врезка.
- **Управляемая правка лабы 08 через ручку ×K** — отклонено по той же
причине; выбран стоп/продолжение потока.
- **Дробить работу на несколько PR** — отклонено: половинчатый курс
(часть уроков про import, часть про backfill) хуже любого из крайних
состояний; этапность — коммитами внутри одной ветки.
- **Цифры «ориентировочно», без сверки со стендом** — отклонено:
воспроизводимость число-в-число — главный козырь эталонного мира.
## Влияние на документацию
Вся работа и есть документация: `docs/course/**` (7 уроков + 2 лабы +
4 метадокумента), одна ссылка в `CONTEXT.md`. Корневой `README.md` и
`docs/OPERATIONS.md` уже описывают новый путь — не трогаем. Issue #7
ссылается на эту спеку и ведёт чек-лист шагов.