feat(generator): дефолт профиля — daily-wave, ci стал служебным

- Зачем:
  - менти доставался плоский тестовый профиль ci; учебный профиль
    должен быть один — daily-wave с суточной волной (issue #6).
- Что:
  - дефолт daily-wave во всех точках: форма пульта generator_control,
    launch.py (API и CLI), Config, Makefile, четыре shell-скрипта.
  - автопроверки передают ci явно; контрактный тест пульта теперь
    проверяет сам дефолт Param профиля (через AST), а не подстроку.
  - доки в том же изменении: README, OPERATIONS, TEST_PLAN,
    generator/README, runbook startup-history.
- Проверка:
  - make test (206 + 31 passed) и make lint — зелёные.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 17:16:07 +03:00
co-authored by Claude Fable 5
parent 5791d42db8
commit b87dde79b7
17 changed files with 78 additions and 54 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
contract-test config-test test lint
COMPOSE ?= docker compose
PROFILE ?= ci
PROFILE ?= daily-wave
PYTEST_ARGS ?= -q -p no:cacheprovider
PYTHON_CHECK_PATHS := airflow/dags configs generator scripts superset tests
BASH_SCRIPTS := $(shell find scripts -type f -name '*.sh' | sort)
+4 -8
View File
@@ -59,14 +59,10 @@ Airflow-образ и подтягивает новые зависимости
make generated-history-analytics
```
По умолчанию это быстрый профиль `ci`: 6 часов модельного времени. Историю на
2 суток с суточной волной можно прогнать одной командой. В live-продолжении
`daily-wave` идёт с ×60: модельные сутки проходят примерно за 24 настенные
минуты.
```bash
PROFILE=daily-wave make generated-history-analytics
```
По умолчанию используется учебный профиль `daily-wave`: 2 суток с суточной
волной. В live-продолжении он идёт с ×60: модельные сутки проходят примерно
за 24 настенные минуты. Плоский профиль `ci` на 6 часов остаётся служебным
для автоматических тестов.
Разовую длительность можно задать без ручного расчёта правой границы:
+1 -1
View File
@@ -221,7 +221,7 @@ with DAG(
),
),
"profile": Param(
sorted(PROFILES)[0],
"daily-wave",
type="string",
enum=sorted(PROFILES),
title="Профиль",
+7 -11
View File
@@ -204,7 +204,7 @@ make generator-logs
| `GEN_MODEL_TIMEZONE` | Часовой пояс модельных часов для дневного коэффициента | `UTC` |
| `GEN_MODEL_TIME_SPEED` | Сколько модельных секунд проходит за одну настенную секунду | `1` |
| `GEN_RUN_MODE` | Режим: `live`, `backfill` или `next-day` | `live` |
| `GEN_LAUNCH_PROFILE` | Имя профиля запуска для логов | `ci` |
| `GEN_LAUNCH_PROFILE` | Имя профиля запуска для логов | `daily-wave` |
| `GEN_STARTUP_HISTORY_ARTIFACT` | JSON-файл для экспорта стартовой истории в режиме `backfill` | пусто |
| `GEN_STATE_ENABLED` | Сохранять state v3 между рестартами | `true` |
| `GEN_STATE_RESET` | Сбросить state при старте | `false` |
@@ -270,14 +270,10 @@ make generated-history-runtime-check
LIVE_SECONDS=45 WAIT_STG_SECONDS=10 make generated-history-runtime-check
```
По умолчанию команда использует быстрый профиль `ci`: 6 часов модельного
времени. Историю на 2 суток с суточной волной можно получить одной командой.
В live-продолжении `daily-wave` идёт с ×60 и тикает раз в секунду, поэтому
модельные сутки проходят примерно за 24 настенные минуты:
```bash
PROFILE=daily-wave make generated-history-analytics
```
По умолчанию команда использует учебный профиль `daily-wave`: 2 суток с
суточной волной. В live-продолжении он идёт с ×60 и тикает раз в секунду,
поэтому модельные сутки проходят примерно за 24 настенные минуты. Плоский
профиль `ci` на 6 часов остаётся служебным для автоматических тестов.
Разовую длительность можно задать без ручного расчёта `GEN_MODEL_T_END`:
@@ -306,7 +302,7 @@ Kafka-топики данных, state и manifest генератора. `make g
Ручной backfill без всего аналитического контура:
```bash
PROFILE=daily-wave make generator-backfill
make generator-backfill
```
Перед записью команда проверяет с хоста, что Kafka data-топики и STG пустые.
@@ -336,7 +332,7 @@ Live-продолжение стартует с этого state. Использ
чистом стенде: она тоже проверяет, что Kafka data-топики и STG пустые.
```bash
PROFILE=daily-wave make generator-continue
make generator-continue
sleep 5
make generator-down
+2 -2
View File
@@ -14,7 +14,7 @@
## Общие принципы
- По умолчанию используем быстрый профиль стартовой истории (`PROFILE=ci`).
- Для smoke и CI явно задаём служебный профиль `ci`.
- Полный прогон выполняем отдельно через `PROFILE=daily-wave`.
- Основной ручной путь запуска — через Airflow DAG `generator_control`.
- Консольный чистый прогон `make generated-history-analytics` остаётся коротким
@@ -31,7 +31,7 @@
```bash
# Чистый быстрый прогон: backfill -> Kafka -> STG -> ODS -> DDS -> DM -> Superset
make generated-history-analytics
PROFILE=ci make generated-history-analytics
# Поднять остальные UI-сервисы после чистого прогона
make up
+10 -11
View File
@@ -79,20 +79,18 @@ STG уже непустые, DAG упадёт до записи и подска
## Экспорт
По умолчанию создаётся быстрый 6-часовой артефакт:
По умолчанию создаётся двухсуточный артефакт `daily-wave` с суточной волной:
```bash
make startup-history-export
```
Файл по умолчанию: `/tmp/clickstream-startup-history.json`.
История на 2 суток с суточной волной. В live-продолжении этот профиль проживает
модельные сутки примерно за 24 настенные минуты:
Файл по умолчанию: `/tmp/clickstream-startup-history.json`. В live-продолжении
профиль `daily-wave` проживает модельные сутки примерно за 24 настенные минуты.
Для быстрой автоматической проверки явно задайте служебный профиль `ci`:
```bash
ARTIFACT=/tmp/clickstream-startup-history-2d.json \
PROFILE=daily-wave \
PROFILE=ci ARTIFACT=/tmp/clickstream-startup-history-ci.json \
make startup-history-export
```
@@ -122,10 +120,11 @@ CHECK_LIVE_SEAM=0 make generated-history-check
compact-топики в Kafka. ClickHouse читает данные через свои Kafka-таблицы и
Materialized View, затем batch строит ODS, DDS и DM.
Если артефакт создан не профилем `ci`, импорт запускайте с тем же профилем:
Импорт запускайте с тем же профилем, на котором создан артефакт. Для служебного
артефакта `ci` профиль нужно задать явно:
```bash
PROFILE=daily-wave ARTIFACT=/tmp/clickstream-startup-history-2d.json \
PROFILE=ci ARTIFACT=/tmp/clickstream-startup-history-ci.json \
make startup-history-import
```
@@ -142,10 +141,10 @@ ClickHouse уже успел прочитать частичные сообще
## Live-продолжение
После импорта запускайте live с тем же профилем, что был в артефакте. Для
артефакта `daily-wave`:
обычного артефакта `daily-wave` достаточно команды:
```bash
PROFILE=daily-wave make generator-continue
make generator-continue
```
У `daily-wave` скорость ×60. Долгий простой стенда создаёт большую дыру в
+6 -9
View File
@@ -78,7 +78,7 @@ generator-service -> Kafka topics -> (потребители отдельно)
| `GEN_MODEL_TIMEZONE` | Часовой пояс модельных часов для дневного коэффициента | `UTC` |
| `GEN_MODEL_TIME_SPEED` | Сколько модельных секунд проходит за одну настенную секунду | `1` |
| `GEN_RUN_MODE` | Режим генератора | `live` |
| `GEN_LAUNCH_PROFILE` | Имя профиля запуска для логов | `ci` |
| `GEN_LAUNCH_PROFILE` | Имя профиля запуска для логов | `daily-wave` |
| `GEN_STARTUP_HISTORY_ARTIFACT` | JSON-файл для экспорта стартовой истории в режиме `backfill` | — |
| `GEN_DATA_DIR` | Путь к JSONL файлам | `/data` |
| `GEN_SEED` | Сид для воспроизводимости | — |
@@ -126,14 +126,11 @@ make generated-history-analytics
проверяет Superset metadata. Файлы `data/*.jsonl` при этом не грузятся в Kafka:
они пока используются только как фактура для генератора.
По умолчанию используется быстрый профиль `ci`: 6 часов модельного времени.
Профиль `daily-wave` даёт 2 суток, чтобы была видна суточная волна. В live он
идёт с `GEN_MODEL_TIME_SPEED=60` и `GEN_TICK_SECONDS=1`: модельные сутки
проходят примерно за 24 настенные минуты.
```bash
PROFILE=daily-wave make generated-history-analytics
```
По умолчанию используется учебный профиль `daily-wave`: 2 суток с суточной
волной. В live он идёт с `GEN_MODEL_TIME_SPEED=60` и
`GEN_TICK_SECONDS=1`: модельные сутки проходят примерно за 24 настенные
минуты. Плоский профиль `ci` на 6 часов остаётся служебным для автоматических
тестов.
Разовую длительность можно задать без ручного расчёта `GEN_MODEL_T_END`:
@@ -102,7 +102,7 @@ class Config:
else None
)
launch_profile: str = field(
default_factory=lambda: os.getenv("GEN_LAUNCH_PROFILE", "ci")
default_factory=lambda: os.getenv("GEN_LAUNCH_PROFILE", "daily-wave")
)
def __post_init__(self):
@@ -88,7 +88,7 @@ def parse_duration(value: str) -> timedelta:
def build_launch_env(
verb: str,
*,
profile_name: str = "ci",
profile_name: str = "daily-wave",
duration: str | None = None,
overrides: dict[str, str] | None = None,
) -> dict[str, str]:
@@ -161,7 +161,11 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
)
parser.add_argument(
"--profile",
default=os.getenv("PROFILE") or os.getenv("GEN_LAUNCH_PROFILE") or "ci",
default=(
os.getenv("PROFILE")
or os.getenv("GEN_LAUNCH_PROFILE")
or "daily-wave"
),
choices=sorted(PROFILES),
)
parser.add_argument("--duration", default=os.getenv("GEN_HISTORY_DURATION"))
+3 -3
View File
@@ -119,14 +119,14 @@ class TestConfigDefaults:
if orig_value is not None:
os.environ["GEN_TICK_SECONDS"] = orig_value
def test_launch_profile_defaults_to_ci(self, monkeypatch, data_dir):
"""По умолчанию выбран быстрый профиль запуска ci."""
def test_launch_profile_defaults_to_daily_wave(self, monkeypatch, data_dir):
"""По умолчанию выбран учебный профиль запуска daily-wave."""
monkeypatch.setenv("GEN_DATA_DIR", str(data_dir))
monkeypatch.delenv("GEN_LAUNCH_PROFILE", raising=False)
config = Config()
assert config.launch_profile == "ci"
assert config.launch_profile == "daily-wave"
def test_launch_profile_can_be_read_from_env(self, monkeypatch, data_dir):
"""Выбранный профиль запуска попадает в конфигурацию для логов."""
@@ -42,6 +42,25 @@ def _declared_param_types() -> dict[str, set[str]]:
return param_types
def _declared_param_defaults() -> dict[str, object]:
param_defaults = {}
for node in ast.walk(_tree()):
if not isinstance(node, ast.Dict):
continue
for key, value in zip(node.keys, node.values, strict=True):
if not (
isinstance(key, ast.Constant)
and isinstance(key.value, str)
and isinstance(value, ast.Call)
and isinstance(value.func, ast.Name)
and value.func.id == "Param"
and value.args
):
continue
param_defaults[key.value] = ast.literal_eval(value.args[0])
return param_defaults
def test_dag_file_exists_and_uses_dynamic_profiles():
"""DAG берёт варианты профилей из PROFILES, а не из ручного списка."""
text = DAG_PATH.read_text(encoding="utf-8")
@@ -55,9 +74,11 @@ def test_dag_file_exists_and_uses_dynamic_profiles():
def test_trigger_form_has_expected_param_enums():
"""Форма запуска ограничивает операции и профили."""
text = DAG_PATH.read_text(encoding="utf-8")
param_defaults = _declared_param_defaults()
assert 'enum=["backfill", "import", "next-day", "check"]' in text
assert "enum=sorted(PROFILES)" in text
assert param_defaults["profile"] == "daily-wave"
assert '"duration": Param(' in text
assert '"artifact_path": Param(' in text
assert '"expected_t_end": Param(' in text
+11
View File
@@ -5,6 +5,17 @@
from datetime import datetime, timezone
def test_daily_wave_is_the_default_for_api_and_cli(monkeypatch):
"""Ручные точки входа без профиля выбирают daily-wave."""
from clickstream_generator.launch import _parse_args, build_launch_env
monkeypatch.delenv("PROFILE", raising=False)
monkeypatch.delenv("GEN_LAUNCH_PROFILE", raising=False)
assert build_launch_env("backfill")["GEN_LAUNCH_PROFILE"] == "daily-wave"
assert _parse_args(["backfill"]).profile == "daily-wave"
def test_duration_2d_sets_model_t_end_from_t0():
"""Длительность 2d считается от T0 без ручного GEN_MODEL_T_END."""
from clickstream_generator.launch import build_launch_env
+1 -1
View File
@@ -10,7 +10,7 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
COMPOSE_BIN="${COMPOSE_BIN:-docker compose}"
CLEAN_START="${CLEAN_START:-1}"
ARTIFACT="${ARTIFACT:-/tmp/clickstream-startup-history.json}"
PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-ci}}"
PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-daily-wave}}"
duration_args=()
if [[ -n "${GEN_HISTORY_DURATION:-}" ]]; then
duration_args=(--duration "${GEN_HISTORY_DURATION}")
+1 -1
View File
@@ -9,7 +9,7 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
COMPOSE_BIN="${COMPOSE_BIN:-docker compose}"
ARTIFACT="${ARTIFACT:-/tmp/clickstream-startup-history.json}"
PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-ci}}"
PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-daily-wave}}"
duration_args=()
if [[ -n "${GEN_HISTORY_DURATION:-}" ]]; then
duration_args=(--duration "${GEN_HISTORY_DURATION}")
+1 -1
View File
@@ -15,7 +15,7 @@ COMPOSE_BIN="${COMPOSE_BIN:-docker compose}"
CLEAN_START="${CLEAN_START:-1}"
WAIT_CLICKHOUSE_SECONDS="${WAIT_CLICKHOUSE_SECONDS:-60}"
WAIT_STG_SECONDS="${WAIT_STG_SECONDS:-10}"
PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-ci}}"
PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-daily-wave}}"
duration_args=()
if [[ -n "${GEN_HISTORY_DURATION:-}" ]]; then
duration_args=(--duration "${GEN_HISTORY_DURATION}")
+1 -1
View File
@@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
COMPOSE_BIN="${COMPOSE_BIN:-docker compose}"
PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-ci}}"
PROFILE="${PROFILE:-${GEN_LAUNCH_PROFILE:-daily-wave}}"
duration_args=()
if [[ -n "${GEN_HISTORY_DURATION:-}" ]]; then
duration_args=(--duration "${GEN_HISTORY_DURATION}")
@@ -137,7 +137,7 @@ def test_generated_history_check_uses_actual_manifest_boundary_and_profile():
assert "kafka-manifest-summary" in script
assert "manifest_model_t_end" in script
assert "CH_MODEL_T_END=\"$(clickhouse_datetime_literal \"${manifest_model_t_end}\")\"" in script
assert "PROFILE ?= ci" in makefile
assert "PROFILE ?= daily-wave" in makefile
assert "GEN_LAUNCH_PROFILE" in makefile
assert "PROFILE" in makefile