Implements docs/api-contract-v2.md. Migration 002 adds follow-up fields. EchoTransport replaced by SMTP->Clipboard selection behind unchanged approval gate. Scheduler (APScheduler) daily 07:00 fetch+score, env-gated, default off. Test image installs workspace packages; build context moved to repo root. Recovered and committed by integration lead after W2 worker hit iteration limit.
92 lines
No EOL
2.7 KiB
Python
92 lines
No EOL
2.7 KiB
Python
"""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") |