From 47897e13cd668eb72d1c247d7d8b398edd6a8d9e Mon Sep 17 00:00:00 2001 From: Dotty Dotter Date: Fri, 10 Apr 2026 09:57:58 +0200 Subject: [PATCH] =?UTF-8?q?#47=20Fix:=20Highlighting=20retroaktiv=20f?= =?UTF-8?q?=C3=BCr=20alle=20bestehenden=20Assessments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Alle Assessments in der Prod-DB haben Pre-#47-URLs (/static/referenzen/X.pdf#page=N). Die _chunk_pdf_url-Änderung wirkt nur auf NEUE Analysen, die noch nicht stattgefunden haben. Fix (zwei Seiten): 1. Endpoint /api/wahlprogramm-cite akzeptiert jetzt auch pdf= als Alternative zu pid=. Reverse-Lookup über PROGRAMME- Registry: pdf-Filename → programm_id. Damit können die statischen URLs aus Pre-#47-Assessments trotzdem an den Cite-Endpoint geleitet werden. 2. Frontend: neue JS-Funktion makeCiteUrl(z) die JEDE Zitat-URL on-the- fly umschreibt: - /static/referenzen/X.pdf#page=N + z.text → /api/wahlprogramm-cite?pdf=X.pdf&seite=N&q= - /api/wahlprogramm-cite?... → durchreichen (schon Cite-URL) - Fallback: URL unverändert Funktioniert retroaktiv für ALLE ~31 Assessments in der DB, ohne Re-Analyse. Sobald ein User auf ein Zitat klickt, wird die Seite des Wahlprogramms mit gelber Markierung gerendert. Tests: 194/194 grün. Refs: #47 --- app/main.py | 13 ++++++++++++- app/templates/index.html | 23 +++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/main.py b/app/main.py index e8b82c7..0a86961 100644 --- a/app/main.py +++ b/app/main.py @@ -596,7 +596,7 @@ async def quellen_page(request: Request): @app.get("/api/wahlprogramm-cite") -async def wahlprogramm_cite(pid: str, seite: int, q: str = ""): +async def wahlprogramm_cite(pid: str = "", pdf: str = "", seite: int = 1, q: str = ""): """Render eine Wahlprogramm-Seite mit gelb hervorgehobener Zitat-Stelle. Issue #47: Klick auf eine Zitat-Quelle im Report soll direkt zur @@ -606,11 +606,22 @@ async def wahlprogramm_cite(pid: str, seite: int, q: str = ""): 1-Seiten-PDF mit ``add_highlight_annot``-Annotation auf den per ``page.search_for`` gefundenen Bounding-Boxes. + Akzeptiert ``pid`` (PROGRAMME-Key) ODER ``pdf`` (Dateiname wie + ``spd-grundsatzprogramm.pdf``). Letzterer ermöglicht die retroaktive + Nutzung von Pre-#47-URLs im Frontend, wo nur der statische Pfad + ``/static/referenzen/#page=`` gespeichert ist. + Security: ``pid`` muss ein registrierter PROGRAMME-Key sein — verhindert Path-Traversal und arbiträren File-Read aus dem referenzen-Verzeichnis. ``seite`` wird per Pydantic-Coercion auf int gezwungen. ``q`` ist auf 200 Zeichen begrenzt im Renderer. """ + # Reverse-Lookup: pdf-Filename → programm_id, falls nur pdf angegeben. + if not pid and pdf: + for p, info in PROGRAMME.items(): + if info.get("pdf") == pdf: + pid = p + break if pid not in PROGRAMME: raise HTTPException(status_code=404, detail="Unbekanntes Wahlprogramm") if seite < 1 or seite > 2000: diff --git a/app/templates/index.html b/app/templates/index.html index 79f2f48..9606b7b 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1498,12 +1498,31 @@ `}).join(''); + // Issue #47: Zitat-URLs zu Cite-Endpoint umschreiben für gelbes + // Highlighting. Funktioniert retroaktiv für Pre-#47-Assessments + // (statische /static/referenzen/X.pdf#page=N) und nativ für + // Post-#47 (die schon /api/wahlprogramm-cite enthalten). + function makeCiteUrl(z) { + if (!z || !z.url) return '#'; + // Schon eine Cite-URL? Durchreichen. + if (z.url.includes('/api/wahlprogramm-cite')) return z.url; + // Statische URL umschreiben: /static/referenzen/X.pdf#page=N + const m = z.url.match(/\/static\/referenzen\/(.+\.pdf)#page=(\d+)/); + if (m && z.text) { + const pdf = m[1]; + const page = m[2]; + const q = encodeURIComponent((z.text || '').substring(0, 200)); + return `/api/wahlprogramm-cite?pdf=${encodeURIComponent(pdf)}&seite=${page}&q=${q}`; + } + return z.url; + } + const wahlprogrammHtml = (item.wahlprogrammScores || []).map(wp => { - // Zitate formatieren mit klickbaren Links + // Zitate formatieren mit klickbaren Links + Highlighting const zitateHtml = (wp.wahlprogramm?.zitate || []).map(z => `