Files changed (2) hide show
  1. app.py +244 -79
  2. requirements.txt +4 -4
app.py CHANGED
@@ -1,87 +1,252 @@
1
- import os
2
  import gradio as gr
3
- import google.generativeai as genai
4
-
5
- # Configure Gemini API (key must be set in Hugging Face Space secrets)
6
- genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
7
-
8
- # ---------- PROMPTS ----------
9
- TRANSCRIPTION_PROMPT = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  Persona:
11
- You are an expert transcriptionist specializing in scientific and mathematical documents.
12
- Your primary goal is to convert handwritten mathematical work into a perfectly formatted,
13
- machine-readable Markdown document using LaTeX for all mathematical notation.
14
-
15
- Rules:
16
- - Transcribe exactly what is written, do not correct errors.
17
- - Use $...$ for inline math, $$...$$ for block math.
18
- - Ignore struck-through text.
19
- - Preserve structure: bold for Q numbers (**1.**), step-by-step math with \\begin{align*}.
20
- - If a symbol is ambiguous, mark as [x?].
21
- Output must be a clean Markdown string.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  """
23
-
24
- GRADING_PROMPT = """
25
- You are an official examiner. Grade the student transcription using the question paper
26
- and the official marking scheme.
27
-
28
- Rules:
29
- 1. Apply marks exactly as per the markscheme (M1, A1, etc.).
30
- 2. M marks must be earned before A marks.
31
- 3. Justify each awarded or withheld mark with clear reasoning.
32
- 4. Classify all errors as Conceptual Error, Silly Mistake, or None.
33
- 5. Follow dependency between M and A strictly.
34
- 6. Do not give marks outside the markscheme.
35
- Output must be a structured grading report with reasoning.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  """
37
-
38
- # ---------- STEP 1: TRANSCRIPTION ----------
39
- def transcribe(ans_file):
40
- try:
41
- ans_uploaded = genai.upload_file(path=ans_file.name, display_name="Answer Sheet")
42
-
43
- model = genai.GenerativeModel("gemini-2.5-pro", generation_config={"temperature": 0})
44
- resp = model.generate_content([TRANSCRIPTION_PROMPT, ans_uploaded])
45
-
46
- transcription = getattr(resp, "text", None) or resp.candidates[0].content.parts[0].text
47
- return transcription
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  except Exception as e:
49
- return f"❌ Error during transcription: {e}"
50
-
51
- # ---------- STEP 2: GRADING ----------
52
- def grade(qp_file, ms_file, transcription):
53
- try:
54
- qp_uploaded = genai.upload_file(path=qp_file.name, display_name="Question Paper")
55
- ms_uploaded = genai.upload_file(path=ms_file.name, display_name="Marking Scheme")
56
-
57
- model = genai.GenerativeModel("gemini-2.5-pro", generation_config={"temperature": 0})
58
- resp = model.generate_content([GRADING_PROMPT, qp_uploaded, ms_uploaded, transcription])
59
-
60
- grading = getattr(resp, "text", None) or resp.candidates[0].content.parts[0].text
61
- return grading
62
- except Exception as e:
63
- return f"❌ Error during grading: {e}"
64
-
65
- # ---------- GRADIO APP ----------
66
- with gr.Blocks(title="πŸ“˜ AI Teacher Assistant") as demo:
67
- gr.Markdown("## πŸ“˜ AI Teacher Assistant\nUpload exam documents to transcribe and grade student answers step by step.")
68
-
69
  with gr.Row():
70
- qp_file = gr.File(label="Upload Question Paper (PDF)", type="filepath")
71
- ms_file = gr.File(label="Upload Mark Scheme (PDF)", type="filepath")
72
- ans_file = gr.File(label="Upload Student Answer Sheet (PDF)", type="filepath")
73
-
74
- # Step 1: Transcription
75
- transcribe_btn = gr.Button("Step 1: Transcribe Answer Sheet")
76
- transcription_out = gr.Markdown(label="πŸ“„ Student Transcription")
77
-
78
- # Step 2: Grading
79
- grade_btn = gr.Button("Step 2: Grade the Student")
80
- grading_out = gr.Textbox(label="βœ… Grading Report (Step-by-Step)", lines=20)
81
-
82
- # Button Logic
83
- transcribe_btn.click(fn=transcribe, inputs=[ans_file], outputs=[transcription_out])
84
- grade_btn.click(fn=grade, inputs=[qp_file, ms_file, transcription_out], outputs=[grading_out])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
  if __name__ == "__main__":
87
- demo.launch()
 
 
1
  import gradio as gr
2
+ import os
3
+ import io
4
+ from google import generativeai as genai
5
+
6
+ def process_exam_papers(question_paper, marking_scheme, answer_sheet, api_key, progress=gr.Progress()):
7
+ """
8
+ Process uploaded exam papers and return transcription and grading
9
+ """
10
+ if not api_key:
11
+ return "Please provide a valid Gemini API key.", "", "Ready"
12
+
13
+ if not all([question_paper, marking_scheme, answer_sheet]):
14
+ return "Please upload all three files.", "", "Ready"
15
+
16
+ try:
17
+ # Configure Gemini API
18
+ genai.configure(api_key=api_key)
19
+
20
+ progress(0.1, desc="Uploading files to Gemini...")
21
+
22
+ # Upload files to Gemini
23
+ qp_file = genai.upload_file(path=question_paper.name, display_name="Question Paper")
24
+ ms_file = genai.upload_file(path=marking_scheme.name, display_name="Marking Scheme")
25
+ ans_file = genai.upload_file(path=answer_sheet.name, display_name="Answer Sheet")
26
+
27
+ progress(0.3, desc="Files uploaded. Starting transcription...")
28
+
29
+ # Transcription instructions
30
+ transcription_instructions = """
31
  Persona:
32
+ 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.
33
+
34
+ Core Task:
35
+ Your task is to transcribe the provided handwritten student solutions into a single, clean Markdown string.
36
+
37
+ Key Directives & Rules:
38
+ 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.
39
+
40
+ 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.
41
+
42
+ 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.
43
+
44
+ Preserve Structure:
45
+ Use Markdown bolding (e.g., **1.**, **2a.**) to clearly separate each question or sub-part.
46
+ 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.
47
+
48
+ 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?].
49
+
50
+ Output Format:
51
+ The final output must be a single Markdown string.
52
+ Ensure all LaTeX renders correctly and the structure is clean and readable.
53
+
54
+ Comprehensive Example:
55
+ If the student's handwritten work for a question looks like this:
56
+ 7. Find the value of y.
57
+ y = (xΒ² + 3) / 2
58
+ for x = 3
59
+ y = (3Β² + 3) / 2
60
+ y = (6+3) / 2
61
+ y = (9 + 3) / 2
62
+ y = 12 / 2
63
+ y = 6
64
+
65
+ Your expected output should be:
66
+ **7.**
67
+
68
+ Find the value of y.
69
+ $$
70
+ y = \\frac{x^2 + 3}{2}
71
+ $$
72
+ for $x = 3$
73
+ $$
74
+ \\begin{align*}
75
+ y &= \\frac{3^2 + 3}{2} \\\\
76
+ y &= \\frac{9 + 3}{2} \\\\
77
+ y &= \\frac{12}{2} \\\\
78
+ y &= 6
79
+ \\end{align*}
80
+ $$
81
  """
82
+
83
+ # Initialize Gemini model for transcription
84
+ model = genai.GenerativeModel(
85
+ "gemini-2.5-pro",
86
+ generation_config={"temperature": 0}
87
+ )
88
+
89
+ progress(0.4, desc="Transcribing handwritten answers...")
90
+
91
+ # Generate transcription
92
+ response = model.generate_content([
93
+ transcription_instructions,
94
+ ans_file
95
+ ])
96
+
97
+ # Extract transcription safely
98
+ student_transcription = getattr(response, "text", None)
99
+ if not student_transcription:
100
+ student_transcription = response.candidates[0].content.parts[0].text
101
+
102
+ progress(0.7, desc="Transcription complete. Starting grading process...")
103
+
104
+ # Return transcription first, then continue with grading
105
+ yield student_transcription, "⏳ Grading in progress...", "Grading"
106
+
107
+ # Grading system instructions
108
+ grading_system = """
109
+ Instructions to Examiners:
110
+ Abbreviations:
111
+ - M: Marks for correct Method.
112
+ - A: Marks for Answer or Accuracy (often depends on preceding M mark).
113
+ - R: Marks for clear Reasoning.
114
+ - AG: Answer given in the question; no marks awarded.
115
+ - FT: Follow Through; award marks for correct method/answer using incorrect earlier results.
116
+
117
+ Marking Rules:
118
+ 1. Always follow the markscheme annotations (M1, A2, etc.).
119
+ 2. M marks must be earned before dependent A marks are awarded (no M0 followed by A1 unless explicitly allowed).
120
+ 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.
121
+ 4. Multiple A marks on the same line are awarded independently unless otherwise noted.
122
+ 5. Do not split M2, A3, etc. unless instructed.
123
+ 6. "Show that" responses do not need to restate the AG line unless noted.
124
+ 7. Once a correct answer is seen, ignore further incorrect working unless it affects a later part (then apply FT as appropriate).
125
+ 8. Do not award the final A mark if an incorrect approximation is used in the same part.
126
+
127
+ Error Avoidance:
128
+ - **No incorrect mark allocation:** Do not award marks unless they are explicitly justified by the markscheme.
129
+ - **No misclassification of errors:** Distinguish correctly between "Conceptual Errors" and "Silly Mistakes."
130
+ - **Follow markscheme logic exactly:** Especially regarding when to withhold accuracy marks if method marks are not earned.
131
  """
132
+
133
+ # Now start grading using the transcribed text
134
+ # Generate grading
135
+ grading_response = model.generate_content([
136
+ f"You are an official examiner. Use the following grading system and rules to assess the answers:\n\n{grading_system}\n\n"
137
+ "Your output must:\n"
138
+ "1. Apply marks exactly as per the markscheme.\n"
139
+ "2. Justify each awarded or withheld mark with reference to the grading rules.\n"
140
+ "3. Identify and classify all errors accurately (Conceptual Error, Silly Mistake, or None).\n"
141
+ "4. Follow the dependency between M and A marks strictly.\n"
142
+ "5. Avoid giving marks that the markscheme does not allow.\n"
143
+ "6. Provide a step-by-step reasoning for each mark awarded or withheld, explaining your thought process clearly.\n",
144
+ qp_file,
145
+ ms_file,
146
+ student_transcription # Use the transcribed text, not the original PDF
147
+ ])
148
+
149
+ progress(0.9, desc="Finalizing grading results...")
150
+
151
+ # Extract grading safely
152
+ grading_text = getattr(grading_response, "text", None)
153
+ if not grading_text and grading_response.candidates:
154
+ grading_text = grading_response.candidates[0].content.parts[0].text
155
+ elif not grading_text:
156
+ grading_text = "No Response"
157
+
158
+ progress(1.0, desc="Complete!")
159
+
160
+ # Return final results
161
+ yield student_transcription, grading_text, "Complete"
162
+
163
  except Exception as e:
164
+ yield f"Error processing files: {str(e)}", "", "Error"
165
+
166
+ # Create Gradio interface
167
+ with gr.Blocks(title="Exam Paper Grading System", theme=gr.themes.Soft()) as demo:
168
+ gr.Markdown("""
169
+ # πŸ“š Automated Exam Paper Grading System
170
+
171
+ Upload your question paper, marking scheme, and answer sheet to get automated transcription and grading using Google's Gemini AI.
172
+ """)
173
+
 
 
 
 
 
 
 
 
 
 
174
  with gr.Row():
175
+ with gr.Column():
176
+ gr.Markdown("### πŸ“‹ Upload Files")
177
+ api_key = gr.Textbox(
178
+ label="Gemini API Key",
179
+ placeholder="Enter your Google Gemini API key",
180
+ type="password"
181
+ )
182
+ question_paper = gr.File(
183
+ label="Question Paper (PDF)",
184
+ file_types=[".pdf"]
185
+ )
186
+ marking_scheme = gr.File(
187
+ label="Marking Scheme (PDF)",
188
+ file_types=[".pdf"]
189
+ )
190
+ answer_sheet = gr.File(
191
+ label="Answer Sheet (PDF)",
192
+ file_types=[".pdf"]
193
+ )
194
+
195
+ process_btn = gr.Button(
196
+ "πŸš€ Process Papers",
197
+ variant="primary",
198
+ size="lg"
199
+ )
200
+
201
+ with gr.Row():
202
+ with gr.Column():
203
+ gr.Markdown("### πŸ“ Student Answer Transcription")
204
+ transcription_output = gr.Textbox(
205
+ label="Transcribed Answers",
206
+ lines=15,
207
+ max_lines=25,
208
+ show_copy_button=True,
209
+ placeholder="Transcribed answers will appear here first..."
210
+ )
211
+
212
+ with gr.Column():
213
+ gr.Markdown("### βœ… Grading Results")
214
+ grading_output = gr.Textbox(
215
+ label="Detailed Grading",
216
+ lines=15,
217
+ max_lines=25,
218
+ show_copy_button=True,
219
+ placeholder="Grading results will appear here after transcription is complete..."
220
+ )
221
+
222
+ # Add status indicator
223
+ with gr.Row():
224
+ status_display = gr.Textbox(
225
+ label="Status",
226
+ value="Ready",
227
+ interactive=False,
228
+ show_label=True
229
+ )
230
+
231
+ # Set up the processing function
232
+ process_btn.click(
233
+ fn=process_exam_papers,
234
+ inputs=[question_paper, marking_scheme, answer_sheet, api_key],
235
+ outputs=[transcription_output, grading_output, status_display]
236
+ )
237
+
238
+ gr.Markdown("""
239
+ ### πŸ“– How to Use:
240
+ 1. **Get a Gemini API Key**: Visit [Google AI Studio](https://makersuite.google.com/app/apikey) to get your free API key
241
+ 2. **Upload PDFs**: Upload your question paper, marking scheme, and student answer sheet
242
+ 3. **Process**: Click the "Process Papers" button to get transcription and grading
243
+ 4. **Review**: Check the transcribed answers and detailed grading results
244
+
245
+ ### ⚠️ Notes:
246
+ - All uploaded files are processed securely and not stored permanently
247
+ - The system transcribes exactly what's written (including errors) for accurate grading
248
+ - LaTeX mathematical notation is automatically formatted for clarity
249
+ """)
250
 
251
  if __name__ == "__main__":
252
+ demo.launch()
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- gradio
2
- PyPDF2
3
- pandas
4
- google-generativeai
 
1
+ gradio>=4.0.0
2
+ google-generativeai>=0.3.0
3
+ PyPDF2>=3.0.0
4
+ pandas>=1.5.0