From b8b45cd5ba0d6f64a26dd38dbb2fd98517366b23 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 24 Aug 2026 15:18:36 +0200 Subject: [PATCH] feat(bisaya): update choice exercise question format and add question number to review --- ...824000000-repair-lesson-38-plan-choice.cjs | 40 +++++++++++++++++++ .../scripts/create-bisaya-course-content.js | 10 ++++- frontend/src/views/social/VocabLessonView.vue | 5 ++- 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 backend/migrations-active/20260824000000-repair-lesson-38-plan-choice.cjs diff --git a/backend/migrations-active/20260824000000-repair-lesson-38-plan-choice.cjs b/backend/migrations-active/20260824000000-repair-lesson-38-plan-choice.cjs new file mode 100644 index 0000000..efbb673 --- /dev/null +++ b/backend/migrations-active/20260824000000-repair-lesson-38-plan-choice.cjs @@ -0,0 +1,40 @@ +'use strict'; + +const AMBIGUOUS_QUESTION = 'Erzähle einen Tagesplan mit drei Schritten und einem Wunsch. Welche Bisaya-Formulierung passt am besten?'; +const REPAIRED_QUESTION = 'Wie sagt man auf Bisaya: „Zuerst gehe ich zum Markt.“?'; + +module.exports = { + async up(queryInterface) { + await queryInterface.sequelize.transaction(async (transaction) => { + // The former free-production prompt has more than one defensible answer, + // while its multiple-choice data accepts only the first core pattern. + await queryInterface.sequelize.query(` + UPDATE community.vocab_grammar_exercise AS exercise + SET question_data = JSONB_SET(exercise.question_data, '{question}', TO_JSONB(CAST(:repairedQuestion AS text))), + updated_at = NOW() + FROM community.vocab_course_lesson AS lesson + WHERE exercise.lesson_id = lesson.id + AND (lesson.id = 38 OR (lesson.course_id = 1 AND lesson.lesson_number = 38)) + AND exercise.question_data->>'type' = 'multiple_choice' + AND exercise.question_data->>'question' = :ambiguousQuestion; + `, { + replacements: { ambiguousQuestion: AMBIGUOUS_QUESTION, repairedQuestion: REPAIRED_QUESTION }, + transaction + }); + + // This lesson is designed for a 78% target. With six questions, five + // correct answers (83%) must therefore pass. + await queryInterface.sequelize.query(` + UPDATE community.vocab_course_lesson + SET target_score_percent = 78, + updated_at = NOW() + WHERE id = 38 + OR (course_id = 1 AND lesson_number = 38); + `, { transaction }); + }); + }, + + async down() { + // The repair is intentionally not reverted: the old data is ambiguous. + } +}; diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js index c13d43b..6d3e83f 100755 --- a/backend/scripts/create-bisaya-course-content.js +++ b/backend/scripts/create-bisaya-course-content.js @@ -529,6 +529,14 @@ function buildChoiceExercise(lesson, didactics, pattern, allPatterns, variant = const seed = simpleHash(`${lesson.title}:${pattern}:${variant}`); const options = rotateArray([pattern, ...distractors], seed % 4); const correctAnswer = options.indexOf(pattern); + const normalizedPattern = normalizeCorePatternEntry( + didactics.corePatterns.find((entry) => corePatternTarget(entry) === pattern) + ); + // A free speaking prompt can have several valid answers. It must never be + // used as the question for a single-answer multiple-choice exercise. + const question = normalizedPattern?.gloss + ? `Wie sagt man auf Bisaya: „${normalizedPattern.gloss}“?` + : getChoiceQuestion(lesson, didactics); return { exerciseTypeId: 2, @@ -536,7 +544,7 @@ function buildChoiceExercise(lesson, didactics, pattern, allPatterns, variant = instruction: 'Wähle die natürlichste Formulierung für die Situation oder den Schwerpunkt der Lektion.', questionData: { type: 'multiple_choice', - question: getChoiceQuestion(lesson, didactics), + question, options }, answerData: { diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 7c2c6d5..b961cd5 100755 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -1045,9 +1045,9 @@

{{ $t('socialnetwork.vocab.courses.exerciseExamFailedReviewTitle') }}