Spaces:
Sleeping
Sleeping
File size: 9,167 Bytes
fa2ac16 02d8496 0b422e6 fa2ac16 40d9691 3b4b98e 40d9691 3b4b98e 74c1aa8 3b4b98e 74c1aa8 3b4b98e 74c1aa8 3b4b98e 74c1aa8 3b4b98e 74c1aa8 3b4b98e 74c1aa8 3b4b98e 02d8496 40d9691 1091f22 3b4b98e d5a9c21 1091f22 d5a9c21 3b4b98e 1091f22 3b4b98e d5a9c21 1091f22 d5a9c21 1091f22 d5a9c21 3b4b98e d5a9c21 3b4b98e d5a9c21 3b4b98e d5a9c21 1091f22 d5a9c21 1091f22 3b4b98e d5a9c21 1091f22 3b4b98e 1091f22 d5a9c21 3b4b98e d5a9c21 1091f22 3b4b98e 1091f22 d5a9c21 3b4b98e 1091f22 3b4b98e d5a9c21 3b4b98e 1091f22 4350cad 1091f22 f44aff8 40d9691 fa2ac16 40d9691 c755c74 02d8496 c755c74 fa2ac16 0b422e6 1091f22 0b422e6 fa2ac16 0b422e6 d5a9c21 0b422e6 1091f22 0b422e6 1091f22 0b422e6 40d9691 0b422e6 40d9691 1091f22 40d9691 1091f22 40d9691 1091f22 40d9691 0b422e6 c755c74 40d9691 c755c74 40d9691 7aaa8eb 40d9691 1091f22 02d8496 40d9691 1091f22 02d8496 1091f22 7aaa8eb 40d9691 1091f22 40d9691 1091f22 40d9691 fa2ac16 40d9691 fa2ac16 40d9691 1091f22 fa2ac16 40d9691 fa2ac16 40d9691 c755c74 40d9691 fa2ac16 c755c74 40d9691 c755c74 fa2ac16 d5a9c21 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | import os
import gradio as gr
import google.generativeai as genai
from markdown_pdf import MarkdownPdf, Section
import subprocess
# ---------- PROMPTS ----------
PROMPTS = {
"ALIGNMENT_PROMPT": {
"role": "system",
"content": """Developer: Role: Expert examiner and transcription specialist.
Your objective is to align three sources per question/sub-question:
- Question Paper (QP)
- Markscheme (MS)
- Student Answer Sheet (AS)
## Instructions
1. Carefully parse all documents and align content per question and sub-question.
2. For each question/sub-question, create a structured Markdown block as follows:
---
## Question X [and sub-question if applicable, e.g., ### (b)(ii)]
*QP:* [Exact question text or [Not found]]
*MS:* [Relevant markscheme section or [Not found]]
*AS:* [Final cleaned student answer; use fenced code for mathematics; insert [illegible] or [No response] as required]
---
3. Formatting requirements:
- Use '##' for main questions, '###' for sub-questions.
- Maintain section order: QP | MS | AS (always in that sequence).
- Enclose all mathematical expressions in Markdown fenced code blocks (``` triple backticks).
- If a diagram/graph is omitted, write [Graph omitted] in its place.
- For unreadable portions of the student's answer, insert [illegible]; if the answer is wholly unreadable, set AS to [illegible].
- If a question is skipped or unanswered, AS must be exactly [No response].
- Keep MS annotations (e.g., M1, A1, R1) verbatim.
- Diagrams/graphs are not to be recreated.
- If any QP, MS, or AS content is missing, specify [Not found] for that section.
- Ensure consistency and determinism in formatting so subsequent models can grade directly from this aligned format.
- List all main questions and sub-questions in their original order, clearly denoting sub-questions (e.g., '### (b)(i)', '### (b)(ii)').
After each alignment action, briefly validate that the content for QP, MS, and AS matches expectations and alignments are correct. If validation fails, self-correct or flag the issue.
## Example
---
## Question 1
*QP:* Expand (1+x)^3
*MS:* M1 for binomial expansion, A1 for coefficients, A1 for final form
*AS:* x^3 + 3x^2 + 3x + 1
---
"""
},
"GRADING_PROMPT": {
"role": "system",
"content": """Developer: You are an official examiner. Apply the following grading rules precisely.
## Grading Checklist
- Assess each question part against the provided markscheme.
- Award marks for correct methods (M), accurate answers (A), and clear reasoning (R).
- Use Follow Through (FT) for correctly applied subsequent working using a previous error.
- Always state BOTH:
1. What was wrong (the error).
2. What is right (the correct method/answer from markscheme).
- Summarize total marks and classify error types.
- End with an Examiner’s Report table.
### Abbreviations:
- **M**: Method
- **A**: Accuracy/Answer
- **R**: Reasoning
- **AG**: Answer given (no marks)
- **FT**: Follow Through
---
## Grading Instructions
1. Award marks using official annotations (M1, A1, etc.).
2. A marks generally require valid M marks.
3. Allow FT unless result is nonsensical.
4. Accept valid alternative forms.
5. Apply accuracy requirements (default 3 s.f. if not stated).
6. Ignore crossed-out work unless requested otherwise.
7. Mark only the first full solution unless otherwise indicated.
8. Assume graphs/diagrams are correct if required.
---
## Output Format
Produce a GitHub-flavored Markdown table:
| Student wrote | Marks Awarded | Reason |
|---------------|---------------|--------|
Rules:
- Each row matches a markable step.
- For blanks, write “(no answer)” and indicate lost mark(s).
- Lost marks: wrap in red with `<span style="color:red">A0</span>` (or M0, R0) and make Reason column red. Always also show the correct method/answer.
- Awarded marks remain plain text.
- For partial awards (M1A0A1), highlight only lost marks.
- After each question, show total in square brackets: `[2/4]`.
---
### Examiner’s Report
At the very end, provide a summary table:
Codes:
- A : All Good
- B : Silly Mistake
- C : Conceptual Error
- D : Hard Question
- E : Not Applicable
| Question Number | Marks | Remark |
|-----------------|-------|--------|
| 1 | 6/9 | C |
| 2 | 7/7 | A |
| 3 | 8/14 | D |
| … | … | … |
Then show total clearly:
`Total: 40/61`
Optionally, if reasons are available, extend with:
| Question Number | Marks | Remark | Reason |
|-----------------|-------|--------|--------|
⚠️ Do NOT add any "Validation" or meta commentary. End the output after Examiner’s Report.
"""
}
}
# -------------------- CONFIG --------------------
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
# ---------- HELPER: Save to PDF ----------
def save_as_pdf(text, filename="output.pdf"):
pdf = MarkdownPdf()
pdf.add_section(Section(text, toc=False))
pdf.save(filename)
return filename
# ---------- HELPER: Compress PDF ----------
def compress_pdf(input_path, output_path=None, max_size=20*1024*1024):
if output_path is None:
base, ext = os.path.splitext(input_path)
output_path = f"{base}_compressed{ext}"
if os.path.getsize(input_path) <= max_size:
return input_path
try:
gs_cmd = [
"gs", "-sDEVICE=pdfwrite",
"-dCompatibilityLevel=1.4",
"-dPDFSETTINGS=/ebook",
"-dNOPAUSE", "-dQUIET", "-dBATCH",
f"-sOutputFile={output_path}", input_path
]
subprocess.run(gs_cmd, check=True)
if os.path.getsize(output_path) <= max_size:
print(f"✅ Compressed {input_path} → {output_path}")
return output_path
else:
print(f"⚠️ Compression failed to reduce below {max_size/1024/1024} MB")
return input_path
except Exception as e:
print(f"⚠️ Compression error: {e}")
return input_path
# ---------- HELPER: Create Model with Fallback ----------
def create_model():
try:
print("⚡ Using gemini-2.5-pro model")
return genai.GenerativeModel("gemini-2.5-pro", generation_config={"temperature": 0})
except Exception:
print("⚡ Falling back to gemini-2.5-flash model")
return genai.GenerativeModel("gemini-2.5-flash", generation_config={"temperature": 0})
# ---------- PIPELINE: ALIGN + GRADE ----------
def align_and_grade(qp_file, ms_file, ans_file):
try:
qp_file = compress_pdf(qp_file, "qp_compressed.pdf")
ms_file = compress_pdf(ms_file, "ms_compressed.pdf")
ans_file = compress_pdf(ans_file, "ans_compressed.pdf")
qp_uploaded = genai.upload_file(path=qp_file, display_name="Question Paper")
ms_uploaded = genai.upload_file(path=ms_file, display_name="Markscheme")
ans_uploaded = genai.upload_file(path=ans_file, display_name="Answer Sheet")
model = create_model()
resp = model.generate_content([
PROMPTS["ALIGNMENT_PROMPT"]["content"],
qp_uploaded,
ms_uploaded,
ans_uploaded
])
aligned_text = getattr(resp, "text", None)
if not aligned_text and resp.candidates:
aligned_text = resp.candidates[0].content.parts[0].text
aligned_pdf_path = save_as_pdf(aligned_text, "aligned_qp_ms_as.pdf")
response = model.generate_content([
PROMPTS["GRADING_PROMPT"]["content"],
aligned_text
])
grading = getattr(response, "text", None)
if not grading and response.candidates:
grading = response.candidates[0].content.parts[0].text
base_name = os.path.splitext(os.path.basename(ans_file))[0]
grading_pdf_path = save_as_pdf(grading, f"{base_name}_graded.pdf")
return aligned_text, aligned_pdf_path, grading, grading_pdf_path
except Exception as e:
return f"❌ Error: {e}", None, None, None
# ---------- GRADIO APP ----------
with gr.Blocks(title="LeadIB AI Grading (Alignment + Auto-Grading)") as demo:
gr.Markdown("## LeadIB AI Grading\nUpload Question Paper, Markscheme, and Student Answer Sheet.\nThe system will align and grade automatically.")
with gr.Row():
qp_file = gr.File(label="Upload Question Paper (PDF)", type="filepath")
ms_file = gr.File(label="Upload Markscheme (PDF)", type="filepath")
ans_file = gr.File(label="Upload Student Answer Sheet (PDF)", type="filepath")
run_btn = gr.Button("Start Alignment + Auto-Grading")
with gr.Row():
aligned_out = gr.Textbox(label="📄 Aligned QP | MS | AS", lines=20)
aligned_pdf = gr.File(label="⬇️ Download Aligned (PDF)")
with gr.Row():
grading_out = gr.Textbox(label="✅ Grading Report", lines=20)
grading_pdf = gr.File(label="⬇️ Download Grading Report (PDF)")
run_btn.click(
fn=align_and_grade,
inputs=[qp_file, ms_file, ans_file],
outputs=[aligned_out, aligned_pdf, grading_out, grading_pdf],
show_progress=True
)
if __name__ == "__main__":
demo.launch()
|