"""BAN YARO — Rückruf-Alarm (Tierfutter) RASFF EU Rapid Alert System for Food and Feed """ import logging import httpx from fastapi import APIRouter from database import db router = APIRouter() logger = logging.getLogger(__name__) RASFF_URL = "https://webgate.ec.europa.eu/rasff-window/backend/public/notification/list/with-filters" RASFF_PARAMS = { "filters": '{"subject.product_category":["pet food and animal feed"]}', "pageNumber": 0, "pageSize": 20, "sortColumn": "notificationDate", "sortDirection": "DESC", } # ------------------------------------------------------------------ # GET /api/recalls — Letzte 50 Rückrufe # ------------------------------------------------------------------ @router.get("") async def list_recalls(q: str = ""): with db() as conn: if q: like = f"%{q}%" rows = conn.execute(""" SELECT id, external_id, titel, produkt, gefahr, herkunft, datum, quelle, url, created_at FROM feed_recalls WHERE titel LIKE ? OR produkt LIKE ? OR gefahr LIKE ? OR herkunft LIKE ? ORDER BY datum DESC LIMIT 50 """, (like, like, like, like)).fetchall() else: rows = conn.execute(""" SELECT id, external_id, titel, produkt, gefahr, herkunft, datum, quelle, url, created_at FROM feed_recalls ORDER BY datum DESC LIMIT 50 """).fetchall() return [dict(r) for r in rows] # ------------------------------------------------------------------ # Interne Hilfsfunktion: RASFF API abfragen # ------------------------------------------------------------------ async def fetch_rasff_recalls() -> list[dict]: """Fragt die RASFF API ab und gibt eine Liste normalisierter Einträge zurück. Hinweis: Die EU hat die API mehrfach umgezogen — wenn der Endpoint 404 oder andere persistent fehler liefert, geben wir [] zurück und loggen nur als Warning (nicht Error), damit das Error-Digest nicht täglich spammt. """ try: async with httpx.AsyncClient(timeout=10.0) as client: resp = await client.get(RASFF_URL, params=RASFF_PARAMS) resp.raise_for_status() data = resp.json() except httpx.HTTPStatusError as e: if e.response.status_code in (404, 410, 503): # API umgezogen oder temporär unten — Warning, kein Error logger.warning( f"RASFF API liefert {e.response.status_code} (Endpoint vermutlich umgezogen) — überspringe." ) else: logger.error(f"RASFF API-HTTP-Fehler: {e}") return [] except Exception as e: logger.error(f"RASFF API-Fehler: {e}") return [] entries = [] try: items = data.get("data", {}).get("list", []) for item in items: reference = item.get("reference", "") if not reference: continue # Datum datum_raw = item.get("notificationDate", "") datum = datum_raw[:10] if datum_raw else "" # Produkt subject = item.get("subject") or {} produkt = subject.get("product", "") or "" # Gefahr hazards = subject.get("hazard") or [] gefahr = "" if hazards: gefahr = hazards[0].get("hazardDescription", "") or "" # Herkunft origin = item.get("origin") or {} herkunft = origin.get("name", "") or "" # URL zur RASFF-Seite url = f"https://webgate.ec.europa.eu/rasff-window/screen/notificationDetail?notifRef={reference}" entries.append({ "external_id": reference, "titel": produkt or reference, "produkt": produkt, "gefahr": gefahr, "herkunft": herkunft, "datum": datum, "quelle": "rasff", "url": url, }) except Exception as e: logger.error(f"RASFF Parsing-Fehler: {e}") return entries # ------------------------------------------------------------------ # Interne Hilfsfunktion: Neue Einträge in DB speichern # ------------------------------------------------------------------ def save_new_recalls(entries: list[dict]) -> list[dict]: """Speichert neue Einträge und gibt die Liste der neuen Einträge zurück.""" new_entries = [] for entry in entries: try: with db() as conn: exists = conn.execute( "SELECT id FROM feed_recalls WHERE external_id=?", (entry["external_id"],) ).fetchone() if not exists: conn.execute(""" INSERT INTO feed_recalls (external_id, titel, produkt, gefahr, herkunft, datum, quelle, url) VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( entry["external_id"], entry["titel"], entry["produkt"], entry["gefahr"], entry["herkunft"], entry["datum"], entry["quelle"], entry["url"], )) new_entries.append(entry) except Exception as e: logger.warning(f"Recall DB-Fehler für {entry.get('external_id')}: {e}") return new_entries