educational-content-analyzer.ts•24.6 kB
/**
* Educational Content Analyzer
* AI-driven content analysis with educational best practices
*/
import { BrazilianEducationalAnalyzer, BrazilianEducationalContext } from './brazilian-educational-analyzer.js';
export interface ContentIntent {
type: 'memorization' | 'comprehension' | 'assessment' | 'demonstration' | 'exploration' | 'practice';
confidence: number;
indicators: string[];
suggestedElements: string[];
}
export interface EducationalContext {
learningObjectives: string[];
targetAudience: 'elementary' | 'middle' | 'high' | 'college' | 'adult' | 'professional';
contentComplexity: 'basic' | 'intermediate' | 'advanced';
engagementLevel: 'low' | 'medium' | 'high';
estimatedDuration: number; // minutes
}
export interface ContentChunk {
id: string;
content: string;
intent: ContentIntent;
suggestedElements: ElementSuggestion[];
position: number;
}
export interface ElementSuggestion {
type: string;
confidence: number;
reasoning: string;
properties: Record<string, any>;
}
export class EducationalContentAnalyzer {
private brazilianAnalyzer: BrazilianEducationalAnalyzer;
constructor() {
this.brazilianAnalyzer = new BrazilianEducationalAnalyzer();
}
private memorializationPatterns = [
/\b(memorize|remember|recall|learn by heart|commit to memory)\b/i,
/\b(vocabulary|terms|definitions|formulas|facts)\b/i,
/\b(study cards|flash cards|drill|repetition)\b/i,
// Brazilian Portuguese patterns
/\b(memorizar|lembrar|recordar|decorar|gravar)\b/i,
/\b(vocabulário|termos|definições|fórmulas|fatos)\b/i,
/\b(cartões de estudo|repetição|treino|exercitar)\b/i,
];
private comprehensionPatterns = [
/\b(understand|explain|comprehend|grasp|interpret)\b/i,
/\b(concept|theory|principle|idea|how it works)\b/i,
/\b(demonstrate|show|illustrate|example)\b/i,
// Brazilian Portuguese patterns
/\b(entender|explicar|compreender|interpretar|captar)\b/i,
/\b(conceito|teoria|princípio|ideia|como funciona)\b/i,
/\b(demonstrar|mostrar|ilustrar|exemplo|exemplificar)\b/i,
];
private assessmentPatterns = [
/\b(test|quiz|assess|evaluate|check|verify)\b/i,
/\b(knowledge|understanding|learning|mastery)\b/i,
/\b(exam|assessment|evaluation|grade)\b/i,
/\btest\s+knowledge\s+of\b/i,
/\bassess\s+understanding\b/i,
/\bcheck\s+if\s+students?\s+learned?\b/i,
/\bverify\s+comprehension\b/i,
/\bmeasure\s+progress\b/i,
/\bevaluate\s+learning\b/i,
/\bquiz\s+on\b/i,
/\btest\s+your\s+knowledge\b/i,
/\bhow\s+well\s+do\s+you\s+know\b/i,
/\bcan\s+you\s+answer\b/i,
/\bmultiple\s+choice\b/i,
/\bselect\s+the\s+correct\b/i,
/\bchoose\s+the\s+best\b/i,
/\bfinal\s+exam\b/i,
/\bmidterm\s+test\b/i,
/\bpop\s+quiz\b/i,
/\bdiagnostic\s+assessment\b/i,
];
private demonstrationPatterns = [
/\b(show|demonstrate|display|present|exhibit)\b/i,
/\b(video|visual|watch|see|observe)\b/i,
/\b(process|procedure|steps|how to)\b/i,
/\bshow\s+a\s+video\s+about\b/i,
/\bdemonstrate\s+the\s+process\b/i,
/\bwatch\s+this\s+video\b/i,
/\bplay\s+the\s+video\b/i,
/\bvideo\s+explanation\b/i,
/\bvisual\s+demonstration\b/i,
/\bsee\s+how\s+it\s+works\b/i,
/\bobserve\s+the\s+technique\b/i,
/\bvideo\s+tutorial\b/i,
/\bscreen\s+recording\b/i,
/\banimated\s+explanation\b/i,
/\bstep-by-step\s+video\b/i,
/\bwalkthrough\s+video\b/i,
/\binstructional\s+video\b/i,
/\beducational\s+video\b/i,
];
private explorationPatterns = [
/\b(explore|discover|investigate|research)\b/i,
/\b(interactive|hands-on|experiment|try)\b/i,
/\b(click|navigate|browse|search)\b/i,
];
private practicePatterns = [
/\b(practice|exercise|drill|apply|try out)\b/i,
/\b(simulation|scenario|case study)\b/i,
/\b(hands-on|interactive practice)\b/i,
];
public analyzeContent(prompt: string, title?: string): {
chunks: ContentChunk[];
overallContext: EducationalContext;
recommendedSequence: string[];
brazilianContext?: BrazilianEducationalContext;
} {
// Analyze Brazilian educational context first
const brazilianContext = this.brazilianAnalyzer.analyzeBrazilianContext(prompt, title);
// Analyze overall educational context with Brazilian input
const context = this.analyzeEducationalContext(prompt, title, brazilianContext);
// Break content into meaningful chunks
const chunks = this.chunkContent(prompt);
// Analyze each chunk for learning intent
const analyzedChunks = chunks.map((chunk, index) =>
this.analyzeChunk(chunk, index, context)
);
// Generate recommended learning sequence
const sequence = this.generateLearningSequence(analyzedChunks, context);
return {
chunks: analyzedChunks,
overallContext: context,
recommendedSequence: sequence,
brazilianContext,
};
}
private analyzeEducationalContext(prompt: string, title?: string, brazilianContext?: BrazilianEducationalContext): EducationalContext {
const fullText = `${title || ''} ${prompt}`.toLowerCase();
// Use Brazilian context if available, otherwise fallback to generic analysis
const targetAudience = brazilianContext?.gradeLevel
? this.brazilianAnalyzer.mapToInternationalAudience(brazilianContext.gradeLevel)
: this.determineTargetAudience(fullText);
// Assess content complexity (use Brazilian duration mapping if available)
const contentComplexity = brazilianContext?.duration
? this.brazilianAnalyzer.mapComplexityFromDuration(brazilianContext.duration)
: this.assessComplexity(fullText);
// Extract learning objectives
const learningObjectives = this.extractLearningObjectives(prompt);
// Estimate engagement level needed
const engagementLevel = this.assessEngagementNeeds(fullText);
// Estimate duration (use Brazilian context if available)
const estimatedDuration = brazilianContext?.duration || this.estimateContentDuration(prompt);
return {
learningObjectives,
targetAudience,
contentComplexity,
engagementLevel,
estimatedDuration,
};
}
private analyzeChunk(content: string, position: number, context: EducationalContext): ContentChunk {
const intent = this.detectContentIntent(content);
const suggestedElements = this.suggestElementsForChunk(content, intent, context);
return {
id: `chunk-${position}`,
content,
intent,
suggestedElements,
position,
};
}
private detectContentIntent(content: string): ContentIntent {
const text = content.toLowerCase();
const intents: Array<{type: ContentIntent['type'], score: number, indicators: string[]}> = [];
// Check for memorization intent
const memorizationScore = this.calculatePatternScore(text, this.memorializationPatterns);
if (memorizationScore > 0) {
intents.push({
type: 'memorization',
score: memorizationScore,
indicators: this.findMatchingPatterns(text, this.memorializationPatterns)
});
}
// Check for comprehension intent
const comprehensionScore = this.calculatePatternScore(text, this.comprehensionPatterns);
if (comprehensionScore > 0) {
intents.push({
type: 'comprehension',
score: comprehensionScore,
indicators: this.findMatchingPatterns(text, this.comprehensionPatterns)
});
}
// Check for assessment intent
const assessmentScore = this.calculatePatternScore(text, this.assessmentPatterns);
if (assessmentScore > 0) {
intents.push({
type: 'assessment',
score: assessmentScore,
indicators: this.findMatchingPatterns(text, this.assessmentPatterns)
});
}
// Check for demonstration intent
const demonstrationScore = this.calculatePatternScore(text, this.demonstrationPatterns);
if (demonstrationScore > 0) {
intents.push({
type: 'demonstration',
score: demonstrationScore,
indicators: this.findMatchingPatterns(text, this.demonstrationPatterns)
});
}
// Check for exploration intent
const explorationScore = this.calculatePatternScore(text, this.explorationPatterns);
if (explorationScore > 0) {
intents.push({
type: 'exploration',
score: explorationScore,
indicators: this.findMatchingPatterns(text, this.explorationPatterns)
});
}
// Check for practice intent
const practiceScore = this.calculatePatternScore(text, this.practicePatterns);
if (practiceScore > 0) {
intents.push({
type: 'practice',
score: practiceScore,
indicators: this.findMatchingPatterns(text, this.practicePatterns)
});
}
// Return highest scoring intent, or default to comprehension
if (intents.length === 0) {
return {
type: 'comprehension',
confidence: 0.3,
indicators: ['default content type'],
suggestedElements: ['text-1', 'head-1']
};
}
const topIntent = intents.sort((a, b) => b.score - a.score)[0];
return {
type: topIntent.type,
confidence: Math.min(topIntent.score, 1.0),
indicators: topIntent.indicators,
suggestedElements: this.getElementsForIntent(topIntent.type)
};
}
private suggestElementsForChunk(
content: string,
intent: ContentIntent,
context: EducationalContext
): ElementSuggestion[] {
const suggestions: ElementSuggestion[] = [];
switch (intent.type) {
case 'memorization':
suggestions.push({
type: 'flashcards-1',
confidence: 0.9,
reasoning: 'Flashcards are optimal for memorization and recall practice',
properties: this.generateFlashcardProperties(content)
});
// Add spaced repetition if content is complex
if (context.contentComplexity !== 'basic') {
suggestions.push({
type: 'quiz-1',
confidence: 0.7,
reasoning: 'Quiz reinforces memorization through active recall',
properties: this.generateQuizProperties(content, 'memorization')
});
}
break;
case 'comprehension':
// Start with content presentation
suggestions.push({
type: 'text-1',
confidence: 0.8,
reasoning: 'Clear text explanation establishes foundational understanding',
properties: this.generateTextProperties(content)
});
// Add visual demonstration if complex
if (context.contentComplexity !== 'basic' || content.includes('process') || content.includes('how')) {
suggestions.push({
type: 'video-1',
confidence: 0.85,
reasoning: 'Video demonstration enhances conceptual understanding',
properties: this.generateVideoProperties(content)
});
}
// Add interactive exploration for engagement
if (context.engagementLevel === 'high') {
suggestions.push({
type: 'hotspot-1',
confidence: 0.6,
reasoning: 'Interactive elements maintain engagement during concept learning',
properties: this.generateHotspotProperties(content)
});
}
break;
case 'assessment':
suggestions.push({
type: 'quiz-1',
confidence: 0.95,
reasoning: 'Multiple choice quiz directly addresses assessment needs',
properties: this.generateQuizProperties(content, 'assessment')
});
break;
case 'demonstration':
suggestions.push({
type: 'video-1',
confidence: 0.9,
reasoning: 'Video is ideal for demonstrating processes and procedures',
properties: this.generateVideoProperties(content)
});
// Add step-by-step breakdown if complex
if (content.includes('step') || content.includes('process')) {
suggestions.push({
type: 'list-1',
confidence: 0.7,
reasoning: 'Structured list breaks down complex procedures',
properties: this.generateListProperties(content)
});
}
break;
case 'exploration':
suggestions.push({
type: 'hotspot-1',
confidence: 0.8,
reasoning: 'Interactive hotspots enable exploratory learning',
properties: this.generateHotspotProperties(content)
});
break;
case 'practice':
suggestions.push({
type: 'flashcards-1',
confidence: 0.75,
reasoning: 'Flashcards provide structured practice opportunities',
properties: this.generateFlashcardProperties(content)
});
suggestions.push({
type: 'quiz-1',
confidence: 0.8,
reasoning: 'Quiz provides practice with immediate feedback',
properties: this.generateQuizProperties(content, 'practice')
});
break;
}
return suggestions.sort((a, b) => b.confidence - a.confidence);
}
private generateLearningSequence(chunks: ContentChunk[], context: EducationalContext): string[] {
const sequence: string[] = [];
// Apply educational best practices for sequencing
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const isLast = i === chunks.length - 1;
// Always start with introduction/overview
if (i === 0) {
sequence.push('head-1'); // Header
}
// Main content based on intent
switch (chunk.intent.type) {
case 'comprehension':
sequence.push('text-1');
if (context.contentComplexity !== 'basic') {
sequence.push('video-1');
}
break;
case 'memorization':
sequence.push('text-1'); // Brief explanation
sequence.push('flashcards-1'); // Main memorization tool
break;
case 'demonstration':
sequence.push('video-1');
sequence.push('text-1'); // Supporting explanation
break;
case 'assessment':
sequence.push('quiz-1');
break;
case 'exploration':
sequence.push('text-1');
sequence.push('hotspot-1');
break;
case 'practice':
sequence.push('flashcards-1');
sequence.push('quiz-1');
break;
}
// Add engagement elements at optimal intervals
if (i > 0 && i % 3 === 0 && context.engagementLevel === 'high') {
sequence.push('hotspot-1'); // Break up content with interaction
}
// Assessment after learning sequences
if (!isLast && (chunk.intent.type === 'comprehension' || chunk.intent.type === 'memorization')) {
const nextChunk = chunks[i + 1];
if (nextChunk.intent.type !== 'assessment') {
sequence.push('quiz-1'); // Knowledge check
}
}
}
// Final assessment if not already included
const lastElement = sequence[sequence.length - 1];
if (lastElement !== 'quiz-1' && context.learningObjectives.length > 0) {
sequence.push('quiz-1');
}
return sequence;
}
// Helper methods for pattern matching and scoring
private calculatePatternScore(text: string, patterns: RegExp[]): number {
let score = 0;
patterns.forEach(pattern => {
const matches = text.match(pattern);
if (matches) {
score += matches.length * 0.3;
}
});
return Math.min(score, 1.0);
}
private findMatchingPatterns(text: string, patterns: RegExp[]): string[] {
const matches: string[] = [];
patterns.forEach(pattern => {
const found = text.match(pattern);
if (found) {
matches.push(...found);
}
});
return matches;
}
private getElementsForIntent(intent: ContentIntent['type']): string[] {
const mapping = {
memorization: ['flashcards-1', 'quiz-1'],
comprehension: ['text-1', 'video-1', 'hotspot-1'],
assessment: ['quiz-1'],
demonstration: ['video-1', 'list-1'],
exploration: ['hotspot-1', 'gallery-1'],
practice: ['flashcards-1', 'quiz-1'],
};
return mapping[intent] || ['text-1'];
}
// Content chunking methods
private chunkContent(prompt: string): string[] {
// Split by paragraphs, sentences, or logical breaks
const chunks = prompt
.split(/\n\s*\n+/) // Double newlines
.filter(chunk => chunk.trim().length > 20) // Minimum chunk size
.map(chunk => chunk.trim());
// If no clear paragraphs, split by sentences for very long content
if (chunks.length === 1 && chunks[0].length > 500) {
return chunks[0]
.split(/[.!?]+/)
.filter(sentence => sentence.trim().length > 30)
.map(sentence => sentence.trim() + '.');
}
return chunks.length > 0 ? chunks : [prompt];
}
// Context analysis helper methods
private determineTargetAudience(text: string): EducationalContext['targetAudience'] {
if (text.includes('elementary') || text.includes('kids') || text.includes('children')) {
return 'elementary';
}
if (text.includes('middle school') || text.includes('junior')) {
return 'middle';
}
if (text.includes('high school') || text.includes('teenager')) {
return 'high';
}
if (text.includes('college') || text.includes('university') || text.includes('student')) {
return 'college';
}
if (text.includes('professional') || text.includes('workplace') || text.includes('corporate')) {
return 'professional';
}
return 'adult'; // Default
}
private assessComplexity(text: string): EducationalContext['contentComplexity'] {
const complexityIndicators = {
basic: ['simple', 'basic', 'introduction', 'overview', 'beginner'],
intermediate: ['intermediate', 'detailed', 'comprehensive', 'analysis'],
advanced: ['advanced', 'complex', 'sophisticated', 'expert', 'research'],
};
for (const [level, indicators] of Object.entries(complexityIndicators)) {
if (indicators.some(indicator => text.includes(indicator))) {
return level as EducationalContext['contentComplexity'];
}
}
// Assess by content length and vocabulary
if (text.length > 1000) return 'advanced';
if (text.length > 300) return 'intermediate';
return 'basic';
}
private extractLearningObjectives(prompt: string): string[] {
const objectives: string[] = [];
// Look for explicit objectives
const objectivePatterns = [
/students?\s+(?:will|should|must|need to)\s+([^.!?]+)/gi,
/(?:learn|understand|master|know)\s+([^.!?]+)/gi,
/objective[s]?[:]\s*([^.!?]+)/gi,
];
objectivePatterns.forEach(pattern => {
const matches = prompt.match(pattern);
if (matches) {
objectives.push(...matches.map(match => match.trim()));
}
});
return objectives.length > 0 ? objectives : ['Complete the learning content'];
}
private assessEngagementNeeds(text: string): EducationalContext['engagementLevel'] {
const highEngagementIndicators = ['interactive', 'engaging', 'fun', 'exciting', 'hands-on'];
const lowEngagementIndicators = ['formal', 'lecture', 'presentation', 'reading'];
if (highEngagementIndicators.some(indicator => text.includes(indicator))) {
return 'high';
}
if (lowEngagementIndicators.some(indicator => text.includes(indicator))) {
return 'low';
}
return 'medium';
}
private estimateContentDuration(prompt: string): number {
// Rough estimation: 200 words per minute reading, plus interaction time
const wordCount = prompt.split(/\s+/).length;
const baseTime = Math.ceil(wordCount / 200);
// Add time for interactive elements
const interactiveBonus = prompt.toLowerCase().includes('interactive') ? 10 : 0;
return Math.max(5, baseTime + interactiveBonus); // Minimum 5 minutes
}
// Property generation methods for different element types
private generateFlashcardProperties(content: string): Record<string, any> {
return {
cards: this.extractFlashcardPairs(content),
shuffle: true,
showProgress: true,
autoAdvance: false,
};
}
private generateQuizProperties(content: string, purpose: string): Record<string, any> {
return {
questions: this.extractQuizQuestions(content),
showFeedback: true,
randomizeOrder: purpose === 'practice',
allowRetries: purpose !== 'assessment',
passingScore: purpose === 'assessment' ? 80 : 60,
};
}
private generateVideoProperties(content: string): Record<string, any> {
return {
title: this.extractVideoTitle(content),
description: content.substring(0, 200) + '...',
placeholder: true, // User will provide actual video URL
autoplay: false,
controls: true,
};
}
private generateTextProperties(content: string): Record<string, any> {
return {
content: `<p>${content}</p>`,
formatting: 'paragraph',
readingLevel: 'standard',
};
}
private generateHotspotProperties(content: string): Record<string, any> {
return {
hotspots: this.extractInteractivePoints(content),
backgroundImage: 'placeholder.jpg',
title: 'Interactive Exploration',
};
}
private generateListProperties(content: string): Record<string, any> {
return {
items: this.extractListItems(content),
type: 'numbered',
style: 'standard',
};
}
// Content extraction helper methods
private extractFlashcardPairs(content: string): Array<{front: string, back: string}> {
// Simple extraction - can be enhanced with more sophisticated NLP
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 10);
const pairs: Array<{front: string, back: string}> = [];
for (let i = 0; i < sentences.length - 1; i += 2) {
if (sentences[i + 1]) {
pairs.push({
front: sentences[i].trim(),
back: sentences[i + 1].trim(),
});
}
}
return pairs.length > 0 ? pairs : [{
front: 'Key Concept',
back: content.substring(0, 100) + '...',
}];
}
private extractQuizQuestions(content: string): Array<{question: string, options: string[], correct: number}> {
// Generate questions based on content - simplified implementation
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 20);
const questions: Array<{question: string, options: string[], correct: number}> = [];
sentences.slice(0, 3).forEach((sentence, index) => {
questions.push({
question: `What is the main point about: ${sentence.trim()}?`,
options: [
'Option A (correct)',
'Option B',
'Option C',
'Option D',
],
correct: 0,
});
});
return questions.length > 0 ? questions : [{
question: 'What is the main topic of this content?',
options: ['Topic A', 'Topic B', 'Topic C', 'Topic D'],
correct: 0,
}];
}
private extractVideoTitle(content: string): string {
// Extract likely video title from content
const firstSentence = content.split(/[.!?]/)[0];
return firstSentence.length > 50
? firstSentence.substring(0, 50) + '...'
: firstSentence;
}
private extractInteractivePoints(content: string): Array<{x: number, y: number, title: string, content: string}> {
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 15);
const points: Array<{x: number, y: number, title: string, content: string}> = [];
sentences.slice(0, 3).forEach((sentence, index) => {
points.push({
x: 20 + (index * 25), // Distribute across image
y: 30 + (index * 20),
title: `Point ${index + 1}`,
content: sentence.trim(),
});
});
return points;
}
private extractListItems(content: string): string[] {
// Look for existing list items or create from sentences
const listPattern = /^[-•*]\s*(.+)$/gm;
const matches = content.match(listPattern);
if (matches) {
return matches.map(item => item.replace(/^[-•*]\s*/, '').trim());
}
// Create list from sentences if no explicit list found
return content
.split(/[.!?]+/)
.filter(s => s.trim().length > 10)
.slice(0, 5)
.map(s => s.trim());
}
}