fix(faktencheck): Zahl der Faktenaussagen richtet sich nach dem Material

In allen Auftraegen stand fest "5-10 wichtigste Faktenaussagen", bei der
Aktualisierung "3-5 neue". Die Zahl hatte damit nichts mit der Menge des
Materials zu tun. Aus 16 Meldungen wurden genauso viele Fakten wie aus 91.

Beide Wege lasen dieselbe Vorgabe verschieden. Der Weg ueber die Anthropic-CLI
behandelte sie als Richtwert und lag zwischen 9 und 21 Fakten, ueberschritt die
Zehn also regelmaessig. Das Modell auf dem EU-Weg las sie als Zielvorgabe und
traf sie in neun von siebzehn Laeufen exakt. Daher wirkten die EU-Berichte so
gleichfoermig, nicht die Lage bestimmte den Umfang, sondern die Zahl im Text.

Bei der Ceuta-Lage bedeutete das, dass zehn Punkte eine europaweite Krise
abbilden mussten, in der Opferzahlen, die schwimmende Barriere, das
Sondertreffen der Innenminister, Italiens Schengen-Aussetzung, der offene Brief
von 22 Staaten, die Rueckfuehrungen und mehrere Regierungsaeusserungen
vorkamen.

faktenspanne() berechnet den Richtwert jetzt aus der Zahl der Meldungen, grob
eine Aussage je fuenf Meldungen, begrenzt auf 5 bis 20 bei der Erstpruefung und
3 bis 10 bei der Aktualisierung. 16 Meldungen ergeben weiterhin 5 bis 10, 88
Meldungen ergeben 9 bis 18. Alle Werte sind ueber Umgebungsvariablen
einstellbar.

Dazu die Formulierung. Die Spanne ist jetzt ausdruecklich als Richtwert und
nicht als Zielvorgabe bezeichnet, mit der Erlaubnis, weniger zu nennen wenn das
Material nicht mehr hergibt, und mehr wenn sonst wichtige Punkte fehlen wuerden.
Betroffen sind die vier Faktencheck-Vorlagen und die Triage.

Neu sind 25 Pruefungen, insgesamt laufen 279 ohne Netzzugriff und ohne Kosten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-08-02 11:47:56 +00:00
Ursprung b00076d320
Commit e440ef93d1
4 geänderte Dateien mit 196 neuen und 5 gelöschten Zeilen

Datei anzeigen

@@ -162,6 +162,7 @@ tests/:
hinweis: "Laufen ohne Netzzugriff und ohne Kosten, Modell, Suche und Datenbank sind durch Testdoubles ersetzt. Aufruf aus dem Projektstamm, zum Beispiel venv/bin/python tests/test_eu_factcheck.py" hinweis: "Laufen ohne Netzzugriff und ohne Kosten, Modell, Suche und Datenbank sind durch Testdoubles ersetzt. Aufruf aus dem Projektstamm, zum Beispiel venv/bin/python tests/test_eu_factcheck.py"
test_eu_factcheck.py: "EU-Faktencheck, Nachhak-Runde, Quellenzuordnung, plus Regressionsschutz fuer den Anthropic-Weg" test_eu_factcheck.py: "EU-Faktencheck, Nachhak-Runde, Quellenzuordnung, plus Regressionsschutz fuer den Anthropic-Weg"
test_eu_belegsuche.py: "Gezielte Belegsuche ueber staan, Planung, Grenzen, Entdopplung, Ausfallverhalten" test_eu_belegsuche.py: "Gezielte Belegsuche ueber staan, Planung, Grenzen, Entdopplung, Ausfallverhalten"
test_faktenspanne.py: "Richtwert fuer die Zahl der Faktenaussagen, waechst mit der Menge der Meldungen"
test_qc_und_runden.py: "Absicherung der Duplikatpruefung und Abbruch der Suchrunden bei erreichter Treffergrenze" test_qc_und_runden.py: "Absicherung der Duplikatpruefung und Abbruch der Suchrunden bei erreichter Treffergrenze"
test_mehrfachantwort.py: "Antworten, in denen sich das Modell selbst korrigiert und mehrere Fassungen liefert, die ausfuehrlichste muss gewinnen" test_mehrfachantwort.py: "Antworten, in denen sich das Modell selbst korrigiert und mehrere Fassungen liefert, die ausfuehrlichste muss gewinnen"
test_faktencheck_belegtiefe.py: "Belegzaehlung nach Medienhaus, Nachbedingung fuer bestaetigte Fakten, Sperre gegen das Hochstufen von developing, getrennte Bezeichnungen fuer Widerspruch und Widerlegung" test_faktencheck_belegtiefe.py: "Belegzaehlung nach Medienhaus, Nachbedingung fuer bestaetigte Fakten, Sperre gegen das Hochstufen von developing, getrennte Bezeichnungen fuer Widerspruch und Widerlegung"

Datei anzeigen

@@ -29,7 +29,10 @@ STRENGE REGELN - KEINE HALLUZINATIONEN:
- Lieber "unconfirmed" als falsch bestätigt - Lieber "unconfirmed" als falsch bestätigt
AUFTRAG: AUFTRAG:
1. Identifiziere die 5-10 wichtigsten Faktenaussagen aus den Meldungen 1. Identifiziere die wichtigsten Faktenaussagen aus den Meldungen. Als Richtwert
sind das etwa {fakten_min} bis {fakten_max} Aussagen. Das ist ein Richtwert und keine
Zielvorgabe. Nenne weniger, wenn das Material nicht mehr hergibt, und mehr, wenn
sonst wichtige Punkte der Lage fehlen wuerden.
2. Prüfe jeden Claim aktiv per WebSearch gegen mindestens eine weitere unabhängige Quelle 2. Prüfe jeden Claim aktiv per WebSearch gegen mindestens eine weitere unabhängige Quelle
3. Kategorisiere jede Aussage: 3. Kategorisiere jede Aussage:
- "confirmed": Mindestens 2 unabhängige Medienhäuser mit überprüfbarer URL bestätigen die Aussage - "confirmed": Mindestens 2 unabhängige Medienhäuser mit überprüfbarer URL bestätigen die Aussage
@@ -71,7 +74,10 @@ STRENGE REGELN - KEINE HALLUZINATIONEN:
AUFTRAG: AUFTRAG:
Fokus: "Was sind die gesicherten Fakten zu diesem Thema?" Fokus: "Was sind die gesicherten Fakten zu diesem Thema?"
1. Identifiziere die 5-10 wichtigsten Faktenaussagen aus den Quellen 1. Identifiziere die wichtigsten Faktenaussagen aus den Quellen. Als Richtwert
sind das etwa {fakten_min} bis {fakten_max} Aussagen. Das ist ein Richtwert und keine
Zielvorgabe. Nenne weniger, wenn das Material nicht mehr hergibt, und mehr, wenn
sonst wichtige Punkte der Lage fehlen wuerden.
2. Prüfe jeden Claim aktiv per WebSearch gegen weitere unabhängige Quellen 2. Prüfe jeden Claim aktiv per WebSearch gegen weitere unabhängige Quellen
3. Kategorisiere jede Aussage: 3. Kategorisiere jede Aussage:
- "established": Breit dokumentierter Fakt, 3+ unabhängige Medienhäuser mit URL - "established": Breit dokumentierter Fakt, 3+ unabhängige Medienhäuser mit URL
@@ -115,7 +121,8 @@ STRENGE REGELN - KEINE HALLUZINATIONEN:
AUFTRAG: AUFTRAG:
1. Prüfe ob die neuen Meldungen bereits geprüfte Fakten BESTÄTIGEN, WIDERLEGEN oder ERGÄNZEN 1. Prüfe ob die neuen Meldungen bereits geprüfte Fakten BESTÄTIGEN, WIDERLEGEN oder ERGÄNZEN
2. Aktualisiere den Status bestehender Fakten wenn nötig (z.B. "unconfirmed""confirmed") 2. Aktualisiere den Status bestehender Fakten wenn nötig (z.B. "unconfirmed""confirmed")
3. Identifiziere 3-5 NEUE Faktenaussagen aus den neuen Meldungen 3. Identifiziere NEUE Faktenaussagen aus den neuen Meldungen, als Richtwert etwa
{fakten_min} bis {fakten_max}. Auch das ist ein Richtwert, keine Zielvorgabe.
4. Prüfe neue Claims per WebSearch gegen unabhängige Quellen 4. Prüfe neue Claims per WebSearch gegen unabhängige Quellen
5. Markiere wichtige Statusänderungen und neue Entwicklungen mit is_notification: true 5. Markiere wichtige Statusänderungen und neue Entwicklungen mit is_notification: true
@@ -159,7 +166,8 @@ STRENGE REGELN - KEINE HALLUZINATIONEN:
AUFTRAG: AUFTRAG:
1. Prüfe ob die neuen Quellen bereits geprüfte Fakten bestätigen, widerlegen oder ergänzen 1. Prüfe ob die neuen Quellen bereits geprüfte Fakten bestätigen, widerlegen oder ergänzen
2. Aktualisiere den Status bestehender Fakten wenn nötig 2. Aktualisiere den Status bestehender Fakten wenn nötig
3. Identifiziere 3-5 NEUE Faktenaussagen aus den neuen Quellen 3. Identifiziere NEUE Faktenaussagen aus den neuen Quellen, als Richtwert etwa
{fakten_min} bis {fakten_max}. Auch das ist ein Richtwert, keine Zielvorgabe.
4. Prüfe neue Claims per WebSearch 4. Prüfe neue Claims per WebSearch
Status-Kategorien: Status-Kategorien:
@@ -198,7 +206,8 @@ Analysiere die neuen Artikel und identifiziere:
betroffen sein? (neue Bestätigung, Widerlegung, neue Evidenz, Status-Update nötig) betroffen sein? (neue Bestätigung, Widerlegung, neue Evidenz, Status-Update nötig)
2. NEUE FAKTENAUSSAGEN: Welche neuen prüfbaren Faktenaussagen enthalten die Artikel, 2. NEUE FAKTENAUSSAGEN: Welche neuen prüfbaren Faktenaussagen enthalten die Artikel,
die noch nicht in den bestehenden Fakten erfasst sind? (3-5 neue Claims) die noch nicht in den bestehenden Fakten erfasst sind? Als Richtwert etwa
{fakten_min} bis {fakten_max} neue Claims, je nachdem was die Artikel hergeben.
3. GRUPPIERUNG: Gruppiere verwandte Fakten thematisch für die parallele Batch-Verarbeitung. 3. GRUPPIERUNG: Gruppiere verwandte Fakten thematisch für die parallele Batch-Verarbeitung.
Verwandte Fakten MÜSSEN in derselben Gruppe sein. Max {max_per_group} Fakten pro Gruppe. Verwandte Fakten MÜSSEN in derselben Gruppe sein. Max {max_per_group} Fakten pro Gruppe.
@@ -398,6 +407,34 @@ BEWERTUNGSREGELN (verbindlich, sie gehen allen Angaben oben vor):
keine Pflicht zur Erfindung.""" keine Pflicht zur Erfindung."""
def faktenspanne(anzahl_meldungen: int, neu: bool = False) -> tuple[int, int]:
"""Richtwert fuer die Zahl der Faktenaussagen, gemessen am Material.
Frueher stand in allen Auftraegen fest "5-10 wichtigste Faktenaussagen".
Das fuehrte dazu, dass aus 16 Meldungen genauso viele Fakten wurden wie aus
91, obwohl die groessere Lage sichtbar mehr pruefbare Punkte hergab. Das
Modell auf dem EU-Weg traf die Zehn in neun von siebzehn Laeufen exakt, es
las die Spanne als Zielvorgabe. Der Weg ueber die Anthropic-CLI behandelte
sie als Richtwert und lag zwischen neun und einundzwanzig.
neu=True gilt fuer die Aktualisierung, dort zaehlen nur die neu
hinzugekommenen Meldungen und der Korridor ist enger.
"""
from config import (
FAKTEN_JE_ARTIKEL, FAKTEN_MAX, FAKTEN_MIN,
FAKTEN_NEU_JE_ARTIKEL, FAKTEN_NEU_MAX, FAKTEN_NEU_MIN,
)
je_artikel = FAKTEN_NEU_JE_ARTIKEL if neu else FAKTEN_JE_ARTIKEL
kleinstwert = FAKTEN_NEU_MIN if neu else FAKTEN_MIN
groesstwert = FAKTEN_NEU_MAX if neu else FAKTEN_MAX
aus_material = round(max(0, anzahl_meldungen) / max(1, je_artikel))
obergrenze = max(kleinstwert * 2, min(groesstwert, aus_material))
untergrenze = max(kleinstwert, obergrenze // 2)
return untergrenze, obergrenze
def bewertungsregeln() -> str: def bewertungsregeln() -> str:
"""Verbindlicher Regelblock, wird an jeden Faktencheck-Auftrag angehaengt. """Verbindlicher Regelblock, wird an jeden Faktencheck-Auftrag angehaengt.
@@ -1055,10 +1092,16 @@ class FactCheckerAgent:
articles_text = self._format_articles_text(articles) articles_text = self._format_articles_text(articles)
template = RESEARCH_FACTCHECK_PROMPT_TEMPLATE if incident_type == "research" else FACTCHECK_PROMPT_TEMPLATE template = RESEARCH_FACTCHECK_PROMPT_TEMPLATE if incident_type == "research" else FACTCHECK_PROMPT_TEMPLATE
fakten_min, fakten_max = faktenspanne(len(articles))
logger.info(
"Faktencheck: %d Meldungen, Richtwert %d bis %d Faktenaussagen",
len(articles), fakten_min, fakten_max)
prompt = template.format( prompt = template.format(
title=title, title=title,
articles_text=articles_text, articles_text=articles_text,
output_language=output_language, output_language=output_language,
fakten_min=fakten_min,
fakten_max=fakten_max,
) + bewertungsregeln() ) + bewertungsregeln()
try: try:
@@ -1105,11 +1148,17 @@ class FactCheckerAgent:
else: else:
template = INCREMENTAL_FACTCHECK_PROMPT_TEMPLATE template = INCREMENTAL_FACTCHECK_PROMPT_TEMPLATE
fakten_min, fakten_max = faktenspanne(len(new_articles), neu=True)
logger.info(
"Inkrementeller Faktencheck: %d neue Meldungen, Richtwert %d bis %d neue Aussagen",
len(new_articles), fakten_min, fakten_max)
prompt = template.format( prompt = template.format(
title=title, title=title,
articles_text=articles_text, articles_text=articles_text,
existing_facts_text=existing_facts_text, existing_facts_text=existing_facts_text,
output_language=output_language, output_language=output_language,
fakten_min=fakten_min,
fakten_max=fakten_max,
) + bewertungsregeln() ) + bewertungsregeln()
try: try:
@@ -1164,6 +1213,7 @@ class FactCheckerAgent:
articles_text = self._format_articles_text(new_articles, max_articles=15) articles_text = self._format_articles_text(new_articles, max_articles=15)
from config import CLAUDE_MODEL_FAST from config import CLAUDE_MODEL_FAST
triage_min, triage_max = faktenspanne(len(new_articles), neu=True)
triage_prompt = TRIAGE_PROMPT_TEMPLATE.format( triage_prompt = TRIAGE_PROMPT_TEMPLATE.format(
output_language=output_language, output_language=output_language,
fact_count=len(existing_facts), fact_count=len(existing_facts),
@@ -1171,6 +1221,8 @@ class FactCheckerAgent:
article_count=len(new_articles), article_count=len(new_articles),
articles_text=articles_text, articles_text=articles_text,
max_per_group=MAX_FACTS_PER_VERIFY_GROUP, max_per_group=MAX_FACTS_PER_VERIFY_GROUP,
fakten_min=triage_min,
fakten_max=triage_max,
) )
try: try:

Datei anzeigen

@@ -140,6 +140,19 @@ EU_VERIFY_COUNTER_SHARE = float(os.environ.get("EU_VERIFY_COUNTER_SHARE", "0.3")
EU_VERIFY_FULLTEXT_QUERIES = int(os.environ.get("EU_VERIFY_FULLTEXT_QUERIES", "3")) EU_VERIFY_FULLTEXT_QUERIES = int(os.environ.get("EU_VERIFY_FULLTEXT_QUERIES", "3"))
EU_VERIFY_FULLTEXT_CHARS = int(os.environ.get("EU_VERIFY_FULLTEXT_CHARS", "1800")) EU_VERIFY_FULLTEXT_CHARS = int(os.environ.get("EU_VERIFY_FULLTEXT_CHARS", "1800"))
# Wie viele Faktenaussagen ein Faktencheck aufstellen soll. Der Richtwert waechst
# mit der Menge des Materials. Eine feste Obergrenze von zehn fuehrte dazu, dass
# aus 16 Meldungen genauso viele Fakten wurden wie aus 91, obwohl die Lage im
# zweiten Fall deutlich mehr pruefbare Punkte hergibt.
FAKTEN_JE_ARTIKEL = int(os.environ.get("FAKTEN_JE_ARTIKEL", "5"))
FAKTEN_MIN = int(os.environ.get("FAKTEN_MIN", "5"))
FAKTEN_MAX = int(os.environ.get("FAKTEN_MAX", "20"))
# Dasselbe fuer die Aktualisierung, dort zaehlen nur die neu hinzugekommenen
# Meldungen.
FAKTEN_NEU_JE_ARTIKEL = int(os.environ.get("FAKTEN_NEU_JE_ARTIKEL", "5"))
FAKTEN_NEU_MIN = int(os.environ.get("FAKTEN_NEU_MIN", "3"))
FAKTEN_NEU_MAX = int(os.environ.get("FAKTEN_NEU_MAX", "10"))
# --- Verbrauchssaetze (Credits je Aktion) ------------------------------------- # --- Verbrauchssaetze (Credits je Aktion) -------------------------------------
# Beschlossen 2026-07-23, der Verbrauchsrechner im Verwaltungsportal nutzt # Beschlossen 2026-07-23, der Verbrauchsrechner im Verwaltungsportal nutzt
# dieselben Werte: Live-Lauf 45, Recherche je Durchlauf 40 (das Anlegen faehrt # dieselben Werte: Live-Lauf 45, Recherche je Durchlauf 40 (das Anlegen faehrt

125
tests/test_faktenspanne.py Normale Datei
Datei anzeigen

@@ -0,0 +1,125 @@
"""Testet, dass die Zahl der Faktenaussagen mit dem Material waechst.
Laeuft ohne Netzzugriff und ohne Kosten, der Modellaufruf ist ersetzt.
Aufruf aus dem Projektstamm:
venv/bin/python tests/test_faktenspanne.py
"""
import asyncio
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src"))
import agents.factchecker as fcmod # noqa: E402
import agents.claude_client as cc # noqa: E402
from agents.factchecker import ( # noqa: E402
FactCheckerAgent, faktenspanne, FACTCHECK_PROMPT_TEMPLATE,
RESEARCH_FACTCHECK_PROMPT_TEMPLATE, INCREMENTAL_FACTCHECK_PROMPT_TEMPLATE,
INCREMENTAL_RESEARCH_FACTCHECK_PROMPT_TEMPLATE, TRIAGE_PROMPT_TEMPLATE,
)
from agents.claude_client import _ai_backend_var, ClaudeUsage # noqa: E402
from config import FAKTEN_MAX, FAKTEN_MIN, FAKTEN_NEU_MAX, FAKTEN_NEU_MIN # noqa: E402
ok = 0
fail = 0
def pruefe(name, bedingung, extra=""):
global ok, fail
if bedingung:
ok += 1
print(" OK " + name)
else:
fail += 1
print(" FEHL " + name + " " + str(extra))
print("\nA) Die Spanne waechst mit der Zahl der Meldungen")
klein = faktenspanne(16)
mittel = faktenspanne(53)
gross = faktenspanne(88)
sehr_gross = faktenspanne(300)
pruefe("A1 wenige Meldungen ergeben die alte Spanne", klein == (5, 10), klein)
pruefe("A2 mehr Meldungen ergeben mehr Fakten", gross[1] > klein[1], (klein, gross))
pruefe("A3 die Reihenfolge stimmt",
klein[1] <= mittel[1] <= gross[1] <= sehr_gross[1],
(klein, mittel, gross, sehr_gross))
pruefe("A4 Obergrenze wird eingehalten", sehr_gross[1] == FAKTEN_MAX, sehr_gross)
pruefe("A5 Untergrenze wird eingehalten", faktenspanne(0)[0] == FAKTEN_MIN, faktenspanne(0))
pruefe("A6 Untergrenze liegt nie ueber der Obergrenze",
all(faktenspanne(n)[0] <= faktenspanne(n)[1] for n in range(0, 400, 7)))
pruefe("A7 negative Eingabe bricht nicht", faktenspanne(-5) == (5, 10), faktenspanne(-5))
print("\nB) Fuer die Aktualisierung gilt ein engerer Korridor")
neu_klein = faktenspanne(8, neu=True)
neu_gross = faktenspanne(200, neu=True)
pruefe("B1 wenige neue Meldungen ergeben die alte Spanne",
neu_klein == (FAKTEN_NEU_MIN, FAKTEN_NEU_MIN * 2), neu_klein)
pruefe("B2 Obergrenze der Aktualisierung greift", neu_gross[1] == FAKTEN_NEU_MAX, neu_gross)
pruefe("B3 Aktualisierung bleibt unter der Erstpruefung",
faktenspanne(200, neu=True)[1] < faktenspanne(200)[1])
print("\nC) Die Auftraege enthalten Platzhalter statt fester Zahlen")
for name, vorlage in (
("Erstpruefung adhoc", FACTCHECK_PROMPT_TEMPLATE),
("Erstpruefung Recherche", RESEARCH_FACTCHECK_PROMPT_TEMPLATE),
("Aktualisierung adhoc", INCREMENTAL_FACTCHECK_PROMPT_TEMPLATE),
("Aktualisierung Recherche", INCREMENTAL_RESEARCH_FACTCHECK_PROMPT_TEMPLATE),
("Triage", TRIAGE_PROMPT_TEMPLATE)):
pruefe(f"C {name} nutzt den Richtwert",
"{fakten_min}" in vorlage and "{fakten_max}" in vorlage)
pruefe(f"C {name} ohne feste Vorgabe",
"5-10" not in vorlage and "3-5 " not in vorlage and "(3-5" not in vorlage)
pruefe("C Richtwert ist ausdruecklich keine Zielvorgabe",
"Richtwert und keine" in FACTCHECK_PROMPT_TEMPLATE
and "Zielvorgabe" in FACTCHECK_PROMPT_TEMPLATE)
pruefe("C Abweichen nach oben und unten ist erlaubt",
"weniger" in FACTCHECK_PROMPT_TEMPLATE and "mehr" in FACTCHECK_PROMPT_TEMPLATE)
print("\nD) Der berechnete Wert landet im Auftrag")
letzter = {"prompt": ""}
async def fake_call_claude(prompt, tools="WebSearch,WebFetch", model=None,
raw_text=False, timeout=None):
letzter["prompt"] = prompt
return "[]", ClaudeUsage(input_tokens=10, output_tokens=5, cost_usd=0.01)
fcmod.call_claude = fake_call_claude
cc.call_claude = fake_call_claude
_ai_backend_var.set("cli")
fc = FactCheckerAgent()
def meldungen(n):
return [{"headline": f"Meldung {i}", "source": "tagesschau",
"source_url": f"https://tagesschau.de/{i}", "content_original": "t"}
for i in range(n)]
asyncio.run(fc.check("Lage", meldungen(16), "adhoc", "Deutsch"))
u, o = faktenspanne(16)
pruefe("D1 kleine Lage traegt ihren Richtwert",
f"etwa {u} bis {o} Aussagen" in letzter["prompt"],
[z for z in letzter["prompt"].splitlines() if "Richtwert" in z][:1])
asyncio.run(fc.check("Lage", meldungen(88), "adhoc", "Deutsch"))
u2, o2 = faktenspanne(88)
pruefe("D2 grosse Lage traegt einen groesseren Richtwert",
f"etwa {u2} bis {o2} Aussagen" in letzter["prompt"] and o2 > o,
[z for z in letzter["prompt"].splitlines() if "Richtwert" in z][:1])
asyncio.run(fc.check_incremental(
"Lage", meldungen(30),
[{"claim": "Ein bestehender Punkt", "status": "confirmed", "evidence": "e"}],
"adhoc", "Deutsch"))
u3, o3 = faktenspanne(30, neu=True)
pruefe("D3 Aktualisierung nutzt den engeren Korridor",
f"etwa\n {u3} bis {o3}" in letzter["prompt"] or f"{u3} bis {o3}" in letzter["prompt"],
[z for z in letzter["prompt"].splitlines() if "Richtwert" in z][:1])
print("\nErgebnis: " + str(ok) + " bestanden, " + str(fail) + " fehlgeschlagen")
sys.exit(1 if fail else 0)