feat: Umsetzungsgrad in Ketten-Detail anzeigen

- Backend: KI-Umsetzungsbewertung (Score, Bewertung, Begründung, Kernpunkt, Details) im Ketten-Detail-Response
- Frontend: Umsetzungsgrad zwischen Ampel und Timeline im Explorer Panel 2
  - Prozent-Kreis (grün/amber/rot) + Bewertungstext + Kernpunkt-Status
  - Begründung + Details als Fließtext
This commit is contained in:
Dotty Dotter 2026-04-01 23:23:25 +02:00
parent 2d2e335ac3
commit 726f5c06ad
3 changed files with 58 additions and 0 deletions

View File

@ -123,6 +123,7 @@ class KetteDetail(BaseModel):
graph: dict | None = None
strang: str | None = None
ampel: dict | None = None
umsetzung: dict | None = None
class PaginatedVorlagen(BaseModel):

View File

@ -191,6 +191,33 @@ def get_kette(kette_id: int, conn=Depends(_db)):
# Graph/Perlenschnur data
graph = get_kette_graph(conn, kette_id)
# Umsetzungsbewertung (neueste KI-Bewertung für Ursprungsvorlage)
umsetzung = None
if row["ursprung_id"]:
ub_row = conn.execute(
"""SELECT score, begruendung, anmerkungen, erstellt_at, prompt_version
FROM ki_bewertungen
WHERE vorlage_id = ? AND typ = 'umsetzung_match'
ORDER BY id DESC LIMIT 1""",
(row["ursprung_id"],),
).fetchone()
if ub_row:
import json as _json
details = {}
if ub_row["anmerkungen"]:
try:
details = _json.loads(ub_row["anmerkungen"])
except Exception:
pass
umsetzung = {
"score": ub_row["score"],
"bewertung": details.get("bewertung", ""),
"begruendung": ub_row["begruendung"],
"kernpunkt_erfuellt": details.get("kernpunkt_erfuellt"),
"details": details.get("details", ""),
"erstellt_at": ub_row["erstellt_at"],
}
strang = row["strang"]
ampel_data = get_ampel(strang or "", row["status"] or "")
@ -216,4 +243,5 @@ def get_kette(kette_id: int, conn=Depends(_db)):
graph=graph,
strang=strang,
ampel=ampel_data,
umsetzung=umsetzung,
)

View File

@ -338,6 +338,35 @@
{/if}
</div>
<!-- Umsetzungsgrad -->
{#if selectedKette.umsetzung}
{@const u = selectedKette.umsetzung}
<div class="px-4 pb-3 border-b border-gray-100">
<div class="flex items-center gap-2 mb-1.5">
<div class="w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold
{u.score >= 0.7 ? 'bg-green-200 text-green-800' : u.score >= 0.4 ? 'bg-amber-200 text-amber-800' : 'bg-red-200 text-red-800'}">
{Math.round((u.score || 0) * 100)}%
</div>
<div>
<div class="text-xs font-semibold {u.score >= 0.7 ? 'text-green-800' : u.score >= 0.4 ? 'text-amber-800' : 'text-red-800'}">
{u.bewertung || (u.score >= 0.7 ? 'Umgesetzt' : u.score >= 0.4 ? 'Teilweise' : 'Kaum umgesetzt')}
</div>
{#if u.kernpunkt_erfuellt !== null && u.kernpunkt_erfuellt !== undefined}
<div class="text-[10px] text-gray-500">
Kernpunkt: {u.kernpunkt_erfuellt ? '✅ erfüllt' : '❌ nicht erfüllt'}
</div>
{/if}
</div>
</div>
{#if u.begruendung}
<p class="text-[11px] text-gray-600 leading-snug">{u.begruendung}</p>
{/if}
{#if u.details}
<p class="text-[10px] text-gray-500 mt-1 leading-snug">{u.details}</p>
{/if}
</div>
{/if}
<!-- Timeline -->
<div class="p-3">
<div class="text-xs font-medium text-gray-500 uppercase mb-3">Ketten-Glieder</div>