You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
101 lines
3.3 KiB
Python
101 lines
3.3 KiB
Python
"""
|
|
APScheduler-based batch scheduler for the Overlay pipeline.
|
|
|
|
Integrated into FastAPI's lifespan via start_scheduler() / stop_scheduler().
|
|
"""
|
|
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_scheduler = None
|
|
|
|
|
|
def _get_scheduler():
|
|
"""Lazily create the APScheduler instance."""
|
|
global _scheduler
|
|
if _scheduler is None:
|
|
try:
|
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
from apscheduler.triggers.cron import CronTrigger
|
|
_scheduler = AsyncIOScheduler(timezone="UTC")
|
|
except ImportError:
|
|
logger.warning("apscheduler not installed; overlay batch scheduling disabled")
|
|
return None
|
|
return _scheduler
|
|
|
|
|
|
async def _run_collect_job() -> None:
|
|
"""Scheduled job: collect RSS + wiki + youtube + trends."""
|
|
from app.core.database import AsyncSessionLocal
|
|
from app.services.overlay.overlay_pipeline import OverlayPipeline
|
|
|
|
pipeline = OverlayPipeline()
|
|
async with AsyncSessionLocal() as db:
|
|
try:
|
|
counts = await pipeline.collect_all(db)
|
|
logger.info(f"[Scheduler] collect_all completed: {counts}")
|
|
except Exception as e:
|
|
logger.error(f"[Scheduler] collect_all failed: {e}")
|
|
|
|
|
|
async def _run_build_job() -> None:
|
|
"""Scheduled job: build features for all TOP_50 symbols."""
|
|
from app.core.database import AsyncSessionLocal
|
|
from app.services.overlay.overlay_pipeline import OverlayPipeline
|
|
|
|
pipeline = OverlayPipeline()
|
|
async with AsyncSessionLocal() as db:
|
|
try:
|
|
built = await pipeline.build_features_batch(db)
|
|
logger.info(f"[Scheduler] feature_build completed: {built} symbols")
|
|
except Exception as e:
|
|
logger.error(f"[Scheduler] feature_build failed: {e}")
|
|
|
|
|
|
def start_scheduler() -> None:
|
|
"""Start the APScheduler with overlay jobs. Call from FastAPI lifespan startup."""
|
|
from app.core.config import settings
|
|
|
|
if not getattr(settings, "OVERLAY_ENABLED", True):
|
|
logger.info("Overlay disabled; scheduler not started")
|
|
return
|
|
|
|
sched = _get_scheduler()
|
|
if sched is None:
|
|
return
|
|
|
|
try:
|
|
from apscheduler.triggers.cron import CronTrigger
|
|
|
|
# Collect: 23:30 UTC daily on weekdays (≈ 18:30 ET)
|
|
sched.add_job(
|
|
_run_collect_job,
|
|
trigger=CronTrigger(day_of_week="mon-fri", hour=23, minute=30, timezone="UTC"),
|
|
id="overlay_collect",
|
|
replace_existing=True,
|
|
misfire_grace_time=600,
|
|
)
|
|
# Feature build: 01:30 UTC (≈ 20:30 ET)
|
|
sched.add_job(
|
|
_run_build_job,
|
|
trigger=CronTrigger(day_of_week="mon-fri", hour=1, minute=30, timezone="UTC"),
|
|
id="overlay_feature_build",
|
|
replace_existing=True,
|
|
misfire_grace_time=600,
|
|
)
|
|
|
|
sched.start()
|
|
logger.info("Overlay scheduler started (collect @ 23:30 UTC, build @ 01:30 UTC, weekdays)")
|
|
except Exception as e:
|
|
logger.error(f"Overlay scheduler start failed: {e}")
|
|
|
|
|
|
def stop_scheduler() -> None:
|
|
"""Stop the scheduler. Call from FastAPI lifespan shutdown."""
|
|
sched = _get_scheduler()
|
|
if sched and sched.running:
|
|
sched.shutdown(wait=False)
|
|
logger.info("Overlay scheduler stopped")
|