This commit is contained in:
team3
2026-06-19 14:04:19 +02:00
parent 26e097dc5a
commit 0868a768a2
7 changed files with 219 additions and 157 deletions

View File

@@ -14,11 +14,11 @@ from database import (
list_progress, set_progress, delete_progress,
create_element, list_elements, get_element, update_element, delete_element,
list_baustein_progress, get_baustein_progress, set_offene_frage,
set_baustein_score, set_baustein_streak, set_baustein_absolviert, set_baustein_verstanden, set_baustein_gemeistert, delete_baustein_daten,
set_baustein_score, set_baustein_absolviert, set_baustein_verstanden, set_baustein_gemeistert, delete_baustein_daten,
)
from bausteine import generate_bausteine, cancel_bausteine, bausteine_status, active_bausteine, reset_bausteine, lade_quelle, lade_uebersicht, subbausteine_titel
from elements import generate_element, chat_with_guide, chat_with_element, check_element, style_element, refine_suggestion
from lernen import NOETIG, MASTERY, MEISTERN, baustein_chat, baustein_diskussion, baustein_element_anlegen, pruefung_bewertung, pruefung_frage, score_berechnen, streak_berechnen
from lernen import NOETIG, MASTERY, MEISTERN, baustein_chat, baustein_diskussion, baustein_element_anlegen, pruefung_bewertung, pruefung_bewertung_schnell, pruefung_frage, score_berechnen
from guide import generate_guide, guide_slot_dateien
from pipeline import cancel_guide
from regeln import FORMATE, formate_stats, guide_lock, ist_absolviert, lade_lernstand, thema_abgeschlossen
@@ -247,14 +247,16 @@ def _pruef_lock(topic: str, baustein: str) -> asyncio.Lock:
return lock
def _basis(stand: dict, frage: str) -> tuple[int, int, bool]:
"""Score-/Streak-Basis VOR der Frage. Gleiche offene Frage → Re-Bewertung auf derselben
Basis (idempotent); sonst neue Frage auf dem aktuellen Stand. → (basis, streak_basis, re_bewertung)."""
def _basis(stand: dict, frage: str) -> tuple[int, bool]:
"""Score-Basis VOR der Frage. Gleiche offene Frage → Re-Bewertung auf derselben Basis
(idempotent); sonst neue Frage auf dem aktuellen Stand. → (basis, re_bewertung)."""
re_bewertung = stand["offene_frage"] == frage and stand["offene_basis"] is not None
if re_bewertung:
streak_basis = stand["offene_streak"] if stand["offene_streak"] is not None else stand["streak"]
return stand["offene_basis"], streak_basis, True
return stand["gute_antworten"], stand["streak"], False
return (stand["offene_basis"] if re_bewertung else stand["gute_antworten"]), re_bewertung
def _farbe(niveau: str) -> str:
"""Niveau → grobe Einfärbung der Bubble."""
return "gut" if niveau in ("solide", "stark", "komplett") else ("neutral" if niveau == "teilweise" else "schlecht")
@router.post("/bausteine/pruefung", response_model=BausteinPruefungResponse)
@@ -285,36 +287,38 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest):
raise HTTPException(502, "Diskussion fehlgeschlagen — bitte erneut versuchen")
return {"reply": reply, "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
# aktion == "antwort_pruefen" — mindestens eine Nutzer-Antwort muss im Dialog stehen
# (nach einer Diskussion endet der Dialog mit dem Tutor; Re-Bewertung bleibt erlaubt).
# aktion "antwort" (Agent 1 schnell) / "antwort_pruefen" (Agent 2 genau).
if not any(m.get("role") == "user" for m in msgs):
raise HTTPException(400, "Antwort braucht eine Nutzer-Antwort")
if not req.frage.strip():
raise HTTPException(400, "Antwort braucht eine laufende Frage")
genau = req.aktion == "antwort_pruefen"
# Verbindlich, persistiert. Serialisiert je Baustein, damit die Basis immer den
# geprüften Vorgänger-Stand sieht (kein Stale-Overwrite). Punkte kommen NUR hier.
# Persist serialisiert je Baustein, Basis driftfrei über den offene_frage-Anker
# (schnell setzt den Anker, genau re-bewertet dieselbe Frage). Beide vergeben Punkte.
async with _pruef_lock(req.topic, req.baustein):
stand = await get_baustein_progress(req.topic, req.baustein) # nach evtl. Vorgänger-Persist neu lesen
stand = await get_baustein_progress(req.topic, req.baustein)
absolviert = stand["absolviert"] is not None
verstanden = stand["verstanden"] is not None
gemeistert = stand["gemeistert"] is not None
basis, streak_basis, re_bewertung = _basis(stand, req.frage)
basis, re_bewertung = _basis(stand, req.frage)
if not re_bewertung:
# Neue Frage: Basis/Streak einfrieren (Anker für spätere Re-Bewertung dieser Frage).
await set_offene_frage(req.topic, req.baustein, req.frage, basis, streak_basis)
await set_offene_frage(req.topic, req.baustein, req.frage, basis, 0)
data = await pruefung_bewertung(
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, basis, provider=req.provider,
role="guide" if req.gruendlich else "judge",
)
if genau:
data = await pruefung_bewertung(
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, provider=req.provider,
role="guide" if req.gruendlich else "judge", begruendung=req.begruendung,
)
else:
data = await pruefung_bewertung_schnell(
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, provider=req.provider,
)
if data is None:
raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
score = score_berechnen(
basis, data["bewertung"] == "gut", streak_basis, req.tier2, req.tier3, absolviert, verstanden, gemeistert,
)
war_absolviert, war_verstanden = absolviert, verstanden
niveau = data["niveau"]
score = score_berechnen(basis, niveau, req.nachgefragt, req.tier2, req.tier3, absolviert, verstanden, gemeistert)
gute = await set_baustein_score(req.topic, req.baustein, score)
if score >= NOETIG and not absolviert:
absolviert = True
@@ -326,13 +330,7 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest):
if score >= MEISTERN and not gemeistert:
await set_baustein_gemeistert(req.topic, req.baustein)
gemeistert = True
# Streak persistieren — Reset nur am Deckel der gerade aktiven Stufe.
streak = streak_berechnen(
streak_basis, data["bewertung"] == "gut", req.tier2, req.tier3,
absolviert and not war_absolviert, verstanden and not war_verstanden,
)
await set_baustein_streak(req.topic, req.baustein, streak)
return {"feedback": data["feedback"], "bewertung": data["bewertung"], "gute_antworten": gute, "streak": streak, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
return {"feedback": data["feedback"], "niveau": niveau, "bewertung": _farbe(niveau), "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
# --- Guides ---