fix(faktencheck): Belegtiefe zaehlt Medienhaeuser, Widerspruch ist keine Widerlegung

Block 2 der Auswertung des Ceuta-Vergleichs vom 01.08.2026. Der Faktencheck der
EU-Fassung zeigte dort drei Fehler, die alle dieselbe Wurzel haben: Der Status
behauptete eine Belegtiefe, die niemand nachgerechnet hat.

1. Die Zaehlung war kaputt. Das Muster fuer die Quellenzaehlung war
   r'https?://' ohne den Rest der Adresse, findall lieferte also eine Liste aus
   "https://"-Bruchstuecken und len(set(...)) ergab immer 1 oder 2. Daher stand
   im Bericht eine Behauptung als BESTAETIGT mit 1 Quelle, obwohl vier Haeuser
   in der Evidenz standen, und der gesamte Faktencheck der Anthropic-Fassung
   wies durchgehend 1 bis 2 Quellen aus. Die Adresse wird jetzt vollstaendig
   erfasst.

2. Gezaehlt wird nach Medienhaus, nicht nach Fundstelle. Sechs France24-Treffer,
   davon vier Videofassungen derselben Meldung, sind ein Haus. Grundlage ist die
   registrierbare Domain aus services/media_registry.py. Weiterleitungsportale
   bekommen keine gemeinsame Identitaet, hinter news.google.com koennen
   beliebige Zeitungen stecken. Nachgerechnet an Lage 53 sinken die Angaben von
   9 auf 6, von 8 auf 4 und von 7 auf 5 Belege.

3. Neue Nachbedingung. pruefe_belegtiefe stuft jeden Fakt ab, dessen Status mehr
   behauptet als vorliegt: confirmed braucht zwei, established drei unabhaengige
   Haeuser. Ein Primaerbeleg (Originalzitat im Interview, amtliche Mitteilung,
   Urteil) traegt allein, dafuer liefert das Modell jetzt "evidence_type". Damit
   verschwindet auch die Unterkonfidenz der Gegenprobe, in der ein ZDF-Interview
   als unbestaetigt galt, obwohl der Kritisierte dort selbst spricht.

4. Die Nachhak-Runde stuft "developing" nicht mehr hoch. Sie hatte die
   Behauptung "Der Ausloeser ist weiterhin unklar" auf BESTAETIGT gesetzt, weil
   developing in der Statusordnung die niedrigste Stufe ist. Eine Unklarheit
   laesst sich nicht bestaetigen. Ausserdem rechnet die Runde die Belegzahl nach
   dem Hochstufen neu und nimmt die Stufe zurueck, wenn die Belege sie nicht
   decken.

5. Widerspruch und Widerlegung sind getrennt. "contradicted" meint laut Auftrag
   Belege, die einander widersprechen, wurde aber als "Widerlegt" angezeigt. Im
   Bericht stand deshalb die sachlich zutreffende Spanne von 50.000 bis 60.000
   Grenzuebertritten als widerlegt, obwohl die Evidenz woertlich sagt "Die
   Zahlen variieren je nach Quelle". Jetzt: contradicted = "Widersprüchlich",
   der neue Status "false" = "Widerlegt" und setzt einen entkraeftenden Beleg
   voraus. Angepasst in Bericht, Oberflaeche, beiden Sprachdateien, Farben und
   Statusordnung.

6. Ein verbindlicher Regelblock haengt an jedem Faktencheck-Auftrag
   (bewertungsregeln()): Unabhaengigkeit heisst verschiedene Medienhaeuser,
   Agenturuebernahmen zaehlen einmal, unterschiedliche Zahlen verschiedener
   Erhebungsstellen sind kein Widerspruch sondern eine Spanne mit Zuordnung,
   und Aussagen ueber den Kenntnisstand ("Der Ausloeser ist unklar") sind keine
   pruefbaren Behauptungen und gehoeren nicht in den Faktencheck.

Die Aenderungen wirken auf beide Modellwege. Die Fixtures in test_eu_factcheck
liefern jetzt Belege aus mehreren Haeusern, weil eine einzelne Quelle keine
Bestaetigung mehr traegt.

Nachgerechnet an beiden gespeicherten Laeufen: kein Fakt wird durch die neue
Nachbedingung abgestuft, die Belegzahlen werden aber durchgehend ehrlicher.

Neu sind 27 Pruefungen, insgesamt laufen 156 ohne Netzzugriff und ohne Kosten.

Offen: Die Oberflaechen-Aenderungen (components.js, app.js, style.css, i18n)
sind noch nicht in den Lokal-Fork portiert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-08-01 22:01:30 +02:00
Ursprung f87d377082
Commit 3495409377
14 geänderte Dateien mit 512 neuen und 66 gelöschten Zeilen

Datei anzeigen

@@ -279,6 +279,8 @@ _FACT_STATUS_GROUPS = [
{"confirmed", "established"}),
("Umstritten (Quellen widersprechen sich oder Faktencheck hat Widersprüche dokumentiert):",
{"contradicted", "disputed"}),
("Widerlegt (ein Beleg entkräftet die Aussage, sie darf im Lagebild nicht als Tatsache stehen):",
{"false"}),
("Unbestätigt (nur eine einzelne Quelle, eine unabhängige Bestätigung steht aus):",
{"unconfirmed", "unverified"}),
("In Entwicklung (laufender Sachverhalt, Stand offen):",
@@ -287,7 +289,7 @@ _FACT_STATUS_GROUPS = [
_FACT_STATUS_PRIORITY = {
"confirmed": 5, "established": 5,
"contradicted": 4, "disputed": 4,
"contradicted": 4, "disputed": 4, "false": 4,
"unconfirmed": 3, "unverified": 3,
"developing": 1,
}

Datei anzeigen

@@ -32,16 +32,18 @@ AUFTRAG:
1. Identifiziere die 5-10 wichtigsten Faktenaussagen aus den Meldungen
2. Prüfe jeden Claim aktiv per WebSearch gegen mindestens eine weitere unabhängige Quelle
3. Kategorisiere jede Aussage:
- "confirmed": Durch 2+ unabhängige seriöse Quellen mit überprüfbarer URL bestätigt
- "unconfirmed": Nur 1 Quelle oder nicht unabhängig verifizierbar
- "contradicted": Widersprüchliche Informationen aus verschiedenen Quellen
- "confirmed": Mindestens 2 unabhängige Medienhäuser mit überprüfbarer URL bestätigen die Aussage
- "unconfirmed": Nur ein Medienhaus, oder nicht unabhängig verifizierbar
- "contradicted": Die Belege widersprechen EINANDER in der Sache, keine Seite ist entschieden
- "false": Ein Beleg widerlegt die Behauptung nachweislich
- "developing": Situation noch unklar, entwickelt sich
4. Markiere WICHTIGE NEUE Entwicklungen mit is_notification: true
Antworte AUSSCHLIESSLICH als JSON-Array. Jedes Element hat:
- "claim": Die Faktenaussage auf {output_language}
- "status": "confirmed" | "unconfirmed" | "contradicted" | "developing"
- "sources_count": Anzahl unabhängiger Quellen mit überprüfbarer URL
- "status": "confirmed" | "unconfirmed" | "contradicted" | "false" | "developing"
- "sources_count": Anzahl unabhängiger Medienhäuser mit überprüfbarer URL
- "evidence_type": "primary" | "secondary" | "agency" (siehe Bewertungsregeln)
- "evidence": Begründung MIT konkreten Quellen-URLs als Beleg (z.B. "Bestätigt durch: tagesschau.de (URL), Reuters (URL)")
- "is_notification": true/false (nur bei wichtigen Entwicklungen true)
@@ -72,16 +74,18 @@ Fokus: "Was sind die gesicherten Fakten zu diesem Thema?"
1. Identifiziere die 5-10 wichtigsten Faktenaussagen aus den Quellen
2. Prüfe jeden Claim aktiv per WebSearch gegen weitere unabhängige Quellen
3. Kategorisiere jede Aussage:
- "established": Breit dokumentierter, gesicherter Fakt (3+ unabhängige Quellen mit URL)
- "disputed": Umstrittener Sachverhalt, verschiedene Positionen dokumentiert
- "established": Breit dokumentierter Fakt, 3+ unabhängige Medienhäuser mit URL
- "disputed": Die Belege widersprechen EINANDER, verschiedene Positionen dokumentiert
- "false": Ein Beleg widerlegt die Behauptung nachweislich
- "unverified": Einzelbehauptung, nicht unabhängig verifizierbar
- "developing": Aktuelle Entwicklung, Faktenlage noch im Fluss
4. Markiere WICHTIGE Erkenntnisse mit is_notification: true
Antworte AUSSCHLIESSLICH als JSON-Array. Jedes Element hat:
- "claim": Die Faktenaussage auf {output_language}
- "status": "established" | "disputed" | "unverified" | "developing"
- "sources_count": Anzahl unabhängiger Quellen mit überprüfbarer URL
- "status": "established" | "disputed" | "false" | "unverified" | "developing"
- "sources_count": Anzahl unabhängiger Medienhäuser mit überprüfbarer URL
- "evidence_type": "primary" | "secondary" | "agency" (siehe Bewertungsregeln)
- "evidence": Begründung MIT konkreten Quellen-URLs als Beleg
- "is_notification": true/false
@@ -116,16 +120,18 @@ AUFTRAG:
5. Markiere wichtige Statusänderungen und neue Entwicklungen mit is_notification: true
Status-Kategorien:
- "confirmed": 2+ unabhängige seriöse Quellen mit URL
- "unconfirmed": Nur 1 Quelle
- "contradicted": Widersprüchliche Informationen
- "confirmed": 2+ unabhängige Medienhäuser mit URL
- "unconfirmed": Nur ein Medienhaus
- "contradicted": Die Belege widersprechen einander
- "false": Ein Beleg widerlegt die Behauptung nachweislich
- "developing": Situation unklar
Antworte AUSSCHLIESSLICH als JSON-Array mit ALLEN Fakten (bestehende aktualisiert + neue).
Jedes Element hat:
- "claim": Die Faktenaussage auf {output_language}
- "status": "confirmed" | "unconfirmed" | "contradicted" | "developing"
- "sources_count": Anzahl unabhängiger Quellen
- "status": "confirmed" | "unconfirmed" | "contradicted" | "false" | "developing"
- "sources_count": Anzahl unabhängiger Medienhäuser
- "evidence_type": "primary" | "secondary" | "agency"
- "evidence": Begründung MIT konkreten Quellen-URLs
- "is_notification": true/false
@@ -157,16 +163,18 @@ AUFTRAG:
4. Prüfe neue Claims per WebSearch
Status-Kategorien:
- "established": 3+ unabhängige Quellen mit URL
- "disputed": Verschiedene Positionen dokumentiert
- "established": 3+ unabhängige Medienhäuser mit URL
- "disputed": Die Belege widersprechen einander, verschiedene Positionen dokumentiert
- "false": Ein Beleg widerlegt die Behauptung nachweislich
- "unverified": Nicht unabhängig verifizierbar
- "developing": Faktenlage im Fluss
Antworte AUSSCHLIESSLICH als JSON-Array mit ALLEN Fakten (bestehende aktualisiert + neue).
Jedes Element hat:
- "claim": Die Faktenaussage auf {output_language}
- "status": "established" | "disputed" | "unverified" | "developing"
- "sources_count": Anzahl unabhängiger Quellen
- "status": "established" | "disputed" | "false" | "unverified" | "developing"
- "sources_count": Anzahl unabhängiger Medienhäuser
- "evidence_type": "primary" | "secondary" | "agency"
- "evidence": Begründung MIT konkreten Quellen-URLs
- "is_notification": true/false
@@ -237,14 +245,15 @@ ANWEISUNGEN:
Für JEDE Faktenaussage (bestehend UND neu):
1. Suche per WebSearch nach unabhängigen Bestätigungen oder Widerlegungen
2. Bewerte den Status:
- "confirmed": Mindestens 2 unabhängige Quellen mit verifizierter URL bestätigen
- "confirmed": Mindestens 2 unabhängige Medienhäuser mit verifizierter URL bestätigen
- "unconfirmed": Nicht genug unabhängige Bestätigung
- "contradicted": Glaubwürdige Quellen widersprechen
- "contradicted": Die Belege widersprechen einander in der Sache
- "false": Ein Beleg widerlegt die Behauptung nachweislich
- "developing": Lage noch im Fluss
3. Dokumentiere die Evidenz mit konkreten, verifizierten URLs
QUALITÄTSREGELN:
- "confirmed" NUR bei mindestens 2 unabhängigen Quellen mit ECHTER URL
- "confirmed" NUR bei mindestens 2 unabhängigen Medienhäusern mit ECHTER URL
- KEINE Halluzinationen — nur tatsächlich per WebSearch gefundene Quellen
- Bestehende Evidenz BEIBEHALTEN, nur neue ergänzen
- Jeder Claim MUSS eine konkrete Quellen-URL als Beleg enthalten
@@ -254,8 +263,9 @@ Antworte AUSSCHLIESSLICH als JSON-Array:
{{{{
"id": 123,
"claim": "Die Faktenaussage...",
"status": "confirmed|unconfirmed|contradicted|developing",
"status": "confirmed|unconfirmed|contradicted|false|developing",
"sources_count": 3,
"evidence_type": "primary|secondary|agency",
"evidence": "Bestätigt durch: Quelle1 (URL)\\nQuelle2 (URL)",
"is_notification": false
}}}}
@@ -283,14 +293,15 @@ ANWEISUNGEN:
Für JEDE Faktenaussage (bestehend UND neu):
1. Suche per WebSearch nach unabhängigen Bestätigungen oder Widerlegungen
2. Bewerte den Status:
- "established": 3+ unabhängige Quellen mit verifizierter URL
- "established": 3+ unabhängige Medienhäuser mit verifizierter URL
- "unverified": Nicht unabhängig verifizierbar
- "disputed": Verschiedene Positionen dokumentiert
- "disputed": Die Belege widersprechen einander, verschiedene Positionen dokumentiert
- "false": Ein Beleg widerlegt die Behauptung nachweislich
- "developing": Faktenlage im Fluss
3. Dokumentiere die Evidenz mit konkreten, verifizierten URLs
QUALITÄTSREGELN:
- "established" NUR bei mindestens 3 unabhängigen Quellen mit ECHTER URL
- "established" NUR bei mindestens 3 unabhängigen Medienhäusern mit ECHTER URL
- KEINE Halluzinationen — nur tatsächlich per WebSearch gefundene Quellen
- Bestehende Evidenz BEIBEHALTEN, nur neue ergänzen
@@ -299,8 +310,9 @@ Antworte AUSSCHLIESSLICH als JSON-Array:
{{{{
"id": 123,
"claim": "Die Faktenaussage...",
"status": "established|unverified|disputed|developing",
"status": "established|unverified|disputed|false|developing",
"sources_count": 3,
"evidence_type": "primary|secondary|agency",
"evidence": "Bestätigt durch: Quelle1 (URL)\\nQuelle2 (URL)",
"is_notification": false
}}}}
@@ -321,11 +333,62 @@ _STOPWORDS = frozenset({
STATUS_PRIORITY = {
"confirmed": 5, "established": 5,
"contradicted": 4, "disputed": 4,
"contradicted": 4, "disputed": 4, "false": 4,
"unconfirmed": 3, "unverified": 3,
"developing": 1,
}
# Status, die eine belegte Bestaetigung behaupten und deshalb an der Zahl
# unabhaengiger Medienhaeuser gemessen werden.
BESTAETIGT_STATUS = {"confirmed": 2, "established": 3}
# Ein Primaerbeleg (Originalzitat im Interview, amtliche Mitteilung, Urteil,
# Pressemitteilung der handelnden Stelle) traegt allein. Mehrfache
# Berichterstattung ueber denselben O-Ton ist keine zusaetzliche Verifikation,
# und ihr Fehlen ist kein Mangel. Ohne diese Ausnahme wurde im Vergleich vom
# 01.08.2026 ein ZDF-Interview als "unbestaetigt" gefuehrt, obwohl der
# Betroffene die Aussage dort selbst getroffen hat.
PRIMAERBELEG_MINDESTZAHL = 1
_BEWERTUNGSREGELN = """
BEWERTUNGSREGELN (verbindlich, sie gehen allen Angaben oben vor):
- Unabhängig heißt: verschiedene Medienhäuser. Mehrere Artikel derselben
Redaktion, Videofassungen derselben Meldung und wortgleiche Übernahmen
derselben Agenturmeldung zählen zusammen als EINE Quelle.
- Weiterleitungen über Sammelportale (news.google.com und ähnliche) sind kein
eigenständiger Beleg. Zähle das Zielmedium, wenn es erkennbar ist.
- "sources_count" ist die Zahl unabhängiger MEDIENHÄUSER, nicht die Zahl der
Fundstellen oder Adressen.
- "evidence_type" beschreibt den besten vorliegenden Beleg:
"primary" = die handelnde oder sprechende Stelle selbst (Originalzitat im
Interview, amtliche Mitteilung, Urteil, Pressemitteilung),
"secondary" = unabhängige eigene Berichterstattung eines Mediums,
"agency" = Agenturmeldung und ihre Übernahmen.
Bei "primary" genügt diese eine Quelle für eine Bestätigung.
- Unterschiedliche Zahlenangaben verschiedener Erhebungsstellen sind KEIN
Widerspruch. Nenne die Spanne mit Zuordnung ("50.000 laut Innenministerium,
60.000 laut Regionalregierung") und bewerte die Aussage nach ihrer
Belegtiefe. Ein Widerspruchs-Status ist nur richtig, wenn die Belege
einander in der Sache widersprechen.
- Der Status für eine nachweisliche Widerlegung ist ausschließlich "false" und
setzt einen Beleg voraus, der die Behauptung entkräftet. Eine fehlende
Bestätigung ist keine Widerlegung.
- Prüfe nur Tatsachenbehauptungen. Aussagen über den Kenntnisstand ("Der
Auslöser ist unklar", "Eine unabhängige Bestätigung steht aus", "Die Lage
bleibt unübersichtlich") sind nicht prüfbar und gehören NICHT in den
Faktencheck. Formuliere stattdessen die prüfbare Tatsache dahinter oder lass
den Punkt weg."""
def bewertungsregeln() -> str:
"""Verbindlicher Regelblock, wird an jeden Faktencheck-Auftrag angehaengt.
Bewusst am Ende des Auftrags und an einer einzigen Stelle gepflegt, damit
die sechs Vorlagen nicht auseinanderlaufen.
"""
return _BEWERTUNGSREGELN
# Zwei-Phasen-Faktencheck: Konfiguration
MAX_FACTS_PER_VERIFY_GROUP = 8 # Max Fakten pro Verifikationsgruppe
TWOPHASE_MIN_FACTS = 25 # Ab dieser Anzahl bestehender Fakten wird der Zwei-Phasen-Ansatz genutzt
@@ -363,6 +426,66 @@ Antworte NUR mit einem JSON-Array, ein Objekt je oben genannter Behauptung:
[{{"claim": "Behauptung im Wortlaut wie oben", "status": "{status_werte}", "evidence": "Begruendung mit [S..]"}}]"""
_URL_RE = re.compile(r'https?://[^\s\)\]\},;"\'<>]+')
def zaehle_medienhaeuser(urls: list[str]) -> int:
"""Zaehlt unabhaengige Medienhaeuser statt Fundstellen.
Sechs France24-Treffer, davon vier Videofassungen derselben Meldung, sind
ein Haus und kein sechsfacher Beleg. Gezaehlt wird deshalb nach
registrierbarer Domain. Weiterleitungsportale bekommen keine gemeinsame
Identitaet, weil hinter news.google.com beliebige Zeitungen stecken
koennen: dort zaehlt jede Adresse einzeln.
"""
from services import media_registry
haeuser: set[str] = set()
for url in urls or []:
sauber = (url or "").rstrip(".,;:)]}\"'")
if not sauber:
continue
domain = media_registry.registrable_domain(sauber)
if not domain:
continue
haeuser.add(sauber.lower() if media_registry.ist_aggregator(sauber) else domain)
return len(haeuser)
def pruefe_belegtiefe(fact: dict) -> dict:
"""Stuft einen Fakt ab, wenn sein Status mehr Beleg behauptet als vorliegt.
Ohne diese Nachbedingung stand im Bericht vom 01.08.2026 eine Behauptung
als BESTAETIGT mit einer einzigen Quelle, deren Evidenz woertlich mit "Nur
durch Al Jazeera berichtet" begann. Ursache war, dass die Nachhak-Runde den
Status hochsetzte, ohne die Belegzahl neu zu bestimmen.
Ein Primaerbeleg (evidence_type 'primary') traegt allein, siehe
PRIMAERBELEG_MINDESTZAHL.
"""
status = (fact.get("status") or "").strip().lower()
noetig = BESTAETIGT_STATUS.get(status)
if not noetig:
return fact
if (fact.get("evidence_type") or "").strip().lower() == "primary":
noetig = PRIMAERBELEG_MINDESTZAHL
vorhanden = fact.get("sources_count")
if not isinstance(vorhanden, int):
vorhanden = zaehle_medienhaeuser(_URL_RE.findall(fact.get("evidence") or ""))
fact["sources_count"] = vorhanden
if vorhanden < noetig:
neuer = "unconfirmed" if status == "confirmed" else "unverified"
logger.warning(
"Fakt herabgestuft (%s -> %s): %d unabhaengige Quelle(n), noetig waeren %d: '%s...'",
status, neuer, vorhanden, noetig, (fact.get("claim") or "")[:60],
)
fact["status"] = neuer
return fact
def normalize_claim(claim: str) -> str:
"""Normalisiert einen Claim fuer Aehnlichkeitsvergleich."""
c = claim.lower().strip()
@@ -614,15 +737,15 @@ class FactCheckerAgent:
logger.info("EU-Nachhak-Runde: keine zusaetzlichen Belege gefunden, Fakten bleiben unveraendert")
return facts, gesamt
status_werte = ("established|unverified|disputed|developing"
status_werte = ("established|unverified|disputed|false|developing"
if incident_type == "research"
else "confirmed|unconfirmed|contradicted|developing")
else "confirmed|unconfirmed|contradicted|false|developing")
prompt = EU_FOLLOWUP_PROMPT_TEMPLATE.format(
output_language=output_language,
title=title,
open_claims_text="\n".join(f"- {c}" for c in claims),
status_werte=status_werte,
)
) + bewertungsregeln()
try:
result, usage = await call_claude(build_eu_prompt(prompt, block), tools=None)
@@ -656,11 +779,36 @@ class FactCheckerAgent:
alt = treffer.get("status", "developing")
if STATUS_PRIORITY.get(neuer_status, 0) <= STATUS_PRIORITY.get(alt, 0):
continue
# "developing" heisst: der Sachverhalt selbst ist noch offen. Das
# ist keine schwache Beleglage, die sich nachrecherchieren laesst,
# und wird deshalb nicht hochgestuft. Ohne diese Sperre wurde am
# 01.08.2026 die Behauptung "Der Ausloeser ist weiterhin unklar"
# auf BESTAETIGT gesetzt, weil mehrere Quellen die Unklarheit
# beschrieben. Eine Unklarheit laesst sich nicht bestaetigen.
if alt == "developing" and neuer_status not in ("contradicted", "disputed", "false"):
logger.info(
"EU-Nachhak-Runde: '%s...' bleibt developing, %s nicht zulaessig",
claim[:60], neuer_status,
)
continue
treffer["status"] = neuer_status
belege = str(eintrag.get("evidence", "")).strip()
if belege:
bisher = (treffer.get("evidence") or "").strip()
treffer["evidence"] = f"{bisher} Nachrecherche: {belege}".strip() if bisher else belege
# Die Belegzahl folgt der neuen Evidenz, nicht dem alten Stand.
treffer["sources_count"] = zaehle_medienhaeuser(
_URL_RE.findall(treffer.get("evidence") or "")
)
if eintrag.get("evidence_type"):
treffer["evidence_type"] = eintrag.get("evidence_type")
pruefe_belegtiefe(treffer)
if treffer["status"] != neuer_status:
logger.info(
"EU-Nachhak-Runde: '%s...' Hochstufung auf %s nicht gedeckt, bleibt %s",
claim[:60], neuer_status, treffer["status"],
)
continue
hochgestuft += 1
logger.info("EU-Nachhak-Runde: '%s...' %s -> %s", claim[:60], alt, neuer_status)
@@ -682,7 +830,7 @@ class FactCheckerAgent:
title=title,
articles_text=articles_text,
output_language=output_language,
)
) + bewertungsregeln()
try:
# EU-Modus: gezielte staan-Belegsuche plus Nachhak-Runde statt WebSearch.
@@ -733,7 +881,7 @@ class FactCheckerAgent:
articles_text=articles_text,
existing_facts_text=existing_facts_text,
output_language=output_language,
)
) + bewertungsregeln()
try:
# EU-Modus: gezielte Belegsuche zu offenen Fakten plus Nachhak-Runde.
@@ -854,7 +1002,7 @@ class FactCheckerAgent:
facts_text=facts_text,
new_claims_text=new_claims_text,
articles_text=articles_text,
)
) + bewertungsregeln()
try:
# EU-Modus: gezielte Belegsuche je Faktengruppe plus Nachhak-Runde.
@@ -965,9 +1113,11 @@ class FactCheckerAgent:
"""Validiert Fakten und ordnet Quellen-URLs aus den Artikeln zu.
Stellt sicher, dass jeder confirmed/established Fakt URLs in der
evidence hat, damit das Frontend die Quellen korrekt anzeigen kann.
evidence hat, damit das Frontend die Quellen korrekt anzeigen kann,
zaehlt die Belege nach unabhaengigen Medienhaeusern und stuft Fakten
ab, deren Status mehr Belegtiefe behauptet als vorhanden ist.
"""
url_pattern = re.compile(r'https?://')
url_pattern = _URL_RE
# Verfuegbare Artikel-URLs sammeln (dedupliziert nach URL)
article_sources = []
seen_urls = set()
@@ -1025,8 +1175,9 @@ class FactCheckerAgent:
)
# sources_count aktualisieren
all_urls = url_pattern.findall(fact["evidence"])
fact["sources_count"] = len(set(all_urls))
fact["sources_count"] = zaehle_medienhaeuser(
url_pattern.findall(fact["evidence"])
)
logger.info(
f"Fakt '{fact.get('claim', '')[:50]}...' ergaenzt mit "
@@ -1040,6 +1191,15 @@ class FactCheckerAgent:
f"Fakt herabgestuft ({old_status} -> {fact['status']}): "
f"keine Quellen zuordnebar: '{fact.get('claim', '')[:60]}...'"
)
else:
# Nur Belege aus der Antwort des Modells: die Zahl trotzdem
# selbst bestimmen, statt der Selbstauskunft zu vertrauen.
fact["sources_count"] = zaehle_medienhaeuser(
url_pattern.findall(fact["evidence"] or "")
)
for fact in facts:
pruefe_belegtiefe(fact)
return facts
def _parse_response(self, response: str, articles: list[dict] = None) -> list[dict]:

Datei anzeigen

@@ -2073,7 +2073,7 @@ class AgentOrchestrator:
# Status-Statistik sammeln
if new_status in ("confirmed", "established"):
confirmed_count += 1
elif new_status in ("contradicted", "disputed"):
elif new_status in ("contradicted", "disputed", "false"):
contradicted_count += 1
await db.commit()
@@ -2161,7 +2161,7 @@ class AgentOrchestrator:
"type": "status_change",
"title": title,
"text": f"{sc['claim']}: {sc['old_status']} \u2192 {sc['new_status']}",
"icon": "error" if sc["new_status"] in ("contradicted", "disputed") else "success",
"icon": "error" if sc["new_status"] in ("contradicted", "disputed", "false") else "success",
})
if created_by:

Datei anzeigen

@@ -31,23 +31,27 @@ FC_STATUS_LABELS_DE = {
# 1:1 vom Monitor-Frontend (components.js) — konsistent zum UI.
"confirmed": "Bestätigt",
"unconfirmed": "Unbestätigt",
"contradicted": "Widerlegt",
# "Widerlegt" war hier falsch: Der Status meint laut Auftrag Belege, die
# einander widersprechen. Im Bericht vom 01.08.2026 stand deshalb eine
# sachlich zutreffende Zahlenspanne als widerlegt. Die echte Widerlegung
# hat jetzt den eigenen Status "false".
"contradicted": "Widersprüchlich",
"developing": "Unklar",
"established": "Gesichert",
"disputed": "Umstritten",
"unverified": "Ungeprüft",
"false": "Falsch",
"false": "Widerlegt",
}
FC_STATUS_LABELS_EN = {
"confirmed": "Confirmed",
"unconfirmed": "Unconfirmed",
"contradicted": "Contradicted",
"contradicted": "Conflicting",
"developing": "Developing",
"established": "Established",
"disputed": "Disputed",
"unverified": "Unverified",
"false": "False",
"false": "Disproved",
}

Datei anzeigen

@@ -16,7 +16,7 @@ logger = logging.getLogger("osint.fact_consolidation")
STATUS_PRIORITY = {
"confirmed": 5, "established": 5,
"contradicted": 4, "disputed": 4,
"contradicted": 4, "disputed": 4, "false": 4,
"unconfirmed": 3, "unverified": 3,
"developing": 1,
}

Datei anzeigen

@@ -20,7 +20,7 @@ logger = logging.getLogger("osint.post_refresh_qc")
STATUS_PRIORITY = {
"confirmed": 5, "established": 5,
"contradicted": 4, "disputed": 4,
"contradicted": 4, "disputed": 4, "false": 4,
"unconfirmed": 3, "unverified": 3,
"developing": 1,
}

Datei anzeigen

@@ -1199,6 +1199,12 @@ a.dev-source-pill:hover {
}
.factcheck-icon.contradicted {
background: var(--tint-warning);
color: var(--warning);
}
/* Widerlegt: eigener Status seit der Trennung von Widerspruch und Widerlegung */
.factcheck-icon.false {
background: var(--tint-error);
color: var(--error);
}
@@ -1914,6 +1920,11 @@ a.dev-source-pill:hover {
}
.badge-contradicted {
background: var(--tint-warning);
color: var(--warning);
}
.badge-false {
background: var(--tint-error);
color: var(--error);
}

Datei anzeigen

@@ -111,7 +111,8 @@
"card.sources_overview": "Quellenübersicht",
"fc.label.confirmed": "Bestätigt durch mehrere Quellen",
"fc.label.unconfirmed": "Nicht unabhängig bestätigt",
"fc.label.contradicted": "Widerlegt",
"fc.label.contradicted": "Quellen widersprechen sich",
"fc.label.false": "Widerlegt",
"fc.label.developing": "Faktenlage noch im Fluss",
"fc.label.established": "Gesicherter Fakt (3+ Quellen)",
"fc.label.disputed": "Umstrittener Sachverhalt",
@@ -122,10 +123,12 @@
"fc.tooltip.unconfirmed": "Unbestätigt: Bisher nur aus einer Quelle bekannt. Eine unabhängige Bestätigung steht aus.",
"fc.tooltip.unverified": "Ungeprüft: Die Aussage konnte bisher nicht anhand verfügbarer Quellen überprüft werden.",
"fc.tooltip.disputed": "Umstritten: Quellen widersprechen sich. Es gibt sowohl stützende als auch widersprechende Belege.",
"fc.tooltip.contradicted": "Widerlegt: Zuverlässige Quellen widersprechen dieser Aussage. Wahrscheinlich falsch.",
"fc.tooltip.contradicted": "Widersprüchlich: Die Belege widersprechen einander, keine Seite ist entschieden. Unterschiedliche Zahlen verschiedener Erhebungsstellen gelten nicht als Widerspruch.",
"fc.tooltip.false": "Widerlegt: Ein Beleg entkräftet diese Aussage nachweislich.",
"fc.chip.confirmed": "Bestätigt",
"fc.chip.unconfirmed": "Unbestätigt",
"fc.chip.contradicted": "Widerlegt",
"fc.chip.contradicted": "Widersprüchlich",
"fc.chip.false": "Widerlegt",
"fc.chip.developing": "Unklar",
"fc.chip.established": "Gesichert",
"fc.chip.disputed": "Umstritten",
@@ -133,7 +136,7 @@
"refresh.no_developments": "Keine neuen Entwicklungen",
"refresh.new_articles_suffix": "neue Artikel",
"refresh.confirmed_suffix": "Fakten bestätigt",
"refresh.contradicted_suffix": "widerlegt",
"refresh.contradicted_suffix": "widersprüchlich",
"progress.status.queued": "In Warteschlange",
"progress.status.researching": "Recherchiert...",
"progress.status.deep_researching": "Tiefenrecherche...",

Datei anzeigen

@@ -111,7 +111,8 @@
"card.sources_overview": "Sources overview",
"fc.label.confirmed": "Confirmed by multiple sources",
"fc.label.unconfirmed": "Not independently confirmed",
"fc.label.contradicted": "Contradicted",
"fc.label.contradicted": "Sources conflict",
"fc.label.false": "Disproved",
"fc.label.developing": "Facts still developing",
"fc.label.established": "Established fact (3+ sources)",
"fc.label.disputed": "Disputed matter",
@@ -122,10 +123,12 @@
"fc.tooltip.unconfirmed": "Unconfirmed: known from only one source so far. Independent confirmation is pending.",
"fc.tooltip.unverified": "Unverified: the claim could not yet be checked against available sources.",
"fc.tooltip.disputed": "Disputed: sources disagree. There is both supporting and contradicting evidence.",
"fc.tooltip.contradicted": "Contradicted: reliable sources contradict this claim. Likely false.",
"fc.tooltip.contradicted": "Conflicting: the evidence contradicts itself and no side is settled. Differing figures from different authorities do not count as a conflict.",
"fc.tooltip.false": "Disproved: evidence refutes this claim.",
"fc.chip.confirmed": "Confirmed",
"fc.chip.unconfirmed": "Unconfirmed",
"fc.chip.contradicted": "Contradicted",
"fc.chip.contradicted": "Conflicting",
"fc.chip.false": "Disproved",
"fc.chip.developing": "Developing",
"fc.chip.established": "Established",
"fc.chip.disputed": "Disputed",
@@ -133,7 +136,7 @@
"refresh.no_developments": "No new developments",
"refresh.new_articles_suffix": "new articles",
"refresh.confirmed_suffix": "facts confirmed",
"refresh.contradicted_suffix": "contradicted",
"refresh.contradicted_suffix": "conflicting",
"progress.status.queued": "Queued",
"progress.status.researching": "Researching...",
"progress.status.deep_researching": "Deep research...",

Datei anzeigen

@@ -2385,7 +2385,8 @@ async handleRefresh() {
confirmed: 'Bestätigt',
established: 'Gesichert',
unconfirmed: 'Unbestätigt',
contradicted: 'Widersprochen',
contradicted: 'Widersprüchlich',
false: 'Widerlegt',
disputed: 'Umstritten',
developing: 'In Entwicklung',
unverified: 'Ungeprüft',

Datei anzeigen

@@ -82,7 +82,8 @@ const UI = {
_fcLabelDefaultsDE: {
confirmed: 'Bestätigt durch mehrere Quellen',
unconfirmed: 'Nicht unabhängig bestätigt',
contradicted: 'Widerlegt',
contradicted: 'Quellen widersprechen sich',
false: 'Widerlegt',
developing: 'Faktenlage noch im Fluss',
established: 'Gesicherter Fakt (3+ Quellen)',
disputed: 'Umstrittener Sachverhalt',
@@ -95,12 +96,14 @@ const UI = {
unconfirmed: 'Unbestätigt: Bisher nur aus einer Quelle bekannt. Eine unabhängige Bestätigung steht aus.',
unverified: 'Ungeprüft: Die Aussage konnte bisher nicht anhand verfügbarer Quellen überprüft werden.',
disputed: 'Umstritten: Quellen widersprechen sich. Es gibt sowohl stützende als auch widersprechende Belege.',
contradicted: 'Widerlegt: Zuverlässige Quellen widersprechen dieser Aussage. Wahrscheinlich falsch.',
contradicted: 'Widersprüchlich: Die Belege widersprechen einander, keine Seite ist entschieden. Unterschiedliche Zahlen verschiedener Erhebungsstellen gelten nicht als Widerspruch.',
false: 'Widerlegt: Ein Beleg entkräftet diese Aussage nachweislich.',
},
_fcChipDefaultsDE: {
confirmed: 'Bestätigt',
unconfirmed: 'Unbestätigt',
contradicted: 'Widerlegt',
contradicted: 'Widersprüchlich',
false: 'Widerlegt',
developing: 'Unklar',
established: 'Gesichert',
disputed: 'Umstritten',
@@ -138,7 +141,8 @@ const UI = {
factCheckIcons: {
confirmed: '&#10003;',
unconfirmed: '?',
contradicted: '&#10007;',
contradicted: '&#8644;',
false: '&#10007;',
developing: '&#8635;',
established: '&#10003;',
disputed: '&#9888;',
@@ -154,7 +158,7 @@ const UI = {
factchecks.forEach(fc => {
statusCounts[fc.status] = (statusCounts[fc.status] || 0) + 1;
});
const statusOrder = ['confirmed', 'established', 'developing', 'unconfirmed', 'unverified', 'disputed', 'contradicted'];
const statusOrder = ['confirmed', 'established', 'developing', 'unconfirmed', 'unverified', 'disputed', 'contradicted', 'false'];
const usedStatuses = statusOrder.filter(s => statusCounts[s]);
if (usedStatuses.length <= 1) return '';
@@ -558,7 +562,7 @@ const UI = {
const parts = [];
if (data.new_articles > 0) parts.push(data.new_articles + ' neue Artikel');
if (data.confirmed_count > 0) parts.push(data.confirmed_count + ' Fakten best\u00e4tigt');
if (data.contradicted_count > 0) parts.push(data.contradicted_count + ' widerlegt');
if (data.contradicted_count > 0) parts.push(data.contradicted_count + ' widersprüchlich');
const summaryText = parts.length > 0 ? parts.join(', ') : 'Keine neuen Entwicklungen';
const summaryEl = document.getElementById('progress-complete-summary');