grantforge-api / backend /core /scheduler.py
GrantForge Bot
Deploy to Hugging Face
afd56bc
"""
Background scheduler dla GrantForge AI.
Odświeża cache PARP i NCBR co 24h.
Używa czystego asyncio — bez zewnętrznych zależności (APScheduler, Celery).
Uruchamiany przez FastAPI lifespan context manager.
"""
import asyncio
import logging
from datetime import datetime, timezone
logger = logging.getLogger(__name__)
REFRESH_INTERVAL_HOURS = 6
_scheduler_task: asyncio.Task | None = None
async def _refresh_grant_caches() -> None:
"""Odświeżenie cache wszystkich źródeł (Ultimate Grant Search Engine)."""
from core.search.grant_search_service import grant_search_service
started = datetime.now(timezone.utc).isoformat()
logger.info(f"[Scheduler] Odświeżanie cache naborów dla wszystkich 9 źródeł (Faza 3) — {started}")
try:
results = await grant_search_service.get_all_grants(force_refresh=True)
logger.info(f"[Scheduler] Pomyślnie zaktualizowano bazę naborów. Łączna liczba: {len(results)}")
# Faza 6: Uruchomienie Compliance Guardian dla aktywnych projektów
try:
from agents.compliance_guardian import check_legal_updates
# W środowisku produkcyjnym pobralibyśmy aktywne projekty z bazy
# Tutaj testowo wysyłamy do admina
check_legal_updates("global", "admin@grantforge.ai", "Wszystkie zaktualizowane programy")
except Exception as e:
logger.error(f"[Scheduler] Błąd modułu Compliance Guardian: {e}")
except Exception as e:
logger.error(f"[Scheduler] Błąd podczas globalnego odświeżania: {e}")
async def _scheduler_loop() -> None:
"""Pętla działająca w tle: odśwież → czekaj 24h → powtórz."""
logger.info("[Scheduler] Uruchomiono background scheduler (interwał: 24h).")
# Pierwsze uruchomienie po starcie serwera — małe opóźnienie żeby nie blokować startu
await asyncio.sleep(10)
while True:
try:
await _refresh_grant_caches()
except Exception as e:
logger.error(f"[Scheduler] Nieoczekiwany błąd: {e}")
next_run = REFRESH_INTERVAL_HOURS * 3600
logger.info(f"[Scheduler] Następne odświeżanie za {REFRESH_INTERVAL_HOURS}h.")
await asyncio.sleep(next_run)
def start_scheduler() -> None:
"""Uruchamia scheduler jako asyncio task. Wywołaj z lifespan FastAPI."""
global _scheduler_task
loop = asyncio.get_event_loop()
_scheduler_task = loop.create_task(_scheduler_loop())
logger.info("[Scheduler] Task zarejestrowany.")
def stop_scheduler() -> None:
"""Zatrzymuje scheduler. Wywołaj przy shutdown FastAPI."""
global _scheduler_task
if _scheduler_task and not _scheduler_task.done():
_scheduler_task.cancel()
logger.info("[Scheduler] Task anulowany.")
_scheduler_task = None