#97 Neu bewerten: manueller Re-Analyse-Button + Bewertungsdatum

Fußzeile unter jedem Assessment-Detail jetzt mit:
- Bewertungsdatum ("Bewertet am DD.MM.YYYY") aus updated_at
- Quelle + Modell (batch-reanalyze / webapp, qwen-plus)
- "Neu bewerten"-Button (Auth-pflichtig, ausgegraut ohne Login)

Flow: Klick → DELETE /api/assessment/delete → POST /api/analyze-drucksache
→ Queue → pollAnalysis → Detail neu laden

Neuer DELETE-Endpoint /api/assessment/delete mit require_auth.

API-Response erweitert um updatedAt, source, model für beide
Endpoints (list + single assessment).

Tests: 206 passed.

Refs: #97
This commit is contained in:
Dotty Dotter 2026-04-10 21:10:33 +02:00
parent 790fe1a121
commit f728388286
2 changed files with 59 additions and 2 deletions

View File

@ -335,6 +335,9 @@ async def list_assessments(bundesland: Optional[str] = None):
"themen": row.get("themen", []),
"antragZusammenfassung": row.get("antrag_zusammenfassung"),
"antragKernpunkte": row.get("antrag_kernpunkte", []),
"updatedAt": row.get("updated_at"),
"source": row.get("source"),
"model": row.get("model"),
})
return assessments
@ -370,9 +373,26 @@ async def get_single_assessment(drucksache: str):
"themen": row.get("themen", []),
"antragZusammenfassung": row.get("antrag_zusammenfassung"),
"antragKernpunkte": row.get("antrag_kernpunkte", []),
"updatedAt": row.get("updated_at"),
"source": row.get("source"),
"model": row.get("model"),
}
# API: Delete assessment for re-analysis (#97)
@app.delete("/api/assessment/delete")
async def delete_assessment_endpoint(
drucksache: str,
user: dict = Depends(require_auth),
):
"""Löscht ein Assessment, damit es neu analysiert werden kann."""
drucksache = validate_drucksache(drucksache)
deleted = await delete_assessment(drucksache)
if not deleted:
raise HTTPException(status_code=404, detail="Assessment nicht gefunden")
return {"status": "deleted", "drucksache": drucksache}
# API: Generate PDF on demand for an assessment
@app.get("/api/assessment/pdf")
async def download_assessment_pdf(drucksache: str):

View File

@ -1240,6 +1240,31 @@
}
}
async function reAnalyze(drucksache, bundesland, btn) {
btn.disabled = true;
btn.textContent = '⏳ Wird neu bewertet...';
try {
// Altes Assessment löschen
await fetch(`/api/assessment/delete?drucksache=${encodeURIComponent(drucksache)}`, {method: 'DELETE'});
// Neue Analyse enqueuen
const resp = await fetch('/api/analyze-drucksache', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: `drucksache=${encodeURIComponent(drucksache)}&bundesland=${encodeURIComponent(bundesland)}`
});
const data = await resp.json();
if (data.status === 'queued') {
pollAnalysis(data.job_id, drucksache, btn);
} else {
btn.textContent = '❌ Fehler';
setTimeout(() => { btn.textContent = '🔄 Neu bewerten'; btn.disabled = false; }, 3000);
}
} catch (e) {
btn.textContent = '❌ Fehler';
setTimeout(() => { btn.textContent = '🔄 Neu bewerten'; btn.disabled = false; }, 3000);
}
}
async function pollAnalysis(jobId, drucksache, btn) {
try {
const resp = await fetch(`/status/${jobId}`);
@ -1673,8 +1698,20 @@
<strong>${item.empfehlungSymbol || ''} ${item.empfehlung || '-'}</strong>
</div>
<a href="${item.link}" target="_blank" class="btn-pdf">📄 Original-PDF öffnen</a>
<a href="/api/assessment/pdf?drucksache=${encodeURIComponent(item.drucksache)}" class="btn-pdf" style="background: var(--color-green); margin-left: 0.5rem;">📥 GWÖ-Report als PDF</a>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem;">
<a href="${item.link}" target="_blank" class="btn-pdf">📄 Original-PDF</a>
<a href="/api/assessment/pdf?drucksache=${encodeURIComponent(item.drucksache)}" class="btn-pdf" style="background: var(--color-green);">📥 GWÖ-Report</a>
<button class="btn-pdf" style="background: #6c757d; border: none; cursor: pointer;"
${currentUser ? '' : 'disabled title="Nur nach Anmeldung verfügbar" style="background:#6c757d;opacity:0.5;cursor:not-allowed;"'}
onclick="reAnalyze('${item.drucksache}', '${item.bundesland}', this)">
🔄 Neu bewerten
</button>
</div>
<div style="margin-top: 0.75rem; font-size: 0.8rem; color: #999; border-top: 1px solid #eee; padding-top: 0.5rem;">
Bewertet am ${item.updatedAt ? new Date(item.updatedAt).toLocaleDateString('de-DE') : ''}
${item.source ? ` · Quelle: ${item.source}` : ''}
${item.model ? ` · Modell: ${item.model}` : ''}
</div>
</div>
`;