grantforge-api / backend /scripts /monitor_deadlines_cron.py
GrantForge Bot
Deploy to Hugging Face
afd56bc
#!/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())