Fix: Duplikat-Vorschläge + Stale-Check nur für RSS-Feeds
- Duplikat-Check basiert auf source_id+type statt exaktem Titel - add_source ohne source_id prüft per Domain-Match - Stale-Check überspringt web_sources (nur RSS-Feeds prüfen) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -1,4 +1,4 @@
|
||||
"""Quellen-Health-Check Engine - prüft Erreichbarkeit, Feed-Validität, Duplikate."""
|
||||
"""Quellen-Health-Check Engine - prüft Erreichbarkeit, Feed-Validität, Duplikate."""
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
@@ -12,7 +12,7 @@ logger = logging.getLogger("osint.source_health")
|
||||
|
||||
|
||||
async def run_health_checks(db: aiosqlite.Connection) -> dict:
|
||||
"""Führt alle Health-Checks für aktive Grundquellen durch."""
|
||||
"""Führt alle Health-Checks für aktive Grundquellen durch."""
|
||||
logger.info("Starte Quellen-Health-Check...")
|
||||
|
||||
# Alle aktiven Grundquellen laden
|
||||
@@ -22,14 +22,14 @@ async def run_health_checks(db: aiosqlite.Connection) -> dict:
|
||||
)
|
||||
sources = [dict(row) for row in await cursor.fetchall()]
|
||||
|
||||
# Aktuelle Health-Check-Ergebnisse löschen (werden neu geschrieben)
|
||||
# Aktuelle Health-Check-Ergebnisse löschen (werden neu geschrieben)
|
||||
await db.execute("DELETE FROM source_health_checks")
|
||||
await db.commit()
|
||||
|
||||
checks_done = 0
|
||||
issues_found = 0
|
||||
|
||||
# 1. Erreichbarkeit + Feed-Validität (nur Quellen mit URL)
|
||||
# 1. Erreichbarkeit + Feed-Validität (nur Quellen mit URL)
|
||||
sources_with_url = [s for s in sources if s["url"]]
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
@@ -46,7 +46,7 @@ async def run_health_checks(db: aiosqlite.Connection) -> dict:
|
||||
if isinstance(result, Exception):
|
||||
await _save_check(
|
||||
db, source["id"], "reachability", "error",
|
||||
f"Prüfung fehlgeschlagen: {result}",
|
||||
f"Prüfung fehlgeschlagen: {result}",
|
||||
)
|
||||
issues_found += 1
|
||||
else:
|
||||
@@ -61,7 +61,7 @@ async def run_health_checks(db: aiosqlite.Connection) -> dict:
|
||||
|
||||
# 2. Veraltete Quellen (kein Artikel seit >30 Tagen)
|
||||
for source in sources:
|
||||
if source["source_type"] == "excluded":
|
||||
if source["source_type"] in ("excluded", "web_source"):
|
||||
continue
|
||||
stale_check = _check_stale(source)
|
||||
if stale_check:
|
||||
@@ -83,7 +83,7 @@ async def run_health_checks(db: aiosqlite.Connection) -> dict:
|
||||
|
||||
await db.commit()
|
||||
logger.info(
|
||||
f"Health-Check abgeschlossen: {checks_done} Quellen geprüft, "
|
||||
f"Health-Check abgeschlossen: {checks_done} Quellen geprüft, "
|
||||
f"{issues_found} Probleme gefunden"
|
||||
)
|
||||
return {"checked": checks_done, "issues": issues_found}
|
||||
@@ -92,7 +92,7 @@ async def run_health_checks(db: aiosqlite.Connection) -> dict:
|
||||
async def _check_source_reachability(
|
||||
client: httpx.AsyncClient, source: dict,
|
||||
) -> list[dict]:
|
||||
"""Prüft Erreichbarkeit und Feed-Validität einer Quelle."""
|
||||
"""Prüft Erreichbarkeit und Feed-Validität einer Quelle."""
|
||||
checks = []
|
||||
url = source["url"]
|
||||
|
||||
@@ -125,14 +125,14 @@ async def _check_source_reachability(
|
||||
"message": "Erreichbar",
|
||||
})
|
||||
|
||||
# Feed-Validität nur für RSS-Feeds
|
||||
# Feed-Validität nur für RSS-Feeds
|
||||
if source["source_type"] == "rss_feed":
|
||||
text = resp.text[:20000]
|
||||
if "<rss" not in text and "<feed" not in text and "<channel" not in text:
|
||||
checks.append({
|
||||
"type": "feed_validity",
|
||||
"status": "error",
|
||||
"message": "Kein gültiger RSS/Atom-Feed",
|
||||
"message": "Kein gültiger RSS/Atom-Feed",
|
||||
})
|
||||
else:
|
||||
feed = await asyncio.to_thread(feedparser.parse, text)
|
||||
@@ -155,7 +155,7 @@ async def _check_source_reachability(
|
||||
checks.append({
|
||||
"type": "feed_validity",
|
||||
"status": "ok",
|
||||
"message": f"Feed gültig ({len(feed.entries)} Einträge)",
|
||||
"message": f"Feed gültig ({len(feed.entries)} Einträge)",
|
||||
})
|
||||
|
||||
except httpx.TimeoutException:
|
||||
@@ -181,7 +181,7 @@ async def _check_source_reachability(
|
||||
|
||||
|
||||
def _check_stale(source: dict) -> dict | None:
|
||||
"""Prüft ob eine Quelle veraltet ist (keine Artikel seit >30 Tagen)."""
|
||||
"""Prüft ob eine Quelle veraltet ist (keine Artikel seit >30 Tagen)."""
|
||||
if source["source_type"] == "excluded":
|
||||
return None
|
||||
|
||||
@@ -249,7 +249,7 @@ async def _save_check(
|
||||
|
||||
|
||||
async def get_health_summary(db: aiosqlite.Connection) -> dict:
|
||||
"""Gibt eine Zusammenfassung der letzten Health-Check-Ergebnisse zurück."""
|
||||
"""Gibt eine Zusammenfassung der letzten Health-Check-Ergebnisse zurück."""
|
||||
cursor = await db.execute("""
|
||||
SELECT
|
||||
h.id, h.source_id, s.name, s.domain, s.url, s.source_type,
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren