fix: Job-Polling vor Redirect statt sofortigem Antrag-nicht-gefunden

Vorher: Klick 'Analysieren' -> POST /api/analyze-drucksache -> sofort
window.location.href = '/antrag/{ds}' -> aber Job laeuft noch im Hintergrund
-> Detail-Seite zeigt 'Antrag nicht gefunden'.

Jetzt:
- already_checked -> sofortiger Redirect
- skipped (nicht abstimmbar) -> Hinweistext im Form
- queued -> Polling auf /status/{job_id} alle 2s, max 3 Min
- completed -> Redirect zur Detail-Seite
- failed/rejected -> Fehlermeldung mit Grund

Anwendung in v2/screens/landtag_suche.html + v2/screens/neu.html.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dotty Dotter 2026-04-25 21:35:55 +02:00
parent c38bca615d
commit 38bffb23fa
2 changed files with 77 additions and 6 deletions

View File

@ -292,9 +292,40 @@ async function lsAnalyse(btn, drucksache, bundesland) {
}
var data = await resp.json();
var ds = data.drucksache || drucksache;
// Backend gibt {job_id, drucksache} zurück (Queue) — nicht direkt redirecten,
// sondern auf /antrag/{ds} gehen, dort wird dann ggf. der Polling-Status sichtbar
// Falls bereits bewertet oder skipped: direkt redirecten
if (data.status === 'already_checked') {
window.location.href = '/antrag/' + encodeURIComponent(ds);
return;
}
if (data.status === 'skipped') {
btn.textContent = 'Übersprungen';
btn.title = 'Typ "' + (data.typ || 'unbekannt') + '" ist nicht abstimmbar';
return;
}
// Sonst Job-Polling bis fertig, dann redirect
btn.textContent = 'Analysiere…';
var jobId = data.job_id;
if (!jobId) { window.location.href = '/antrag/' + encodeURIComponent(ds); return; }
var attempts = 0;
var maxAttempts = 90; // 90 × 2s = 3 min
while (attempts < maxAttempts) {
await new Promise(function (r) { setTimeout(r, 2000); });
attempts++;
var st = await fetch('/status/' + jobId).then(function (r) { return r.json(); }).catch(function () { return null; });
if (!st) continue;
if (st.status === 'completed') {
window.location.href = '/antrag/' + encodeURIComponent(ds);
return;
}
if (st.status === 'failed' || st.status === 'rejected') {
throw new Error('Analyse fehlgeschlagen: ' + (st.error || 'unbekannt'));
}
btn.textContent = 'Analysiere… (' + (st.status || '...') + ')';
}
throw new Error('Analyse-Timeout (>3 min)');
} catch (err) {
btn.disabled = false;
btn.textContent = 'Fehler';

View File

@ -154,16 +154,56 @@ async function startAnalyse(e) {
if (model) fd.append('model', model);
const resp = await fetch('/api/analyze-drucksache', { method: 'POST', body: fd });
if (resp.status === 401 || resp.status === 403) {
progEl.style.display = 'none';
errEl.style.display = '';
errEl.textContent = 'Sitzung abgelaufen — bitte erneut anmelden.';
btn.disabled = false;
if (typeof window.v2AuthModalOpen === 'function') window.v2AuthModalOpen();
return;
}
if (!resp.ok) {
const err = await resp.json().catch(() => ({ detail: resp.statusText }));
throw new Error(err.detail || ('HTTP ' + resp.status));
}
const data = await resp.json();
// Redirect to result page
const ds = data.drucksache || drucksache;
progText.textContent = 'Analyse abgeschlossen. Weiterleitung …';
setTimeout(() => { window.location.href = '/antrag/' + encodeURIComponent(ds); }, 600);
if (data.status === 'already_checked') {
progText.textContent = 'Bereits bewertet. Weiterleitung …';
setTimeout(() => { window.location.href = '/antrag/' + encodeURIComponent(ds); }, 400);
return;
}
if (data.status === 'skipped') {
progEl.style.display = 'none';
errEl.style.display = '';
errEl.textContent = 'Antrag-Typ "' + (data.typ || 'unbekannt') + '" ist nicht abstimmbar — keine GWÖ-Bewertung sinnvoll.';
btn.disabled = false;
return;
}
// Job-Polling bis Abschluss, dann redirect
const jobId = data.job_id;
if (!jobId) { window.location.href = '/antrag/' + encodeURIComponent(ds); return; }
let attempts = 0;
const maxAttempts = 90;
while (attempts < maxAttempts) {
await new Promise(r => setTimeout(r, 2000));
attempts++;
const st = await fetch('/status/' + jobId).then(r => r.json()).catch(() => null);
if (!st) continue;
progText.textContent = 'Analyse läuft … (' + (st.status || '?') + ', ~' + (attempts * 2) + 's)';
if (st.status === 'completed') {
progText.textContent = 'Fertig. Weiterleitung …';
setTimeout(() => { window.location.href = '/antrag/' + encodeURIComponent(ds); }, 400);
return;
}
if (st.status === 'failed' || st.status === 'rejected') {
throw new Error('Analyse fehlgeschlagen: ' + (st.error || 'unbekannt'));
}
}
throw new Error('Analyse-Timeout (>3 min)');
} catch (err) {
progEl.style.display = 'none';