fix: Abbildungs-Gegenprobe ohne kopierbares Beispiel, Kennungen vereinheitlicht
- Prompt-Beispiel '1 → 2: 3' durch Schema 'A → B: Wert' ersetzt: eine Lesung hatte nur die Beispielzeile übernommen statt aller fünf Pfeile; alle Datenpunkte verlangt - canonical_figure_id: '1' und 'Abbildung 1' gelten als dieselbe Abbildung – beim Vergleich, in der Pipeline und im Report (Formfehler im zweiten Testlauf) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
101d55b713
commit
89b61d8c62
8 changed files with 35 additions and 9 deletions
|
|
@ -22,7 +22,7 @@ from modeling.extraction import revise_model, text_to_model
|
|||
from reporting.report import write_report
|
||||
from solving.script_export import build_solver_script
|
||||
from solving.solver_stage import check_feasibility, check_multistart, check_sensitivity, run_solver_code, solve
|
||||
from understanding.figures import FigureError, split_figures, with_transcriptions
|
||||
from understanding.figures import FigureError, normalize_figure_ids, split_figures, with_transcriptions
|
||||
from understanding.understand import read_figures, understand_task
|
||||
|
||||
MAX_REVISIONS = int(os.environ.get("MAX_REVISIONS", "2"))
|
||||
|
|
@ -76,7 +76,8 @@ def _checked_figure_readings(session, task: str, figures, first, label: str) ->
|
|||
und der Befund macht das Ergebnis unzuverlässig – die Daten der Aufgabe stünden auf ungeprüftem Ablesen.
|
||||
"""
|
||||
log(f"[{label}] Lese {len(figures)} Abbildung(en) ein zweites Mal (Gegenprobe) ...")
|
||||
zweite = read_figures(session, task, figures).figures
|
||||
first = normalize_figure_ids(first)
|
||||
zweite = normalize_figure_ids(read_figures(session, task, figures).figures)
|
||||
befunde = check_figure_readings(first, zweite)
|
||||
if not befunde:
|
||||
return first, []
|
||||
|
|
@ -84,7 +85,7 @@ def _checked_figure_readings(session, task: str, figures, first, label: str) ->
|
|||
for nummer, lesung in ((1, first), (2, zweite)):
|
||||
for f in lesung:
|
||||
log(f" Lesung {nummer}, {f.id}: " + f.transcription.replace("\n", " | ")[:300])
|
||||
dritte = read_figures(session, task, figures).figures
|
||||
dritte = normalize_figure_ids(read_figures(session, task, figures).figures)
|
||||
if not check_figure_readings(first, dritte):
|
||||
return first, []
|
||||
if not check_figure_readings(zweite, dritte):
|
||||
|
|
|
|||
|
|
@ -236,9 +236,10 @@ def check_figure_readings(first, second) -> List[CheckIssue]:
|
|||
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.
|
||||
"""
|
||||
from understanding.figures import canonical_figure_id
|
||||
issues = []
|
||||
erste = {f.id: f for f in first}
|
||||
zweite = {f.id: f for f in second}
|
||||
erste = {canonical_figure_id(f.id): f for f in first}
|
||||
zweite = {canonical_figure_id(f.id): f for f in second}
|
||||
for kennung in sorted(set(erste) | set(zweite)):
|
||||
if kennung not in erste or kennung not in zweite:
|
||||
issues.append(CheckIssue(severity=CheckSeverity.ERROR, code="FIGURE_READINGS_DIFFER",
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ class FigureTranscription(BaseModel):
|
|||
id: str = Field(description="Kennung wie im Text, z. B. 'Abbildung 1'")
|
||||
shows: str = Field(description="Was die Abbildung zeigt, in einem Satz (z. B. 'gerichteter Graph mit 4 Knoten')")
|
||||
transcription: str = Field(
|
||||
description="Nur die Daten, eine Zeile je Datenpunkt, jede Zahl genau einmal (Graph: '1 → 2: 3' je Pfeil; "
|
||||
description="Nur die Daten, eine Zeile je Datenpunkt, jede Zahl genau einmal (Graph: je Pfeil 'A → B: Wert', alle Pfeile; "
|
||||
"Tabelle: 'Zeile / Spalte: Wert'). Keine Knotenaufzählung, keine Lagebeschreibung – das gehört nach shows.")
|
||||
uncertain: List[str] = Field(
|
||||
default_factory=list,
|
||||
|
|
|
|||
|
|
@ -305,7 +305,8 @@ def _figure_blocks(result: PipelineResult) -> list:
|
|||
"""Abbildungen der Aufgabe: Original neben der Übertragung, mit der die Pipeline gerechnet hat."""
|
||||
if not result.figures:
|
||||
return []
|
||||
uebertragen = {f.id: f for f in (result.understanding.figures if result.understanding else [])}
|
||||
from understanding.figures import canonical_figure_id
|
||||
uebertragen = {canonical_figure_id(f.id): f for f in (result.understanding.figures if result.understanding else [])}
|
||||
doc = [("h3", "Abbildungen"),
|
||||
("p", "Die Pipeline rechnet nicht mit dem Bild, sondern mit seiner Übertragung in Text. "
|
||||
"Bitte Original und Übertragung vergleichen – ein verlesener Wert ändert das ganze Ergebnis.")]
|
||||
|
|
|
|||
|
|
@ -150,3 +150,16 @@ def test_reading_comparison_ignores_wording_but_not_assignment():
|
|||
assert check_figure_readings([GRAPH], [vertauscht])
|
||||
richtung = GRAPH.model_copy(update={"transcription": GRAPH.transcription.replace("3 → 2: 1", "2 → 3: 1")})
|
||||
assert check_figure_readings([GRAPH], [richtung])
|
||||
|
||||
|
||||
def test_figure_ids_are_compared_by_number():
|
||||
# beobachtet: Lesung 1 schrieb als Kennung nur „1“, Lesung 2 „Abbildung 1“ – Inhalt identisch
|
||||
from understanding.figures import canonical_figure_id
|
||||
assert check_figure_readings([GRAPH.model_copy(update={"id": "1"})], [GRAPH]) == []
|
||||
assert canonical_figure_id("Abb. 2") == "Abbildung 2"
|
||||
|
||||
|
||||
def test_short_id_in_understanding_still_reaches_the_report():
|
||||
result, _ = _lauf(GRAPH.model_copy(update={"id": "1"}), [GRAPH])
|
||||
html = to_html(build_document(result))
|
||||
assert "nicht übertragen" not in html and "Pfeil 3 → 4: 5" in html
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Deine Übertragung wird ohne LLM mit einer zweiten, unabhängigen Übertragung d
|
|||
Für jede Abbildung, in der Reihenfolge ihrer Kennung („Abbildung 1“, „Abbildung 2“, …), ein Eintrag in figures:
|
||||
- id: die Kennung genau wie angegeben.
|
||||
- shows: was die Abbildung zeigt, in einem Satz (z. B. „gerichteter Graph mit 4 Knoten und 5 bewerteten Pfeilen“); hier ist Platz für Lage und Aufbau.
|
||||
- transcription: NUR die Daten, eine Zeile je Datenpunkt, jede Zahl genau einmal – bei Graphen je Pfeil „Start → Ziel: Bewertung“ (z. B. „1 → 2: 3“), bei Tabellen je Zelle „Zeile / Spalte: Wert“, bei Diagrammen je Punkt „x: y“. Keine Aufzählung von Knoten, keine Lagebeschreibung, keine Wiederholung – das gehört nach shows. Achte bei Pfeilen genau auf die Richtung (wo sitzt die Spitze?).
|
||||
- transcription: NUR die Daten, eine Zeile je Datenpunkt, jede Zahl genau einmal, ALLE Datenpunkte (ein Graph mit fünf Pfeilen ergibt fünf Zeilen) – bei Graphen je Pfeil „Start → Ziel: Bewertung“ (Schema „A → B: Wert“ mit den Knotennamen und Bewertungen aus dem Bild), bei Tabellen je Zelle „Zeile / Spalte: Wert“, bei Diagrammen je Punkt „x: y“. Keine Aufzählung von Knoten, keine Lagebeschreibung, keine Wiederholung – das gehört nach shows. Achte bei Pfeilen genau auf die Richtung (wo sitzt die Spitze?).
|
||||
- uncertain: jede Stelle, die du nicht sicher lesen kannst (verdeckt, unscharf, mehrdeutige Richtung oder Zuordnung einer Zahl zu einem Pfeil). Leer nur, wenn alles eindeutig ist.
|
||||
|
||||
Ergänze nichts, was nicht zu sehen ist, und rechne nichts aus.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,6 @@ GEMEINSAMER RAHMEN (gilt für alle vier Stufen dieser Pipeline: Verstehen, Model
|
|||
- ignored_numbers ist für Aufgabennummern („3.8“), Zahlen in Einheiten („in 1000 Stunden“), Jahreszahlen, Seitenzahlen, Nummern von Sätzen. Immer mit Grund.
|
||||
Eine Zahl, die weder im einen noch im anderen steht, gilt als übersehen. [→ UNCAPTURED_NUMBERS]
|
||||
|
||||
4. ABBILDUNGEN: Liegen der Aufgabe Abbildungen bei (im Text als „[Abbildung N]“, die Bilder folgen der Nachricht), trage je Abbildung einen Eintrag in figures ein: id genau wie im Text, shows in einem Satz, transcription mit ALLEN Daten, NUR die Daten, eine Zeile je Datenpunkt, jede Zahl genau einmal – bei Graphen je Pfeil „Start → Ziel: Bewertung“ (z. B. „1 → 2: 3“), bei Tabellen je Zelle „Zeile / Spalte: Wert“, bei Diagrammen je Punkt „x: y“. Keine Aufzählung von Knoten, keine Lagebeschreibung, keine Wiederholung – das gehört nach shows. Achte auf die Richtung der Pfeilspitze., uncertain für alles, was nicht sicher lesbar ist. Die Zahlen der Abbildung gehören außerdem in facts, mit quote „Abbildung N: …“. Eine zweite, unabhängige Übertragung wird Zahl für Zahl mit deiner verglichen. [→ check_figure_readings]
|
||||
4. ABBILDUNGEN: Liegen der Aufgabe Abbildungen bei (im Text als „[Abbildung N]“, die Bilder folgen der Nachricht), trage je Abbildung einen Eintrag in figures ein: id genau wie im Text, shows in einem Satz, transcription mit ALLEN Daten, NUR die Daten, eine Zeile je Datenpunkt, jede Zahl genau einmal, ALLE Datenpunkte (ein Graph mit fünf Pfeilen ergibt fünf Zeilen) – bei Graphen je Pfeil „Start → Ziel: Bewertung“ (Schema „A → B: Wert“ mit den Knotennamen und Bewertungen aus dem Bild), bei Tabellen je Zelle „Zeile / Spalte: Wert“, bei Diagrammen je Punkt „x: y“. Keine Aufzählung von Knoten, keine Lagebeschreibung, keine Wiederholung – das gehört nach shows. Achte auf die Richtung der Pfeilspitze., uncertain für alles, was nicht sicher lesbar ist. Die Zahlen der Abbildung gehören außerdem in facts, mit quote „Abbildung N: …“. Eine zweite, unabhängige Übertragung wird Zahl für Zahl mit deiner verglichen. [→ check_figure_readings]
|
||||
|
||||
5. Erfinde nichts. Was der Text nicht sagt, gehört nicht in facts – fehlende Angaben werden später als Annahme ausgewiesen, nicht hier geraten.
|
||||
|
|
|
|||
|
|
@ -64,6 +64,16 @@ def split_figures(task: str, basis_dir: Path | str | None = None) -> Tuple[str,
|
|||
return text, figures
|
||||
|
||||
|
||||
def canonical_figure_id(kennung: str) -> str:
|
||||
"""„1“, „Abb. 1“, „Abbildung 1“ → „Abbildung 1“ – beobachtet: eine Lesung schrieb nur „1“."""
|
||||
nummer = re.search(r"\d+", kennung or "")
|
||||
return f"Abbildung {nummer.group(0)}" if nummer else (kennung or "").strip()
|
||||
|
||||
|
||||
def normalize_figure_ids(transcriptions: List[FigureTranscription]) -> List[FigureTranscription]:
|
||||
return [t.model_copy(update={"id": canonical_figure_id(t.id)}) for t in transcriptions]
|
||||
|
||||
|
||||
def image_parts(figures: List[FigureInput]) -> list:
|
||||
"""Nachrichtenteile im OpenAI-Format: je Abbildung ihre Kennung als Text, dann das Bild als Data-URI."""
|
||||
teile = []
|
||||
|
|
|
|||
Loading…
Reference in a new issue