feat(sources): Review-Queue-UI fuer LLM-Klassifikations-Vorschlaege (Admin)
- Tab-Schalter im Quellen-Modal: "Quellenliste" vs. "Klassifikations-Review" (Review-Tab nur fuer org_admin sichtbar, mit Pending-Counter-Badge). - Review-Karten zeigen Diff aktueller Wert -> LLM-Vorschlag pro Achse, Konfidenz-Indikator (gruen/gelb/rot), LLM-Begruendung, Buttons fuer Uebernehmen / Verwerfen / Neu klassifizieren. - Toolbar: Konfidenz-Filter, "Klassifikation starten" (Bulk im Hintergrund), "Alle >= 0.85 genehmigen" (Bulk-Approve). - API-Wrapper in api.js fuer alle 6 neuen Endpoints + erweiterte listSources-Filter. - Backend-Endpoint POST /api/sources/classification/bulk-approve (Admin-only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -937,3 +937,62 @@ async def trigger_bulk_classify(
|
||||
raise HTTPException(status_code=400, detail="limit muss zwischen 1 und 500 liegen")
|
||||
background_tasks.add_task(_bulk_classify_background, limit, only_unclassified)
|
||||
return {"status": "started", "limit": limit, "only_unclassified": only_unclassified}
|
||||
|
||||
|
||||
@router.post("/classification/bulk-approve")
|
||||
async def bulk_approve_classifications(
|
||||
min_confidence: float = 0.85,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db: aiosqlite.Connection = Depends(db_dependency),
|
||||
):
|
||||
"""Genehmigt alle Pending-Vorschlaege ueber dem confidence-Schwellwert (nur Admins).
|
||||
|
||||
Globale Quellen werden nur bearbeitet, wenn der Aufrufer org_admin ist;
|
||||
Tenant-eigene Quellen sowieso.
|
||||
"""
|
||||
if current_user.get("role") != "org_admin":
|
||||
raise HTTPException(status_code=403, detail="Nur Admins koennen Bulk-Approve nutzen")
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
cursor = await db.execute(
|
||||
"""SELECT id, proposed_political_orientation, proposed_media_type,
|
||||
proposed_reliability, proposed_state_affiliated,
|
||||
proposed_country_code, proposed_alignments_json, tenant_id
|
||||
FROM sources
|
||||
WHERE proposed_political_orientation IS NOT NULL
|
||||
AND COALESCE(proposed_confidence, 0) >= ?
|
||||
AND (tenant_id IS NULL OR tenant_id = ?)""",
|
||||
(min_confidence, tenant_id),
|
||||
)
|
||||
rows = [dict(r) for r in await cursor.fetchall()]
|
||||
approved_ids: list[int] = []
|
||||
for src in rows:
|
||||
try:
|
||||
proposed_aligns = json.loads(src.get("proposed_alignments_json") or "[]")
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
proposed_aligns = []
|
||||
await db.execute(
|
||||
"""UPDATE sources SET
|
||||
political_orientation = ?,
|
||||
media_type = ?,
|
||||
reliability = ?,
|
||||
state_affiliated = ?,
|
||||
country_code = ?,
|
||||
classification_source = 'llm_approved',
|
||||
classified_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?""",
|
||||
(
|
||||
src["proposed_political_orientation"],
|
||||
src["proposed_media_type"],
|
||||
src["proposed_reliability"],
|
||||
1 if src.get("proposed_state_affiliated") else 0,
|
||||
src.get("proposed_country_code"),
|
||||
src["id"],
|
||||
),
|
||||
)
|
||||
await _replace_alignments(
|
||||
db, src["id"], [a for a in proposed_aligns if a in ALLOWED_ALIGNMENTS]
|
||||
)
|
||||
await _clear_proposed(db, src["id"])
|
||||
approved_ids.append(src["id"])
|
||||
await db.commit()
|
||||
return {"approved_count": len(approved_ids), "min_confidence": min_confidence}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren