fix(course): правки по итогам слепого ревью лаб 07/08 (#22)

Зачем: два слепых ревью (Codex CODE-1..9, Claude TASK-1..2) и
перепроверки нашли дефекты в лабах и метадоках; финал лабы 08
переделан по решению пользователя.

Что:
- канонический сброс в README курса теперь снимает DAG'и с паузы
  (все создаются на паузе);
- имя дашборда исправлено на «E-commerce Analytics Dashboard»;
- финал лабы 08: вместо мифа «users == sessions на статике» —
  вернувшийся пользователь через границу заморозки и измеренное
  обещание свежести; §2 велит записать границы и время (§6 их требует);
- CONTEXT.md: эталонный мир — замороженный живой,
  4 056 пользователей / 26 083 визита, возвраты уже есть;
- уроки 5–6 ведут в обязательный маршрут, живое упражнение
  урока 5 требует канонический сброс;
- контрактные тесты: убран якорь на удалённую команду; добавленные
  на триаже тесты-цитаты срезаны до устойчивых инвариантов
  (прозаический легаси-жанр — issue #24);
- handoff обновлён до текущего состояния.

Проверка: make test (219 generator + 31 contract) и make lint зелёные;
SQL вернувшегося пользователя проверен на эфемерном ClickHouse 25.1
(на полном наборе — в #23).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 14:48:02 +03:00
co-authored by Claude Opus 4.8
parent 1dd79a3287
commit cbf8b2d770
8 changed files with 167 additions and 99 deletions
+39 -1
View File
@@ -188,7 +188,45 @@ def test_course_readme_lists_uv_before_first_command():
text = (REPO_ROOT / "docs" / "course" / "README.md").read_text(encoding="utf-8")
assert "uv" in text
assert text.index("uv") < text.index("make generated-history-analytics")
assert text.index("uv") < text.index("make clean")
def test_course_canonical_setup_unpauses_dags_before_world_init():
"""Канонический блок называет три DAG, снятие паузы и порядок etl→world_init."""
# Прозаические формулировки не проверяем — их сторожит ревью, не pytest.
# Держим только структурные факты: якорь-заголовок существует, в блоке
# названы три DAG-а, упомянута пауза, а etl_pipeline идёт до world_init.
text = (REPO_ROOT / "docs" / "course" / "README.md").read_text(encoding="utf-8")
setup = text.split("## Подготовка и канонический сброс", maxsplit=1)[1].split(
"## Уроки",
maxsplit=1,
)[0]
assert "пауз" in setup
for dag_id in ("ddl_init", "etl_pipeline", "world_init"):
assert dag_id in setup
assert setup.index("etl_pipeline") < setup.index("world_init")
def test_next_day_lab_uses_provisioned_superset_dashboard_name():
"""Лаба следующего дня ведёт на существующий дашборд Superset."""
text = (
REPO_ROOT / "docs" / "course" / "lessons" / "07_lab_next_day.md"
).read_text(encoding="utf-8")
assert "E-commerce Analytics Dashboard" in text
assert "Clickstream Analytics" not in text
def test_monitoring_lesson_links_to_canonical_reset():
"""Урок мониторинга ведёт обратно к каноническому сбросу курса."""
# Проверяем живую перекрёстную ссылку: битый якорь — это дефект, а не
# переписанная проза. Точные фразы упражнения не пиним.
text = (
REPO_ROOT / "docs" / "course" / "lessons" / "05_monitoring.md"
).read_text(encoding="utf-8")
assert "../README.md#подготовка-и-канонический-сброс" in text
def test_startup_history_runbook_warns_about_daily_wave_idle_gap():