diff --git a/backend/app/workers/beat_schedule.py b/backend/app/workers/beat_schedule.py index f13fef52..694cde42 100644 --- a/backend/app/workers/beat_schedule.py +++ b/backend/app/workers/beat_schedule.py @@ -53,16 +53,28 @@ def _default_regions() -> list[int]: def _build_beat_schedule_from_db() -> dict: """Строит beat_schedule из job_settings БД. - Возвращает пустой словарь при любой ошибке — caller добавит fallback. - """ - try: - from app.core.db import SessionLocal - from app.services.job_settings import get_all_safe + Returns: schedule (может быть пустой если все enabled rows без cron или disabled). - rows = get_all_safe(SessionLocal) - except Exception as e: - logger.warning("build_beat_schedule: не удалось прочитать job_settings: %s", e) - return {} + `get_all` маскирует DB-unreachable case через возврат _DEFAULTS (4 synthetic + rows, все enabled=True с дефолтными cron'ами) — это значит "DB unreachable" и + "fresh install (table empty)" неотличимы от "normal с _DEFAULTS". В обоих + случаях schedule НЕ будет пустым — _DEFAULTS дадут scrape_kn + objective_sync. + + КРИТИЧНО для operator-disable scenario: когда DB reachable и оператор сделал + `UPDATE job_settings SET enabled=false WHERE job_type IN (...)` — get_all + вернёт реальные rows (НЕ _DEFAULTS), loop их пропустит → schedule={}. Caller + (build_beat_schedule) НЕ должен срывался на env fallback в этом случае, + иначе disable бесполезен. См. инцидент 2026-05-24 (WAF cooldown disable + scrape_kn → old code fallback'ал scrape_kn обратно из env). + """ + from app.core.db import SessionLocal + from app.services.job_settings import get_all + + db = SessionLocal() + try: + rows = get_all(db) + finally: + db.close() schedule: dict = {} for row in rows: @@ -168,19 +180,18 @@ def _build_beat_schedule_fallback() -> dict: def build_beat_schedule() -> dict: - """Строит beat_schedule: сначала из DB, при неудаче — fallback на env. + """Строит beat_schedule из DB (job_settings) + добавляет hardcoded entries. - Всегда добавляет refresh-ekb-districts-medians (нет в job_settings). + `_build_beat_schedule_from_db` использует `get_all` который сам fallback'ает на + _DEFAULTS при DB unreachable / fresh install → возвращает 4 synthetic rows + (scrape_kn + objective_sync enabled). То есть schedule из DB-builder ПУСТ + только когда оператор явно disabled все cron-able jobs через UPDATE + job_settings — это **намерение**, респектим без env-fallback. + + Hardcoded entries (refresh-analytics, OSM POI/noise, nspd cleanup) добавляются + ВСЕГДА — они не управляются через job_settings. """ - schedule: dict = {} - try: - schedule = _build_beat_schedule_from_db() - except Exception as e: - logger.error("build_beat_schedule: неожиданная ошибка: %s — fallback", e) - - if not schedule: - # БД пустая или недоступна — полный fallback - return _build_beat_schedule_fallback() + schedule = _build_beat_schedule_from_db() # Всегда добавляем refresh_analytics (этот job нет в job_settings, # он технический и не требует конфигурации через UI).