Skip to content

fix(enrich): roh-extrahierter DOI übersteht CrossRef-Fehlschlag (#282) - #299

Merged
TillQuandel merged 1 commit into
masterfrom
fix/282-doi-datacite-fallback
Jul 15, 2026
Merged

fix(enrich): roh-extrahierter DOI übersteht CrossRef-Fehlschlag (#282)#299
TillQuandel merged 1 commit into
masterfrom
fix/282-doi-datacite-fallback

Conversation

@TillQuandel

Copy link
Copy Markdown
Owner

Problem

Der Quality-Agent fragt für die DOI-Auflösung ausschließlich api.crossref.org ab. Wenn ein direkt aus dem PDF-Text extrahierter, format-valider DOI bei CrossRef nicht auflösbar ist, ging er bisher komplett verloren — die Note trug fälschlich das Flag „⚠️ kein DOI", obwohl der DOI wörtlich im Dokument stand und beim Enrichment geloggt wurde.

Betrifft strukturell alle DataCite-registrierten DOIs (z. B. deutsche Repositorien wie peDOCS oder edoc.hu-berlin.de) — keine transiente Störung, sondern eine Registry-Lücke: CrossRef kennt DataCite-DOIs grundsätzlich nicht.

Beleg aus der Testlauf-Serie (siehe Issue):

  • Lauf 2 (Deci & Ryan): DOI 10.25656/01:11173 (peDOCS) im PDF-Text gefunden und geloggt, CrossRef-Lookup lieferte nichts → alle 5 Notes fälschlich „kein DOI".
  • Lauf 5 (Dreisiebner): DOI 10.18452/35926 (HU-Berlin, DataCite-Präfix), websuch-verifiziert, aber CrossRef-only-Abfrage findet ihn strukturell nicht → alle 5 Notes fälschlich „kein DOI".
  • Gegenprobe Lauf 3 (Schüller): CrossRef-bekannter DOI wird korrekt aufgelöst → kein Flag. Mechanismus funktioniert, sobald CrossRef den DOI kennt.

Ursache

generative/tools/pdf_enrich.py::enrich(), Stage 1: extract_doi(header_text) findet den DOI per Regex und loggt ihn, aber wenn crossref_lookup(doi) None liefert (oder unvollständige Daten), wurde der Roh-Treffer nirgends zwischengespeichert. Lieferte eine spätere Stage (z. B. Stage 5, Zotero-Dateiname) Autor/Titel/Jahr ohne eigenen DOI, blieb meta["doi"] leer → quality.check_quality(doi=None, ...) setzt das „kein DOI"-Flag (quality.py:138) → die #263-Durchreiche-Kette (orchestrator.py, Stage 0) hatte strukturell nie eine Chance, weil ihr Input bereits leer war.

Fix

enrich() merkt sich den Stage-1-Rohtreffer (_raw_doi) und übernimmt ihn am Ende in meta["doi"], falls keine spätere Stage selbst einen DOI geliefert hat. Kein doi_from_title-Marker — das ist keine Titel-Heuristik (#263), sondern eine harte, wörtliche Quelle. Überschreibt nie einen bereits gesetzten (auch titel-geratenen) DOI, damit die #263-Provenienz-Markierung unangetastet bleibt.

orchestrator.py/quality.py mussten nicht angefasst werden: die #263-Durchreiche-Kette (orchestrator.py ~2083, verschoben durch den zwischenzeitlichen #280/#281-Merge, Logik unverändert) und das „kein DOI"-Flag (quality.py:138, nur bei doi=None) funktionieren bereits korrekt, sobald enrich() einen DOI liefert — das war exakt die Lücke.

Bewusst nicht umgesetzt: DataCite als Zweit-Registry

Das Issue nennt als Fix-Richtung „…und/oder zusätzlich DataCite als zweite Registry-Quelle abfragen". Das würde zusätzlich Peer-Review-Signal/Zitationszahl/CrossRef-Cross-Check auch für DataCite-DOIs liefern (aktuell bleiben peer_reviewed/citation_count für sie None). Bewusst ausgeklammert, weil:

  • beide im Issue konkret genannten Symptome („kein DOI"-Flag, DOI-False-Negative: enrich-CrossRef-DOI wird nicht an Edition-Gate propagiert — unnötiger Vault-Routing-Block #263-Anreicherung wirkungslos) durch den Roh-DOI-Passthrough allein vollständig behoben sind (Issue-Wortlaut „und/oder" — eine der beiden Richtungen reicht),
  • eine neue Registry-Anbindung ohne live verifiziertes Response-Schema (DataCite REST API, JSON:API-Format) dem „nicht raten"-Prinzip widerspräche — ich habe die Doku kurz geprüft, sie liefert keine belastbaren Feldnamen ohne Sandbox-Test,
  • kein bestehendes Muster/Codepfad im Repo dafür existiert (grep -ri datacite fand nur die neuen Kommentare/Tests dieses Fixes).

Vorschlag: eigenes Folge-Issue für „DataCite als Zweit-Registry im Quality-Agent" (Sandbox api.test.datacite.org zum Schema-Verifizieren nutzen), falls das Peer-Review-/Zitations-Signal für diese Quellenklasse gewünscht ist.

Verwertung der Vorarbeit

Der Worktree enthielt bereits unfertige, uncommittete Arbeit an pdf_enrich.py + test_pdf_enrich.py aus einer abgebrochenen Session. Diff geprüft: deckt sich exakt mit der im Issue vorgeschlagenen Fix-Richtung (Format-Validierung statt Registry-Bestätigung als Mindestkriterium) und respektiert die #263-Provenienz-Semantik. Übernommen und eigenständig verifiziert (siehe Testnachweis unten) — keine Änderungen an der Vorarbeit nötig.

Testnachweis

RED (Fix gestasht, nur neue Tests gegen alten Code):

generative/tests/test_pdf_enrich.py::test_enrich_keeps_raw_doi_when_crossref_lookup_fails FAILED
AssertionError: assert '' == '10.25656/01:11173'
generative/tests/test_pdf_enrich.py::test_enrich_does_not_invent_doi_without_format_match PASSED
generative/tests/test_pdf_enrich.py::test_enrich_raw_doi_does_not_override_title_guessed_doi PASSED
1 failed, 2 passed, 89 deselected

(Die zwei Kontroll-Tests sind by design bereits vorher grün — Negativkontrolle und #263-Gegenprobe greifen nicht in den gefixten Pfad ein.)

GREEN (Fix zurückgeholt): test_pdf_enrich.py 92 passed.

Volle Suite (generative lib/decision_engine/tests shared/tests): 5956 passed, 3 skipped, 8 deselected (441s).

ruff (geänderte Dateien): ruff check — All checks passed. ruff format --check — 2 files already formatted.

Offene Punkte

Fixes #282

Ein per Regex im Kopfbereich gefundener, format-valider DOI ging bisher
komplett verloren, wenn CrossRef ihn nicht kennt — betrifft strukturell
alle DataCite-registrierten DOIs (peDOCS, edoc.hu-berlin.de u.ä.), da
CrossRef diese grundsätzlich nicht führt (keine transiente Störung,
sondern eine Registry-Lücke). Der Quality-Agent setzte dadurch
fälschlich das Flag "kein DOI", obwohl der DOI wörtlich im Dokument
stand und geloggt wurde; die #263-Durchreiche-Kette blieb für diese
Quellenklasse wirkungslos.

Fix: enrich() merkt sich den Stage-1-Rohtreffer (_raw_doi) und übernimmt
ihn am Ende in meta["doi"], falls keine spätere Stage (Zotero-Dateiname,
Titelsuche) selbst einen DOI geliefert hat. Kein doi_from_title-Marker
— das ist keine Titel-Heuristik, sondern eine harte, wörtliche Quelle.
Überschreibt nie einen bereits gesetzten (auch titel-geratenen) DOI,
damit die #263-Provenienz-Markierung unangetastet bleibt.

orchestrator.py/quality.py mussten nicht angefasst werden: die
#263-Durchreiche-Kette (orchestrator.py ~2083) und das "kein DOI"-Flag
(quality.py:138, nur bei doi=None) funktionieren bereits korrekt,
sobald enrich() den DOI liefert.

Tests: 3 neue Fälle in test_pdf_enrich.py (Positivfall DataCite-DOI,
Negativkontrolle ohne Format-Treffer, #263-Gegenprobe: Titel-geratener
DOI wird nicht überschrieben). Volle Suite 5956 passed/3 skipped/8
deselected, ruff clean.
@TillQuandel
TillQuandel merged commit 78454e1 into master Jul 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MITTEL] Extrahierter DOI geht bei CrossRef-Fehlschlag verloren — DataCite-DOIs strukturell unerreichbar

2 participants