quality-validator.js•34.4 kB
/**
* Assessment Quality Validation System
* @module content-generation/quality-validator
* @description Validates and improves assessment quality across all components
* @version 5.0.0-alpha
*/
export class QualityValidator {
constructor(config = {}) {
this.config = {
minQualityScore: 70,
enableAutoCorrection: true,
strictValidation: false,
maxCorrectionAttempts: 3,
qualityThreshold: 0.7,
...config
};
// Quality validation rules
this.validationRules = {
flashcards: {
minCount: 8,
maxCount: 30,
minContentLength: 10,
maxContentLength: 500,
balanceThreshold: 0.3,
qualityChecks: ['content_relevance', 'clarity', 'difficulty_balance', 'type_variety']
},
quiz: {
minQuestions: 5,
maxQuestions: 20,
minOptionCount: 2,
maxOptionCount: 5,
difficultyDistribution: { easy: [0.2, 0.5], medium: [0.3, 0.6], hard: [0.1, 0.4] },
qualityChecks: ['question_clarity', 'answer_distribution', 'distractor_quality', 'type_variety']
},
overall: {
educationalAlignment: true,
gradeAppropriate: true,
contentAccuracy: true,
assessmentValidity: true
}
};
// Error patterns and corrections
this.correctionStrategies = {
insufficient_content: this.correctInsufficientContent.bind(this),
poor_balance: this.correctPoorBalance.bind(this),
low_quality_questions: this.correctLowQualityQuestions.bind(this),
inappropriate_difficulty: this.correctInappropriateDifficulty.bind(this),
poor_distractors: this.correctPoorDistractors.bind(this),
unclear_content: this.correctUnclearContent.bind(this)
};
}
/**
* Validate complete assessment package
* @param {Object} assessment - Assessment package
* @returns {Promise<Object>} Validated and potentially corrected assessment
*/
async validateAssessment(assessment) {
console.log('[QUALITY-VALIDATOR] Starting assessment validation');
try {
// Run comprehensive validation
const validationResult = await this.runValidation(assessment);
// Apply corrections if needed and enabled
let correctedAssessment = assessment;
if (!validationResult.passed && this.config.enableAutoCorrection) {
correctedAssessment = await this.applyCorrections(assessment, validationResult.issues);
}
// Final quality check
const finalValidation = await this.runValidation(correctedAssessment);
// Add validation metadata
correctedAssessment.validation = {
qualityScore: finalValidation.qualityScore,
validationPassed: finalValidation.passed,
issues: finalValidation.issues,
corrections: validationResult.passed ? [] : this.getCorrectionsSummary(validationResult.issues),
validatedAt: new Date().toISOString(),
validatorVersion: '5.0.0-alpha'
};
console.log(`[QUALITY-VALIDATOR] Validation complete. Quality score: ${finalValidation.qualityScore}`);
return correctedAssessment;
} catch (error) {
console.error('[QUALITY-VALIDATOR] Validation failed:', error);
throw new Error(`Assessment validation failed: ${error.message}`);
}
}
/**
* Run comprehensive validation on assessment
* @param {Object} assessment - Assessment to validate
* @returns {Promise<Object>} Validation result
*/
async runValidation(assessment) {
const validationResult = {
passed: false,
qualityScore: 0,
issues: [],
componentScores: {}
};
// Validate flashcards
if (assessment.flashcards) {
const flashcardValidation = await this.validateFlashcards(assessment.flashcards);
validationResult.componentScores.flashcards = flashcardValidation.score;
validationResult.issues.push(...flashcardValidation.issues);
}
// Validate quiz
if (assessment.quiz) {
const quizValidation = await this.validateQuiz(assessment.quiz);
validationResult.componentScores.quiz = quizValidation.score;
validationResult.issues.push(...quizValidation.issues);
}
// Validate overall assessment
const overallValidation = await this.validateOverallAssessment(assessment);
validationResult.componentScores.overall = overallValidation.score;
validationResult.issues.push(...overallValidation.issues);
// Calculate overall quality score
validationResult.qualityScore = this.calculateOverallQualityScore(validationResult.componentScores);
validationResult.passed = validationResult.qualityScore >= this.config.minQualityScore;
return validationResult;
}
/**
* Validate flashcards component
* @param {Array} flashcards - Flashcards to validate
* @returns {Promise<Object>} Flashcard validation result
*/
async validateFlashcards(flashcards) {
const validation = {
score: 100,
issues: []
};
const rules = this.validationRules.flashcards;
// Check minimum count
if (flashcards.length < rules.minCount) {
validation.issues.push({
type: 'insufficient_content',
severity: 'high',
component: 'flashcards',
message: `Too few flashcards: ${flashcards.length} < ${rules.minCount}`,
expectedValue: rules.minCount,
actualValue: flashcards.length
});
validation.score -= 30;
}
// Check maximum count
if (flashcards.length > rules.maxCount) {
validation.issues.push({
type: 'excessive_content',
severity: 'medium',
component: 'flashcards',
message: `Too many flashcards: ${flashcards.length} > ${rules.maxCount}`,
expectedValue: rules.maxCount,
actualValue: flashcards.length
});
validation.score -= 10;
}
// Check content quality
for (const [index, flashcard] of flashcards.entries()) {
const cardValidation = await this.validateFlashcard(flashcard, index);
validation.issues.push(...cardValidation.issues);
validation.score = Math.max(0, validation.score - cardValidation.deductions);
}
// Check type variety
const typeVariety = this.checkFlashcardTypeVariety(flashcards);
if (typeVariety.score < 70) {
validation.issues.push({
type: 'poor_variety',
severity: 'medium',
component: 'flashcards',
message: 'Insufficient flashcard type variety',
details: typeVariety
});
validation.score -= 15;
}
// Check difficulty balance
const difficultyBalance = this.checkFlashcardDifficultyBalance(flashcards);
if (difficultyBalance.imbalanceScore > rules.balanceThreshold) {
validation.issues.push({
type: 'poor_balance',
severity: 'medium',
component: 'flashcards',
message: 'Poor difficulty balance in flashcards',
details: difficultyBalance
});
validation.score -= 20;
}
return {
score: Math.max(0, validation.score),
issues: validation.issues
};
}
/**
* Validate individual flashcard
* @param {Object} flashcard - Flashcard to validate
* @param {number} index - Flashcard index
* @returns {Promise<Object>} Single flashcard validation
*/
async validateFlashcard(flashcard, index) {
const validation = {
issues: [],
deductions: 0
};
const rules = this.validationRules.flashcards;
// Check required fields
if (!flashcard.front || !flashcard.back) {
validation.issues.push({
type: 'missing_content',
severity: 'high',
component: 'flashcard',
index: index,
message: 'Flashcard missing front or back content'
});
validation.deductions += 10;
}
// Check content length
if (flashcard.front && flashcard.front.length < rules.minContentLength) {
validation.issues.push({
type: 'insufficient_content',
severity: 'medium',
component: 'flashcard',
index: index,
field: 'front',
message: 'Flashcard front content too short'
});
validation.deductions += 2;
}
if (flashcard.back && flashcard.back.length < rules.minContentLength) {
validation.issues.push({
type: 'insufficient_content',
severity: 'medium',
component: 'flashcard',
index: index,
field: 'back',
message: 'Flashcard back content too short'
});
validation.deductions += 2;
}
// Check content clarity
if (flashcard.front && this.isContentUnclear(flashcard.front)) {
validation.issues.push({
type: 'unclear_content',
severity: 'medium',
component: 'flashcard',
index: index,
field: 'front',
message: 'Flashcard front content unclear'
});
validation.deductions += 3;
}
if (flashcard.back && this.isContentUnclear(flashcard.back)) {
validation.issues.push({
type: 'unclear_content',
severity: 'medium',
component: 'flashcard',
index: index,
field: 'back',
message: 'Flashcard back content unclear'
});
validation.deductions += 3;
}
// Check educational relevance
if (!this.isEducationallyRelevant(flashcard)) {
validation.issues.push({
type: 'poor_educational_value',
severity: 'high',
component: 'flashcard',
index: index,
message: 'Flashcard has poor educational value'
});
validation.deductions += 5;
}
return validation;
}
/**
* Validate quiz component
* @param {Object} quiz - Quiz to validate
* @returns {Promise<Object>} Quiz validation result
*/
async validateQuiz(quiz) {
const validation = {
score: 100,
issues: []
};
if (!quiz.questions || !Array.isArray(quiz.questions)) {
validation.issues.push({
type: 'missing_content',
severity: 'critical',
component: 'quiz',
message: 'Quiz missing questions array'
});
return { score: 0, issues: validation.issues };
}
const rules = this.validationRules.quiz;
// Check question count
if (quiz.questions.length < rules.minQuestions) {
validation.issues.push({
type: 'insufficient_content',
severity: 'high',
component: 'quiz',
message: `Too few questions: ${quiz.questions.length} < ${rules.minQuestions}`
});
validation.score -= 30;
}
if (quiz.questions.length > rules.maxQuestions) {
validation.issues.push({
type: 'excessive_content',
severity: 'medium',
component: 'quiz',
message: `Too many questions: ${quiz.questions.length} > ${rules.maxQuestions}`
});
validation.score -= 10;
}
// Validate individual questions
for (const [index, question] of quiz.questions.entries()) {
const questionValidation = await this.validateQuestion(question, index);
validation.issues.push(...questionValidation.issues);
validation.score = Math.max(0, validation.score - questionValidation.deductions);
}
// Check question type variety
const typeVariety = this.checkQuestionTypeVariety(quiz.questions);
if (typeVariety.score < 70) {
validation.issues.push({
type: 'poor_variety',
severity: 'medium',
component: 'quiz',
message: 'Insufficient question type variety',
details: typeVariety
});
validation.score -= 15;
}
// Check difficulty distribution
const difficultyDistribution = this.checkQuestionDifficultyDistribution(quiz.questions);
if (!this.isDifficultyDistributionValid(difficultyDistribution)) {
validation.issues.push({
type: 'poor_balance',
severity: 'medium',
component: 'quiz',
message: 'Poor difficulty distribution',
details: difficultyDistribution
});
validation.score -= 20;
}
// Check answer position distribution (for multiple choice)
const answerDistribution = this.checkAnswerPositionDistribution(quiz.questions);
if (answerDistribution.imbalanceScore > 0.3) {
validation.issues.push({
type: 'poor_answer_distribution',
severity: 'low',
component: 'quiz',
message: 'Uneven answer position distribution',
details: answerDistribution
});
validation.score -= 10;
}
return {
score: Math.max(0, validation.score),
issues: validation.issues
};
}
/**
* Validate individual question
* @param {Object} question - Question to validate
* @param {number} index - Question index
* @returns {Promise<Object>} Question validation result
*/
async validateQuestion(question, index) {
const validation = {
issues: [],
deductions: 0
};
// Check required fields
if (!question.question) {
validation.issues.push({
type: 'missing_content',
severity: 'critical',
component: 'question',
index: index,
message: 'Question missing question text'
});
validation.deductions += 10;
return validation;
}
// Validate by question type
switch (question.type) {
case 'multiple_choice':
const mcValidation = await this.validateMultipleChoiceQuestion(question, index);
validation.issues.push(...mcValidation.issues);
validation.deductions += mcValidation.deductions;
break;
case 'true_false':
const tfValidation = await this.validateTrueFalseQuestion(question, index);
validation.issues.push(...tfValidation.issues);
validation.deductions += tfValidation.deductions;
break;
case 'fill_blank':
const fbValidation = await this.validateFillBlankQuestion(question, index);
validation.issues.push(...fbValidation.issues);
validation.deductions += fbValidation.deductions;
break;
case 'short_answer':
const saValidation = await this.validateShortAnswerQuestion(question, index);
validation.issues.push(...saValidation.issues);
validation.deductions += saValidation.deductions;
break;
case 'matching':
const matchValidation = await this.validateMatchingQuestion(question, index);
validation.issues.push(...matchValidation.issues);
validation.deductions += matchValidation.deductions;
break;
}
// Check question clarity
if (this.isContentUnclear(question.question)) {
validation.issues.push({
type: 'unclear_content',
severity: 'medium',
component: 'question',
index: index,
message: 'Question text unclear'
});
validation.deductions += 3;
}
// Check educational value
if (!this.isEducationallyRelevant(question)) {
validation.issues.push({
type: 'poor_educational_value',
severity: 'high',
component: 'question',
index: index,
message: 'Question has poor educational value'
});
validation.deductions += 5;
}
return validation;
}
/**
* Validate multiple choice question
* @param {Object} question - Multiple choice question
* @param {number} index - Question index
* @returns {Promise<Object>} Validation result
*/
async validateMultipleChoiceQuestion(question, index) {
const validation = {
issues: [],
deductions: 0
};
const rules = this.validationRules.quiz;
// Check options
if (!question.options || !Array.isArray(question.options)) {
validation.issues.push({
type: 'missing_content',
severity: 'critical',
component: 'question',
index: index,
message: 'Multiple choice question missing options'
});
validation.deductions += 10;
return validation;
}
// Check option count
if (question.options.length < rules.minOptionCount) {
validation.issues.push({
type: 'insufficient_content',
severity: 'high',
component: 'question',
index: index,
message: `Too few options: ${question.options.length}`
});
validation.deductions += 5;
}
if (question.options.length > rules.maxOptionCount) {
validation.issues.push({
type: 'excessive_content',
severity: 'medium',
component: 'question',
index: index,
message: `Too many options: ${question.options.length}`
});
validation.deductions += 2;
}
// Check correct answer index
if (typeof question.correct !== 'number' ||
question.correct < 0 ||
question.correct >= question.options.length) {
validation.issues.push({
type: 'invalid_answer',
severity: 'critical',
component: 'question',
index: index,
message: 'Invalid correct answer index'
});
validation.deductions += 10;
}
// Check distractor quality
const distractorQuality = this.assessDistractorQuality(question.options, question.correct);
if (distractorQuality.score < 70) {
validation.issues.push({
type: 'poor_distractors',
severity: 'medium',
component: 'question',
index: index,
message: 'Poor quality distractors',
details: distractorQuality
});
validation.deductions += 3;
}
return validation;
}
/**
* Validate true/false question
* @param {Object} question - True/false question
* @param {number} index - Question index
* @returns {Promise<Object>} Validation result
*/
async validateTrueFalseQuestion(question, index) {
const validation = {
issues: [],
deductions: 0
};
// Check correct answer
if (typeof question.correct !== 'boolean') {
validation.issues.push({
type: 'invalid_answer',
severity: 'critical',
component: 'question',
index: index,
message: 'True/false question correct answer must be boolean'
});
validation.deductions += 5;
}
return validation;
}
/**
* Validate fill-in-the-blank question
* @param {Object} question - Fill blank question
* @param {number} index - Question index
* @returns {Promise<Object>} Validation result
*/
async validateFillBlankQuestion(question, index) {
const validation = {
issues: [],
deductions: 0
};
// Check answer
if (!question.answer || typeof question.answer !== 'string') {
validation.issues.push({
type: 'missing_content',
severity: 'critical',
component: 'question',
index: index,
message: 'Fill blank question missing answer'
});
validation.deductions += 5;
}
// Check for blank in question
if (!question.question.includes('_')) {
validation.issues.push({
type: 'invalid_format',
severity: 'high',
component: 'question',
index: index,
message: 'Fill blank question missing blank marker'
});
validation.deductions += 3;
}
return validation;
}
/**
* Validate short answer question
* @param {Object} question - Short answer question
* @param {number} index - Question index
* @returns {Promise<Object>} Validation result
*/
async validateShortAnswerQuestion(question, index) {
const validation = {
issues: [],
deductions: 0
};
// Check expected elements or rubric
if (!question.expectedElements && !question.rubric) {
validation.issues.push({
type: 'missing_content',
severity: 'medium',
component: 'question',
index: index,
message: 'Short answer question missing expected elements or rubric'
});
validation.deductions += 2;
}
return validation;
}
/**
* Validate matching question
* @param {Object} question - Matching question
* @param {number} index - Question index
* @returns {Promise<Object>} Validation result
*/
async validateMatchingQuestion(question, index) {
const validation = {
issues: [],
deductions: 0
};
// Check columns
if (!question.leftColumn || !question.rightColumn) {
validation.issues.push({
type: 'missing_content',
severity: 'critical',
component: 'question',
index: index,
message: 'Matching question missing columns'
});
validation.deductions += 10;
return validation;
}
// Check correct pairs
if (!question.correctPairs || !Array.isArray(question.correctPairs)) {
validation.issues.push({
type: 'missing_content',
severity: 'critical',
component: 'question',
index: index,
message: 'Matching question missing correct pairs'
});
validation.deductions += 10;
}
return validation;
}
/**
* Validate overall assessment
* @param {Object} assessment - Complete assessment
* @returns {Promise<Object>} Overall validation result
*/
async validateOverallAssessment(assessment) {
const validation = {
score: 100,
issues: []
};
// Check metadata
if (!assessment.metadata) {
validation.issues.push({
type: 'missing_metadata',
severity: 'medium',
component: 'assessment',
message: 'Assessment missing metadata'
});
validation.score -= 10;
}
// Check educational alignment
if (!this.isEducationallyAligned(assessment)) {
validation.issues.push({
type: 'poor_educational_alignment',
severity: 'high',
component: 'assessment',
message: 'Poor educational alignment'
});
validation.score -= 20;
}
// Check content coherence
if (!this.isContentCoherent(assessment)) {
validation.issues.push({
type: 'poor_coherence',
severity: 'medium',
component: 'assessment',
message: 'Poor content coherence between components'
});
validation.score -= 15;
}
return {
score: Math.max(0, validation.score),
issues: validation.issues
};
}
// Quality check helper methods
isContentUnclear(content) {
if (!content || typeof content !== 'string') return true;
// Simple clarity checks
const hasBasicStructure = content.length > 5;
const hasProperCapitalization = /^[A-Z]/.test(content.trim());
const hasEndingPunctuation = /[.!?]$/.test(content.trim());
const hasReasonableWords = content.split(' ').length >= 2;
return !(hasBasicStructure && hasProperCapitalization && hasEndingPunctuation && hasReasonableWords);
}
isEducationallyRelevant(item) {
// Basic educational relevance check
if (!item) return false;
const content = item.question || item.front || item.back || '';
const hasEducationalKeywords = /\b(conceito|definição|exemplo|aplicação|princípio|teoria|método|processo|análise|estudo|compreender|explicar|demonstrar|identificar)\b/i.test(content);
const hasSubstantiveContent = content.length > 20;
return hasEducationalKeywords && hasSubstantiveContent;
}
isEducationallyAligned(assessment) {
// Check if flashcards and quiz are aligned with the same subject/topic
const flashcardSubjects = new Set();
const questionSubjects = new Set();
if (assessment.flashcards) {
assessment.flashcards.forEach(card => {
if (card.subject) flashcardSubjects.add(card.subject);
});
}
if (assessment.quiz && assessment.quiz.questions) {
assessment.quiz.questions.forEach(question => {
if (question.subject) questionSubjects.add(question.subject);
});
}
// Check for subject alignment
const hasCommonSubjects = [...flashcardSubjects].some(subject => questionSubjects.has(subject));
return hasCommonSubjects || flashcardSubjects.size === 0 || questionSubjects.size === 0;
}
isContentCoherent(assessment) {
// Basic coherence check - could be more sophisticated
return true; // Placeholder - implement actual coherence checking
}
checkFlashcardTypeVariety(flashcards) {
const types = {};
flashcards.forEach(card => {
const type = card.type || 'unknown';
types[type] = (types[type] || 0) + 1;
});
const uniqueTypes = Object.keys(types).length;
const totalCards = flashcards.length;
const varietyScore = totalCards > 0 ? (uniqueTypes / Math.min(4, totalCards)) * 100 : 0;
return {
score: Math.min(100, varietyScore),
types: types,
uniqueTypes: uniqueTypes
};
}
checkFlashcardDifficultyBalance(flashcards) {
const difficulties = { easy: 0, medium: 0, hard: 0 };
flashcards.forEach(card => {
const difficulty = card.difficulty || 'medium';
if (difficulties[difficulty] !== undefined) {
difficulties[difficulty]++;
}
});
const total = flashcards.length;
const distribution = {
easy: total > 0 ? difficulties.easy / total : 0,
medium: total > 0 ? difficulties.medium / total : 0,
hard: total > 0 ? difficulties.hard / total : 0
};
// Calculate imbalance score
const ideal = { easy: 0.4, medium: 0.4, hard: 0.2 };
const imbalanceScore = Object.keys(ideal).reduce((sum, key) =>
sum + Math.abs(distribution[key] - ideal[key]), 0) / 2;
return {
distribution: distribution,
imbalanceScore: imbalanceScore,
counts: difficulties
};
}
checkQuestionTypeVariety(questions) {
const types = {};
questions.forEach(question => {
const type = question.type || 'unknown';
types[type] = (types[type] || 0) + 1;
});
const uniqueTypes = Object.keys(types).length;
const totalQuestions = questions.length;
const varietyScore = totalQuestions > 0 ? (uniqueTypes / Math.min(5, totalQuestions)) * 100 : 0;
return {
score: Math.min(100, varietyScore),
types: types,
uniqueTypes: uniqueTypes
};
}
checkQuestionDifficultyDistribution(questions) {
const difficulties = { easy: 0, medium: 0, hard: 0 };
questions.forEach(question => {
const difficulty = question.difficulty || 'medium';
if (difficulties[difficulty] !== undefined) {
difficulties[difficulty]++;
}
});
const total = questions.length;
return {
easy: total > 0 ? difficulties.easy / total : 0,
medium: total > 0 ? difficulties.medium / total : 0,
hard: total > 0 ? difficulties.hard / total : 0,
counts: difficulties
};
}
isDifficultyDistributionValid(distribution) {
const rules = this.validationRules.quiz.difficultyDistribution;
return Object.keys(rules).every(difficulty => {
const [min, max] = rules[difficulty];
const actual = distribution[difficulty] || 0;
return actual >= min && actual <= max;
});
}
checkAnswerPositionDistribution(questions) {
const positions = {};
let totalMultipleChoice = 0;
questions.forEach(question => {
if (question.type === 'multiple_choice' && typeof question.correct === 'number') {
totalMultipleChoice++;
const position = question.correct;
positions[position] = (positions[position] || 0) + 1;
}
});
if (totalMultipleChoice === 0) {
return { imbalanceScore: 0, distribution: {} };
}
// Calculate distribution
const distribution = {};
Object.keys(positions).forEach(position => {
distribution[position] = positions[position] / totalMultipleChoice;
});
// Calculate imbalance (variance from uniform distribution)
const expectedProbability = 1 / Object.keys(positions).length;
const imbalanceScore = Object.values(distribution).reduce((sum, probability) =>
sum + Math.abs(probability - expectedProbability), 0) / 2;
return {
imbalanceScore: imbalanceScore,
distribution: distribution,
counts: positions
};
}
assessDistractorQuality(options, correctIndex) {
if (!options || options.length < 2) {
return { score: 0, issues: ['Insufficient options'] };
}
const correctAnswer = options[correctIndex];
const distractors = options.filter((_, index) => index !== correctIndex);
let score = 100;
const issues = [];
// Check for obviously wrong distractors
distractors.forEach((distractor, index) => {
if (this.isObviouslyWrong(distractor, correctAnswer)) {
score -= 20;
issues.push(`Distractor ${index + 1} is obviously wrong`);
}
});
// Check for duplicate or very similar distractors
for (let i = 0; i < distractors.length; i++) {
for (let j = i + 1; j < distractors.length; j++) {
if (this.areTooSimilar(distractors[i], distractors[j])) {
score -= 15;
issues.push(`Distractors ${i + 1} and ${j + 1} are too similar`);
}
}
}
return {
score: Math.max(0, score),
issues: issues
};
}
isObviouslyWrong(distractor, correctAnswer) {
// Simple checks for obviously wrong distractors
const correctWords = correctAnswer.toLowerCase().split(' ');
const distractorWords = distractor.toLowerCase().split(' ');
// Check if distractor is completely unrelated (no common words)
const commonWords = correctWords.filter(word =>
distractorWords.includes(word) && word.length > 3
);
return commonWords.length === 0 && Math.abs(correctAnswer.length - distractor.length) > correctAnswer.length * 0.5;
}
areTooSimilar(option1, option2) {
// Simple similarity check
const words1 = option1.toLowerCase().split(' ');
const words2 = option2.toLowerCase().split(' ');
const commonWords = words1.filter(word => words2.includes(word));
const totalWords = new Set([...words1, ...words2]).size;
return commonWords.length / totalWords > 0.7;
}
calculateOverallQualityScore(componentScores) {
const weights = {
flashcards: 0.4,
quiz: 0.4,
overall: 0.2
};
let totalScore = 0;
let totalWeight = 0;
Object.entries(componentScores).forEach(([component, score]) => {
const weight = weights[component] || 0;
totalScore += score * weight;
totalWeight += weight;
});
return totalWeight > 0 ? Math.round(totalScore / totalWeight) : 0;
}
// Correction methods
async applyCorrections(assessment, issues) {
console.log('[QUALITY-VALIDATOR] Applying corrections to assessment');
let correctedAssessment = { ...assessment };
let attempts = 0;
while (attempts < this.config.maxCorrectionAttempts) {
const appliedCorrections = [];
for (const issue of issues) {
const correctionStrategy = this.correctionStrategies[issue.type];
if (correctionStrategy) {
try {
correctedAssessment = await correctionStrategy(correctedAssessment, issue);
appliedCorrections.push(issue.type);
} catch (error) {
console.warn(`[QUALITY-VALIDATOR] Correction failed for ${issue.type}:`, error.message);
}
}
}
if (appliedCorrections.length === 0) break;
// Re-validate after corrections
const revalidation = await this.runValidation(correctedAssessment);
if (revalidation.passed) break;
issues = revalidation.issues;
attempts++;
}
return correctedAssessment;
}
async correctInsufficientContent(assessment, issue) {
// Add placeholder content based on component type
if (issue.component === 'flashcards') {
const needed = issue.expectedValue - issue.actualValue;
for (let i = 0; i < needed; i++) {
assessment.flashcards.push(this.generatePlaceholderFlashcard(i));
}
} else if (issue.component === 'quiz') {
const needed = issue.expectedValue - issue.actualValue;
for (let i = 0; i < needed; i++) {
assessment.quiz.questions.push(this.generatePlaceholderQuestion(i));
}
}
return assessment;
}
async correctPoorBalance(assessment, issue) {
// Implement balance correction logic
return assessment;
}
async correctLowQualityQuestions(assessment, issue) {
// Implement question quality improvement
return assessment;
}
async correctInappropriateDifficulty(assessment, issue) {
// Implement difficulty adjustment
return assessment;
}
async correctPoorDistractors(assessment, issue) {
// Implement distractor improvement
return assessment;
}
async correctUnclearContent(assessment, issue) {
// Implement content clarity improvement
return assessment;
}
generatePlaceholderFlashcard(index) {
return {
id: `placeholder-${index}-${Date.now()}`,
type: 'concept',
front: 'Conceito importante',
back: 'Definição ou explicação relevante para o aprendizado',
difficulty: 'easy',
tags: ['placeholder'],
source: 'quality_correction'
};
}
generatePlaceholderQuestion(index) {
return {
id: `placeholder-${index}-${Date.now()}`,
type: 'multiple_choice',
question: 'Qual é a importância do conceito estudado?',
options: ['Muito importante', 'Pouco importante', 'Sem importância', 'Não sei'],
correct: 0,
difficulty: 'easy',
explanation: 'O conceito estudado é fundamental para o aprendizado.',
source: 'quality_correction'
};
}
getCorrectionsSummary(issues) {
const corrections = {};
issues.forEach(issue => {
if (!corrections[issue.type]) {
corrections[issue.type] = 0;
}
corrections[issue.type]++;
});
return corrections;
}
}
// Factory function for creating QualityValidator instances
export function createQualityValidator(config = {}) {
return new QualityValidator(config);
}