feat(bisaya): update choice exercise question format and add question number to review
This commit is contained in:
@@ -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.
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -529,6 +529,14 @@ function buildChoiceExercise(lesson, didactics, pattern, allPatterns, variant =
|
|||||||
const seed = simpleHash(`${lesson.title}:${pattern}:${variant}`);
|
const seed = simpleHash(`${lesson.title}:${pattern}:${variant}`);
|
||||||
const options = rotateArray([pattern, ...distractors], seed % 4);
|
const options = rotateArray([pattern, ...distractors], seed % 4);
|
||||||
const correctAnswer = options.indexOf(pattern);
|
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 {
|
return {
|
||||||
exerciseTypeId: 2,
|
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.',
|
instruction: 'Wähle die natürlichste Formulierung für die Situation oder den Schwerpunkt der Lektion.',
|
||||||
questionData: {
|
questionData: {
|
||||||
type: 'multiple_choice',
|
type: 'multiple_choice',
|
||||||
question: getChoiceQuestion(lesson, didactics),
|
question,
|
||||||
options
|
options
|
||||||
},
|
},
|
||||||
answerData: {
|
answerData: {
|
||||||
|
|||||||
@@ -1045,9 +1045,9 @@
|
|||||||
<div v-if="chapterExamFailedDetails.length > 0" class="exam-failed-review">
|
<div v-if="chapterExamFailedDetails.length > 0" class="exam-failed-review">
|
||||||
<h4>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedReviewTitle') }}</h4>
|
<h4>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedReviewTitle') }}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(item, index) in chapterExamFailedDetails" :key="`failed-${index}-${item.id}`">
|
<li v-for="item in chapterExamFailedDetails" :key="`failed-${item.id}`">
|
||||||
<p class="exam-failed-review__question">
|
<p class="exam-failed-review__question">
|
||||||
<strong>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedQuestionLabel') }} {{ index + 1 }}:</strong>
|
<strong>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedQuestionLabel') }} {{ item.questionNumber }}:</strong>
|
||||||
{{ item.question }}
|
{{ item.question }}
|
||||||
</p>
|
</p>
|
||||||
<p class="exam-failed-review__answer">
|
<p class="exam-failed-review__answer">
|
||||||
@@ -2450,6 +2450,7 @@ export default {
|
|||||||
const userAnswerRaw = this.exerciseAnswers[exercise.id];
|
const userAnswerRaw = this.exerciseAnswers[exercise.id];
|
||||||
out.push({
|
out.push({
|
||||||
id: exercise.id,
|
id: exercise.id,
|
||||||
|
questionNumber: all.findIndex((candidate) => candidate.id === exercise.id) + 1,
|
||||||
question,
|
question,
|
||||||
userAnswer: this.formatExerciseAnswerForReview(exercise, userAnswerRaw),
|
userAnswer: this.formatExerciseAnswerForReview(exercise, userAnswerRaw),
|
||||||
correctAnswer: String(result.correctAnswer || '').trim()
|
correctAnswer: String(result.correctAnswer || '').trim()
|
||||||
|
|||||||
Reference in New Issue
Block a user