#!/usr/bin/env python3 import sys import os import json import logging import asyncio import hashlib import requests from datetime import datetime # Dodanie ścieżki projektu do PYTHONPATH sys.path.append(os.path.join(os.path.dirname(__file__), "../..")) from backend.core.search.grant_search_service import grant_search_service logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) CACHE_FILE = os.path.join(os.path.dirname(__file__), ".monitoring_cache.json") def load_cache(): if os.path.exists(CACHE_FILE): try: with open(CACHE_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: logger.error(f"Błąd odczytu cache: {e}") return {} def save_cache(cache_data): try: with open(CACHE_FILE, "w", encoding="utf-8") as f: json.dump(cache_data, f, ensure_ascii=False, indent=4) except Exception as e: logger.error(f"Błąd zapisu cache: {e}") async def check_content_hash(url: str) -> str: """Pobiera zawartość strony i zwraca hash SHA-256.""" try: response = await asyncio.to_thread(requests.get, url, timeout=10, allow_redirects=True) if response.status_code == 200: return hashlib.sha256(response.text.encode('utf-8')).hexdigest() except Exception as e: logger.warning(f"Błąd podczas pobierania treści {url}: {e}") return None async def monitor_grants(): logger.info("Rozpoczynam sprawdzanie zmian w regulaminach i terminach...") cache = load_cache() alerts = [] for source in grant_search_service.sources: if hasattr(source, "_get_verified_fallback"): fallback_list = source._get_verified_fallback() for grant in fallback_list: program_id = grant.get("id") or grant.get("name") url = grant.get("url", "") name = grant.get("name", "Brak nazwy") current_deadline = grant.get("deadline", "") if not program_id or not url.startswith("http"): continue logger.info(f"Monitorowanie: {name}") current_hash = await check_content_hash(url) if program_id in cache: prev_data = cache[program_id] prev_deadline = prev_data.get("deadline", "") prev_hash = prev_data.get("content_hash", "") changes = [] if current_deadline and current_deadline != prev_deadline: changes.append(f"Zmieniono termin z {prev_deadline} na {current_deadline}") if current_hash and prev_hash and current_hash != prev_hash: changes.append("Wykryto zmianę w treści strony (regulamin/ogłoszenie)") if changes: alerts.append(f"⚠️ {name}:\n- " + "\n- ".join(changes) + f"\nLink: {url}") # Aktualizacja cache cache[program_id] = { "deadline": current_deadline, "content_hash": current_hash or cache.get(program_id, {}).get("content_hash", ""), "last_checked": datetime.now().isoformat() } save_cache(cache) # Wysyłanie powiadomień if alerts: logger.info(f"Wykryto {len(alerts)} zmian. Przygotowuję powiadomienia administracyjne.") alert_body = "\n\n".join(alerts) try: from backend.gsd.email_notifier import send_hitl_email # Używamy istniejącego powiadomiacza dla administratorów (DEFAULT_TARGET) # Wysłanie jednej wiadomości ze wszystkimi alertami admin_email = os.environ.get("ADMIN_EMAIL", "bogmaz1@gmail.com") subject = "Dotacje AI: Zmiany w regulaminach lub terminach naborów" # W send_hitl_email parametr to hitl_question, ale możemy to lekko obejść budując treść # Dla Fazy 1 wyślemy po prostu log / mail. logger.warning(f"[EMAIL DO {admin_email}]\nTemat: {subject}\n{alert_body}") send_hitl_email(f"ALERTY MONITORINGU:\n\n{alert_body}", "SYSTEM_MONITOR") logger.info("Wysłano e-mail do administratorów.") except Exception as e: logger.error(f"Nie udało się wysłać powiadomienia email: {e}") else: logger.info("Brak zmian w regulaminach i terminach.") if __name__ == "__main__": asyncio.run(monitor_grants())