fix: Gegenprobe der Abbildungen je Datenzeile; Standardmodell ~deepseek/deepseek-flash-latest

- check_figure_readings vergleicht Zahlentupel je Datenzeile statt aller Zahlen des
  Freitexts (Fehlalarm im ersten echten Lauf: zusätzliche Knotenaufzählung); fängt
  falsch zugeordnete Bewertungen und vertauschte Pfeilrichtungen
- feste Form 'Start → Ziel: Bewertung' in Verstehen- und Abbildungs-Prompt;
  abweichende Lesungen stehen im Log
- Standardmodell ist der Alias ~deepseek/deepseek-flash-latest (derzeit V4.1 Flash);
  Benchmarks weiter mit festen IDs

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-09-17 18:01:37 +02:00
commit 101d55b713

View file

@ -206,23 +206,35 @@ def check_understanding(understanding, task: str) -> List[CheckIssue]:
return issues
def _numbers_multiset(text: str) -> Counter:
"""Zahlen eines Textes mit Häufigkeit – eine Kante mit Bewertung 3 zweimal ist nicht dasselbe wie einmal."""
zaehler = Counter()
for roh in NUMBER_PATTERN.findall(text or ""):
try:
zaehler[normalize_number(roh)] += 1
except ValueError:
continue
return zaehler
def _number_rows(text: str) -> Counter:
"""Je Datenzeile das Tupel ihrer Zahlen: „Pfeil 1 → 2: 3“ und „(1,2): 3“ ergeben beide (1, 2, 3).
Zeilen trennt der Zeilenumbruch oder ein Semikolon; Zeilen ohne Zahl zählen nicht. So ist der Vergleich
unabhängig von der Schreibweise, bemerkt aber eine Bewertung am falschen Pfeil.
"""
zeilen = Counter()
# „(1,2)“ ist ein Pfeil, keine Dezimalzahl 1,2 – ganzzahlige Paare in Klammern vorher trennen
text = re.sub(r"\((\d+)\s*,\s*(\d+)\)", r"(\1 \2)", text or "")
for zeile in re.split(r"[\n;]", text):
tupel = []
for roh in NUMBER_PATTERN.findall(zeile):
try:
tupel.append(normalize_number(roh))
except ValueError:
continue
if tupel:
zeilen[tuple(tupel)] += 1
return zeilen
def check_figure_readings(first, second) -> List[CheckIssue]:
"""Zwei unabhängige Übertragungen derselben Abbildungen vergleichen – Zahl für Zahl, ohne LLM.
Ein Bildmodell kann eine Pfeilbewertung dem falschen Pfeil zuordnen oder eine Ziffer verlesen; eine einzige
Lesung wäre dann ungeprüft die Datengrundlage jeder weiteren Stufe. Verglichen werden die Zahlen je Abbildung
samt Häufigkeit. Richtungsfehler bei gleichen Zahlen fängt das nicht – dafür steht das Original im Report.
Lesung wäre dann ungeprüft die Datengrundlage jeder weiteren Stufe. Verglichen werden die Datenzeilen als
Zahlentupel (`_number_rows`). Erster Ansatz – alle Zahlen des Freitexts – schlug im ersten echten Lauf
fälschlich an: eine Lesung zählte zusätzlich „Knoten: 1, 2, 3, 4“ auf (test_Image.md, 17.09.2026).
Eine vertauschte Pfeilrichtung (2 → 3 statt 3 → 2) fällt auf; eine in BEIDEN Lesungen gleich falsche nicht.
"""
issues = []
erste = {f.id: f for f in first}
@ -232,15 +244,14 @@ def check_figure_readings(first, second) -> List[CheckIssue]:
issues.append(CheckIssue(severity=CheckSeverity.ERROR, code="FIGURE_READINGS_DIFFER",
message=f"{kennung} wurde nur in einer von zwei Lesungen übertragen."))
continue
a = _numbers_multiset(erste[kennung].transcription)
b = _numbers_multiset(zweite[kennung].transcription)
a = _number_rows(erste[kennung].transcription)
b = _number_rows(zweite[kennung].transcription)
if a != b:
nur_a = sorted((a - b).elements(), key=float)
nur_b = sorted((b - a).elements(), key=float)
zeilen = lambda z: ", ".join("(" + " ".join(t) + ")" for t in sorted(z.elements())) or "–"
issues.append(CheckIssue(
severity=CheckSeverity.ERROR, code="FIGURE_READINGS_DIFFER",
message=f"{kennung} uneinheitlich gelesen – nur in Lesung 1: {', '.join(nur_a) or '–'}; "
f"nur in Lesung 2: {', '.join(nur_b) or '–'}."))
message=f"{kennung} uneinheitlich gelesen – Datenzeilen nur in Lesung 1: {zeilen(a - b)}; "
f"nur in Lesung 2: {zeilen(b - a)}."))
return issues