"""Morning Briefing 워커 — 야간 수집 뉴스 (KST 00:00~05:00) topic×country 비교 분석. - APScheduler cron (매일 05:10 KST, PR-3 에서 등록) + 수동 호출 공용 진입점 - PIPELINE_HARD_CAP = 600초 hard cap 으로 cron stuck 절대 방지 - 단독 실행: `python -m workers.briefing_worker` """ import asyncio from datetime import date from core.config import settings from core.utils import setup_logger from services.briefing.pipeline import run_briefing_pipeline logger = setup_logger("briefing_worker") PIPELINE_HARD_CAP = 600 async def run(target_date: date | None = None) -> dict | None: """APScheduler + 수동 호출 공용 진입점. Args: target_date: KST 기준 briefing_date (None = 오늘). API regenerate 가 명시 지정 가능. """ if "briefing" in settings.pipeline_held_stages: logger.info("[briefing] 보류 (pipeline.held_stages) — 이번 실행 skip") return None try: result = await asyncio.wait_for( run_briefing_pipeline(target_date), timeout=PIPELINE_HARD_CAP, ) logger.info(f"[briefing] 워커 완료: {result}") return result except asyncio.TimeoutError: logger.error( f"[briefing] HARD CAP {PIPELINE_HARD_CAP}s 초과 — 워커 강제 중단. " f"기존 briefing 은 commit 시점에만 갱신되므로 그대로 유지됨." ) except Exception as e: logger.exception(f"[briefing] 워커 실패: {e}") return None if __name__ == "__main__": asyncio.run(run())