#47 Fix: Highlighting retroaktiv für alle bestehenden Assessments

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=<filename>
   als Alternative zu pid=<programm_id>. 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=<urlencoded text>
   - /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
This commit is contained in:
Dotty Dotter 2026-04-10 09:57:58 +02:00
parent 2b2a363127
commit 47897e13cd
2 changed files with 33 additions and 3 deletions

View File

@ -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/<pdf>#page=<N>`` 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:

View File

@ -1498,12 +1498,31 @@
</div>
`}).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 => `
<div style="margin: 0.5rem 0; padding: 0.5rem; background: #f8f9fa; border-left: 3px solid #889e33; font-size: 0.85rem;">
<em>"${z.text}"</em><br>
<a href="${z.url || '#'}" target="_blank" style="color: #009da5; font-size: 0.8rem;">
<a href="${makeCiteUrl(z)}" target="_blank" style="color: #009da5; font-size: 0.8rem;">
📄 ${z.quelle}
</a>
</div>