Faktencheck: Ursprungsquellen bei fehlender URL anzeigen statt blind herabstufen

Dieser Commit ist enthalten in:
Claude Dev
2026-03-13 19:54:18 +01:00
Ursprung 2792e916c2
Commit f9ebd7b289
3 geänderte Dateien mit 786 neuen und 747 gelöschten Zeilen

Datei anzeigen

@@ -469,7 +469,7 @@ class FactCheckerAgent:
try:
result, usage = await call_claude(prompt)
facts = self._parse_response(result)
facts = self._parse_response(result, articles=articles)
logger.info(f"Faktencheck: {len(facts)} Fakten geprüft")
return facts, usage
except TimeoutError:
@@ -511,7 +511,7 @@ class FactCheckerAgent:
try:
result, usage = await call_claude(prompt)
facts = self._parse_response(result)
facts = self._parse_response(result, articles=new_articles)
logger.info(f"Inkrementeller Faktencheck: {len(facts)} Fakten (neu + aktualisiert)")
return facts, usage
except TimeoutError:
@@ -705,27 +705,66 @@ class FactCheckerAgent:
logger.warning("Konnte Triage-Antwort nicht als JSON parsen")
return None
def _validate_facts(self, facts: list[dict]) -> list[dict]:
"""Validiert Fakten: confirmed/established ohne URL wird herabgestuft."""
def _validate_facts(self, facts: list[dict], articles: list[dict] = None) -> list[dict]:
"""Validiert Fakten: Bei fehlender URL werden Ursprungsquellen aus den Artikeln ergaenzt."""
url_pattern = re.compile(r'https?://')
# Verfuegbare Artikel-URLs sammeln
article_sources = []
if articles:
for a in articles:
url = a.get("source_url", "")
source = a.get("source", "")
headline = a.get("headline_de") or a.get("headline", "")
if url:
article_sources.append({"url": url, "source": source, "headline": headline})
for fact in facts:
status = fact.get("status", "")
evidence = fact.get("evidence") or ""
if status in ("confirmed", "established") and not url_pattern.search(evidence):
# Passende Ursprungsquellen finden (Keyword-Match auf Claim)
claim_lower = (fact.get("claim") or "").lower()
claim_words = [w for w in claim_lower.split() if len(w) >= 4][:8]
matched_sources = []
for src in article_sources:
src_text = (src["headline"] + " " + src["source"]).lower()
matches = sum(1 for w in claim_words if w in src_text)
if matches >= max(1, len(claim_words) // 4):
matched_sources.append(src)
if len(matched_sources) >= 3:
break
if matched_sources:
# Ursprungsquellen anhaengen statt herabstufen
source_refs = "; ".join(
f"{s['source']} ({s['url']})" for s in matched_sources
)
fact["evidence"] = (
evidence.rstrip(". ") +
". [Ursprungsquellen: " + source_refs +
" — Quellenlinks zum Zeitpunkt der Recherche moeglicherweise nicht mehr verfuegbar]"
)
logger.info(
f"Fakt '{fact.get('claim', '')[:50]}...' ergaenzt mit "
f"{len(matched_sources)} Ursprungsquelle(n)"
)
else:
# Keine passende Quelle gefunden -> herabstufen
old_status = status
fact["status"] = "unconfirmed" if status == "confirmed" else "unverified"
logger.warning(
f"Fakt herabgestuft ({old_status} -> {fact['status']}): "
f"keine URL in Evidenz: '{fact.get('claim', '')[:60]}...'"
f"keine URL in Evidenz und keine passende Ursprungsquelle: "
f"'{fact.get('claim', '')[:60]}...'"
)
return facts
def _parse_response(self, response: str) -> list[dict]:
def _parse_response(self, response: str, articles: list[dict] = None) -> list[dict]:
"""Parst die Claude-Antwort als JSON-Array."""
try:
data = json.loads(response)
if isinstance(data, list):
return self._validate_facts(data)
return self._validate_facts(data, articles=articles)
except json.JSONDecodeError:
pass

Datei anzeigen

@@ -78,7 +78,7 @@ MAGIC_LINK_EXPIRE_MINUTES = 10
MAGIC_LINK_BASE_URL = os.environ.get("MAGIC_LINK_BASE_URL", "https://monitor.aegis-sight.de")
# Telegram (Telethon)
TELEGRAM_API_ID = int(os.environ.get("TELEGRAM_API_ID", "2040"))
TELEGRAM_API_HASH = os.environ.get("TELEGRAM_API_HASH", "b18441a1ff607e10a989891a5462e627")
TELEGRAM_API_ID = int(os.environ.get("TELEGRAM_API_ID", "31330502"))
TELEGRAM_API_HASH = os.environ.get("TELEGRAM_API_HASH", "842db7220ad2d5371269d6d88cde6a84")
TELEGRAM_SESSION_PATH = os.environ.get("TELEGRAM_SESSION_PATH", "/home/claude-dev/.telegram/telegram_session")

Datei anzeigen

@@ -4557,16 +4557,16 @@ a.map-popup-article:hover {
.tg-cat-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px 16px;
gap: 8px 24px;
}
.tg-cat-item {
display: flex;
align-items: center;
gap: 6px;
gap: 10px;
font-size: 13px;
color: var(--text-primary);
cursor: pointer;
padding: 3px 0;
padding: 5px 0;
}
.tg-cat-item input[type="checkbox"] {
accent-color: var(--accent);