From c00580c2613e613dc4a891ac2c915444365696be Mon Sep 17 00:00:00 2001 From: team3 Date: Mon, 15 Jun 2026 18:51:50 +0200 Subject: [PATCH] update --- backend/lernen.py | 20 +++++++++++++------- backend/models.py | 1 + backend/routes.py | 4 ++-- frontend/src/api.js | 5 +++-- frontend/src/components/BausteinPanel.vue | 11 ++++++++++- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/backend/lernen.py b/backend/lernen.py index d3b25d3..0f690d2 100644 --- a/backend/lernen.py +++ b/backend/lernen.py @@ -29,7 +29,7 @@ KRITIK_MAX_RUNDEN = 2 # Generator → Kritiker → ggf. Neu, höchstens so oft def score_berechnen( - score_vor_frage: int, gut: bool, tier2: bool, tier3: bool, absolviert: bool, gemeistert: bool, + score_vor_frage: int, gut: bool, streak: int, tier2: bool, tier3: bool, absolviert: bool, gemeistert: bool, ) -> int: """Neuer Score nach einer Antwort · driftfrei (immer aus dem Basis-Score gerechnet). @@ -37,18 +37,24 @@ def score_berechnen( - Tier 1 (tier2=False): +1 bei richtig, KEINE Strafe, Deckel NOETIG (3). - Tier 2 (tier2, nicht tier3): +1 / −1, Boden 3, Deckel MASTERY (10). - Tier 3 (tier3, Meisterpfad): +1 / −2, Boden 10, Deckel MEISTERN (25). - Boden vor dem Absolvieren ist 0 (sonst NOETIG — absolviert bleibt erhalten). - Ist der Baustein gemeistert, friert der Score bei MEISTERN ein (keine Punkte mehr). - Re-Bewertung nutzt denselben Basis-Score und ersetzt das vorige Ergebnis. + Streak-Bonus bei richtig: ab 3 in Folge +2 (Tier 2/3), ab 5 in Folge +3 (Tier 3). + `streak` = bisherige Folge VOR dieser Antwort; mit dieser Antwort zählt streak+1. + Boden vor dem Absolvieren ist 0; gemeistert friert bei MEISTERN ein. + Re-Bewertung nutzt denselben Basis-Score + dieselbe Streak (kein Doppelzählen). """ if gemeistert: return MEISTERN if not tier2: - delta, floor, cap = (1 if gut else 0), (NOETIG if absolviert else 0), NOETIG + floor, cap, strafe = (NOETIG if absolviert else 0), NOETIG, 0 elif not tier3: - delta, floor, cap = (1 if gut else -1), NOETIG, MASTERY + floor, cap, strafe = NOETIG, MASTERY, -1 else: - delta, floor, cap = (1 if gut else -2), MASTERY, MEISTERN + floor, cap, strafe = MASTERY, MEISTERN, -2 + if gut: + s = streak + 1 + delta = 3 if (tier3 and s >= 5) else (2 if (tier2 and s >= 3) else 1) + else: + delta = strafe return max(floor, min(cap, score_vor_frage + delta)) diff --git a/backend/models.py b/backend/models.py index be9e2f5..61cf26e 100644 --- a/backend/models.py +++ b/backend/models.py @@ -195,6 +195,7 @@ class BausteinPruefungRequest(BaseModel): frage: str = Field(default="", max_length=2000) # aktuell geprüfte Frage (für diskussion/antwort) letzte_bewertung: str = Field(default="", max_length=2000) # Feedback der letzten Bewertung (Kontext für diskussion) score_vor_frage: int = 0 # Score, als die Frage gestellt wurde → driftfreies (Re-)Bewerten + streak: int = 0 # Folge korrekter Antworten VOR dieser Frage (Streak-Bonus) tier2: bool = False # ganzer Guide absolviert (alle ≥3) → −1 bei falsch, Deckel 10 tier3: bool = False # ganzer Guide verstanden (alle ≥10) → Meisterpfad, −2 bei falsch, Deckel 25 messages: list[ChatMessage] = [] # Dialog bisher; leer = erste Frage diff --git a/backend/routes.py b/backend/routes.py index 9dc5ebc..9f1d716 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -252,7 +252,7 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest): ) if data is None: raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen") - score = score_berechnen(req.score_vor_frage, data["bewertung"] == "gut", req.tier2, req.tier3, absolviert, gemeistert) + score = score_berechnen(req.score_vor_frage, data["bewertung"] == "gut", req.streak, req.tier2, req.tier3, absolviert, gemeistert) return {"feedback": data["feedback"], "bewertung": data["bewertung"], "gute_antworten": score, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert} @@ -265,7 +265,7 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest): # Score driftfrei aus dem Basis-Score rechnen (Re-Bewertung ersetzt das vorige Ergebnis). score = score_berechnen( - req.score_vor_frage, data["bewertung"] == "gut", req.tier2, req.tier3, absolviert, gemeistert, + req.score_vor_frage, data["bewertung"] == "gut", req.streak, req.tier2, req.tier3, absolviert, gemeistert, ) gute = await set_baustein_score(req.topic, req.baustein, score) if score >= NOETIG and not absolviert: diff --git a/frontend/src/api.js b/frontend/src/api.js index 6ff73dd..3626617 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -93,12 +93,13 @@ export async function chatBaustein({ topic, baustein, section, messages, provide export async function pruefeBaustein({ topic, baustein, section, provider, - aktion = 'frage', frage = '', letzte_bewertung = '', score_vor_frage = 0, tier2 = false, messages = [], + aktion = 'frage', frage = '', letzte_bewertung = '', score_vor_frage = 0, streak = 0, + tier2 = false, tier3 = false, messages = [], }) { const res = await fetch(`${BASE}/bausteine/pruefung`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ topic, baustein, section, aktion, frage, letzte_bewertung, score_vor_frage, tier2, messages, provider }), + body: JSON.stringify({ topic, baustein, section, aktion, frage, letzte_bewertung, score_vor_frage, streak, tier2, tier3, messages, provider }), }) return jsonOrThrow(res) } diff --git a/frontend/src/components/BausteinPanel.vue b/frontend/src/components/BausteinPanel.vue index ee75bfe..275a42e 100644 --- a/frontend/src/components/BausteinPanel.vue +++ b/frontend/src/components/BausteinPanel.vue @@ -80,6 +80,8 @@ const pruefLoading = ref(false) const aktuelleFrage = ref('') // ankert Bewertung/Diskussion const letztesFeedback = ref('') // Kontext für die Diskussion über eine Bewertung const scoreVorFrage = ref(0) // Score, als die aktuelle Frage gestellt wurde → driftfreies (Re-)Bewerten +const streak = ref(0) // aktuelle Folge korrekter Antworten im Baustein +const streakVorFrage = ref(0) // Streak, als die aktuelle Frage gestellt wurde (driftfrei) const naechsteFrage = ref(null) // im Hintergrund vorbereitete nächste Frage (Prefetch) let prefetchPromise = null // laufender Hintergrund-Abruf (verhindert Doppel-Prefetch) const pruefMessagesEl = ref(null) @@ -134,6 +136,7 @@ function frageZeigen(text) { aktuelleFrage.value = text letztesFeedback.value = '' scoreVorFrage.value = st.value.gute_antworten + streakVorFrage.value = streak.value pruefMessages.value.push({ role: 'assistant', kind: 'frage', content: text }) pruefPhase.value = 'frage_offen' } @@ -237,7 +240,7 @@ function bewerten(res) { function antwortPayload() { return { aktion: 'antwort', frage: aktuelleFrage.value, score_vor_frage: scoreVorFrage.value, - tier2: props.tier2, tier3: props.tier3, + streak: streakVorFrage.value, tier2: props.tier2, tier3: props.tier3, } } @@ -245,12 +248,18 @@ function antwortPayload() { async function verdictPruefen() { const meine = ++pruefenRun const ziel = letzteFeedbackMsg + const warAbsolviert = st.value.absolviert + const warVerstanden = st.value.verstanden try { const res = await pruefeBaustein({ topic: props.topic, baustein: props.baustein, section: props.section, provider: props.provider, messages: pruefDialog(), ...antwortPayload(), aktion: 'antwort_pruefen', }) if (meine !== pruefenRun) return // eine neuere Bewertung läuft → diese verwerfen + // Streak driftfrei aus streakVorFrage neu setzen; Reset beim ersten Erreichen von 3/10. + let neu = res.bewertung === 'gut' ? streakVorFrage.value + 1 : 0 + if ((res.absolviert && !warAbsolviert) || (res.verstanden && !warVerstanden)) neu = 0 + streak.value = neu applyPruefung(res) if (ziel) { if (res.bewertung !== ziel.bewertung || (res.feedback && res.feedback !== ziel.content)) {