Faktencheck: Ursprungsquellen bei fehlender URL anzeigen statt blind herabstufen
Dieser Commit ist enthalten in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren