"""APScheduler integration: daily fetch + batch score job. Starts during app lifespan when SCHEDULER_ENABLED=true (default false). Runs a daily job at 07:00 that fetches postings and batch-scores pending applications. """ from __future__ import annotations import logging import os logger = logging.getLogger(__name__) _scheduler = None def is_scheduler_enabled() -> bool: """Check if the scheduler is enabled via env.""" return os.environ.get("SCHEDULER_ENABLED", "false").lower() in ( "true", "1", "yes", ) async def _daily_fetch_and_score() -> None: """Daily job: fetch postings and batch-score pending applications.""" logger.info("Scheduler: running daily fetch + batch score") try: # Import here to avoid circular imports from app.main import _fetch_and_create_postings, _batch_score_internal # Fetch default query fetch_result = _fetch_and_create_postings( query="developer", region="Skane lan", ) logger.info( "Scheduler: fetched %s new, %s dupes", fetch_result.get("new", 0), fetch_result.get("dupes", 0), ) # Batch score all discovered applications from app.db import repo_app apps = repo_app.list_applications() discovered_ids = [ a["id"] for a in apps if a["state"] == "discovered" ] if discovered_ids: results = _batch_score_internal(discovered_ids) logger.info( "Scheduler: batch-scored %s applications", len(results) ) except Exception: logger.exception("Scheduler: daily job failed") def start_scheduler() -> None: """Start the APScheduler if enabled.""" global _scheduler if not is_scheduler_enabled(): logger.info("Scheduler disabled (SCHEDULER_ENABLED != true)") return try: from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger except ImportError: logger.warning( "APScheduler not installed; scheduler will not start." ) return _scheduler = AsyncIOScheduler() _scheduler.add_job( _daily_fetch_and_score, CronTrigger(hour=7, minute=0), id="daily_fetch_score", replace_existing=True, ) _scheduler.start() logger.info("Scheduler started: daily fetch+score at 07:00") def stop_scheduler() -> None: """Stop the scheduler if running.""" global _scheduler if _scheduler is not None: _scheduler.shutdown(wait=False) _scheduler = None logger.info("Scheduler stopped")