Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import re | |
| import language_tool_python | |
| from typing import Dict, List, Tuple | |
| import random | |
| import nltk | |
| from nltk.tokenize import sent_tokenize, word_tokenize | |
| class CalibratedOntarioEssayGrader: | |
| def __init__(self): | |
| self.setup_nltk() | |
| self.setup_grammar_tool() | |
| self.setup_semantic_analyzers() | |
| self.setup_feedback_templates() | |
| def setup_nltk(self): | |
| try: | |
| nltk.download('punkt', quiet=True) | |
| except: | |
| pass | |
| def setup_grammar_tool(self): | |
| try: | |
| self.grammar_tool = language_tool_python.LanguageTool('en-US') | |
| self.grammar_enabled = True | |
| except: | |
| self.grammar_enabled = False | |
| def setup_semantic_analyzers(self): | |
| self.thesis_keywords = [ | |
| 'important', 'essential', 'crucial', 'significant', 'key', 'vital', | |
| 'necessary', 'valuable', 'beneficial', 'should', 'must', 'need to', | |
| 'critical', 'plays a role', 'contributes to', 'impacts', 'affects', | |
| 'influences', 'matters because', 'is important because' | |
| ] | |
| self.example_indicators = [ | |
| 'for example', 'for instance', 'such as', 'like when', 'as an example', | |
| 'specifically', 'including', 'case in point', 'to illustrate', | |
| 'as evidence', 'demonstrated by', 'shown by', 'evidenced by' | |
| ] | |
| self.analysis_indicators = [ | |
| 'because', 'this shows', 'therefore', 'as a result', 'thus', 'so', | |
| 'which means', 'this demonstrates', 'consequently', 'this indicates', | |
| 'this suggests', 'for this reason', 'due to', 'owing to', 'leads to', | |
| 'results in', 'implies that', 'suggests that', 'indicates that' | |
| ] | |
| self.insight_indicators = [ | |
| 'in my experience', 'from my perspective', 'personally', 'i have learned', | |
| 'this taught me', 'i realized', 'what this means for me', 'my understanding', | |
| 'this applies to', 'real-world application', 'in real life', 'this reminds me', | |
| 'similar to how', 'just like when', 'in my opinion', 'from my viewpoint', | |
| 'i believe that', 'i feel that', 'in my view' | |
| ] | |
| self.emotional_indicators = [ | |
| 'important', 'valuable', 'meaningful', 'significant', 'challenging', | |
| 'difficult', 'rewarding', 'inspiring', 'painful', 'confident', 'proud', | |
| 'grateful', 'frustrating', 'encouraging', 'motivating', 'impactful' | |
| ] | |
| def setup_feedback_templates(self): | |
| self.teacher_feedback_templates = { | |
| 'thesis': [ | |
| "Your main idea is clear, but try stating it more explicitly in the introduction", | |
| "The thesis is present but could be stronger with more specific language", | |
| "Good start on the main idea; consider making it more focused and direct" | |
| ], | |
| 'examples': [ | |
| "Your examples are relevant; try adding more specific details to strengthen them", | |
| "Good use of examples; consider including examples from different contexts", | |
| "The examples work well; now connect them more clearly to your main points" | |
| ], | |
| 'analysis': [ | |
| "You're explaining your points well; deepen the analysis by discussing the 'why'", | |
| "Good analysis; try to connect each example back to your thesis more explicitly", | |
| "Your explanation is clear; expand on how your examples prove your argument" | |
| ], | |
| 'structure': [ | |
| "The essay structure is logical; work on smoother transitions between paragraphs", | |
| "Good organization; ensure each paragraph has a clear topic sentence", | |
| "The structure works well; consider varying sentence structure for better flow" | |
| ], | |
| 'application': [ | |
| "You've made good real-world connections; add more personal reflection", | |
| "Good application; connect your examples more explicitly to broader life lessons", | |
| "The personal insights are valuable; expand on what this means to you personally" | |
| ] | |
| } | |
| def grade_essay(self, essay_text: str) -> Dict: | |
| if not essay_text or len(essay_text.strip()) < 100: | |
| return self.handle_short_essay(essay_text) | |
| stats = self.analyze_basic_stats(essay_text) | |
| structure = self.analyze_essay_structure_semantic(essay_text) | |
| content = self.analyze_essay_content_semantic(essay_text) | |
| grammar = self.check_grammar_errors(essay_text) | |
| application = self.analyze_personal_application_semantic(essay_text) | |
| score = self.calculate_calibrated_ontario_score(stats, structure, content, grammar, application) | |
| rubric_level = self.get_accurate_rubric_level(score) | |
| feedback = self.generate_ontario_teacher_feedback(score, rubric_level, stats, structure, content, grammar, application, essay_text) | |
| corrections = self.get_grammar_corrections(essay_text) | |
| return { | |
| "score": score, | |
| "rubric_level": rubric_level, | |
| "feedback": feedback, | |
| "corrections": corrections, | |
| "detailed_analysis": { | |
| "statistics": stats, | |
| "structure": structure, | |
| "content": content, | |
| "grammar": grammar, | |
| "application": application | |
| } | |
| } | |
| def analyze_basic_stats(self, text: str) -> Dict: | |
| words = text.split() | |
| sentences = [s.strip() for s in re.split(r'[.!?]+', text) if s.strip()] | |
| paragraphs = [p.strip() for p in text.split('\n\n') if p.strip()] | |
| avg_sentence_len = len(words) / max(1, len(sentences)) if sentences else 0 | |
| return { | |
| "word_count": len(words), | |
| "sentence_count": len(sentences), | |
| "paragraph_count": len(paragraphs), | |
| "avg_sentence_length": round(avg_sentence_len, 1) | |
| } | |
| def analyze_essay_structure_semantic(self, text: str) -> Dict: | |
| text_lower = text.lower() | |
| paragraphs = [p.strip() for p in text.split('\n\n') if p.strip()] | |
| intro_score = self.assess_introduction_quality_semantic(text, paragraphs) | |
| conclusion_score = self.assess_conclusion_quality_semantic(text, paragraphs) | |
| coherence_score = self.assess_paragraph_coherence_semantic(paragraphs) | |
| structure_score = (intro_score + conclusion_score + coherence_score) / 3 * 10 | |
| return { | |
| "score": min(10, structure_score), | |
| "has_introduction": intro_score >= 0.6, | |
| "has_conclusion": conclusion_score >= 0.6, | |
| "paragraph_count": len(paragraphs), | |
| "intro_quality": round(intro_score, 2), | |
| "conclusion_quality": round(conclusion_score, 2), | |
| "coherence_score": round(coherence_score, 2) | |
| } | |
| def assess_introduction_quality_semantic(self, text: str, paragraphs: List[str]) -> float: | |
| if not paragraphs: | |
| return 0.0 | |
| first_para = paragraphs[0].lower() | |
| score = 0.0 | |
| thesis_indicators = sum(1 for word in self.thesis_keywords if word in first_para) | |
| if thesis_indicators >= 2: | |
| score += 0.6 | |
| elif thesis_indicators >= 1: | |
| score += 0.4 | |
| topic_indicators = sum(1 for phrase in ['this essay', 'will discuss', 'i think', 'i believe', | |
| 'the purpose', 'topic', 'subject'] if phrase in first_para) | |
| if topic_indicators >= 1: | |
| score += 0.3 | |
| if len(first_para.split()) > 20: | |
| score += 0.4 | |
| elif len(first_para.split()) > 10: | |
| score += 0.2 | |
| if any(word in first_para for word in ['everyone', 'many', 'often', 'when', 'in today']): | |
| score += 0.2 | |
| return min(1.0, score) | |
| def assess_conclusion_quality_semantic(self, text: str, paragraphs: List[str]) -> float: | |
| if not paragraphs: | |
| return 0.0 | |
| last_para = paragraphs[-1].lower() | |
| score = 0.0 | |
| conclusion_phrases = sum(1 for phrase in ['in conclusion', 'in summary', 'to conclude', | |
| 'overall', 'finally', 'ultimately'] if phrase in last_para) | |
| if conclusion_phrases >= 1: | |
| score += 0.4 | |
| elif len(last_para.split()) > 15: | |
| score += 0.3 | |
| summary_indicators = sum(1 for word in ['therefore', 'thus', 'so', 'should', 'must', | |
| 'important', 'key', 'essential'] if word in last_para) | |
| if summary_indicators >= 2: | |
| score += 0.4 | |
| elif summary_indicators >= 1: | |
| score += 0.2 | |
| if any(word in last_para for word in ['learn', 'teach', 'show', 'demonstrate', 'understand']): | |
| score += 0.2 | |
| return min(1.0, score) | |
| def assess_paragraph_coherence_semantic(self, paragraphs: List[str]) -> float: | |
| if len(paragraphs) <= 1: | |
| return 0.5 | |
| transition_words = ['however', 'therefore', 'furthermore', 'additionally', | |
| 'moreover', 'consequently', 'nevertheless', 'thus', | |
| 'also', 'another', 'first', 'second', 'finally', 'next', | |
| 'in addition', 'on the other hand', 'for instance'] | |
| transition_count = 0 | |
| for para in paragraphs: | |
| para_lower = para.lower() | |
| transition_count += sum(1 for word in transition_words if word in para_lower) | |
| expected_transitions = max(1, (len(paragraphs) - 1) * 1.5) | |
| coherence_ratio = min(1.0, transition_count / expected_transitions) | |
| return coherence_ratio | |
| def analyze_essay_content_semantic(self, text: str) -> Dict: | |
| text_lower = text.lower() | |
| thesis_score = self.assess_thesis_presence_semantic(text) | |
| example_score, example_count = self.assess_examples_quality_semantic(text) | |
| analysis_score = self.assess_analysis_depth_semantic(text) | |
| content_score = (thesis_score + example_score + analysis_score) / 3 * 10 | |
| return { | |
| "score": min(10, content_score), | |
| "has_thesis": thesis_score >= 0.6, | |
| "example_count": example_count, | |
| "analysis_count": int(analysis_score * 5), | |
| "thesis_quality": round(thesis_score, 2), | |
| "example_quality": round(example_score, 2), | |
| "analysis_quality": round(analysis_score, 2) | |
| } | |
| def assess_thesis_presence_semantic(self, text: str) -> float: | |
| text_lower = text.lower() | |
| paragraphs = [p.strip() for p in text.split('\n\n') if p.strip()] | |
| if not paragraphs: | |
| return 0.0 | |
| first_para = paragraphs[0].lower() | |
| score = 0.0 | |
| thesis_indicators = sum(1 for word in self.thesis_keywords if word in first_para) | |
| if thesis_indicators >= 2: | |
| score += 0.6 | |
| elif thesis_indicators >= 1: | |
| score += 0.4 | |
| topic_indicators = sum(1 for phrase in ['this essay', 'will discuss', 'i think', | |
| 'i believe', 'the purpose'] if phrase in first_para) | |
| if topic_indicators >= 1: | |
| score += 0.3 | |
| supporting_evidence = 0 | |
| for para in paragraphs[1:]: | |
| para_lower = para.lower() | |
| supporting_evidence += sum(1 for word in self.thesis_keywords if word in para_lower) | |
| if supporting_evidence >= 2: | |
| score += 0.3 | |
| elif supporting_evidence >= 1: | |
| score += 0.2 | |
| return min(1.0, score) | |
| def assess_examples_quality_semantic(self, text: str) -> Tuple[float, int]: | |
| text_lower = text.lower() | |
| explicit_examples = sum(1 for phrase in self.example_indicators if phrase in text_lower) | |
| implicit_examples = 0 | |
| example_contexts = ['test', 'game', 'edison', 'invent', 'try', 'attempt', | |
| 'experience', 'student', 'teacher', 'school', 'work', | |
| 'life', 'society', 'people', 'when', 'where'] | |
| for context in example_contexts: | |
| if context in text_lower: | |
| implicit_examples += text_lower.count(context) * 0.3 | |
| total_examples = explicit_examples + implicit_examples | |
| example_quality = 0.0 | |
| if total_examples >= 4: | |
| example_quality = 1.0 | |
| elif total_examples >= 3: | |
| example_quality = 0.8 | |
| elif total_examples >= 2: | |
| example_quality = 0.6 | |
| elif total_examples >= 1: | |
| example_quality = 0.4 | |
| return example_quality, int(total_examples) | |
| def assess_analysis_depth_semantic(self, text: str) -> float: | |
| text_lower = text.lower() | |
| analysis_count = sum(1 for indicator in self.analysis_indicators if indicator in text_lower) | |
| explanation_quality = 0.0 | |
| if analysis_count >= 4: | |
| explanation_quality = 1.0 | |
| elif analysis_count >= 3: | |
| explanation_quality = 0.8 | |
| elif analysis_count >= 2: | |
| explanation_quality = 0.6 | |
| elif analysis_count >= 1: | |
| explanation_quality = 0.4 | |
| cause_effect = sum(1 for word in ['because', 'therefore', 'thus', 'as a result'] | |
| if word in text_lower) | |
| if cause_effect >= 2: | |
| explanation_quality = min(1.0, explanation_quality + 0.2) | |
| return explanation_quality | |
| def analyze_personal_application_semantic(self, text: str) -> Dict: | |
| text_lower = text.lower() | |
| insight_score = self.assess_personal_insight_semantic(text) | |
| real_world_score = self.assess_real_world_connections_semantic(text) | |
| lexical_score = self.assess_lexical_diversity_semantic(text) | |
| application_score = (insight_score + real_world_score + lexical_score) / 3 * 10 | |
| return { | |
| "score": min(10, application_score), | |
| "insight_score": round(insight_score, 2), | |
| "real_world_score": round(real_world_score, 2), | |
| "lexical_score": round(lexical_score, 2) | |
| } | |
| def assess_personal_insight_semantic(self, text: str) -> float: | |
| text_lower = text.lower() | |
| insight_count = sum(1 for indicator in self.insight_indicators if indicator in text_lower) | |
| emotional_count = sum(1 for word in self.emotional_indicators if word in text_lower) | |
| total_insight = insight_count + (emotional_count * 0.5) | |
| if total_insight >= 3: | |
| return 1.0 | |
| elif total_insight >= 2: | |
| return 0.8 | |
| elif total_insight >= 1: | |
| return 0.6 | |
| else: | |
| return 0.4 | |
| def assess_real_world_connections_semantic(self, text: str) -> float: | |
| text_lower = text.lower() | |
| real_world_indicators = sum(1 for word in ['real life', 'real world', 'society', | |
| 'everyday', 'experience', 'actual', 'today'] | |
| if word in text_lower) | |
| specific_contexts = sum(1 for word in ['test', 'game', 'edison', 'invent', | |
| 'school', 'work', 'life', 'world'] if word in text_lower) | |
| total_connections = real_world_indicators + (specific_contexts * 0.5) | |
| if total_connections >= 4: | |
| return 1.0 | |
| elif total_connections >= 3: | |
| return 0.8 | |
| elif total_connections >= 2: | |
| return 0.6 | |
| elif total_connections >= 1: | |
| return 0.4 | |
| else: | |
| return 0.3 | |
| def assess_lexical_diversity_semantic(self, text: str) -> float: | |
| words = text.lower().split() | |
| if not words: | |
| return 0.0 | |
| unique_words = set(words) | |
| lexical_diversity = len(unique_words) / len(words) | |
| sophisticated_words = sum(1 for word in words if len(word) > 7 and word.isalpha()) | |
| sophistication_ratio = sophisticated_words / len(words) | |
| combined_score = (lexical_diversity * 0.6) + (sophistication_ratio * 0.4) | |
| return min(1.0, combined_score) | |
| def check_grammar_errors(self, text: str) -> Dict: | |
| if not self.grammar_enabled: | |
| return {"error_count": 0, "score": 8} | |
| try: | |
| matches = self.grammar_tool.check(text) | |
| error_count = len(matches) | |
| if error_count == 0: | |
| grammar_score = 10 | |
| elif error_count <= 2: | |
| grammar_score = 9 | |
| elif error_count <= 5: | |
| grammar_score = 8 | |
| elif error_count <= 8: | |
| grammar_score = 7 | |
| else: | |
| grammar_score = 6 | |
| return { | |
| "error_count": error_count, | |
| "score": grammar_score | |
| } | |
| except: | |
| return {"error_count": 0, "score": 8} | |
| def get_grammar_corrections(self, text: str) -> List[Dict]: | |
| if not self.grammar_enabled: | |
| return [] | |
| try: | |
| matches = self.grammar_tool.check(text) | |
| corrections = [] | |
| for match in matches[:10]: | |
| if match.replacements: | |
| correction = { | |
| 'offset': match.offset, | |
| 'length': match.errorLength, | |
| 'original': text[match.offset:match.offset + match.errorLength], | |
| 'suggestion': match.replacements[0], | |
| 'message': match.message | |
| } | |
| corrections.append(correction) | |
| return corrections | |
| except: | |
| return [] | |
| def calculate_calibrated_ontario_score(self, stats: Dict, structure: Dict, content: Dict, | |
| grammar: Dict, application: Dict) -> int: | |
| weights = { | |
| 'content': 0.30, | |
| 'structure': 0.20, | |
| 'grammar': 0.15, | |
| 'application': 0.35 | |
| } | |
| base_score = ( | |
| content['score'] * weights['content'] * 10 + | |
| structure['score'] * weights['structure'] * 10 + | |
| grammar['score'] * weights['grammar'] * 10 + | |
| application['score'] * weights['application'] * 10 | |
| ) | |
| word_count = stats['word_count'] | |
| if word_count >= 320: | |
| length_bonus = 3 | |
| elif word_count >= 280: | |
| length_bonus = 2 | |
| elif word_count >= 240: | |
| length_bonus = 1 | |
| else: | |
| length_bonus = -1 | |
| calibration_factor = 1.1 | |
| final_score = (base_score + length_bonus) * calibration_factor | |
| if content['has_thesis'] and structure['has_introduction'] and grammar['score'] >= 8: | |
| final_score += 2 | |
| return max(65, min(95, int(final_score))) | |
| def get_accurate_rubric_level(self, score: int) -> Dict: | |
| if score >= 85: | |
| return {"level": "Level 4", "description": "Excellent - Exceeds Standards"} | |
| elif score >= 75: | |
| return {"level": "Level 3", "description": "Good - Meets Standards"} | |
| elif score >= 70: | |
| return {"level": "Level 2+", "description": "Developing - Approaching Standards"} | |
| elif score >= 65: | |
| return {"level": "Level 2", "description": "Developing - Basic Standards"} | |
| elif score >= 60: | |
| return {"level": "Level 1", "description": "Limited - Below Standards"} | |
| else: | |
| return {"level": "R", "description": "Remedial - Needs Significant Improvement"} | |
| def generate_ontario_teacher_feedback(self, score: int, rubric: Dict, stats: Dict, | |
| structure: Dict, content: Dict, grammar: Dict, | |
| application: Dict, essay_text: str) -> List[str]: | |
| feedback = [] | |
| feedback.append(f"Overall Score: {score}/100") | |
| feedback.append(f"Ontario Level: {rubric['level']} - {rubric['description']}") | |
| feedback.append("") | |
| strengths = self.identify_strengths_semantic(structure, content, grammar, application, stats) | |
| improvements = self.identify_improvements_semantic(structure, content, grammar, application, stats, essay_text) | |
| feedback.append("✅ STRENGTHS:") | |
| for strength in strengths: | |
| feedback.append(f"• {strength}") | |
| if not strengths: | |
| feedback.append("• Building a solid foundation for essay writing") | |
| feedback.append("") | |
| feedback.append("📝 AREAS TO IMPROVE:") | |
| for improvement in improvements: | |
| feedback.append(f"• {improvement}") | |
| if not improvements: | |
| feedback.append("• Excellent work - continue developing your skills") | |
| feedback.append("") | |
| feedback.append("👨🏫 TEACHER'S VOICE:") | |
| teacher_comments = self.generate_teacher_comments_semantic(structure, content, application, essay_text) | |
| for comment in teacher_comments: | |
| feedback.append(f" {comment}") | |
| feedback.append("") | |
| feedback.append("🎯 NEXT STEPS:") | |
| next_steps = self.get_ontario_next_steps(score, structure, content, application) | |
| for step in next_steps: | |
| feedback.append(f"• {step}") | |
| return feedback | |
| def identify_strengths_semantic(self, structure: Dict, content: Dict, grammar: Dict, | |
| application: Dict, stats: Dict) -> List[str]: | |
| strengths = [] | |
| if content['thesis_quality'] >= 0.7: | |
| strengths.append("Clear main idea and thesis development") | |
| elif content['thesis_quality'] >= 0.5: | |
| strengths.append("Good attempt at establishing a main idea") | |
| if content['example_count'] >= 3: | |
| strengths.append("Strong use of supporting examples") | |
| elif content['example_count'] >= 2: | |
| strengths.append("Good examples to support your points") | |
| if structure['intro_quality'] >= 0.7: | |
| strengths.append("Effective introduction that engages the reader") | |
| if structure['conclusion_quality'] >= 0.7: | |
| strengths.append("Strong conclusion that summarizes key points") | |
| if grammar['score'] >= 9: | |
| strengths.append("Excellent grammar and sentence structure") | |
| elif grammar['score'] >= 8: | |
| strengths.append("Good control of grammar and mechanics") | |
| if application['score'] >= 8: | |
| strengths.append("Strong personal insight and real-world connections") | |
| elif application['score'] >= 6: | |
| strengths.append("Good attempt at personal application") | |
| if stats['word_count'] >= 300: | |
| strengths.append("Appropriate length for thorough development") | |
| return strengths | |
| def identify_improvements_semantic(self, structure: Dict, content: Dict, grammar: Dict, | |
| application: Dict, stats: Dict, essay_text: str) -> List[str]: | |
| improvements = [] | |
| if content['thesis_quality'] < 0.6: | |
| improvements.append("Strengthen your thesis statement in the introduction") | |
| if content['example_count'] < 2: | |
| improvements.append("Add more specific examples to support each main point") | |
| if content['analysis_quality'] < 0.6: | |
| improvements.append("Deepen your analysis by explaining how examples prove your points") | |
| if structure['intro_quality'] < 0.6: | |
| improvements.append("Work on creating a more engaging introduction") | |
| if structure['conclusion_quality'] < 0.6: | |
| improvements.append("Develop a stronger conclusion that reinforces your main idea") | |
| if structure['coherence_score'] < 0.5: | |
| improvements.append("Improve transitions between paragraphs for better flow") | |
| if grammar['error_count'] > 3: | |
| improvements.append(f"Proofread to address {grammar['error_count']} grammar issues") | |
| if application['score'] < 6: | |
| improvements.append("Add more personal reflection and real-world connections") | |
| if stats['word_count'] < 280: | |
| improvements.append("Develop your ideas more fully with additional details") | |
| return improvements | |
| def generate_teacher_comments_semantic(self, structure: Dict, content: Dict, | |
| application: Dict, essay_text: str) -> List[str]: | |
| comments = [] | |
| if content['example_count'] < 3: | |
| comments.append(random.choice(self.teacher_feedback_templates['examples'])) | |
| if content['analysis_quality'] < 0.7: | |
| comments.append(random.choice(self.teacher_feedback_templates['analysis'])) | |
| if application['score'] < 7: | |
| comments.append(random.choice(self.teacher_feedback_templates['application'])) | |
| if structure['coherence_score'] < 0.6: | |
| comments.append(random.choice(self.teacher_feedback_templates['structure'])) | |
| if not comments: | |
| comments.append("Excellent work! Your essay demonstrates strong understanding and organization.") | |
| comments.append("Continue developing your analytical skills and personal voice.") | |
| return comments | |
| def get_ontario_next_steps(self, score: int, structure: Dict, content: Dict, | |
| application: Dict) -> List[str]: | |
| next_steps = [] | |
| if score >= 80: | |
| next_steps.extend([ | |
| "Focus on more sophisticated vocabulary and complex sentence structures", | |
| "Develop deeper analytical insights and unique perspectives", | |
| "Experiment with different organizational patterns" | |
| ]) | |
| elif score >= 70: | |
| next_steps.extend([ | |
| "Strengthen thesis development and supporting evidence", | |
| "Improve paragraph transitions and overall coherence", | |
| "Add more personal reflection and real-world applications" | |
| ]) | |
| else: | |
| next_steps.extend([ | |
| "Practice writing clear thesis statements", | |
| "Work on including 2-3 specific examples per main point", | |
| "Focus on basic paragraph structure and organization" | |
| ]) | |
| return next_steps | |
| def handle_short_essay(self, text: str) -> Dict: | |
| word_count = len(text.split()) if text else 0 | |
| return { | |
| "score": max(60, min(75, word_count)), | |
| "rubric_level": self.get_accurate_rubric_level(max(60, min(75, word_count))), | |
| "feedback": [ | |
| "Essay is too short for full assessment", | |
| f"Current length: {word_count} words", | |
| "Recommended length: 250-500 words for Ontario high school essays", | |
| "", | |
| "Please expand your essay with:", | |
| "- A clear introduction with main idea", | |
| "- 3-5 developed paragraphs with specific examples", | |
| "- Analysis explaining how examples support your points", | |
| "- A conclusion that summarizes your argument" | |
| ], | |
| "corrections": [], | |
| "detailed_analysis": { | |
| "statistics": {"word_count": word_count}, | |
| "structure": {"score": 5}, | |
| "content": {"score": 5}, | |
| "grammar": {"score": 8}, | |
| "application": {"score": 5} | |
| } | |
| } | |
| def apply_corrections(self, text: str, corrections: List[Dict]) -> str: | |
| if not corrections: | |
| return text | |
| corrected_text = text | |
| for correction in sorted(corrections, key=lambda x: x['offset'], reverse=True): | |
| start = correction['offset'] | |
| end = correction['offset'] + correction['length'] | |
| corrected_text = corrected_text[:start] + correction['suggestion'] + corrected_text[end:] | |
| return corrected_text | |
| def create_calibrated_grading_interface(): | |
| grader = CalibratedOntarioEssayGrader() | |
| def analyze_essay(essay_text): | |
| if not essay_text.strip(): | |
| return "Please enter an essay to analyze.", "", "" | |
| result = grader.grade_essay(essay_text) | |
| feedback = result['feedback'] | |
| corrections = result['corrections'] | |
| corrected_essay = grader.apply_corrections(essay_text, corrections) | |
| score_color = "#e74c3c" | |
| if result['score'] >= 85: | |
| score_color = "#27ae60" | |
| elif result['score'] >= 75: | |
| score_color = "#2ecc71" | |
| elif result['score'] >= 70: | |
| score_color = "#f39c12" | |
| elif result['score'] >= 65: | |
| score_color = "#e67e22" | |
| assessment_html = f""" | |
| <div style="font-family: Arial, sans-serif; max-width: 1000px; margin: 0 auto;"> | |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px; border-radius: 15px; color: white; text-align: center; margin-bottom: 20px;"> | |
| <h1 style="margin: 0 0 10px 0; font-size: 2.2em;">Calibrated Ontario Essay Assessment</h1> | |
| <p style="margin: 0; opacity: 0.9; font-size: 1.1em;">Accurate Ontario Standards • Semantic Analysis • Teacher-Approved</p> | |
| </div> | |
| <div style="display: grid; grid-template-columns: 1fr 2fr; gap: 20px; margin-bottom: 20px;"> | |
| <div style="background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); text-align: center;"> | |
| <div style="font-size: 3.5em; font-weight: bold; color: {score_color}; margin-bottom: 10px;"> | |
| {result['score']}/100 | |
| </div> | |
| <div style="font-size: 1.4em; font-weight: bold; color: #2c3e50; margin-bottom: 5px;"> | |
| {result['rubric_level']['level']} | |
| </div> | |
| <div style="color: #7f8c8d; font-size: 1em;"> | |
| {result['rubric_level']['description']} | |
| </div> | |
| </div> | |
| <div style="background: white; padding: 25px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);"> | |
| <h3 style="margin-top: 0; color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px;">Comprehensive Feedback</h3> | |
| <div style="max-height: 400px; overflow-y: auto; line-height: 1.6;"> | |
| {''.join([f'<p style="margin: 10px 0;">{line}</p>' for line in feedback])} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| return assessment_html, corrected_essay, f"Found {len(corrections)} suggested corrections" | |
| with gr.Blocks(title="Calibrated Ontario Essay Grader", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# 🎓 Calibrated Ontario Essay Grader") | |
| gr.Markdown("### Professional Essay Assessment Based on Accurate Ontario Standards") | |
| gr.Markdown("*Level 4 = 80+ Points • Real-time Corrections • Teacher-style Feedback*") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| essay_input = gr.Textbox( | |
| label="Paste your essay here", | |
| placeholder="Enter your essay text (250-500 words is typical for high school)...", | |
| lines=12 | |
| ) | |
| with gr.Row(): | |
| grade_btn = gr.Button("Analyze Essay", variant="primary") | |
| clear_btn = gr.Button("Clear") | |
| with gr.Column(scale=1): | |
| output = gr.HTML() | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### Corrected Version") | |
| corrected_output = gr.Textbox( | |
| lines=8, | |
| interactive=True, | |
| show_copy_button=True | |
| ) | |
| with gr.Column(): | |
| gr.Markdown("### Correction Details") | |
| correction_info = gr.Markdown() | |
| grade_btn.click( | |
| analyze_essay, | |
| inputs=[essay_input], | |
| outputs=[output, corrected_output, correction_info] | |
| ) | |
| clear_btn.click( | |
| lambda: ("", "", "", ""), | |
| outputs=[essay_input, output, corrected_output, correction_info] | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| demo = create_calibrated_grading_interface() | |
| demo.launch(server_name="0.0.0.0", server_port=7860) | |