feat: KI-Bewertungs-Versionierung — alte Versionen behalten

Backend:
- DELETE vor INSERT entfernt — neue Bewertungen werden hinzugefügt
- erstellt_at Timestamp bei jeder Neubewertung
- API liefert ki_versionen[] (ältere Versionen, neueste zuerst)

Frontend (Explorer + Vorlagen-Detail):
- Neueste Version als Hauptanzeige (wie bisher)
- Button 'X vorherige Versionen' → aufklappbar
- Jede Version mit Zeitstempel + prompt_version
This commit is contained in:
Dotty Dotter 2026-04-01 21:15:17 +02:00
parent df917725e2
commit 3fd1bc5bd7
5 changed files with 89 additions and 22 deletions

View File

@ -85,6 +85,7 @@ class VorlageDetail(BaseModel):
kette_id: int | None = None
umsetzungsbewertungen: list[UmsetzungsBewertung] = []
ampel: dict | None = None
ki_versionen: list[dict] | None = None
class KettenGliedOut(BaseModel):

View File

@ -162,12 +162,12 @@ def _run_zusammenfassung(vorlage_id: int, anmerkung: str, job_id: str):
_jobs[job_id] = {"status": "error", "error": str(result)}
return
# Delete old, insert new
conn.execute("DELETE FROM ki_bewertungen WHERE vorlage_id = ? AND typ = 'zusammenfassung'", (vorlage_id,))
# Keep old versions, insert new
conn.execute(
"""INSERT INTO ki_bewertungen (vorlage_id, typ, begruendung, anmerkungen, modell, prompt_version)
VALUES (?, 'zusammenfassung', ?, ?, 'qwen-plus-latest', 'v2-reeval')""",
(vorlage_id, result.get("zusammenfassung"), json.dumps(result, ensure_ascii=False)),
"""INSERT INTO ki_bewertungen (vorlage_id, typ, begruendung, anmerkungen, modell, prompt_version, erstellt_at)
VALUES (?, 'zusammenfassung', ?, ?, 'qwen-plus-latest', 'v2-reeval', ?)""",
(vorlage_id, result.get("zusammenfassung"), json.dumps(result, ensure_ascii=False),
datetime.now().isoformat()),
)
if result.get("kernforderung"):
conn.execute("UPDATE vorlagen SET thema_kurz = ? WHERE id = ?", (result["kernforderung"][:200], vorlage_id))
@ -249,19 +249,16 @@ def _run_ketten_bewertung(kette_id: int, anmerkung: str, job_id: str):
_jobs[job_id] = {"status": "error", "error": str(result)}
return
# Delete old umsetzung_match, insert new
# Keep old versions, insert new
conn.execute(
"DELETE FROM ki_bewertungen WHERE vorlage_id = ? AND typ = 'umsetzung_match'",
(kette["ursprung_id"],),
)
conn.execute(
"""INSERT INTO ki_bewertungen (vorlage_id, typ, score, begruendung, anmerkungen, modell, prompt_version)
VALUES (?, 'umsetzung_match', ?, ?, ?, 'qwen-plus-latest', 'v2-reeval')""",
"""INSERT INTO ki_bewertungen (vorlage_id, typ, score, begruendung, anmerkungen, modell, prompt_version, erstellt_at)
VALUES (?, 'umsetzung_match', ?, ?, ?, 'qwen-plus-latest', 'v2-reeval', ?)""",
(
kette["ursprung_id"],
result.get("score"),
result.get("begruendung"),
json.dumps(result, ensure_ascii=False),
datetime.now().isoformat(),
),
)

View File

@ -297,16 +297,28 @@ def get_vorlage(vorlage_id: int, conn=Depends(_db)):
if kette_info and kette_info["strang"]:
kette_ampel = get_ampel(kette_info["strang"], kette_info["status"] or "")
# KI-Zusammenfassung
ki_row = conn.execute(
"SELECT anmerkungen FROM ki_bewertungen WHERE vorlage_id = ? AND typ = 'zusammenfassung' LIMIT 1",
# KI-Zusammenfassung (alle Versionen, neueste zuerst)
ki_rows = conn.execute(
"SELECT anmerkungen, erstellt_at, prompt_version FROM ki_bewertungen WHERE vorlage_id = ? AND typ = 'zusammenfassung' ORDER BY id DESC",
(vorlage_id,),
).fetchone()
).fetchall()
ki_zusammenfassung = None
if ki_row and ki_row["anmerkungen"]:
ki_versionen = []
for i, ki_row in enumerate(ki_rows):
if ki_row["anmerkungen"]:
try:
ki_data = json.loads(ki_row["anmerkungen"])
if i == 0:
ki_zusammenfassung = KiZusammenfassung(**ki_data)
else:
ki_versionen.append({
"zusammenfassung": ki_data.get("zusammenfassung", ""),
"kernforderung": ki_data.get("kernforderung", ""),
"begruendung": ki_data.get("begruendung", ""),
"thema": ki_data.get("thema", ""),
"erstellt_at": ki_row["erstellt_at"],
"prompt_version": ki_row["prompt_version"],
})
except (json.JSONDecodeError, TypeError):
pass
@ -351,4 +363,5 @@ def get_vorlage(vorlage_id: int, conn=Depends(_db)):
ki_zusammenfassung=ki_zusammenfassung,
umsetzungsbewertungen=umsetzungsbewertungen,
ampel=kette_ampel,
ki_versionen=ki_versionen if ki_versionen else None,
)

View File

@ -30,6 +30,7 @@
// Mobile tab
let mobileTab = $state<'liste' | 'kette' | 'detail'>('liste');
let showVolltext = $state(false);
let showVersionen = $state(false);
const STRANG_TABS = [
{ value: '', label: 'Alle' },
@ -438,6 +439,33 @@
</div>
{/if}
<!-- Vorherige KI-Versionen -->
{#if selectedVorlage.ki_versionen?.length}
<div>
<button onclick={() => showVersionen = !showVersionen}
class="text-xs text-gray-500 hover:text-gray-700 flex items-center gap-1">
<span>{showVersionen ? '▼' : '▶'}</span>
{selectedVorlage.ki_versionen.length} vorherige Version{selectedVorlage.ki_versionen.length > 1 ? 'en' : ''}
</button>
{#if showVersionen}
<div class="mt-2 space-y-3">
{#each selectedVorlage.ki_versionen as v, i}
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-xs text-gray-400">Version {selectedVorlage.ki_versionen.length - i} · {v.erstellt_at || 'unbekannt'}</span>
<span class="text-[10px] px-1.5 py-0.5 rounded bg-gray-200 text-gray-500">{v.prompt_version || ''}</span>
</div>
<p class="text-sm text-gray-600">{v.zusammenfassung}</p>
{#if v.kernforderung}
<p class="text-xs text-gray-500 mt-1"><strong>Kernforderung:</strong> {v.kernforderung}</p>
{/if}
</div>
{/each}
</div>
{/if}
</div>
{/if}
<!-- Umsetzungsbewertung -->
{#if selectedVorlage.umsetzungsbewertungen?.length}
<div class="rounded-xl border border-gray-200 p-5">

View File

@ -9,6 +9,7 @@
let error: string | null = $state(null);
let showVolltext = $state(false);
let showReeval = $state(false);
let showVersionen = $state(false);
let reevalAnmerkung = $state('');
let reevalStatus = $state<'idle' | 'running' | 'done' | 'error'>('idle');
let reevalError = $state('');
@ -201,6 +202,33 @@
</div>
{/if}
<!-- Vorherige KI-Versionen -->
{#if vorlage.ki_versionen?.length}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<button onclick={() => showVersionen = !showVersionen}
class="text-sm text-gray-500 hover:text-gray-700 flex items-center gap-1.5">
<span>{showVersionen ? '▼' : '▶'}</span>
{vorlage.ki_versionen.length} vorherige KI-Version{vorlage.ki_versionen.length > 1 ? 'en' : ''}
</button>
{#if showVersionen}
<div class="mt-3 space-y-3">
{#each vorlage.ki_versionen as v, i}
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-xs text-gray-400">Version {vorlage.ki_versionen.length - i} · {v.erstellt_at || 'unbekannt'}</span>
<span class="text-[10px] px-1.5 py-0.5 rounded bg-gray-200 text-gray-500">{v.prompt_version || ''}</span>
</div>
<p class="text-sm text-gray-600">{v.zusammenfassung}</p>
{#if v.kernforderung}
<p class="text-xs text-gray-500 mt-1"><strong>Kernforderung:</strong> {v.kernforderung}</p>
{/if}
</div>
{/each}
</div>
{/if}
</div>
{/if}
<!-- Umsetzungsbewertung -->
{#if vorlage.umsetzungsbewertungen?.length}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">