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: try:
result, usage = await call_claude(prompt) 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") logger.info(f"Faktencheck: {len(facts)} Fakten geprüft")
return facts, usage return facts, usage
except TimeoutError: except TimeoutError:
@@ -511,7 +511,7 @@ class FactCheckerAgent:
try: try:
result, usage = await call_claude(prompt) 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)") logger.info(f"Inkrementeller Faktencheck: {len(facts)} Fakten (neu + aktualisiert)")
return facts, usage return facts, usage
except TimeoutError: except TimeoutError:
@@ -705,27 +705,66 @@ class FactCheckerAgent:
logger.warning("Konnte Triage-Antwort nicht als JSON parsen") logger.warning("Konnte Triage-Antwort nicht als JSON parsen")
return None return None
def _validate_facts(self, facts: list[dict]) -> list[dict]: def _validate_facts(self, facts: list[dict], articles: list[dict] = None) -> list[dict]:
"""Validiert Fakten: confirmed/established ohne URL wird herabgestuft.""" """Validiert Fakten: Bei fehlender URL werden Ursprungsquellen aus den Artikeln ergaenzt."""
url_pattern = re.compile(r'https?://') 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: for fact in facts:
status = fact.get("status", "") status = fact.get("status", "")
evidence = fact.get("evidence") or "" evidence = fact.get("evidence") or ""
if status in ("confirmed", "established") and not url_pattern.search(evidence): if status in ("confirmed", "established") and not url_pattern.search(evidence):
old_status = status # Passende Ursprungsquellen finden (Keyword-Match auf Claim)
fact["status"] = "unconfirmed" if status == "confirmed" else "unverified" claim_lower = (fact.get("claim") or "").lower()
logger.warning( claim_words = [w for w in claim_lower.split() if len(w) >= 4][:8]
f"Fakt herabgestuft ({old_status} -> {fact['status']}): " matched_sources = []
f"keine URL in Evidenz: '{fact.get('claim', '')[:60]}...'" 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 und keine passende Ursprungsquelle: "
f"'{fact.get('claim', '')[:60]}...'"
)
return facts 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.""" """Parst die Claude-Antwort als JSON-Array."""
try: try:
data = json.loads(response) data = json.loads(response)
if isinstance(data, list): if isinstance(data, list):
return self._validate_facts(data) return self._validate_facts(data, articles=articles)
except json.JSONDecodeError: except json.JSONDecodeError:
pass 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") MAGIC_LINK_BASE_URL = os.environ.get("MAGIC_LINK_BASE_URL", "https://monitor.aegis-sight.de")
# Telegram (Telethon) # Telegram (Telethon)
TELEGRAM_API_ID = int(os.environ.get("TELEGRAM_API_ID", "2040")) TELEGRAM_API_ID = int(os.environ.get("TELEGRAM_API_ID", "31330502"))
TELEGRAM_API_HASH = os.environ.get("TELEGRAM_API_HASH", "b18441a1ff607e10a989891a5462e627") 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") 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 { .tg-cat-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 6px 16px; gap: 8px 24px;
} }
.tg-cat-item { .tg-cat-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 10px;
font-size: 13px; font-size: 13px;
color: var(--text-primary); color: var(--text-primary);
cursor: pointer; cursor: pointer;
padding: 3px 0; padding: 5px 0;
} }
.tg-cat-item input[type="checkbox"] { .tg-cat-item input[type="checkbox"] {
accent-color: var(--accent); accent-color: var(--accent);