""" 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