flashcard-generator.js•21.8 kB
/**
* Intelligent Flashcard Generator
* @module content-generation/flashcard-generator
* @description Generates contextually relevant flashcards from educational content
* @version 5.0.0-alpha
*/
export class FlashcardGenerator {
constructor(config = {}) {
this.config = {
minFlashcards: 8,
maxFlashcards: 25,
targetFlashcards: 15,
difficultyBalance: { easy: 0.4, medium: 0.4, hard: 0.2 },
typeBalance: { concept: 0.4, example: 0.3, application: 0.2, relationship: 0.1 },
...config
};
// Flashcard type templates
this.flashcardTypes = {
concept: {
template: 'definition',
difficulty: 'easy',
examples: ['Conceito → Definição', 'Termo → Explicação']
},
example: {
template: 'illustration',
difficulty: 'medium',
examples: ['Conceito → Exemplo prático', 'Princípio → Aplicação']
},
application: {
template: 'scenario',
difficulty: 'medium',
examples: ['Situação → Solução', 'Problema → Método']
},
relationship: {
template: 'connection',
difficulty: 'hard',
examples: ['Causa → Efeito', 'Conceito A → Relação → Conceito B']
}
};
}
/**
* Generate comprehensive flashcard set from content
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @returns {Promise<Array>} Generated flashcards
*/
async generateFlashcards(content, topicAnalysis) {
console.log('[FLASHCARD-GENERATOR] Generating flashcards for:', topicAnalysis.subject);
try {
const flashcards = [];
// Extract concept flashcards
flashcards.push(...await this.extractConceptCards(content, topicAnalysis));
// Generate example flashcards
flashcards.push(...await this.generateExampleCards(content, topicAnalysis));
// Create application flashcards
flashcards.push(...await this.createApplicationCards(content, topicAnalysis));
// Build relationship flashcards
flashcards.push(...await this.buildRelationshipCards(content, topicAnalysis));
// Optimize flashcard set
const optimized = this.optimizeFlashcardSet(flashcards);
// Add metadata
const withMetadata = this.addFlashcardMetadata(optimized, topicAnalysis);
console.log(`[FLASHCARD-GENERATOR] Generated ${withMetadata.length} flashcards`);
return withMetadata;
} catch (error) {
console.error('[FLASHCARD-GENERATOR] Flashcard generation failed:', error);
throw new Error(`Flashcard generation failed: ${error.message}`);
}
}
/**
* Extract concept flashcards from content
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @returns {Promise<Array>} Concept flashcards
*/
async extractConceptCards(content, topicAnalysis) {
const conceptCards = [];
const { keywords, concepts } = topicAnalysis;
// Generate concept definition cards
const allConcepts = [...new Set([...keywords, ...concepts])];
const targetCount = Math.floor(this.config.targetFlashcards * this.config.typeBalance.concept);
for (let i = 0; i < Math.min(targetCount, allConcepts.length); i++) {
const concept = allConcepts[i];
const definition = this.generateConceptDefinition(concept, topicAnalysis);
conceptCards.push({
id: `concept-${i}-${Date.now()}`,
type: 'concept',
cardType: 'definition',
front: `O que é ${concept}?`,
back: definition,
concept: concept,
difficulty: 'easy',
tags: [concept, 'conceito', 'definição'],
source: 'content_extraction'
});
}
// Generate key term cards from content
if (content.components.explanation) {
const keyTerms = this.extractKeyTerms(content.components.explanation.content);
keyTerms.slice(0, 3).forEach((term, index) => {
conceptCards.push({
id: `term-${index}-${Date.now()}`,
type: 'concept',
cardType: 'term',
front: `Defina: ${term}`,
back: this.generateTermDefinition(term, content.components.explanation.content),
concept: term,
difficulty: 'easy',
tags: [term, 'termo', 'vocabulário'],
source: 'term_extraction'
});
});
}
return conceptCards;
}
/**
* Generate example flashcards
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @returns {Promise<Array>} Example flashcards
*/
async generateExampleCards(content, topicAnalysis) {
const exampleCards = [];
const targetCount = Math.floor(this.config.targetFlashcards * this.config.typeBalance.example);
// Use examples from content
if (content.components.examples && content.components.examples.examples) {
const examples = content.components.examples.examples;
examples.slice(0, targetCount).forEach((example, index) => {
exampleCards.push({
id: `example-${index}-${Date.now()}`,
type: 'example',
cardType: 'illustration',
front: `Dê um exemplo de ${topicAnalysis.subject}`,
back: example,
difficulty: 'medium',
tags: ['exemplo', 'prático', topicAnalysis.subject],
source: 'content_examples'
});
});
}
// Generate subject-specific examples
const subjectExamples = this.generateSubjectExamples(topicAnalysis);
subjectExamples.slice(0, Math.max(0, targetCount - exampleCards.length)).forEach((example, index) => {
exampleCards.push({
id: `subject-example-${index}-${Date.now()}`,
type: 'example',
cardType: 'subject_specific',
front: example.front,
back: example.back,
difficulty: 'medium',
tags: ['exemplo', topicAnalysis.subject, 'específico'],
source: 'subject_generation'
});
});
return exampleCards;
}
/**
* Create application flashcards
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @returns {Promise<Array>} Application flashcards
*/
async createApplicationCards(content, topicAnalysis) {
const applicationCards = [];
const targetCount = Math.floor(this.config.targetFlashcards * this.config.typeBalance.application);
// Generate problem-solution cards
const problems = this.generateProblemScenarios(topicAnalysis);
problems.slice(0, targetCount).forEach((problem, index) => {
applicationCards.push({
id: `application-${index}-${Date.now()}`,
type: 'application',
cardType: 'scenario',
front: problem.scenario,
back: problem.solution,
difficulty: 'medium',
tags: ['aplicação', 'problema', 'solução'],
source: 'scenario_generation'
});
});
return applicationCards;
}
/**
* Build relationship flashcards
* @param {Object} content - Educational content
* @param {Object} topicAnalysis - Topic analysis
* @returns {Promise<Array>} Relationship flashcards
*/
async buildRelationshipCards(content, topicAnalysis) {
const relationshipCards = [];
const targetCount = Math.floor(this.config.targetFlashcards * this.config.typeBalance.relationship);
// Generate concept relationship cards
const relationships = this.identifyConceptRelationships(topicAnalysis);
relationships.slice(0, targetCount).forEach((relationship, index) => {
relationshipCards.push({
id: `relationship-${index}-${Date.now()}`,
type: 'relationship',
cardType: 'connection',
front: `Como ${relationship.concept1} se relaciona com ${relationship.concept2}?`,
back: relationship.explanation,
difficulty: 'hard',
tags: ['relação', 'conexão', relationship.concept1, relationship.concept2],
source: 'relationship_analysis'
});
});
return relationshipCards;
}
/**
* Generate concept definition
* @param {string} concept - Concept to define
* @param {Object} topicAnalysis - Topic analysis
* @returns {string} Concept definition
*/
generateConceptDefinition(concept, topicAnalysis) {
const { subject } = topicAnalysis;
const definitions = {
física: `${concept} é um conceito fundamental da física que descreve propriedades ou fenômenos relacionados à matéria e energia.`,
química: `${concept} refere-se a aspectos químicos relacionados à estrutura, propriedades e transformações da matéria.`,
história: `${concept} é um elemento histórico importante para compreender eventos, processos e transformações sociais.`,
matemática: `${concept} é um conceito matemático que representa relações quantitativas e estruturas lógicas.`,
geral: `${concept} é um conceito importante para compreender os aspectos fundamentais do tópico estudado.`
};
return definitions[subject] || definitions.geral;
}
/**
* Generate term definition from content
* @param {string} term - Term to define
* @param {string} content - Content text
* @returns {string} Term definition
*/
generateTermDefinition(term, content) {
// Simple extraction: find sentence containing the term
const sentences = content.split(/[.!?]+/);
const definitionSentence = sentences.find(sentence =>
sentence.toLowerCase().includes(term.toLowerCase())
);
if (definitionSentence) {
return definitionSentence.trim() + '.';
}
return `${term} é um termo importante relacionado ao tópico estudado.`;
}
/**
* Extract key terms from content
* @param {string} content - Content text
* @returns {Array} Key terms
*/
extractKeyTerms(content) {
const text = content.toLowerCase();
// Simple term extraction based on capitalized words and important patterns
const terms = [];
// Look for definitions patterns
const definitionPatterns = [
/(\w+)\s+é\s+/g,
/(\w+)\s+refere-se\s+/g,
/(\w+)\s+representa\s+/g,
/(\w+)\s+significa\s+/g
];
definitionPatterns.forEach(pattern => {
let match;
while ((match = pattern.exec(text)) !== null) {
if (match[1].length > 3) {
terms.push(match[1]);
}
}
});
return [...new Set(terms)]; // Remove duplicates
}
/**
* Generate subject-specific examples
* @param {Object} topicAnalysis - Topic analysis
* @returns {Array} Subject examples
*/
generateSubjectExamples(topicAnalysis) {
const { subject } = topicAnalysis;
const examples = {
física: [
{
front: 'Exemplo de força no cotidiano',
back: 'Empurrar uma porta, puxar uma gaveta, peso dos objetos'
},
{
front: 'Exemplo de energia cinética',
back: 'Carro em movimento, bola sendo arremessada, pessoa correndo'
}
],
química: [
{
front: 'Exemplo de reação química cotidiana',
back: 'Combustão do gás de cozinha, digestão dos alimentos, ferrugem'
},
{
front: 'Exemplo de mistura homogênea',
back: 'Água e sal dissolvido, ar atmosférico, álcool e água'
}
],
história: [
{
front: 'Exemplo de causa histórica',
back: 'Crise econômica levando a mudanças políticas'
},
{
front: 'Exemplo de fonte primária',
back: 'Documento da época, fotografia histórica, relato de testemunha'
}
],
geral: [
{
front: 'Exemplo prático do conceito',
back: 'Aplicação do conceito em situação real do cotidiano'
}
]
};
return examples[subject] || examples.geral;
}
/**
* Generate problem scenarios
* @param {Object} topicAnalysis - Topic analysis
* @returns {Array} Problem scenarios
*/
generateProblemScenarios(topicAnalysis) {
const { subject } = topicAnalysis;
const scenarios = {
física: [
{
scenario: 'Como calcular a velocidade de um objeto em queda livre?',
solution: 'Use v = gt, onde g = 9,8 m/s² e t é o tempo de queda'
},
{
scenario: 'Como determinar a força necessária para mover um objeto?',
solution: 'Use F = ma, considerando massa e aceleração desejada'
}
],
química: [
{
scenario: 'Como balancear uma equação química?',
solution: 'Ajuste coeficientes para igualar átomos de cada elemento'
},
{
scenario: 'Como calcular concentração molar?',
solution: 'Use M = n/V, onde n = mols de soluto e V = volume em litros'
}
],
história: [
{
scenario: 'Como analisar um documento histórico?',
solution: 'Identifique autor, data, contexto e objetivo do documento'
},
{
scenario: 'Como estabelecer relação de causa e efeito?',
solution: 'Identifique eventos anteriores e suas consequências diretas'
}
],
geral: [
{
scenario: 'Como aplicar este conceito na prática?',
solution: 'Identifique situações relevantes e aplique os princípios aprendidos'
}
]
};
return scenarios[subject] || scenarios.geral;
}
/**
* Identify concept relationships
* @param {Object} topicAnalysis - Topic analysis
* @returns {Array} Concept relationships
*/
identifyConceptRelationships(topicAnalysis) {
const { concepts, keywords } = topicAnalysis;
const relationships = [];
// Generate relationships between concepts
for (let i = 0; i < concepts.length && i < 3; i++) {
for (let j = i + 1; j < concepts.length && j < 3; j++) {
relationships.push({
concept1: concepts[i],
concept2: concepts[j],
explanation: this.generateRelationshipExplanation(concepts[i], concepts[j], topicAnalysis)
});
}
}
return relationships;
}
/**
* Generate relationship explanation
* @param {string} concept1 - First concept
* @param {string} concept2 - Second concept
* @param {Object} topicAnalysis - Topic analysis
* @returns {string} Relationship explanation
*/
generateRelationshipExplanation(concept1, concept2, topicAnalysis) {
const { subject } = topicAnalysis;
const templates = {
física: `${concept1} e ${concept2} estão relacionados através de princípios físicos fundamentais.`,
química: `${concept1} influencia ${concept2} através de processos químicos específicos.`,
história: `${concept1} teve impacto direto em ${concept2} no contexto histórico estudado.`,
geral: `${concept1} está conectado a ${concept2} dentro do tópico abordado.`
};
return templates[subject] || templates.geral;
}
/**
* Optimize flashcard set for quality and balance
* @param {Array} flashcards - Raw flashcards
* @returns {Array} Optimized flashcards
*/
optimizeFlashcardSet(flashcards) {
// Remove duplicates
const unique = this.removeDuplicates(flashcards);
// Balance difficulty levels
const balanced = this.balanceDifficulty(unique);
// Ensure target count
const sized = this.adjustToTargetSize(balanced);
// Sort by importance
const sorted = this.sortByImportance(sized);
return sorted;
}
/**
* Remove duplicate flashcards
* @param {Array} flashcards - Flashcards array
* @returns {Array} Unique flashcards
*/
removeDuplicates(flashcards) {
const seen = new Set();
return flashcards.filter(card => {
const key = `${card.front}|${card.back}`;
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
}
/**
* Balance difficulty levels
* @param {Array} flashcards - Flashcards array
* @returns {Array} Balanced flashcards
*/
balanceDifficulty(flashcards) {
const byDifficulty = {
easy: flashcards.filter(card => card.difficulty === 'easy'),
medium: flashcards.filter(card => card.difficulty === 'medium'),
hard: flashcards.filter(card => card.difficulty === 'hard')
};
const target = this.config.targetFlashcards;
const balanced = [];
// Add cards according to balance configuration
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;
}
/**
* Adjust to target size
* @param {Array} flashcards - Flashcards array
* @returns {Array} Size-adjusted flashcards
*/
adjustToTargetSize(flashcards) {
if (flashcards.length < this.config.minFlashcards) {
// Add more cards if below minimum
const needed = this.config.minFlashcards - flashcards.length;
const additional = this.generateAdditionalCards(needed);
return [...flashcards, ...additional];
}
if (flashcards.length > this.config.maxFlashcards) {
// Trim if above maximum
return flashcards.slice(0, this.config.maxFlashcards);
}
return flashcards;
}
/**
* Generate additional cards when needed
* @param {number} count - Number of cards needed
* @returns {Array} Additional flashcards
*/
generateAdditionalCards(count) {
const additional = [];
for (let i = 0; i < count; i++) {
additional.push({
id: `additional-${i}-${Date.now()}`,
type: 'concept',
cardType: 'general',
front: 'Conceito importante do tópico',
back: 'Definição ou explicação relevante para o aprendizado',
difficulty: 'easy',
tags: ['adicional', 'conceito'],
source: 'gap_filling'
});
}
return additional;
}
/**
* Sort flashcards by importance
* @param {Array} flashcards - Flashcards array
* @returns {Array} Sorted flashcards
*/
sortByImportance(flashcards) {
return flashcards.sort((a, b) => {
// Priority order: concept > example > application > relationship
const typeOrder = { concept: 0, example: 1, application: 2, relationship: 3 };
const aOrder = typeOrder[a.type] || 4;
const bOrder = typeOrder[b.type] || 4;
if (aOrder !== bOrder) {
return aOrder - bOrder;
}
// Secondary sort by difficulty (easy first)
const difficultyOrder = { easy: 0, medium: 1, hard: 2 };
return (difficultyOrder[a.difficulty] || 1) - (difficultyOrder[b.difficulty] || 1);
});
}
/**
* Add metadata to flashcards
* @param {Array} flashcards - Flashcards array
* @param {Object} topicAnalysis - Topic analysis
* @returns {Array} Flashcards with metadata
*/
addFlashcardMetadata(flashcards, topicAnalysis) {
return flashcards.map((card, index) => ({
...card,
metadata: {
position: index + 1,
totalCards: flashcards.length,
subject: topicAnalysis.subject,
gradeLevel: topicAnalysis.gradeLevel,
generatedAt: new Date().toISOString(),
version: '5.0.0-alpha'
}
}));
}
/**
* Calculate flashcard set statistics
* @param {Array} flashcards - Flashcards array
* @returns {Object} Statistics
*/
calculateStatistics(flashcards) {
const stats = {
totalCards: flashcards.length,
typeDistribution: {},
difficultyDistribution: {},
averageLength: 0,
qualityScore: 0
};
// Calculate distributions
flashcards.forEach(card => {
stats.typeDistribution[card.type] = (stats.typeDistribution[card.type] || 0) + 1;
stats.difficultyDistribution[card.difficulty] = (stats.difficultyDistribution[card.difficulty] || 0) + 1;
});
// Calculate average length
const totalLength = flashcards.reduce((sum, card) =>
sum + card.front.length + card.back.length, 0
);
stats.averageLength = Math.round(totalLength / flashcards.length);
// Calculate quality score
stats.qualityScore = this.calculateQualityScore(flashcards);
return stats;
}
/**
* Calculate quality score for flashcard set
* @param {Array} flashcards - Flashcards array
* @returns {number} Quality score (0-100)
*/
calculateQualityScore(flashcards) {
let score = 100;
// Deduct for insufficient cards
if (flashcards.length < this.config.minFlashcards) {
score -= 30;
}
// Deduct for poor balance
const typeCount = Object.keys(
flashcards.reduce((acc, card) => ({ ...acc, [card.type]: true }), {})
).length;
if (typeCount < 2) {
score -= 20; // Lack of variety
}
// Add for good difficulty distribution
const difficultyCount = Object.keys(
flashcards.reduce((acc, card) => ({ ...acc, [card.difficulty]: true }), {})
).length;
score += difficultyCount * 5; // Bonus for difficulty variety
return Math.max(0, Math.min(100, score));
}
}
// Factory function for creating FlashcardGenerator instances
export function createFlashcardGenerator(config = {}) {
return new FlashcardGenerator(config);
}