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:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: '✓',
|
||||
unconfirmed: '?',
|
||||
contradicted: '✗',
|
||||
contradicted: '⇄',
|
||||
false: '✗',
|
||||
developing: '↻',
|
||||
established: '✓',
|
||||
disputed: '⚠',
|
||||
@@ -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');
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren