feat(generator): ускорен учебный профиль daily-wave

- Зачем:
  - суточная волна должна быть видна на занятии за минуты, а не за сутки работы стенда.
- Что:
  - профиль daily-wave переведён на speed 60 при тике 1 секунда.
  - приглушены подробные live-логи успешного тика без изменения сохранения state.
  - обновлены тесты, спека и инструкции запуска быстрого профиля.
- Проверка:
  - make generator-test.
  - git diff --cached --check.
This commit is contained in:
2026-07-04 21:17:46 +03:00
parent 200bb82890
commit 9d1bcc43fa
9 changed files with 86 additions and 23 deletions
+5 -3
View File
@@ -127,7 +127,9 @@ make generated-history-analytics
они пока используются только как фактура для генератора.
По умолчанию используется быстрый профиль `ci`: 6 часов модельного времени.
Профиль `daily-wave` даёт 2 суток, чтобы была видна суточная волна:
Профиль `daily-wave` даёт 2 суток, чтобы была видна суточная волна. В live он
идёт с `GEN_MODEL_TIME_SPEED=60` и `GEN_TICK_SECONDS=1`: модельные сутки
проходят примерно за 24 настенные минуты.
```bash
PROFILE=daily-wave make generated-history-analytics
@@ -139,9 +141,9 @@ PROFILE=daily-wave make generated-history-analytics
GEN_HISTORY_DURATION=2d make generated-history-analytics
```
### Режим "раз в минуту" (для демо)
### Режим "раз в минуту" (для ручных экспериментов)
Для контролируемых демо можно установить:
Для ручных экспериментов можно установить:
```bash
GEN_TICK_SECONDS=60
GEN_MIN_EVENTS_PER_TICK=50
@@ -52,8 +52,8 @@ PROFILES = {
"GEN_SEED": "4242",
"GEN_MODEL_T0": "2026-01-01T00:00:00+00:00",
"GEN_MODEL_TIMEZONE": "UTC",
"GEN_MODEL_TIME_SPEED": "1",
"GEN_TICK_SECONDS": "60",
"GEN_MODEL_TIME_SPEED": "60",
"GEN_TICK_SECONDS": "1",
"GEN_LAMBDA_BASE_PER_MIN": "60",
"GEN_JITTER_PCT": "0",
"GEN_MIN_EVENTS_PER_TICK": "1",
@@ -529,13 +529,13 @@ class GeneratorService:
model_time = self._model_time
with METRICS_TICK_DURATION.time():
logger.info(f"=== Tick {self._tick} (batch_id={batch_id}) ===")
logger.debug(f"=== Tick {self._tick} (batch_id={batch_id}) ===")
try:
events_count = self.generator._calculate_events_count(
now=model_time,
)
logger.info(f"Generating with event budget ~{events_count}")
logger.debug(f"Generating with event budget ~{events_count}")
gen_start = time.time()
batch = self.stream.generate_tick(
@@ -591,7 +591,7 @@ class GeneratorService:
METRICS_ERRORS_TOTAL.labels(topic="history").inc()
tick_duration = time.time() - tick_start
logger.info(
logger.debug(
f"Batch {batch_id} completed: "
f"sent={total_sent}, errors={total_errors}, "
f"gen_time={gen_duration:.3f}s, pub_time={pub_duration:.3f}s, "
@@ -600,7 +600,7 @@ class GeneratorService:
for topic, counts in sent_counts.items():
if counts["sent"] > 0:
logger.info(f" {topic}: {counts['sent']} sent")
logger.debug(f" {topic}: {counts['sent']} sent")
except Exception as e:
logger.exception(f"Error in tick {self._tick}: {e}")
+3
View File
@@ -24,6 +24,8 @@ def test_daily_wave_profile_uses_two_days_by_default():
assert env["GEN_LAUNCH_PROFILE"] == "daily-wave"
assert env["GEN_HISTORY_DURATION"] == "2d"
assert env["GEN_MODEL_T_END"] == "2026-01-03T00:00:00+00:00"
assert env["GEN_MODEL_TIME_SPEED"] == "60"
assert env["GEN_TICK_SECONDS"] == "1"
def test_backfill_verb_maps_to_existing_low_level_flags():
@@ -61,6 +63,7 @@ def test_ci_profile_keeps_previous_default_backfill_window():
t_end = datetime.fromisoformat(env["GEN_MODEL_T_END"])
assert t0 == datetime(2026, 1, 1, 0, 0, tzinfo=timezone.utc)
assert (t_end - t0).total_seconds() == 6 * 60 * 60
assert env["GEN_MODEL_TIME_SPEED"] == "1"
assert env["GEN_TICK_SECONDS"] == "60"
assert env["GEN_LAMBDA_BASE_PER_MIN"] == "60"
assert env["GEN_JITTER_PCT"] == "0"
+36
View File
@@ -184,6 +184,42 @@ class TestGeneratorServiceSteadyStream:
"2026-01-01 10:10:00.000000",
}
def test_successful_live_tick_does_not_log_per_tick_info(self, base_config, caplog):
"""Успешный live-тик не пишет подробный пер-тиковый журнал на INFO."""
config = replace(
base_config,
tick_seconds=1,
lambda_base_per_min=60,
jitter_pct=0,
min_events_per_tick=1,
max_events_per_tick=1000,
max_session_events=1,
max_active_sessions=10,
population_max=11,
)
service = GeneratorService(config)
service.publisher = MagicMock()
service.publisher.publish.side_effect = (
lambda topic, events: (len(events), 0)
)
service.history = MagicMock()
service._running = True
def stop_after_first_tick(_sleep_seconds):
service._running = False
with caplog.at_level(logging.INFO, logger="generator"), \
patch(
"clickstream_generator.service.time.sleep",
side_effect=stop_after_first_tick,
):
service._main_loop()
assert "=== Tick" not in caplog.text
assert "Generating with event budget" not in caplog.text
assert "completed:" not in caplog.text
assert "sent" not in caplog.text
def _run_service_ticks(self, config, wall_now: datetime, ticks_count: int):
service = GeneratorService(config)
service.publisher = MagicMock()