Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os | |
| import io | |
| from google import generativeai as genai | |
| def process_exam_papers(question_paper, marking_scheme, answer_sheet, api_key, progress=gr.Progress()): | |
| """ | |
| Process uploaded exam papers and return transcription and grading | |
| """ | |
| if not api_key: | |
| return "Please provide a valid Gemini API key.", "", "Ready" | |
| if not all([question_paper, marking_scheme, answer_sheet]): | |
| return "Please upload all three files.", "", "Ready" | |
| try: | |
| # Configure Gemini API | |
| genai.configure(api_key=api_key) | |
| progress(0.1, desc="Uploading files to Gemini...") | |
| # Upload files to Gemini | |
| qp_file = genai.upload_file(path=question_paper.name, display_name="Question Paper") | |
| ms_file = genai.upload_file(path=marking_scheme.name, display_name="Marking Scheme") | |
| ans_file = genai.upload_file(path=answer_sheet.name, display_name="Answer Sheet") | |
| progress(0.3, desc="Files uploaded. Starting transcription...") | |
| # Transcription instructions | |
| transcription_instructions = """ | |
| Persona: | |
| You are an expert transcriptionist specializing in scientific and mathematical documents. Your primary goal is to convert handwritten mathematical work into a perfectly formatted, machine-readable Markdown document using LaTeX for all mathematical notation. | |
| Core Task: | |
| Your task is to transcribe the provided handwritten student solutions into a single, clean Markdown string. | |
| Key Directives & Rules: | |
| Absolute Fidelity: Transcribe exactly what is written. Do NOT correct mathematical errors, logical fallacies, or spelling mistakes. Your role is purely that of a scribe, not a grader or editor. | |
| LaTeX for All Math: All mathematical contentβincluding single variables, numbers in equations, fractions, exponents, roots, and symbolsβmust be enclosed in LaTeX delimiters. Use inline $ ... $ for math within text and block $$ ... $$ for standalone equations. | |
| Handle Strikethroughs: Completely ignore and omit any text, numbers, or expressions that have been struck through by the student. Do not include them in the final output. | |
| Preserve Structure: | |
| Use Markdown bolding (e.g., **1.**, **2a.**) to clearly separate each question or sub-part. | |
| Maintain the vertical, step-by-step flow of the student's derivations. For multi-line aligned equations, use the \\begin{align*} ... \\end{align*} environment within a $$ ... $$ block. | |
| Handle Ambiguity: If a character or symbol is genuinely illegible or ambiguous, make your best interpretation and enclose it in square brackets. For example, if a variable could be u or v, write [u?]. | |
| Output Format: | |
| The final output must be a single Markdown string. | |
| Ensure all LaTeX renders correctly and the structure is clean and readable. | |
| Comprehensive Example: | |
| If the student's handwritten work for a question looks like this: | |
| 7. Find the value of y. | |
| y = (xΒ² + 3) / 2 | |
| for x = 3 | |
| y = (3Β² + 3) / 2 | |
| y = (6+3) / 2 | |
| y = (9 + 3) / 2 | |
| y = 12 / 2 | |
| y = 6 | |
| Your expected output should be: | |
| **7.** | |
| Find the value of y. | |
| $$ | |
| y = \\frac{x^2 + 3}{2} | |
| $$ | |
| for $x = 3$ | |
| $$ | |
| \\begin{align*} | |
| y &= \\frac{3^2 + 3}{2} \\\\ | |
| y &= \\frac{9 + 3}{2} \\\\ | |
| y &= \\frac{12}{2} \\\\ | |
| y &= 6 | |
| \\end{align*} | |
| $$ | |
| """ | |
| # Initialize Gemini model for transcription | |
| model = genai.GenerativeModel( | |
| "gemini-2.5-pro", | |
| generation_config={"temperature": 0} | |
| ) | |
| progress(0.4, desc="Transcribing handwritten answers...") | |
| # Generate transcription | |
| response = model.generate_content([ | |
| transcription_instructions, | |
| ans_file | |
| ]) | |
| # Extract transcription safely | |
| student_transcription = getattr(response, "text", None) | |
| if not student_transcription: | |
| student_transcription = response.candidates[0].content.parts[0].text | |
| progress(0.7, desc="Transcription complete. Starting grading process...") | |
| # Return transcription first, then continue with grading | |
| yield student_transcription, "β³ Grading in progress...", "Grading" | |
| # Grading system instructions | |
| grading_system = """ | |
| Instructions to Examiners: | |
| Abbreviations: | |
| - M: Marks for correct Method. | |
| - A: Marks for Answer or Accuracy (often depends on preceding M mark). | |
| - R: Marks for clear Reasoning. | |
| - AG: Answer given in the question; no marks awarded. | |
| - FT: Follow Through; award marks for correct method/answer using incorrect earlier results. | |
| Marking Rules: | |
| 1. Always follow the markscheme annotations (M1, A2, etc.). | |
| 2. M marks must be earned before dependent A marks are awarded (no M0 followed by A1 unless explicitly allowed). | |
| 3. If M and A marks are on the same line (e.g., M1A1), M is for the method attempt, A is for correct values. | |
| 4. Multiple A marks on the same line are awarded independently unless otherwise noted. | |
| 5. Do not split M2, A3, etc. unless instructed. | |
| 6. "Show that" responses do not need to restate the AG line unless noted. | |
| 7. Once a correct answer is seen, ignore further incorrect working unless it affects a later part (then apply FT as appropriate). | |
| 8. Do not award the final A mark if an incorrect approximation is used in the same part. | |
| Error Avoidance: | |
| - **No incorrect mark allocation:** Do not award marks unless they are explicitly justified by the markscheme. | |
| - **No misclassification of errors:** Distinguish correctly between "Conceptual Errors" and "Silly Mistakes." | |
| - **Follow markscheme logic exactly:** Especially regarding when to withhold accuracy marks if method marks are not earned. | |
| """ | |
| # Now start grading using the transcribed text | |
| # Generate grading | |
| grading_response = model.generate_content([ | |
| f"You are an official examiner. Use the following grading system and rules to assess the answers:\n\n{grading_system}\n\n" | |
| "Your output must:\n" | |
| "1. Apply marks exactly as per the markscheme.\n" | |
| "2. Justify each awarded or withheld mark with reference to the grading rules.\n" | |
| "3. Identify and classify all errors accurately (Conceptual Error, Silly Mistake, or None).\n" | |
| "4. Follow the dependency between M and A marks strictly.\n" | |
| "5. Avoid giving marks that the markscheme does not allow.\n" | |
| "6. Provide a step-by-step reasoning for each mark awarded or withheld, explaining your thought process clearly.\n", | |
| qp_file, | |
| ms_file, | |
| student_transcription # Use the transcribed text, not the original PDF | |
| ]) | |
| progress(0.9, desc="Finalizing grading results...") | |
| # Extract grading safely | |
| grading_text = getattr(grading_response, "text", None) | |
| if not grading_text and grading_response.candidates: | |
| grading_text = grading_response.candidates[0].content.parts[0].text | |
| elif not grading_text: | |
| grading_text = "No Response" | |
| progress(1.0, desc="Complete!") | |
| # Return final results | |
| yield student_transcription, grading_text, "Complete" | |
| except Exception as e: | |
| yield f"Error processing files: {str(e)}", "", "Error" | |
| # Create Gradio interface | |
| with gr.Blocks(title="Exam Paper Grading System", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown(""" | |
| # π Automated Exam Paper Grading System | |
| Upload your question paper, marking scheme, and answer sheet to get automated transcription and grading using Google's Gemini AI. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### π Upload Files") | |
| api_key = gr.Textbox( | |
| label="Gemini API Key", | |
| placeholder="Enter your Google Gemini API key", | |
| type="password" | |
| ) | |
| question_paper = gr.File( | |
| label="Question Paper (PDF)", | |
| file_types=[".pdf"] | |
| ) | |
| marking_scheme = gr.File( | |
| label="Marking Scheme (PDF)", | |
| file_types=[".pdf"] | |
| ) | |
| answer_sheet = gr.File( | |
| label="Answer Sheet (PDF)", | |
| file_types=[".pdf"] | |
| ) | |
| process_btn = gr.Button( | |
| "π Process Papers", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### π Student Answer Transcription") | |
| transcription_output = gr.Textbox( | |
| label="Transcribed Answers", | |
| lines=15, | |
| max_lines=25, | |
| show_copy_button=True, | |
| placeholder="Transcribed answers will appear here first..." | |
| ) | |
| with gr.Column(): | |
| gr.Markdown("### β Grading Results") | |
| grading_output = gr.Textbox( | |
| label="Detailed Grading", | |
| lines=15, | |
| max_lines=25, | |
| show_copy_button=True, | |
| placeholder="Grading results will appear here after transcription is complete..." | |
| ) | |
| # Add status indicator | |
| with gr.Row(): | |
| status_display = gr.Textbox( | |
| label="Status", | |
| value="Ready", | |
| interactive=False, | |
| show_label=True | |
| ) | |
| # Set up the processing function | |
| process_btn.click( | |
| fn=process_exam_papers, | |
| inputs=[question_paper, marking_scheme, answer_sheet, api_key], | |
| outputs=[transcription_output, grading_output, status_display] | |
| ) | |
| gr.Markdown(""" | |
| ### π How to Use: | |
| 1. **Get a Gemini API Key**: Visit [Google AI Studio](https://makersuite.google.com/app/apikey) to get your free API key | |
| 2. **Upload PDFs**: Upload your question paper, marking scheme, and student answer sheet | |
| 3. **Process**: Click the "Process Papers" button to get transcription and grading | |
| 4. **Review**: Check the transcribed answers and detailed grading results | |
| ### β οΈ Notes: | |
| - All uploaded files are processed securely and not stored permanently | |
| - The system transcribes exactly what's written (including errors) for accurate grading | |
| - LaTeX mathematical notation is automatically formatted for clarity | |
| """) | |
| if __name__ == "__main__": | |
| demo.launch() |