Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import os
|
|
|
|
| 2 |
import gradio as gr
|
| 3 |
import google.generativeai as genai
|
| 4 |
from markdown_pdf import MarkdownPdf, Section
|
|
@@ -173,7 +174,58 @@ def create_model():
|
|
| 173 |
except Exception:
|
| 174 |
return genai.GenerativeModel("gemini-2.5-flash", generation_config={"temperature": 0})
|
| 175 |
|
| 176 |
-
# ----------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
def align_and_grade(qp_file, ms_file, ans_file):
|
| 178 |
try:
|
| 179 |
# Step 0: Compress if needed
|
|
@@ -188,40 +240,44 @@ def align_and_grade(qp_file, ms_file, ans_file):
|
|
| 188 |
|
| 189 |
model = create_model()
|
| 190 |
|
| 191 |
-
# Step 2: Alignment
|
| 192 |
resp = model.generate_content([
|
| 193 |
PROMPTS["ALIGNMENT_PROMPT"]["content"],
|
| 194 |
qp_uploaded,
|
| 195 |
ms_uploaded,
|
| 196 |
ans_uploaded
|
| 197 |
])
|
| 198 |
-
|
| 199 |
-
if not
|
| 200 |
-
|
| 201 |
|
| 202 |
-
|
|
|
|
|
|
|
| 203 |
|
| 204 |
-
# Step 3: Grading
|
| 205 |
response = model.generate_content([
|
| 206 |
PROMPTS["GRADING_PROMPT"]["content"],
|
| 207 |
-
|
| 208 |
])
|
| 209 |
-
|
| 210 |
-
if not
|
| 211 |
-
|
| 212 |
|
| 213 |
-
#
|
|
|
|
| 214 |
base_name = os.path.splitext(os.path.basename(ans_file))[0]
|
| 215 |
-
grading_pdf_path = save_as_pdf(grading, f"{base_name}_graded.pdf")
|
| 216 |
|
| 217 |
-
|
|
|
|
| 218 |
|
| 219 |
except Exception as e:
|
| 220 |
return f"❌ Error: {e}", None, None, None
|
| 221 |
|
| 222 |
# ---------- GRADIO APP ----------
|
| 223 |
with gr.Blocks(title="LeadIB AI Grading (Alignment + Auto-Grading)") as demo:
|
| 224 |
-
gr.Markdown("## LeadIB AI Grading\nUpload Question Paper, Markscheme, and Student Answer Sheet.\nThe system will align and grade automatically.")
|
| 225 |
|
| 226 |
with gr.Row():
|
| 227 |
qp_file = gr.File(label="Upload Question Paper (PDF)", type="filepath")
|
|
|
|
| 1 |
import os
|
| 2 |
+
import re
|
| 3 |
import gradio as gr
|
| 4 |
import google.generativeai as genai
|
| 5 |
from markdown_pdf import MarkdownPdf, Section
|
|
|
|
| 174 |
except Exception:
|
| 175 |
return genai.GenerativeModel("gemini-2.5-flash", generation_config={"temperature": 0})
|
| 176 |
|
| 177 |
+
# ---------- NEW: Pretty math conversion ----------
|
| 178 |
+
_SUPER_MAP = {
|
| 179 |
+
"0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴",
|
| 180 |
+
"5": "⁵", "6": "⁶", "7": "⁷", "8": "⁸", "9": "⁹",
|
| 181 |
+
"+": "⁺", "-": "⁻", "(": "⁽", ")": "⁾",
|
| 182 |
+
"n": "ⁿ", "i": "ⁱ", "a": "ᵃ", "b": "ᵇ", "c": "ᶜ", "d": "ᵈ", "e": "ᵉ",
|
| 183 |
+
"o": "ᵒ", "r": "ʳ", "t": "ᵗ", "u": "ᵘ", "v": "ᵛ", "w": "ʷ", "x": "ˣ", "y": "ʸ"
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
def _to_superscript(s: str) -> str:
|
| 187 |
+
"""Map characters to available Unicode superscripts; fallback to original char if unavailable."""
|
| 188 |
+
out = []
|
| 189 |
+
for ch in s:
|
| 190 |
+
out.append(_SUPER_MAP.get(ch, _SUPER_MAP.get(ch.lower(), ch)))
|
| 191 |
+
return "".join(out)
|
| 192 |
+
|
| 193 |
+
def pretty_math(text: str) -> str:
|
| 194 |
+
"""
|
| 195 |
+
Convert caret-notation exponents to Unicode superscripts.
|
| 196 |
+
|
| 197 |
+
Handles:
|
| 198 |
+
x^2, x^{12}, x^(12), 10^4, (3x10^4)^3, and similar patterns.
|
| 199 |
+
Only characters with known superscripts are converted (digits, +-(), some letters).
|
| 200 |
+
"""
|
| 201 |
+
|
| 202 |
+
if not text:
|
| 203 |
+
return text
|
| 204 |
+
|
| 205 |
+
new = text
|
| 206 |
+
|
| 207 |
+
# Convert instances like ^{...}
|
| 208 |
+
new = re.sub(r'\^\{\s*([^}]+)\s*\}', lambda m: _to_superscript(m.group(1)), new)
|
| 209 |
+
|
| 210 |
+
# Convert instances like ^(...)
|
| 211 |
+
new = re.sub(r'\^\(\s*([^\)]+)\s*\)', lambda m: _to_superscript(m.group(1)), new)
|
| 212 |
+
|
| 213 |
+
# Convert caret followed by a simple integer (e.g., ^12)
|
| 214 |
+
new = re.sub(r'\^([+-]?\d+)', lambda m: _to_superscript(m.group(1)), new)
|
| 215 |
+
|
| 216 |
+
# Convert caret followed by single non-space token (e.g., x^n)
|
| 217 |
+
new = re.sub(r'\^([A-Za-z0-9\+\-\(\)]+)', lambda m: _to_superscript(m.group(1)), new)
|
| 218 |
+
|
| 219 |
+
# Replace common scientific notation '3x10^4' -> '3×10⁴' when 'x' is used as multiplication
|
| 220 |
+
# Only apply when the 'x' sits between digits and '10' (heuristic)
|
| 221 |
+
new = re.sub(r'(\d)\s*[xX]\s*(10)', r'\1×\2', new)
|
| 222 |
+
|
| 223 |
+
# Also compact spaced forms: '3 x 10^4' -> '3×10⁴' (keeps previously superscripted exponent)
|
| 224 |
+
new = re.sub(r'(\d)\s*×\s*(10)', r'\1×\2', new)
|
| 225 |
+
|
| 226 |
+
return new
|
| 227 |
+
|
| 228 |
+
# -------------------- PIPELINE: ALIGN + GRADE --------------------
|
| 229 |
def align_and_grade(qp_file, ms_file, ans_file):
|
| 230 |
try:
|
| 231 |
# Step 0: Compress if needed
|
|
|
|
| 240 |
|
| 241 |
model = create_model()
|
| 242 |
|
| 243 |
+
# Step 2: Alignment (raw)
|
| 244 |
resp = model.generate_content([
|
| 245 |
PROMPTS["ALIGNMENT_PROMPT"]["content"],
|
| 246 |
qp_uploaded,
|
| 247 |
ms_uploaded,
|
| 248 |
ans_uploaded
|
| 249 |
])
|
| 250 |
+
aligned_text_raw = getattr(resp, "text", None)
|
| 251 |
+
if not aligned_text_raw and getattr(resp, "candidates", None):
|
| 252 |
+
aligned_text_raw = resp.candidates[0].content.parts[0].text
|
| 253 |
|
| 254 |
+
# Pretty version for display/PDF (does NOT affect the raw text used for grading)
|
| 255 |
+
aligned_text_pretty = pretty_math(aligned_text_raw) if aligned_text_raw else aligned_text_raw
|
| 256 |
+
aligned_pdf_path = save_as_pdf(aligned_text_pretty or "[No aligned text produced]", "aligned_qp_ms_as.pdf")
|
| 257 |
|
| 258 |
+
# Step 3: Grading (use raw aligned text as input to grader)
|
| 259 |
response = model.generate_content([
|
| 260 |
PROMPTS["GRADING_PROMPT"]["content"],
|
| 261 |
+
aligned_text_raw or "[No aligned text produced]"
|
| 262 |
])
|
| 263 |
+
grading_raw = getattr(response, "text", None)
|
| 264 |
+
if not grading_raw and getattr(response, "candidates", None):
|
| 265 |
+
grading_raw = response.candidates[0].content.parts[0].text
|
| 266 |
|
| 267 |
+
# Pretty version of grading output for display/PDF
|
| 268 |
+
grading_pretty = pretty_math(grading_raw) if grading_raw else grading_raw
|
| 269 |
base_name = os.path.splitext(os.path.basename(ans_file))[0]
|
| 270 |
+
grading_pdf_path = save_as_pdf(grading_pretty or "[No grading produced]", f"{base_name}_graded.pdf")
|
| 271 |
|
| 272 |
+
# Return pretty/display versions (raws remain unmodified for internal processing)
|
| 273 |
+
return aligned_text_pretty or "", aligned_pdf_path, grading_pretty or "", grading_pdf_path
|
| 274 |
|
| 275 |
except Exception as e:
|
| 276 |
return f"❌ Error: {e}", None, None, None
|
| 277 |
|
| 278 |
# ---------- GRADIO APP ----------
|
| 279 |
with gr.Blocks(title="LeadIB AI Grading (Alignment + Auto-Grading)") as demo:
|
| 280 |
+
gr.Markdown("## LeadIB AI Grading\nUpload Question Paper, Markscheme, and Student Answer Sheet.\nThe system will align and grade automatically. Mathematical exponents (e.g. `x^2`) will be converted to Unicode superscripts (e.g. `x²`) in the displayed text and PDFs.")
|
| 281 |
|
| 282 |
with gr.Row():
|
| 283 |
qp_file = gr.File(label="Upload Question Paper (PDF)", type="filepath")
|