quiz-generator.js•24.2 kB
/**
* Intelligent Quiz Generator
* @module content-generation/quiz-generator
* @description Generates contextually relevant quiz questions from educational content
* @version 5.0.0-alpha
*/
export class QuizGenerator {
constructor(config = {}) {
this.config = {
minQuestions: 5,
maxQuestions: 15,
targetQuestions: 10,
questionTypes: {
multiple_choice: 0.5,
true_false: 0.2,
fill_blank: 0.15,
short_answer: 0.1,
matching: 0.05
},
difficultyBalance: { easy: 0.3, medium: 0.5, hard: 0.2 },
...config
};
// Question type templates and generators
this.questionGenerators = {
multiple_choice: this.generateMultipleChoice.bind(this),
true_false: this.generateTrueFalse.bind(this),
fill_blank: this.generateFillBlank.bind(this),
short_answer: this.generateShortAnswer.bind(this),
matching: this.generateMatching.bind(this)
};
}
/**
* Generate comprehensive quiz from content
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @returns {Promise<Object>} Generated quiz
*/
async generateQuiz(content, topicAnalysis) {
console.log('[QUIZ-GENERATOR] Generating quiz for:', topicAnalysis.subject);
try {
// Calculate question distribution
const distribution = this.calculateQuestionDistribution(topicAnalysis);
// Generate questions by type
const questions = [];
for (const [type, count] of Object.entries(distribution)) {
const generator = this.questionGenerators[type];
if (generator && count > 0) {
const typeQuestions = await generator(content, topicAnalysis, count);
questions.push(...typeQuestions);
}
}
// Optimize question set
const optimized = this.optimizeQuestionSet(questions);
// Create quiz structure
const quiz = {
metadata: this.generateQuizMetadata(topicAnalysis),
questions: optimized,
instructions: this.generateInstructions(topicAnalysis),
scoring: this.generateScoringSystem(optimized),
analytics: this.initializeQuizAnalytics()
};
console.log(`[QUIZ-GENERATOR] Generated ${quiz.questions.length} questions`);
return quiz;
} catch (error) {
console.error('[QUIZ-GENERATOR] Quiz generation failed:', error);
throw new Error(`Quiz generation failed: ${error.message}`);
}
}
/**
* Calculate optimal question distribution
* @param {Object} topicAnalysis - Topic analysis
* @returns {Object} Question type distribution
*/
calculateQuestionDistribution(topicAnalysis) {
const totalQuestions = Math.min(
this.config.maxQuestions,
Math.max(this.config.minQuestions, this.config.targetQuestions)
);
const distribution = {};
Object.entries(this.config.questionTypes).forEach(([type, ratio]) => {
const count = Math.round(totalQuestions * ratio);
if (count > 0) {
distribution[type] = count;
}
});
// Ensure minimum total
const total = Object.values(distribution).reduce((sum, count) => sum + count, 0);
if (total < this.config.minQuestions) {
distribution.multiple_choice = (distribution.multiple_choice || 0) +
(this.config.minQuestions - total);
}
return distribution;
}
/**
* Generate multiple choice questions
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @param {number} count - Number of questions to generate
* @returns {Promise<Array>} Multiple choice questions
*/
async generateMultipleChoice(content, topicAnalysis, count) {
const questions = [];
const { concepts, keywords, subject } = topicAnalysis;
// Generate concept-based questions
const conceptQuestions = Math.ceil(count * 0.6);
for (let i = 0; i < Math.min(conceptQuestions, concepts.length); i++) {
const concept = concepts[i];
questions.push({
id: `mc-concept-${i}-${Date.now()}`,
type: 'multiple_choice',
category: 'concept',
question: `Qual é a definição mais adequada para ${concept}?`,
options: this.generateConceptOptions(concept, subject),
correct: 0,
difficulty: 'easy',
explanation: this.generateConceptExplanation(concept, subject),
tags: [concept, 'definição', 'conceito'],
source: 'concept_analysis'
});
}
// Generate application questions
const appQuestions = Math.floor(count * 0.4);
for (let i = 0; i < appQuestions; i++) {
const question = this.generateApplicationQuestion(topicAnalysis, i);
questions.push(question);
}
return questions.slice(0, count);
}
/**
* Generate true/false questions
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @param {number} count - Number of questions to generate
* @returns {Promise<Array>} True/false questions
*/
async generateTrueFalse(content, topicAnalysis, count) {
const questions = [];
const { concepts, subject } = topicAnalysis;
for (let i = 0; i < count && i < concepts.length; i++) {
const concept = concepts[i];
const isTrue = Math.random() > 0.5;
questions.push({
id: `tf-${i}-${Date.now()}`,
type: 'true_false',
category: 'verification',
question: this.generateTrueFalseStatement(concept, subject, isTrue),
correct: isTrue,
difficulty: 'easy',
explanation: this.generateTrueFalseExplanation(concept, subject, isTrue),
tags: [concept, 'verificação', 'verdadeiro-falso'],
source: 'statement_verification'
});
}
return questions;
}
/**
* Generate fill-in-the-blank questions
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @param {number} count - Number of questions to generate
* @returns {Promise<Array>} Fill-in-the-blank questions
*/
async generateFillBlank(content, topicAnalysis, count) {
const questions = [];
const { keywords, subject } = topicAnalysis;
for (let i = 0; i < count && i < keywords.length; i++) {
const keyword = keywords[i];
const sentence = this.generateSentenceWithBlank(keyword, subject);
questions.push({
id: `fb-${i}-${Date.now()}`,
type: 'fill_blank',
category: 'completion',
question: sentence.question,
answer: sentence.answer,
difficulty: 'medium',
hints: this.generateFillBlankHints(keyword, subject),
tags: [keyword, 'completar', 'lacuna'],
source: 'sentence_completion'
});
}
return questions;
}
/**
* Generate short answer questions
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @param {number} count - Number of questions to generate
* @returns {Promise<Array>} Short answer questions
*/
async generateShortAnswer(content, topicAnalysis, count) {
const questions = [];
const { subject } = topicAnalysis;
const prompts = this.getSubjectSpecificPrompts(subject);
for (let i = 0; i < count && i < prompts.length; i++) {
const prompt = prompts[i];
questions.push({
id: `sa-${i}-${Date.now()}`,
type: 'short_answer',
category: 'explanation',
question: prompt.question,
expectedElements: prompt.elements,
difficulty: 'hard',
rubric: prompt.rubric,
tags: ['explicação', 'resposta curta', subject],
source: 'subject_prompts'
});
}
return questions;
}
/**
* Generate matching questions
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @param {number} count - Number of questions to generate
* @returns {Promise<Array>} Matching questions
*/
async generateMatching(content, topicAnalysis, count) {
const questions = [];
const { concepts, keywords, subject } = topicAnalysis;
if (concepts.length >= 4) {
const pairs = this.generateMatchingPairs(concepts, subject);
questions.push({
id: `match-${Date.now()}`,
type: 'matching',
category: 'association',
question: 'Associe os conceitos com suas definições correspondentes:',
leftColumn: pairs.map(pair => pair.term),
rightColumn: this.shuffleArray(pairs.map(pair => pair.definition)),
correctPairs: pairs.map((pair, index) => ({
left: index,
right: pairs.map(p => p.definition).indexOf(pair.definition)
})),
difficulty: 'medium',
instructions: 'Associe cada termo da coluna esquerda com sua definição na coluna direita',
tags: ['associação', 'correspondência', subject],
source: 'concept_matching'
});
}
return questions;
}
/**
* Generate concept options for multiple choice
* @param {string} concept - Target concept
* @param {string} subject - Subject area
* @returns {Array} Answer options
*/
generateConceptOptions(concept, subject) {
const correctDefinition = this.generateConceptDefinition(concept, subject);
const distractors = this.generateDistractors(concept, subject, 3);
const options = [correctDefinition, ...distractors];
return this.shuffleArray(options);
}
/**
* Generate concept definition
* @param {string} concept - Concept to define
* @param {string} subject - Subject area
* @returns {string} Definition
*/
generateConceptDefinition(concept, subject) {
const definitions = {
física: `${concept} é um conceito fundamental da física que descreve propriedades ou fenômenos relacionados à matéria, energia e suas interações.`,
química: `${concept} refere-se a aspectos químicos relacionados à estrutura, propriedades e transformações da matéria através de ligações e reações.`,
história: `${concept} é um elemento histórico importante para compreender eventos, processos sociais e transformações ao longo do tempo.`,
matemática: `${concept} é um conceito matemático que representa relações quantitativas, estruturas lógicas e padrões numéricos.`,
geral: `${concept} é um conceito importante para compreender os aspectos fundamentais do tópico estudado.`
};
return definitions[subject] || definitions.geral;
}
/**
* Generate distractors for multiple choice
* @param {string} concept - Target concept
* @param {string} subject - Subject area
* @param {number} count - Number of distractors
* @returns {Array} Distractor options
*/
generateDistractors(concept, subject, count) {
const distractors = [];
// Common distractor patterns
const patterns = [
`${concept} é apenas um termo técnico sem aplicação prática específica.`,
`${concept} se refere exclusivamente a aspectos teóricos sem base científica.`,
`${concept} é um conceito obsoleto que não se aplica aos estudos modernos.`,
`${concept} representa apenas uma opinião pessoal sobre o assunto.`
];
for (let i = 0; i < count && i < patterns.length; i++) {
distractors.push(patterns[i]);
}
return distractors;
}
/**
* Generate application question
* @param {Object} topicAnalysis - Topic analysis
* @param {number} index - Question index
* @returns {Object} Application question
*/
generateApplicationQuestion(topicAnalysis, index) {
const { subject } = topicAnalysis;
const applications = {
física: {
question: 'Em qual situação cotidiana podemos observar o conceito estudado?',
options: [
'Ao empurrar um objeto pesado',
'Ao observar uma pintura',
'Ao ler um livro',
'Ao ouvir música'
],
correct: 0
},
química: {
question: 'Qual processo químico melhor exemplifica o conceito abordado?',
options: [
'Digestão dos alimentos',
'Leitura de um texto',
'Escrita de uma carta',
'Contagem de objetos'
],
correct: 0
},
história: {
question: 'Qual evento histórico melhor demonstra a aplicação deste conceito?',
options: [
'Revolução Francesa',
'Invenção do telefone',
'Descoberta de um mineral',
'Criação de uma obra de arte'
],
correct: 0
},
geral: {
question: 'Como este conceito se aplica na prática?',
options: [
'Através de exemplos cotidianos relevantes',
'Apenas em situações teóricas',
'Somente em laboratórios',
'Nunca se aplica na realidade'
],
correct: 0
}
};
const template = applications[subject] || applications.geral;
return {
id: `mc-app-${index}-${Date.now()}`,
type: 'multiple_choice',
category: 'application',
question: template.question,
options: template.options,
correct: template.correct,
difficulty: 'medium',
explanation: 'Esta aplicação demonstra como o conceito se manifesta em situações práticas.',
tags: ['aplicação', 'prático', subject],
source: 'application_scenarios'
};
}
/**
* Generate true/false statement
* @param {string} concept - Concept
* @param {string} subject - Subject
* @param {boolean} isTrue - Whether statement should be true
* @returns {string} Statement
*/
generateTrueFalseStatement(concept, subject, isTrue) {
if (isTrue) {
return `${concept} é um conceito importante para compreender ${subject}.`;
} else {
return `${concept} não tem relação alguma com o estudo de ${subject}.`;
}
}
/**
* Generate sentence with blank for fill-in question
* @param {string} keyword - Keyword to blank out
* @param {string} subject - Subject area
* @returns {Object} Question and answer
*/
generateSentenceWithBlank(keyword, subject) {
const sentence = `O conceito de __________ é fundamental para compreender ${subject}.`;
return {
question: sentence,
answer: keyword
};
}
/**
* Generate subject-specific prompts
* @param {string} subject - Subject area
* @returns {Array} Prompt objects
*/
getSubjectSpecificPrompts(subject) {
const prompts = {
física: [
{
question: 'Explique como as leis da física se aplicam no cotidiano.',
elements: ['leis físicas', 'aplicação prática', 'exemplos'],
rubric: 'Deve mencionar leis específicas e dar exemplos práticos'
},
{
question: 'Descreva a relação entre energia e movimento.',
elements: ['energia', 'movimento', 'transformação'],
rubric: 'Deve explicar tipos de energia e como se relacionam com movimento'
}
],
química: [
{
question: 'Explique como as reações químicas afetam nossa vida diária.',
elements: ['reações químicas', 'vida cotidiana', 'exemplos'],
rubric: 'Deve citar reações específicas e sua importância'
},
{
question: 'Descreva a importância das ligações químicas.',
elements: ['ligações químicas', 'propriedades', 'estrutura'],
rubric: 'Deve explicar tipos de ligações e como afetam propriedades'
}
],
história: [
{
question: 'Analise as causas e consequências do evento histórico estudado.',
elements: ['causas', 'consequências', 'contexto histórico'],
rubric: 'Deve identificar múltiplas causas e consequências de longo prazo'
},
{
question: 'Compare diferentes interpretações históricas do período estudado.',
elements: ['interpretações', 'perspectivas', 'evidências'],
rubric: 'Deve apresentar diferentes pontos de vista e evidências'
}
],
geral: [
{
question: 'Explique a importância do conceito estudado.',
elements: ['conceito', 'importância', 'aplicação'],
rubric: 'Deve demonstrar compreensão e relevância'
}
]
};
return prompts[subject] || prompts.geral;
}
/**
* Generate matching pairs
* @param {Array} concepts - List of concepts
* @param {string} subject - Subject area
* @returns {Array} Matching pairs
*/
generateMatchingPairs(concepts, subject) {
return concepts.slice(0, 4).map(concept => ({
term: concept,
definition: this.generateConceptDefinition(concept, subject)
}));
}
/**
* Optimize question set for quality and balance
* @param {Array} questions - Raw questions
* @returns {Array} Optimized questions
*/
optimizeQuestionSet(questions) {
// Remove duplicates
const unique = this.removeDuplicateQuestions(questions);
// Balance difficulty
const balanced = this.balanceQuestionDifficulty(unique);
// Ensure variety
const varied = this.ensureQuestionVariety(balanced);
// Sort by importance
const sorted = this.sortQuestionsByImportance(varied);
return sorted;
}
/**
* Remove duplicate questions
* @param {Array} questions - Questions array
* @returns {Array} Unique questions
*/
removeDuplicateQuestions(questions) {
const seen = new Set();
return questions.filter(question => {
const key = question.question;
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
}
/**
* Balance question difficulty
* @param {Array} questions - Questions array
* @returns {Array} Balanced questions
*/
balanceQuestionDifficulty(questions) {
const byDifficulty = {
easy: questions.filter(q => q.difficulty === 'easy'),
medium: questions.filter(q => q.difficulty === 'medium'),
hard: questions.filter(q => q.difficulty === 'hard')
};
const target = Math.min(this.config.maxQuestions, questions.length);
const balanced = [];
const easyCount = Math.floor(target * this.config.difficultyBalance.easy);
const mediumCount = Math.floor(target * this.config.difficultyBalance.medium);
const hardCount = Math.floor(target * this.config.difficultyBalance.hard);
balanced.push(...byDifficulty.easy.slice(0, easyCount));
balanced.push(...byDifficulty.medium.slice(0, mediumCount));
balanced.push(...byDifficulty.hard.slice(0, hardCount));
return balanced;
}
/**
* Ensure question variety
* @param {Array} questions - Questions array
* @returns {Array} Varied questions
*/
ensureQuestionVariety(questions) {
const byType = {};
questions.forEach(question => {
if (!byType[question.type]) {
byType[question.type] = [];
}
byType[question.type].push(question);
});
// Ensure we have at least one of each available type
const varied = [];
Object.values(byType).forEach(typeQuestions => {
varied.push(...typeQuestions);
});
return varied;
}
/**
* Sort questions by importance
* @param {Array} questions - Questions array
* @returns {Array} Sorted questions
*/
sortQuestionsByImportance(questions) {
return questions.sort((a, b) => {
// Priority: concept > application > verification > completion > association
const typeOrder = {
concept: 0,
application: 1,
verification: 2,
completion: 3,
association: 4,
explanation: 5
};
const aOrder = typeOrder[a.category] || 6;
const bOrder = typeOrder[b.category] || 6;
if (aOrder !== bOrder) {
return aOrder - bOrder;
}
// Secondary sort by difficulty (easy first for learning progression)
const difficultyOrder = { easy: 0, medium: 1, hard: 2 };
return (difficultyOrder[a.difficulty] || 1) - (difficultyOrder[b.difficulty] || 1);
});
}
/**
* Generate quiz metadata
* @param {Object} topicAnalysis - Topic analysis
* @returns {Object} Quiz metadata
*/
generateQuizMetadata(topicAnalysis) {
return {
subject: topicAnalysis.subject,
gradeLevel: topicAnalysis.gradeLevel,
estimatedDuration: '10-15 minutos',
totalQuestions: 0, // Will be updated after generation
questionTypes: Object.keys(this.config.questionTypes),
generatedAt: new Date().toISOString(),
version: '5.0.0-alpha'
};
}
/**
* Generate quiz instructions
* @param {Object} topicAnalysis - Topic analysis
* @returns {Array} Instructions
*/
generateInstructions(topicAnalysis) {
return [
'Leia cada questão cuidadosamente antes de responder.',
'Para questões de múltipla escolha, selecione apenas uma alternativa.',
'Para questões verdadeiro/falso, marque V ou F conforme apropriado.',
'Para questões de completar, escreva a palavra ou frase que melhor completa a sentença.',
'Para questões dissertativas, seja claro e objetivo em suas respostas.',
'Revise suas respostas antes de finalizar.'
];
}
/**
* Generate scoring system
* @param {Array} questions - Quiz questions
* @returns {Object} Scoring system
*/
generateScoringSystem(questions) {
const totalPoints = questions.length * 10; // 10 points per question
return {
totalPoints: totalPoints,
passingScore: Math.floor(totalPoints * 0.7), // 70% to pass
pointsPerQuestion: 10,
gradingScale: {
'Excelente': { min: 90, max: 100 },
'Bom': { min: 80, max: 89 },
'Satisfatório': { min: 70, max: 79 },
'Insuficiente': { min: 0, max: 69 }
}
};
}
/**
* Initialize quiz analytics
* @returns {Object} Analytics initialization
*/
initializeQuizAnalytics() {
return {
generationTime: Date.now(),
questionTypeDistribution: {},
difficultyDistribution: {},
estimatedCompletionTime: null,
qualityScore: null
};
}
/**
* Utility function to shuffle array
* @param {Array} array - Array to shuffle
* @returns {Array} Shuffled array
*/
shuffleArray(array) {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
/**
* Generate concept explanation
* @param {string} concept - Concept
* @param {string} subject - Subject
* @returns {string} Explanation
*/
generateConceptExplanation(concept, subject) {
return `${concept} é fundamental para compreender ${subject} porque estabelece as bases conceituais necessárias para o aprendizado.`;
}
/**
* Generate true/false explanation
* @param {string} concept - Concept
* @param {string} subject - Subject
* @param {boolean} isTrue - Whether statement is true
* @returns {string} Explanation
*/
generateTrueFalseExplanation(concept, subject, isTrue) {
if (isTrue) {
return `Correto. ${concept} é realmente importante para compreender ${subject}.`;
} else {
return `Falso. ${concept} tem relação direta com o estudo de ${subject}.`;
}
}
/**
* Generate fill blank hints
* @param {string} keyword - Keyword
* @param {string} subject - Subject
* @returns {Array} Hints
*/
generateFillBlankHints(keyword, subject) {
return [
`Esta palavra está relacionada a ${subject}`,
`Pense nos conceitos principais do tópico`,
`A resposta tem ${keyword.length} letras`
];
}
}
// Factory function for creating QuizGenerator instances
export function createQuizGenerator(config = {}) {
return new QuizGenerator(config);
}