Feat: Complete CBT PDF upload, AI parsing, and MinIO storage integration

This commit is contained in:
2026-07-07 15:56:44 +05:30
parent c5260eec2e
commit b6ebb9d9c3
8 changed files with 476 additions and 8 deletions

85
backend/pdf_parser.py Normal file
View File

@@ -0,0 +1,85 @@
import fitz # PyMuPDF
import json
import logging
import asyncio
from typing import List, Dict, Any
import os
from openai import AsyncOpenAI
logger = logging.getLogger("pdf_parser")
client = AsyncOpenAI(
base_url=os.getenv("LLM_BASE_URL", "http://host.docker.internal:11434/v1"),
api_key=os.getenv("OPENAI_API_KEY", "ollama"),
)
async def parse_pdf_to_questions(pdf_bytes: bytes, exam_pattern: str) -> List[Dict[str, Any]]:
"""
Extracts text from a PDF and uses the LLM to structure it into a JSON array of questions.
"""
text = ""
try:
doc = fitz.open(stream=pdf_bytes, filetype="pdf")
for page in doc:
text += page.get_text("text") + "\n\n"
except Exception as e:
logger.error(f"Failed to parse PDF with PyMuPDF: {e}")
return []
system_prompt = f"""
You are an AI expert at extracting {exam_pattern} test questions from messy PDF text.
The text contains complex Chemistry and Math formulas.
CRITICAL: You MUST format all formulas perfectly in LaTeX wrapping inline math in $ and block math in $$.
Extract the questions and return them STRICTLY as a JSON array of objects.
If your API requires a JSON object root, return {{"questions": [...]}}.
Schema per question:
{{
"subject": "Physics" | "Chemistry" | "Maths" | "Biology",
"topic": "String",
"subtopic": "String",
"difficulty": "Easy" | "Medium" | "Hard",
"type": "MCQ_Single" | "MCQ_Multiple" | "Numerical",
"question_text": "Full text of the question with LaTeX",
"options": {{"A": "Option A text", "B": "Option B text", "C": "Option C text", "D": "Option D text"}},
"correct_answer": {{"value": "A"}},
"solution_text": "Detailed step-by-step solution if available, else 'Solution not provided'",
"language": "English"
}}
"""
try:
truncated_text = text[:15000]
response = await client.chat.completions.create(
model=os.getenv("LLM_MODEL_NAME", "gemma"),
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": truncated_text}
],
response_format={ "type": "json_object" }
)
output = response.choices[0].message.content
if not output:
return []
if "```json" in output:
output = output.split("```json")[1].split("```")[0].strip()
elif "```" in output:
output = output.split("```")[1].split("```")[0].strip()
data = json.loads(output)
if isinstance(data, list):
return data
elif isinstance(data, dict) and "questions" in data:
return data["questions"]
elif isinstance(data, dict):
return [data]
else:
return []
except Exception as e:
logger.error(f"LLM PDF parsing failed: {e}")
return []