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>
This commit is contained in:
@@ -0,0 +1,50 @@
|
|||||||
|
# Handoff: редизайн лаб курса (issue #7)
|
||||||
|
|
||||||
|
Дата: 2026-07-23. Ветка: `feature/course-labs-redesign` (создана, коммитов нет).
|
||||||
|
Одноразовый документ для продолжения работы в новой сессии (ADR-0003).
|
||||||
|
|
||||||
|
## Состояние
|
||||||
|
|
||||||
|
Работа идёт по плейбуку `/claude-subagent-playbook` (оркестратор Claude,
|
||||||
|
исполнитель Codex, слепые ревью). Пройдено:
|
||||||
|
|
||||||
|
1. **Аудит курса против стенда** — субагентом; отчёт (EN):
|
||||||
|
`/tmp/claude-1000/-home-dementev-sources-clickstream-ch-kafka-superset-demo/28f16040-6abd-4d92-a5b1-71ffd4ebcb99/scratchpad/audit-course-vs-stand.md`
|
||||||
|
(при потере scratchpad не критично: главное перенесено в спеку).
|
||||||
|
2. **Штурм педагогики лаб** с пользователем (`/brainstorm-with-docs`);
|
||||||
|
конспект решений (EN): тот же каталог, `brainstorm-labs-design.md`.
|
||||||
|
3. **Спека написана** — `docs/specs/2026-07-23-course-labs-redesign.md`
|
||||||
|
(источник истины: маршрут, ядра лаб 07/08, вердикты по урокам 0–6 и
|
||||||
|
метадокам, границы, проверка). Ещё НЕ закоммичена.
|
||||||
|
|
||||||
|
## Ключевые решения (детали — в спеке)
|
||||||
|
|
||||||
|
- Один PR на всю работу; дочерних issues нет, #7 — единственный.
|
||||||
|
- Урок 6 (Superset) становится обязательным; лабы 07 (next-day) и
|
||||||
|
08 (continue) — обязательные, после урока 6.
|
||||||
|
- Сброс для лаб и всего курса: чистый стенд + повторный `import`.
|
||||||
|
- Цифры уроков сверяются на живом стенде после `import` (финальный шаг).
|
||||||
|
|
||||||
|
## Следующие шаги (конвейер плейбука)
|
||||||
|
|
||||||
|
1. ▶ **Адверсарное ревью спеки** — Claude-субагент (deep-reasoner, без
|
||||||
|
контекста оркестратора), проверка спеки против кода/доков; вердикт
|
||||||
|
файлом в scratchpad. Этап «постановка» — самый дорогой для тихой
|
||||||
|
ошибки.
|
||||||
|
2. Триаж находок → правки спеки → коммит спеки в ветку
|
||||||
|
(`/conventional-commits`, docs(course)).
|
||||||
|
3. Обновить issue #7: ссылка на спеку + чек-лист шагов.
|
||||||
|
4. Самодостаточный файл-задача для Codex (EN, в scratchpad) → реализация
|
||||||
|
`codex exec` фоном → саморевью → два слепых ревью кода → триаж →
|
||||||
|
сверка цифр на живом стенде → один PR.
|
||||||
|
|
||||||
|
## Подсказки для агента
|
||||||
|
|
||||||
|
- Скиллы: `/claude-subagent-playbook` (рабочий конвейер),
|
||||||
|
`/conventional-commits` (коммит), `/ai-text-lint` (тексты уроков перед
|
||||||
|
финалом — требование LESSON_STANDARD §2).
|
||||||
|
- Память проекта: субагентам модель/effort задавать явно; фоновым
|
||||||
|
запускам — ScheduleWakeup-подстраховка; о каждом запуске сообщать
|
||||||
|
пользователю (этап + чего ждём).
|
||||||
|
- Пользователь планирует `/compact` после ревью спеки — вся фактура
|
||||||
|
должна жить в файлах, не в контексте.
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
# Редизайн лаб курса под три режима менти
|
||||||
|
|
||||||
|
Статус: принято, в работе (ветка `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
|
||||||
|
ссылается на эту спеку и ведёт чек-лист шагов.
|
||||||
Reference in New Issue
Block a user