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

17 KiB
Raw Blame History

Редизайн лаб курса под три режима менти

Статус: принято, в работе (ветка feature/course-labs-redesign). Источник: issue #7; аудит курса против стенда и штурм педагогики лаб с пользователем 2026-07-23. Опирается на спеку редизайна пути менти (реализована, 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_initworld_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_initworld_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 — про свежесть в потоковом; обе лабы растят один и тот же импортированный мир двумя способами.

Существующие уроки и метадокументы

По вердиктам аудита:

  • Все уроки 06: блок подготовки в §2 заменить на новый путь (import); сам блок вынести в одно каноническое место (README курса), уроки ссылаются на него.
  • Все цифры уроков 0–6 недействительны. Старые значения считались на старом мире (другой профиль, другой объём); с переходом на эталонный мир каждое цитируемое число каждого урока пересчитывается заново. Это полноправный этап работы, а не финальная галочка — даже в уроках, где правки текста минимальны.
  • Урок 1: управляемая правка «добавь колонку и получи свежие сообщения» переводится с перегенерации backfill на дозаливку через world_next_day (детерминированно). Известная цена: прогон DAG'а занимает минуты — урок называет её честно; сам DAG подаётся кнопкой- анонсом без разбора («подробно — в лабе 07»), чтобы не красть у лабы её материал.
  • Урок 4: добавить лесенку ddl_initworld_initworld_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 ссылается на эту спеку и ведёт чек-лист шагов.