diff --git a/backend/scripts/bisaya-course-phase3-extension.js b/backend/scripts/bisaya-course-phase3-extension.js index 1b77d88..3f22d45 100644 --- a/backend/scripts/bisaya-course-phase3-extension.js +++ b/backend/scripts/bisaya-course-phase3-extension.js @@ -539,11 +539,11 @@ export const BISAYA_PHASE3_LESSONS = [ { week: 5, day: 1, num: 45, type: 'vocab', title: 'Besuch & Haushalt', desc: 'Haus-, Besuchs- und Haushaltswörter in kurzen Ortssätzen verwenden', targetMin: 22, targetScore: 85, review: true, cultural: null }, { week: 5, day: 2, num: 46, type: 'grammar', title: 'Fragen im Alltag vertiefen', desc: 'Rückfragen, Folgefragen, Bedeutung und höfliches Nachhaken sicher kombinieren', targetMin: 26, targetScore: 78, review: true, cultural: null }, { week: 5, day: 2, num: 47, type: 'conversation', title: 'Termine & Verabredungen', desc: 'Treffen planen, Uhrzeiten absprechen, zusagen und weich verschieben', targetMin: 24, targetScore: 80, review: false, cultural: null }, - { week: 5, day: 3, num: 48, type: 'review', title: 'Woche 5 - Intensivwiederholung I', desc: 'Besuch, Familie, Fürsorge und Terminplanung in schnellen Rollenwechseln mischen', targetMin: 34, targetScore: 82, review: false, cultural: null }, + { week: 5, day: 3, num: 48, type: 'weekly_review', title: 'Woche 5 - Intensivwiederholung I', desc: 'Besuch, Familie, Fürsorge und Terminplanung in schnellen Rollenwechseln mischen', targetMin: 34, targetScore: 82, review: false, cultural: null }, { week: 5, day: 3, num: 49, type: 'vocab', title: 'Spiralwiederholung - Familie & Fürsorge', desc: 'Alte Familien- und Fürsorgemuster aktiv reaktivieren und in neue Szenen übertragen', targetMin: 24, targetScore: 85, review: true, cultural: null }, { week: 5, day: 4, num: 50, type: 'conversation', title: 'Gesundheit im Alltag', desc: 'Beschwerden erfragen, Ruhe/Wasser/Medizin anbieten und nach Besserung fragen', targetMin: 26, targetScore: 80, review: false, cultural: null }, { week: 5, day: 4, num: 51, type: 'vocab', title: 'Medikamente & Beschwerden', desc: 'Symptome, Körperwörter und Hilfewortschatz in Fürsorgesätzen verwenden', targetMin: 24, targetScore: 85, review: true, cultural: null }, - { week: 5, day: 5, num: 52, type: 'review', title: 'Woche 5 - Intensivwiederholung II', desc: 'Gesundheit, Besuch, Fragen und Terminplanung unter Abrufdruck kontrastieren', targetMin: 34, targetScore: 82, review: false, cultural: null }, + { week: 5, day: 5, num: 52, type: 'weekly_review', title: 'Woche 5 - Intensivwiederholung II', desc: 'Gesundheit, Besuch, Fragen und Terminplanung unter Abrufdruck kontrastieren', targetMin: 34, targetScore: 82, review: false, cultural: null }, { week: 5, day: 5, num: 53, type: 'vocab', title: 'Woche 5 - Checkpoint', desc: 'Diagnostischer Checkpoint zu Besuch, Termin, Frageketten und Gesundheit', targetMin: 24, targetScore: 84, review: true, cultural: null }, { week: 6, day: 1, num: 54, type: 'conversation', title: 'Unterwegs & Transport', desc: 'Nach Weg, Haltestelle, Fahrpreis, Ziel und Ausstieg fragen', targetMin: 26, targetScore: 80, review: false, cultural: null }, { week: 6, day: 1, num: 55, type: 'vocab', title: 'Wege & Verkehr', desc: 'Verkehrs-, Richtungs- und Bewegungswortschatz in einfachen Routen verwenden', targetMin: 24, targetScore: 85, review: true, cultural: null }, diff --git a/backend/scripts/bisaya-course-phase4-extension.js b/backend/scripts/bisaya-course-phase4-extension.js index fb51860..e2a4e9f 100644 --- a/backend/scripts/bisaya-course-phase4-extension.js +++ b/backend/scripts/bisaya-course-phase4-extension.js @@ -977,51 +977,51 @@ export const BISAYA_PHASE4_LESSONS = [ { week: 7, day: 1, num: 65, type: 'vocab', title: 'Schule & Betreuung', desc: 'Schul- und Betreuungsvokabeln plus kurze Alltagssätze (Tasche, Buch, Lehrkraft, Aufgabe)', targetMin: 24, targetScore: 85, review: true, cultural: null }, { week: 7, day: 2, num: 66, type: 'grammar', title: 'Fragen an Kinder vereinfachen', desc: 'Kurze Fragen vs. Aufforderungen in Betreuungssituationen sicher bauen', targetMin: 26, targetScore: 78, review: true, cultural: null }, { week: 7, day: 2, num: 67, type: 'conversation', title: 'Hausaufgaben & Routine', desc: 'Hausaufgaben, Lernen, Spielen und Schlafen in eine klare Routine bringen', targetMin: 26, targetScore: 80, review: false, cultural: null }, - { week: 7, day: 3, num: 68, type: 'review', title: 'Woche 7 - Intensivwiederholung I', desc: 'Kinder, Schule und Familienroutine intensiv wiederholen (Abruf + Rollenwechsel)', targetMin: 34, targetScore: 82, review: false, cultural: null }, + { week: 7, day: 3, num: 68, type: 'weekly_review', title: 'Woche 7 - Intensivwiederholung I', desc: 'Kinder, Schule und Familienroutine intensiv wiederholen (Abruf + Rollenwechsel)', targetMin: 34, targetScore: 82, review: false, cultural: null }, { week: 7, day: 3, num: 69, type: 'vocab', title: 'Spiralwiederholung - Familie, Kinder & Fürsorge', desc: 'Frühe Kernmuster in Kinder-/Schulszenen reaktivieren', targetMin: 24, targetScore: 85, review: true, cultural: null }, { week: 7, day: 4, num: 70, type: 'conversation', title: 'Spielen & Freizeit', desc: 'Spiel, Pause, Regeln und ruhige Korrektur im Kinderalltag sprechen', targetMin: 24, targetScore: 80, review: false, cultural: null }, { week: 7, day: 4, num: 71, type: 'vocab', title: 'Spielsachen & Aktivitäten', desc: 'Spiel- und Freizeitwortschatz aktiv nutzen (Auswahl, Fragen, kurze Sätze)', targetMin: 24, targetScore: 85, review: true, cultural: null }, - { week: 7, day: 5, num: 72, type: 'review', title: 'Woche 7 - Intensivwiederholung II', desc: 'Große Mischwiederholung zur Kinder- und Schulwoche (Szene bauen)', targetMin: 34, targetScore: 82, review: false, cultural: null }, + { week: 7, day: 5, num: 72, type: 'weekly_review', title: 'Woche 7 - Intensivwiederholung II', desc: 'Große Mischwiederholung zur Kinder- und Schulwoche (Szene bauen)', targetMin: 34, targetScore: 82, review: false, cultural: null }, { week: 7, day: 5, num: 73, type: 'vocab', title: 'Woche 7 - Checkpoint', desc: 'Checkpoint zu Kindern, Schule, Hausaufgaben und Routine (Diagnose + freie Szene)', targetMin: 24, targetScore: 84, review: true, cultural: null }, { week: 8, day: 1, num: 74, type: 'conversation', title: 'Arzt & Termin', desc: 'Arzttermine, Beschwerden und Wartezeit als Mini-Dialog organisieren', targetMin: 26, targetScore: 80, review: false, cultural: null }, { week: 8, day: 1, num: 75, type: 'vocab', title: 'Apotheke & Medikamente', desc: 'Apotheke, Rezept, Dosierung und Einnahmefragen aktiv abrufen', targetMin: 24, targetScore: 85, review: true, cultural: null }, { week: 8, day: 2, num: 76, type: 'grammar', title: 'Beschwerden genauer beschreiben', desc: 'Stärke, Verlauf, Zeitpunkte und wiederkehrende Beschwerden ausdrücken', targetMin: 26, targetScore: 78, review: true, cultural: null }, { week: 8, day: 2, num: 77, type: 'conversation', title: 'Notfälle & Hilfe', desc: 'Hilfe rufen, kurze Anweisungen geben und Notfallanker sicher sprechen', targetMin: 26, targetScore: 80, review: false, cultural: null }, - { week: 8, day: 3, num: 78, type: 'review', title: 'Woche 8 - Intensivwiederholung I', desc: 'Arzt, Apotheke, Beschwerden und Hilfe intensiv wiederholen (Tempo + Abruf)', targetMin: 34, targetScore: 82, review: false, cultural: null }, + { week: 8, day: 3, num: 78, type: 'weekly_review', title: 'Woche 8 - Intensivwiederholung I', desc: 'Arzt, Apotheke, Beschwerden und Hilfe intensiv wiederholen (Tempo + Abruf)', targetMin: 34, targetScore: 82, review: false, cultural: null }, { week: 8, day: 3, num: 79, type: 'vocab', title: 'Spiralwiederholung - Gesundheit', desc: 'Fürsorge und Gesundheit als Langzeitabruf (Problem -> Antwort)', targetMin: 24, targetScore: 85, review: true, cultural: null }, { week: 8, day: 4, num: 80, type: 'conversation', title: 'Essen, Ruhe & Genesung', desc: 'Pflegegespräche: Essen, Wasser, Ruhe, Medizin und Besserung verbinden', targetMin: 24, targetScore: 80, review: false, cultural: null }, { week: 8, day: 4, num: 81, type: 'vocab', title: 'Körper, Symptome & Pflege', desc: 'Körper, Symptome und Pflegewortschatz mit kurzen Schmerzsätzen kombinieren', targetMin: 24, targetScore: 85, review: true, cultural: null }, - { week: 8, day: 5, num: 82, type: 'review', title: 'Woche 8 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu Gesundheit und Hilfe (Kontraste + freie Antworten)', targetMin: 34, targetScore: 82, review: false, cultural: null }, + { week: 8, day: 5, num: 82, type: 'weekly_review', title: 'Woche 8 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu Gesundheit und Hilfe (Kontraste + freie Antworten)', targetMin: 34, targetScore: 82, review: false, cultural: null }, { week: 8, day: 5, num: 83, type: 'vocab', title: 'Woche 8 - Checkpoint', desc: 'Checkpoint zu Arzt, Apotheke, Beschwerden und Notfall (Diagnose + Typing)', targetMin: 24, targetScore: 84, review: true, cultural: null }, { week: 9, day: 1, num: 84, type: 'conversation', title: 'Einkaufen vertiefen', desc: 'Komplexere Einkaufs- und Auswahlgespräche führen', targetMin: 18, targetScore: 80, review: false, cultural: null }, { week: 9, day: 1, num: 85, type: 'vocab', title: 'Markt & Mengen', desc: 'Wortschatz für Markt, Mengen und Auswahl', targetMin: 18, targetScore: 85, review: true, cultural: null }, { week: 9, day: 2, num: 86, type: 'grammar', title: 'Wünsche, Bedarf und Bitte', desc: 'Wunsch, Notwendigkeit und höfliche Bitte unterscheiden', targetMin: 20, targetScore: 78, review: true, cultural: null }, { week: 9, day: 2, num: 87, type: 'conversation', title: 'Behördengänge & Formulare', desc: 'Nach Formularen, Schaltern und Terminen fragen', targetMin: 18, targetScore: 80, review: false, cultural: null }, - { week: 9, day: 3, num: 88, type: 'review', title: 'Woche 9 - Intensivwiederholung I', desc: 'Einkaufen und Erledigungen intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 9, day: 3, num: 88, type: 'weekly_review', title: 'Woche 9 - Intensivwiederholung I', desc: 'Einkaufen und Erledigungen intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, { week: 9, day: 3, num: 89, type: 'vocab', title: 'Spiralwiederholung - Preise & Erledigungen', desc: 'Preis- und Erledigungsmuster spiralig festigen', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 9, day: 4, num: 90, type: 'conversation', title: 'Bank, Geld & Bezahlen', desc: 'Über Bezahlen, Wechselgeld und Geldsituationen sprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, { week: 9, day: 4, num: 91, type: 'vocab', title: 'Dokumente & Termine', desc: 'Papierkram, Termine und Dokumente benennen', targetMin: 18, targetScore: 85, review: true, cultural: null }, - { week: 9, day: 5, num: 92, type: 'review', title: 'Woche 9 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu Geld, Formularen und Terminen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 9, day: 5, num: 92, type: 'weekly_review', title: 'Woche 9 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu Geld, Formularen und Terminen', targetMin: 28, targetScore: 80, review: false, cultural: null }, { week: 9, day: 5, num: 93, type: 'vocab', title: 'Woche 9 - Checkpoint', desc: 'Checkpoint zu Einkauf, Bezahlen und Erledigungen', targetMin: 16, targetScore: 82, review: true, cultural: null }, { week: 10, day: 1, num: 94, type: 'conversation', title: 'Nachbarschaft & Besuche', desc: 'Mit Nachbarn sprechen und Besuche im Umfeld einordnen', targetMin: 18, targetScore: 80, review: false, cultural: null }, { week: 10, day: 1, num: 95, type: 'vocab', title: 'Hilfe & Unterstützung', desc: 'Wortschatz für Hilfe, Unterstützung und soziale Nähe', targetMin: 18, targetScore: 85, review: true, cultural: null }, { week: 10, day: 2, num: 96, type: 'grammar', title: 'Höflich reagieren und ablehnen', desc: 'Einladungen, Hilfe und Vorschläge fein abstufen', targetMin: 20, targetScore: 78, review: true, cultural: null }, { week: 10, day: 2, num: 97, type: 'conversation', title: 'Feste & Einladungen', desc: 'Über Feste, Besuche und Einladungen sprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, - { week: 10, day: 3, num: 98, type: 'review', title: 'Woche 10 - Intensivwiederholung I', desc: 'Soziale Situationen und Hilfe intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 10, day: 3, num: 98, type: 'weekly_review', title: 'Woche 10 - Intensivwiederholung I', desc: 'Soziale Situationen und Hilfe intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, { week: 10, day: 3, num: 99, type: 'vocab', title: 'Spiralwiederholung - Soziale Situationen', desc: 'Besuche, Hilfe und Reaktionen blockübergreifend festigen', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 10, day: 4, num: 100, type: 'conversation', title: 'Konflikte ruhig lösen', desc: 'Missverständnisse ansprechen und Spannungen abfedern', targetMin: 18, targetScore: 80, review: false, cultural: null }, { week: 10, day: 4, num: 101, type: 'vocab', title: 'Gefühle im Gespräch vertiefen', desc: 'Emotionen in sozialen Gesprächen genauer ausdrücken', targetMin: 18, targetScore: 85, review: true, cultural: null }, - { week: 10, day: 5, num: 102, type: 'review', title: 'Woche 10 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu sozialen Situationen und Emotionen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 10, day: 5, num: 102, type: 'weekly_review', title: 'Woche 10 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu sozialen Situationen und Emotionen', targetMin: 28, targetScore: 80, review: false, cultural: null }, { week: 10, day: 5, num: 103, type: 'vocab', title: 'Woche 10 - Checkpoint', desc: 'Checkpoint zu Hilfe, Besuchen und Gefühlen', targetMin: 16, targetScore: 82, review: true, cultural: null }, { week: 11, day: 1, num: 104, type: 'conversation', title: 'Zuhause organisieren', desc: 'Ordnung, Haushalt und kleine Aufgaben zuhause besprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, { week: 11, day: 1, num: 105, type: 'vocab', title: 'Haushalt & Reparaturen', desc: 'Wörter für Haushalt, Ordnung und kleine Reparaturen', targetMin: 18, targetScore: 85, review: true, cultural: null }, { week: 11, day: 2, num: 106, type: 'grammar', title: 'Vergangenes und Pläne im Alltag', desc: 'Vergangenheit und kommende Schritte in Routinen verbinden', targetMin: 20, targetScore: 78, review: true, cultural: null }, { week: 11, day: 2, num: 107, type: 'conversation', title: 'Arbeit, Schule und Termine verbinden', desc: 'Mehrere Alltagsbereiche in einer Erzählung kombinieren', targetMin: 18, targetScore: 80, review: false, cultural: null }, - { week: 11, day: 3, num: 108, type: 'review', title: 'Woche 11 - Intensivwiederholung I', desc: 'Haushalt, Planung und Alltagslogistik intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 11, day: 3, num: 108, type: 'weekly_review', title: 'Woche 11 - Intensivwiederholung I', desc: 'Haushalt, Planung und Alltagslogistik intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, { week: 11, day: 3, num: 109, type: 'vocab', title: 'Spiralwiederholung - Alltagsmodule', desc: 'Module 6 bis 10 zusammenziehen und wiederholen', targetMin: 20, targetScore: 85, review: true, cultural: null }, { week: 11, day: 4, num: 110, type: 'conversation', title: 'Unterwegs im Familienalltag', desc: 'Familie, Wege und Termine im Alltag verbinden', targetMin: 18, targetScore: 80, review: false, cultural: null }, { week: 11, day: 4, num: 111, type: 'vocab', title: 'Nachbarschaft & Orte', desc: 'Wichtige Orte und Nachbarschaftswortschatz vertiefen', targetMin: 18, targetScore: 85, review: true, cultural: null }, - { week: 11, day: 5, num: 112, type: 'review', title: 'Woche 11 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu Organisation und Familienlogistik', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 11, day: 5, num: 112, type: 'weekly_review', title: 'Woche 11 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu Organisation und Familienlogistik', targetMin: 28, targetScore: 80, review: false, cultural: null }, { week: 11, day: 5, num: 113, type: 'vocab', title: 'Woche 11 - Checkpoint', desc: 'Checkpoint zu Organisation, Haushalt und Alltagslogistik', targetMin: 16, targetScore: 82, review: true, cultural: null }, { week: 12, day: 1, num: 114, type: 'conversation', title: 'Freies Gespräch - Alltag I', desc: 'Längere Alltagsgespräche freier und stabiler führen', targetMin: 22, targetScore: 78, review: false, cultural: null }, { week: 12, day: 1, num: 115, type: 'vocab', title: 'Mischtraining - Kernwortschatz', desc: 'Kernwortschatz aus allen Modulen gemischt trainieren', targetMin: 20, targetScore: 85, review: true, cultural: null }, diff --git a/backend/scripts/bisaya-course-plan-24-43.js b/backend/scripts/bisaya-course-plan-24-43.js index 8498186..0616538 100644 --- a/backend/scripts/bisaya-course-plan-24-43.js +++ b/backend/scripts/bisaya-course-plan-24-43.js @@ -7,7 +7,7 @@ const BISAYA_LESSONS_24_43_BASE = [ { week: 3, day: 3, num: 29, type: 'conversation', title: 'Bitten & Nachfragen', desc: 'Hilfe, Wiederholung, Bedeutung und langsames Sprechen erbitten', targetMin: 18, targetScore: 80, review: false, cultural: null }, { week: 3, day: 4, num: 30, type: 'conversation', title: 'Kinder & Familie', desc: 'Mit Kindern kurz, klar und freundlich sprechen', targetMin: 18, targetScore: 80, review: false, cultural: 'Kurze klare Sätze funktionieren mit Kindern natürlicher als lange Erklärungen.' }, { week: 3, day: 4, num: 31, type: 'vocab', title: 'Kinder, Spiel & Routine', desc: 'Kinderalltag mit Spielen, Fertigsein und Schlaf verbinden', targetMin: 20, targetScore: 85, review: true, cultural: null }, - { week: 3, day: 5, num: 32, type: 'review', title: 'Woche 3 - Intensivwiederholung', desc: 'Gefühle, Gesundheit, Kinder und Höflichkeit kontrastieren', targetMin: 30, targetScore: 82, review: false, cultural: null }, + { week: 3, day: 5, num: 32, type: 'weekly_review', title: 'Woche 3 - Intensivwiederholung', desc: 'Gefühle, Gesundheit, Kinder und Höflichkeit kontrastieren', targetMin: 30, targetScore: 82, review: false, cultural: null }, { week: 3, day: 5, num: 33, type: 'vocab', title: 'Woche 3 - Checkpoint', desc: 'Aktiver Checkpoint zur ersten Alltagserweiterung', targetMin: 18, targetScore: 84, review: true, cultural: null }, { week: 3, day: 5, num: 44, type: 'vocab', title: 'Zahlen & Zählen (Woche 3)', desc: 'Gezielte Zählübungen 1–20 und runde Zahlen', targetMin: 14, targetScore: 85, review: true, cultural: null }, { week: 4, day: 1, num: 34, type: 'conversation', title: 'Alltagsszene: Zuhause morgens', desc: 'Morgens zuhause über Aufstehen, Essen, Schule und Aufgaben sprechen', targetMin: 22, targetScore: 78, review: false, cultural: 'Morgenszenen verbinden Fürsorge, Zeit und Familienorganisation.' }, diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js index b90c011..5628190 100644 --- a/backend/scripts/create-bisaya-course-content.js +++ b/backend/scripts/create-bisaya-course-content.js @@ -806,7 +806,7 @@ function generateExercisesFromDidactics(lesson) { if (String(lesson.title || '').toLowerCase().includes('zeitformen')) { generated.push(...buildZeitformenDrills(lesson.title)); } - } else if (lesson.lessonType === 'review' || lesson.didacticMode === 'intensive_review') { + } else if (lesson.lessonType === 'review' || lesson.lessonType === 'weekly_review' || lesson.didacticMode === 'intensive_review') { generated = [ buildReviewChoiceExercise(lesson, didactics, patternA, lessonPool), buildReviewChoiceExercise(lesson, didactics, patternB, lessonPool), @@ -6188,8 +6188,8 @@ async function createBisayaCourseContent() { exerciseNumber, title: exercise.title, instruction: exercise.instruction, - questionData: JSON.stringify(exercise.questionData), - answerData: JSON.stringify(exercise.answerData), + questionData: exercise.questionData, + answerData: exercise.answerData, explanation: exercise.explanation, createdByUserId: course.ownerUserId || systemUser.id }; @@ -6266,8 +6266,8 @@ async function createBisayaCourseContent() { exerciseNumber: exerciseNumber++, title: exercise.title, instruction: exercise.instruction, - questionData: JSON.stringify(exercise.questionData), - answerData: JSON.stringify(exercise.answerData), + questionData: exercise.questionData, + answerData: exercise.answerData, explanation: exercise.explanation, createdByUserId: course.ownerUserId || systemUser.id }); diff --git a/backend/scripts/create-bisaya-course.js b/backend/scripts/create-bisaya-course.js index 17bec27..570812e 100755 --- a/backend/scripts/create-bisaya-course.js +++ b/backend/scripts/create-bisaya-course.js @@ -896,7 +896,7 @@ const LESSONS = [ targetMin: 20, targetScore: 85, review: true, cultural: null }, - { week: 1, day: 5, num: 9, type: 'review', title: 'Woche 1 - Wiederholung', + { week: 1, day: 5, num: 9, type: 'weekly_review', title: 'Woche 1 - Wiederholung', desc: 'Wiederhole alle Inhalte der ersten Woche', targetMin: 30, targetScore: 80, review: false, cultural: 'Wiederholung ist der Schlüssel zum Erfolg!' }, @@ -962,7 +962,7 @@ const LESSONS = [ targetMin: 18, targetScore: 85, review: true, cultural: null }, - { week: 2, day: 5, num: 22, type: 'review', title: 'Woche 2 - Wiederholung', + { week: 2, day: 5, num: 22, type: 'weekly_review', title: 'Woche 2 - Wiederholung', desc: 'Wiederhole alle Inhalte der zweiten Woche', targetMin: 30, targetScore: 80, review: false, cultural: null }, diff --git a/backend/scripts/create-language-courses.js b/backend/scripts/create-language-courses.js index 1cea1ae..5781fa0 100755 --- a/backend/scripts/create-language-courses.js +++ b/backend/scripts/create-language-courses.js @@ -66,7 +66,7 @@ const LESSON_TEMPLATE = [ targetMin: 20, targetScore: 85, review: true, cultural: null }, - { week: 1, day: 5, num: 9, type: 'review', title: 'Woche 1 - Wiederholung', + { week: 1, day: 5, num: 9, type: 'weekly_review', title: 'Woche 1 - Wiederholung', desc: 'Wiederhole alle Inhalte der ersten Woche', targetMin: 30, targetScore: 80, review: false, cultural: 'Wiederholung ist der Schlüssel zum Erfolg!' }, @@ -132,7 +132,7 @@ const LESSON_TEMPLATE = [ targetMin: 18, targetScore: 85, review: true, cultural: null }, - { week: 2, day: 5, num: 22, type: 'review', title: 'Woche 2 - Wiederholung', + { week: 2, day: 5, num: 22, type: 'weekly_review', title: 'Woche 2 - Wiederholung', desc: 'Wiederhole alle Inhalte der zweiten Woche', targetMin: 30, targetScore: 80, review: false, cultural: null }, @@ -183,7 +183,7 @@ const LESSON_TEMPLATE = [ targetMin: 20, targetScore: 85, review: true, cultural: null }, - { week: 3, day: 5, num: 32, type: 'review', title: 'Woche 3 - Wiederholung', + { week: 3, day: 5, num: 32, type: 'weekly_review', title: 'Woche 3 - Wiederholung', desc: 'Wiederhole alle Inhalte der dritten Woche', targetMin: 30, targetScore: 80, review: false, cultural: null }, diff --git a/backend/scripts/update-bisaya-didactics.js b/backend/scripts/update-bisaya-didactics.js index 98e0561..2df2732 100644 --- a/backend/scripts/update-bisaya-didactics.js +++ b/backend/scripts/update-bisaya-didactics.js @@ -41,6 +41,10 @@ export const LEGACY_DIDACTICS_TITLE_MAP = { 'Kulturelle Tipps & Tricks': 'Kultur: Höflichkeit, Familie, Alltag' }; +function isWeeklyReviewTitle(title) { + return /^Woche \d+ - (?:Wiederholung|Intensivwiederholung)(?:\s|$)/.test(String(title || '').trim()); +} + export const LESSON_DIDACTICS = { 'Begrüßungen & Höflichkeit': { learningGoals: [ @@ -535,9 +539,13 @@ async function updateBisayaDidactics() { }; const didactics = resolveDidacticsForLesson(lessonLike); const pedagogy = getBisayaLessonPedagogy(lessonLike.lessonNumber); - if (!didactics && !pedagogy) continue; + const weeklyReview = isWeeklyReviewTitle(row.title); + if (!didactics && !pedagogy && !weeklyReview) continue; const patch = {}; + if (weeklyReview) { + patch.lessonType = 'weekly_review'; + } if (didactics) { patch.learningGoals = didactics.learningGoals || []; patch.corePatterns = didactics.corePatterns || []; diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index b02246c..7cddebd 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -421,7 +421,7 @@ export default class VocabService { _supportsScheduledReview(lessonData = null) { const lessonType = String(lessonData?.lessonType || '').toLowerCase(); const didacticMode = String(lessonData?.didacticMode || '').toLowerCase(); - if (lessonType === 'culture' || lessonType === 'review' || lessonType === 'vocab_review') { + if (lessonType === 'culture' || lessonType === 'review' || lessonType === 'vocab_review' || lessonType === 'weekly_review') { return false; } if (didacticMode === 'intensive_review' || didacticMode === 'checkpoint') { @@ -1051,7 +1051,7 @@ export default class VocabService { if (title.includes('abschluss') || title.includes('prüfung') || title.includes('test')) { return 'checkpoint'; } - if (plainLesson.isIntensiveReview || lessonType === 'review' || lessonType === 'vocab_review' || title.includes('wiederholung')) { + if (plainLesson.isIntensiveReview || lessonType === 'review' || lessonType === 'vocab_review' || lessonType === 'weekly_review' || title.includes('wiederholung')) { return 'intensive_review'; } if (lessonType === 'grammar') { @@ -2663,7 +2663,7 @@ export default class VocabService { if (!plainLesson?.chapterId) { return list; } - if (plainLesson.lessonType === 'review' || plainLesson.lessonType === 'vocab_review') { + if (plainLesson.lessonType === 'review' || plainLesson.lessonType === 'vocab_review' || plainLesson.lessonType === 'weekly_review') { return list; } let rows = []; @@ -2899,13 +2899,47 @@ export default class VocabService { } }); + const isWeeklyReview = plainLesson.lessonType === 'weekly_review'; + // Lade Vokabeln aus vorherigen Lektionen (für Wiederholung UND für gemischten Vokabeltrainer) - if (plainLesson.lessonNumber > 1) { + if (plainLesson.lessonNumber > 1 && !isWeeklyReview) { plainLesson.previousLessonExercises = await this._getReviewVocabExercises(plainLesson.courseId, plainLesson.lessonNumber); } - // Bei Wiederholungslektionen: Auch Lektions-Liste für Anzeige - if (plainLesson.lessonType === 'review' || plainLesson.lessonType === 'vocab_review') { - plainLesson.reviewLessons = await this._getReviewLessons(plainLesson.courseId, plainLesson.lessonNumber); + if (isWeeklyReview) { + const weeklyLessons = await this._getReviewLessons( + plainLesson.courseId, + plainLesson.lessonNumber, + plainLesson.weekNumber + ); + const weeklyExercises = await this._getReviewVocabExercises( + plainLesson.courseId, + plainLesson.lessonNumber, + plainLesson.weekNumber + ); + plainLesson.reviewLessons = weeklyLessons; + plainLesson.previousLessonExercises = []; + plainLesson.weeklyReviewTrainingExercises = weeklyExercises; + plainLesson.reviewVocabExercises = this._selectWeeklyReviewExamExercises(weeklyExercises, plainLesson.id); + plainLesson.weeklyReviewExamCount = plainLesson.reviewVocabExercises.length; + plainLesson.weeklyReviewTrainingCount = weeklyExercises.length; + plainLesson.corePatterns = weeklyLessons.flatMap((entry) => { + if (Array.isArray(entry.corePatterns)) return entry.corePatterns; + if (typeof entry.corePatterns === 'string') { + try { + const parsed = JSON.parse(entry.corePatterns); + return Array.isArray(parsed) ? parsed : []; + } catch (error) { + return []; + } + } + return []; + }); + } else if (plainLesson.lessonType === 'review' || plainLesson.lessonType === 'vocab_review') { + // Kursweite/gezielt kuratierte Reviews behalten das bisherige Verhalten. + plainLesson.reviewLessons = await this._getReviewLessons( + plainLesson.courseId, + plainLesson.lessonNumber + ); plainLesson.reviewVocabExercises = plainLesson.previousLessonExercises || []; } @@ -3091,19 +3125,24 @@ export default class VocabService { /** * Sammelt alle Lektionen, die in einer Wiederholungslektion wiederholt werden sollen */ - async _getReviewLessons(courseId, currentLessonNumber) { - const lessons = await VocabCourseLesson.findAll({ - where: { - courseId: courseId, - lessonNumber: { - [Op.lt]: currentLessonNumber // Nur Lektionen mit kleinerer Nummer - }, - lessonType: { - [Op.notIn]: ['review', 'vocab_review'] // Keine anderen Wiederholungslektionen - } + async _getReviewLessons(courseId, currentLessonNumber, weekNumber = null) { + const where = { + courseId: courseId, + lessonNumber: { + [Op.lt]: currentLessonNumber // Nur Lektionen mit kleinerer Nummer }, + lessonType: { + [Op.notIn]: ['review', 'vocab_review', 'weekly_review'] // Keine anderen Wiederholungslektionen + } + }; + if (weekNumber != null) { + where.weekNumber = weekNumber; + } + + const lessons = await VocabCourseLesson.findAll({ + where, order: [['lessonNumber', 'ASC']], - attributes: ['id', 'lessonNumber', 'title'] + attributes: ['id', 'lessonNumber', 'title', 'corePatterns'] }); return lessons.map(l => l.get({ plain: true })); } @@ -3111,17 +3150,22 @@ export default class VocabService { /** * Sammelt alle Grammatik-Übungen aus vorherigen Lektionen für Wiederholungslektionen */ - async _getReviewVocabExercises(courseId, currentLessonNumber) { - const previousLessons = await VocabCourseLesson.findAll({ - where: { - courseId: courseId, - lessonNumber: { - [Op.lt]: currentLessonNumber - }, - lessonType: { - [Op.notIn]: ['review', 'vocab_review'] - } + async _getReviewVocabExercises(courseId, currentLessonNumber, weekNumber = null) { + const where = { + courseId: courseId, + lessonNumber: { + [Op.lt]: currentLessonNumber }, + lessonType: { + [Op.notIn]: ['review', 'vocab_review', 'weekly_review'] + } + }; + if (weekNumber != null) { + where.weekNumber = weekNumber; + } + + const previousLessons = await VocabCourseLesson.findAll({ + where, attributes: ['id'] }); @@ -3156,6 +3200,16 @@ export default class VocabService { return exercises.map(e => e.get({ plain: true })); } + _selectWeeklyReviewExamExercises(exercises = [], lessonId) { + const list = Array.isArray(exercises) ? exercises : []; + if (list.length === 0) return []; + + const seed = (Number(lessonId) * 100003) >>> 0; + const percentage = 40 + (seed % 21); + const targetCount = Math.max(1, Math.ceil((list.length * percentage) / 100)); + return this._seededShuffle(list.slice(), seed).slice(0, targetCount); + } + /** * Sammelt Grammatik‑Übungen aus vorherigen Lektionen derselben Woche */ @@ -3166,7 +3220,7 @@ export default class VocabService { courseId: courseId, weekNumber: weekNumber, lessonNumber: { [Op.lt]: currentLessonNumber }, - lessonType: { [Op.notIn]: ['review', 'vocab_review'] } + lessonType: { [Op.notIn]: ['review', 'vocab_review', 'weekly_review'] } }, attributes: ['id'] }); diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 7a6cdd8..20318f7 100644 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -1200,7 +1200,7 @@ export default { if (['dialogue', 'phrases', 'survival', 'grammar'].includes(lessonType)) { return 1.2; } - if (lessonType === 'review' || lessonType === 'vocab_review') { + if (lessonType === 'review' || lessonType === 'vocab_review' || lessonType === 'weekly_review') { return 0.9; } return 1; @@ -1216,7 +1216,7 @@ export default { }, trainerMinimumCurrentExposures() { const mode = this.lessonPedagogy?.didacticMode || this.lesson?.lessonType || ''; - if (mode === 'intensive_review' || mode === 'review' || mode === 'vocab_review') { + if (mode === 'intensive_review' || mode === 'review' || mode === 'vocab_review' || mode === 'weekly_review') { return 2; } if (['grammar', 'dialogue', 'phrases', 'survival'].includes(this.lesson?.lessonType)) { @@ -1243,8 +1243,8 @@ export default { } const lessonType = String(this.lesson?.lessonType || '').toLowerCase(); const didacticMode = String(this.lessonPedagogy?.didacticMode || '').toLowerCase(); - const isReviewLesson = ['review', 'vocab_review'].includes(lessonType) - || ['review', 'vocab_review', 'intensive_review'].includes(didacticMode); + const isReviewLesson = ['review', 'vocab_review', 'weekly_review'].includes(lessonType) + || ['review', 'vocab_review', 'weekly_review', 'intensive_review'].includes(didacticMode); if (isReviewLesson) { // In Wiederholungslektionen soll altes Material frueher und staerker einfliesen. @@ -1266,8 +1266,8 @@ export default { } const lessonType = String(this.lesson?.lessonType || '').toLowerCase(); const didacticMode = String(this.lessonPedagogy?.didacticMode || '').toLowerCase(); - const isReviewLesson = ['review', 'vocab_review'].includes(lessonType) - || ['review', 'vocab_review', 'intensive_review'].includes(didacticMode); + const isReviewLesson = ['review', 'vocab_review', 'weekly_review'].includes(lessonType) + || ['review', 'vocab_review', 'weekly_review', 'intensive_review'].includes(didacticMode); if (isReviewLesson) { return Math.max(0.25, 1 - this.currentReviewShare); @@ -1329,7 +1329,7 @@ export default { canAccessExercises() { if (!this.hasExercises) return false; if (this.exerciseNeedsReinforcement) return false; - const isReview = this.lesson?.lessonType === 'review' || this.lesson?.lessonType === 'vocab_review'; + const isReview = ['review', 'vocab_review', 'weekly_review'].includes(this.lesson?.lessonType); if (isReview) return true; if (this.trainableLessonVocab.length === 0 && this.prepItems.length > 0) { return this.lessonPrepStage >= 2; @@ -1358,7 +1358,7 @@ export default { /** Für Wiederholungslektionen: Übungen aus vorherigen Lektionen (Kapitelprüfung). Sonst: eigene Grammatik-Übungen. */ effectiveExercises() { if (!this.lesson) return []; - const isReview = this.lesson.lessonType === 'review' || this.lesson.lessonType === 'vocab_review'; + const isReview = ['review', 'vocab_review', 'weekly_review'].includes(this.lesson.lessonType); if (isReview && this.lesson.reviewVocabExercises && Array.isArray(this.lesson.reviewVocabExercises) && this.lesson.reviewVocabExercises.length > 0) { return this.lesson.reviewVocabExercises; } @@ -1504,9 +1504,10 @@ export default { importantVocab() { // Extrahiere wichtige Begriffe aus den Übungen try { - // Bei Wiederholungslektionen: Verwende Vokabeln aus vorherigen Lektionen (effectiveExercises = reviewVocabExercises) - // Normale Lektion: Verwende effectiveExercises (grammarExercises) - const exercises = this.effectiveExercises; + // Wochenwiederholungen trainieren die vollständige Wochenmenge; geprüft wird nur eine Teilmenge. + const exercises = this.lesson?.lessonType === 'weekly_review' + ? this.lesson.weeklyReviewTrainingExercises + : this.effectiveExercises; if (!exercises || !Array.isArray(exercises) || exercises.length === 0) { debugLog('[importantVocab] Keine Übungen vorhanden'); return []; @@ -3042,7 +3043,8 @@ export default { conversation: this.$t('socialnetwork.vocab.courses.lessonTypeConversation'), culture: this.$t('socialnetwork.vocab.courses.lessonTypeCulture'), review: this.$t('socialnetwork.vocab.courses.lessonTypeReview'), - vocab_review: this.$t('socialnetwork.vocab.courses.lessonTypeReview') + vocab_review: this.$t('socialnetwork.vocab.courses.lessonTypeReview'), + weekly_review: this.$t('socialnetwork.vocab.courses.lessonTypeReview') }; return labels[lessonType] || lessonType || this.$t('socialnetwork.vocab.courses.lessonTypeVocab'); }, @@ -3869,8 +3871,8 @@ export default { const lessonType = String(this.lesson?.lessonType || '').toLowerCase(); const didacticMode = String(this.lessonPedagogy?.didacticMode || '').toLowerCase(); - const isReviewLesson = ['review', 'vocab_review'].includes(lessonType) - || ['review', 'vocab_review', 'intensive_review'].includes(didacticMode); + const isReviewLesson = ['review', 'vocab_review', 'weekly_review'].includes(lessonType) + || ['review', 'vocab_review', 'weekly_review', 'intensive_review'].includes(didacticMode); // Reviews sollen nicht nur aus Multiple Choice bestehen: // früherer Wechsel zu Typing, damit aktiver Abruf im Vordergrund steht. const switchAfterAttempts = isReviewLesson