# app.py - Essay Grader AI with Award List import gradio as gr import logging import os from datetime import datetime from docx import Document from docx.shared import Pt, RGBColor, Inches from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.oxml.ns import qn from docx.oxml import OxmlElement logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) os.makedirs('outputs', exist_ok=True) try: from modules.groq_client import GroqGrader from modules.report_generator import PDFReportGenerator from modules.firebase_manager import FirebaseManager from modules.file_processor import FileProcessor from modules.utils import format_results, validate_inputs, get_performance_label except ImportError as e: logger.error(f"Failed to import modules: {e}") raise grader = None pdf_gen = None firebase = None try: grader = GroqGrader() logger.info("✓ GroqGrader initialized successfully") except Exception as e: logger.error(f"✗ Failed to initialize GroqGrader: {e}") try: pdf_gen = PDFReportGenerator() logger.info("✓ PDFReportGenerator initialized successfully") except Exception as e: logger.error(f"✗ Failed to initialize PDFReportGenerator: {e}") try: firebase = FirebaseManager() if firebase.enabled: logger.info("✓ FirebaseManager initialized successfully") else: logger.warning("○ Firebase not enabled (optional)") except Exception as e: logger.warning(f"○ Firebase not initialized: {e}") file_processor = FileProcessor() current_user = {"id": "demo_user", "subscription": "free"} # Global storage for class results class_results = [] def create_word_report(student_name: str, grade_level: int, essay_type: str, result: dict, word_count: int) -> str: """Create individual MS Word report""" try: doc = Document() title = doc.add_heading('Essay Grading Report', 0) title.alignment = WD_ALIGN_PARAGRAPH.CENTER date_para = doc.add_paragraph(datetime.now().strftime("%B %d, %Y")) date_para.alignment = WD_ALIGN_PARAGRAPH.CENTER doc.add_paragraph() doc.add_heading('Student Details', 1) doc.add_paragraph(f"Name: {student_name}") doc.add_paragraph(f"Grade: {grade_level}") doc.add_paragraph(f"Essay Type: {essay_type}") doc.add_paragraph(f"Word Count: {word_count}") doc.add_paragraph() doc.add_heading('Total Score', 1) score = result['total_score'] label, _, _ = get_performance_label(score) score_para = doc.add_paragraph(f"{score}/100 - Grade: {result['grade']} - {label}") score_para.runs[0].font.size = Pt(24) score_para.runs[0].font.bold = True doc.add_paragraph() doc.add_heading('Detailed Scores', 1) table = doc.add_table(rows=5, cols=4) table.style = 'Light Grid Accent 1' headers = table.rows[0].cells headers[0].text = 'Category' headers[1].text = 'Score' headers[2].text = 'Maximum' headers[3].text = 'Percentage' categories = [ ('Content & Arguments', result['scores']['content'], 40), ('Organization', result['scores']['organization'], 20), ('Language & Style', result['scores']['language'], 20), ('Grammar & Spelling', result['scores']['grammar'], 20) ] for i, (name, score_val, max_score) in enumerate(categories, 1): row = table.rows[i].cells row[0].text = name row[1].text = str(score_val) row[2].text = str(max_score) row[3].text = f"{(score_val/max_score*100):.1f}%" doc.add_paragraph() # Structure Analysis if 'structure_analysis' in result: doc.add_heading('Structure Analysis', 1) struct = result['structure_analysis'] doc.add_paragraph(f"Introduction: {'✓ Present' if struct.get('has_introduction') else '✗ Missing'}") doc.add_paragraph(f"Body Paragraphs: {struct.get('body_paragraphs_count', 0)}") doc.add_paragraph(f"Transitions: {'✓ Good' if struct.get('has_transitions') else '✗ Weak'}") doc.add_paragraph(f"Conclusion: {'✓ Present' if struct.get('has_conclusion') else '✗ Missing'}") if struct.get('structure_feedback'): doc.add_paragraph(f"Feedback: {struct['structure_feedback']}") doc.add_paragraph() doc.add_heading('Strengths', 1) for strength in result.get('strengths', []): doc.add_paragraph(strength, style='List Bullet') doc.add_paragraph() doc.add_heading('Areas for Improvement', 1) for weakness in result.get('weaknesses', []): p = doc.add_paragraph(style='List Bullet') p.add_run(weakness.get('issue', '')).bold = True if 'suggestion' in weakness: p.add_run(f"\nSuggestion: {weakness['suggestion']}") doc.add_paragraph() doc.add_heading('Overall Feedback', 1) doc.add_paragraph(result.get('overall_feedback', '')) doc.add_paragraph() footer = doc.add_paragraph("Generated by Essay Grader AI | Developed by: Najaf Ali Sharqi") footer.alignment = WD_ALIGN_PARAGRAPH.CENTER footer.runs[0].font.size = Pt(9) footer.runs[0].font.color.rgb = RGBColor(128, 128, 128) filename = f"report_{student_name.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx" output_path = os.path.join('outputs', filename) doc.save(output_path) logger.info(f"Word report created: {output_path}") return output_path except Exception as e: logger.error(f"Word report creation failed: {e}") return None def create_award_list(institution_name: str, grade_level: int, subject: str) -> str: """Create class award list in MS Word""" try: if not class_results: return None doc = Document() # Institution Name - Bold, Center, Top title = doc.add_heading(institution_name, 0) title.alignment = WD_ALIGN_PARAGRAPH.CENTER title.runs[0].bold = True title.runs[0].font.size = Pt(18) # Subtitle subtitle = doc.add_paragraph(f"Essay Grading Award List") subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER subtitle.runs[0].font.size = Pt(14) subtitle.runs[0].bold = True doc.add_paragraph() # Details details = doc.add_paragraph() details.add_run(f"Grade: {grade_level} ").bold = True details.add_run(f"Subject: {subject} ").bold = True details.add_run(f"Date: {datetime.now().strftime('%B %d, %Y')}").bold = True doc.add_paragraph() # Create table - 5 columns only (removed Feedback Summary) num_students = len(class_results) table = doc.add_table(rows=num_students + 1, cols=5) table.style = 'Light Grid Accent 1' # Headers headers = table.rows[0].cells headers[0].text = 'S.No' headers[1].text = 'Student Name' headers[2].text = 'Score' headers[3].text = 'Grade' headers[4].text = 'Performance' # Make headers bold for cell in headers: cell.paragraphs[0].runs[0].bold = True cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER # Sort by score (highest first) sorted_results = sorted(class_results, key=lambda x: x['result']['total_score'], reverse=True) # Fill data for i, entry in enumerate(sorted_results, 1): row = table.rows[i].cells result = entry['result'] score = result['total_score'] label, _, _ = get_performance_label(score) row[0].text = str(i) row[0].paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER row[1].text = entry['student_name'] row[2].text = f"{score}/100" row[2].paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER row[3].text = result['grade'] row[3].paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER row[4].text = label row[4].paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER # Add space for signature doc.add_paragraph() doc.add_paragraph() doc.add_paragraph() doc.add_paragraph() signature = doc.add_paragraph() signature.add_run("_" * 30) signature.add_run("\n") signature.add_run("Subject Teacher's Signature").bold = True signature.add_run("\n") signature.add_run(f"Date: {datetime.now().strftime('%B %d, %Y')}") # Footer doc.add_paragraph() footer = doc.add_paragraph("Generated by Essay Grader AI | Developed by: Najaf Ali Sharqi") footer.alignment = WD_ALIGN_PARAGRAPH.CENTER footer.runs[0].font.size = Pt(8) footer.runs[0].font.color.rgb = RGBColor(128, 128, 128) # Save filename = f"award_list_{institution_name.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx" output_path = os.path.join('outputs', filename) doc.save(output_path) logger.info(f"Award list created: {output_path}") return output_path except Exception as e: logger.error(f"Award list creation failed: {e}") return None def process_essay(student_name: str, grade_level: int, language: str, essay_type: str, essay_text: str, essay_file, add_to_class: bool) -> tuple: try: logger.info("=== Starting essay processing ===") error = validate_inputs(student_name, essay_text, essay_file) if error: logger.warning(f"Validation failed: {error}") return error, None, None, "", "" if essay_file is not None: logger.info(f"Processing file: {essay_file.name}") try: text = file_processor.extract_text(essay_file.name) logger.info(f"Extracted {len(text)} characters from file") if not text or len(text.strip()) < 10: return "❌ فائل سے متن نہیں نکل سکا۔ براہ کرم:\n\n1. واضح تصویر/scan استعمال کریں\n2. یا متن خود ٹائپ کریں", None, None, "", "" except Exception as e: logger.error(f"File processing error: {e}") return f"❌ فائل میں خرابی: {str(e)}", None, None, "", "" else: text = essay_text logger.info(f"Using direct text input: {len(text)} characters") word_count = len(text.split()) logger.info(f"Word count: {word_count}") if word_count < 50: return f"❌ مضمون بہت چھوٹا ہے ({word_count} الفاظ)۔ کم از کم 50 الفاظ درکار ہیں۔", None, None, "", "" if word_count > 5000: return f"❌ مضمون بہت لمبا ہے ({word_count} الفاظ)۔ زیادہ سے زیادہ 5000 الفاظ۔", None, None, "", "" if firebase and firebase.enabled: within_limit, used, remaining = firebase.check_monthly_limit(current_user['id'], current_user['subscription']) if not within_limit: return f"❌ آپ کی ماہانہ حد ({used}) ختم ہو گئی ہے۔", None, None, "", "" else: used, remaining = 0, 999 if not grader: return "❌ Groq API دستیاب نہیں ہے۔ GROQ_API_KEY شامل کریں Settings میں۔", None, None, "", "" logger.info(f"Calling Groq API for grading...") lang_code = "urdu" if language == "اردو" else "english" try: result = grader.grade_essay(essay_text=text, language=lang_code, grade_level=grade_level, essay_type=essay_type) logger.info(f"✓ Grading successful: {result.get('total_score', 0)}/100") except Exception as e: logger.error(f"✗ Groq grading error: {e}") return f"❌ AI جانچ میں خرابی:\n\n{str(e)}", None, None, "", "" # Add to class results if checkbox is selected if add_to_class: class_results.append({ 'student_name': student_name, 'grade_level': grade_level, 'essay_type': essay_type, 'word_count': word_count, 'result': result }) logger.info(f"Added to class results. Total students: {len(class_results)}") logger.info("Formatting results...") output_md = format_results(student_name=student_name, grade_level=grade_level, essay_type=essay_type, result=result, word_count=word_count) pdf_path = None word_path = None if pdf_gen: try: logger.info("Creating PDF report...") pdf_filename = f"report_{student_name.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf" pdf_path = os.path.join('outputs', pdf_filename) pdf_gen.create_report(student_name=student_name, grade_level=grade_level, essay_type=essay_type, grading_result=result, output_path=pdf_path) logger.info(f"✓ PDF created: {pdf_path}") except Exception as e: logger.error(f"✗ PDF generation error: {e}") try: logger.info("Creating Word report...") word_path = create_word_report(student_name, grade_level, essay_type, result, word_count) if word_path: logger.info(f"✓ Word created: {word_path}") except Exception as e: logger.error(f"✗ Word generation error: {e}") if firebase and firebase.enabled: try: essay_data = { 'student_name': student_name, 'grade_level': grade_level, 'essay_type': essay_type, 'language': lang_code, 'word_count': word_count, 'text': text[:1000] } firebase.save_essay_result(user_id=current_user['id'], essay_data=essay_data, grading_result=result) logger.info("✓ Essay saved to Firebase") except Exception as e: logger.error(f"✗ Firebase save error: {e}") limit_msg = f"📊 استعمال: {used + 1}, باقی: {remaining - 1}" class_msg = f"📚 کلاس میں طلبا: {len(class_results)}" if add_to_class else "" logger.info("=== Essay processing completed successfully ===") return output_md, pdf_path, word_path, limit_msg, class_msg except Exception as e: logger.exception("✗ Unexpected error in process_essay") return f"❌ غیر متوقع خرابی:\n\n{str(e)}", None, None, "", "" def generate_award_list_handler(institution_name: str, grade_level: int, subject: str): """Handle award list generation""" try: if not institution_name or not institution_name.strip(): return "❌ ادارے کا نام درکار ہے | Institution name required", None if not subject or not subject.strip(): return "❌ مضمون کا نام درکار ہے | Subject name required", None if not class_results: return "❌ کوئی طالب علم شامل نہیں۔ پہلے مضامین جانچیں اور 'Add to Class Award List' چیک کریں۔", None award_path = create_award_list(institution_name, grade_level, subject) if award_path: return f"✅ Award List بن گئی! کل طلبا: {len(class_results)}", award_path else: return "❌ Award List بنانے میں خرابی", None except Exception as e: logger.error(f"Award list generation error: {e}") return f"❌ خرابی: {str(e)}", None def clear_form(): """Clear all form inputs""" return "", None, "", "" def reset_class_list(): """Reset class results""" global class_results class_results = [] return f"✅ کلاس کی فہرست صاف ہو گئی | Class list cleared" app = gr.Blocks(title="Essay Grader AI") with app: gr.HTML("""
Professional AI-Powered Essay Grading System with OCR & Award List
AI Model: Groq (Llama-3.1-70B) | Framework: Gradio | OCR: Tesseract
Powered by Groq AI • Made with ❤️ for Teachers and Students
Developed by: Najaf Ali Sharqi
© 2024 Essay Grader AI • GB Gen AI Project