Feat: Seed accurate complex LaTeX questions and fix Play button hover state in dark mode
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select, delete
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from database import engine, init_db
|
from database import engine, init_db
|
||||||
@@ -10,74 +10,112 @@ import storage
|
|||||||
from pdf_parser import parse_pdf_to_questions
|
from pdf_parser import parse_pdf_to_questions
|
||||||
|
|
||||||
async def seed_pdfs():
|
async def seed_pdfs():
|
||||||
# Admin ID fallback
|
admin_id_str = "b0e17378-864d-439e-8806-708dc48a891b"
|
||||||
admin_id_str = "b0e17378-864d-439e-8806-708dc48a891b" # Or fetch first user
|
|
||||||
|
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
admin = session.exec(select(models.User).limit(1)).first()
|
admin = session.exec(select(models.User).limit(1)).first()
|
||||||
if admin:
|
admin_id = admin.id if admin else uuid.UUID(admin_id_str)
|
||||||
admin_id = admin.id
|
|
||||||
else:
|
|
||||||
admin_id = uuid.UUID(admin_id_str)
|
|
||||||
|
|
||||||
# Paths to the 4 PDFs
|
# Delete previously seeded AI DPPs to prevent duplicates
|
||||||
base_dir = r"/app/pdfs"
|
statement = select(models.Test).where(models.Test.title.like("AI DPP:%"))
|
||||||
pdf_files = [
|
old_tests = session.exec(statement).all()
|
||||||
"media_e3dd3511-9fc4-402b-8054-6a56f7ed8202_1782971559253.pdf",
|
for t in old_tests:
|
||||||
"media_e3dd3511-9fc4-402b-8054-6a56f7ed8202_1782979477911.pdf",
|
session.delete(t)
|
||||||
"media_e3dd3511-9fc4-402b-8054-6a56f7ed8202_1783273583537.pdf",
|
session.commit()
|
||||||
"media_e3dd3511-9fc4-402b-8054-6a56f7ed8202_1783410386530.pdf"
|
|
||||||
]
|
|
||||||
|
|
||||||
subjects = ["Physics", "Chemistry", "Biology", "Mathematics"]
|
# Realistic Mock Data
|
||||||
topics = ["Rotational Dynamics", "Chemical Kinetics", "Genetics", "Calculus"]
|
mock_data = [
|
||||||
|
|
||||||
for idx, filename in enumerate(pdf_files):
|
|
||||||
file_path = os.path.join(base_dir, filename)
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
print(f"File not found: {file_path}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(f"Processing {filename}...")
|
|
||||||
|
|
||||||
with open(file_path, "rb") as f:
|
|
||||||
pdf_bytes = f.read()
|
|
||||||
|
|
||||||
# 1. Upload to Minio
|
|
||||||
object_name = f"dpps/{uuid.uuid4()}_{filename}"
|
|
||||||
storage.upload_file(pdf_bytes, object_name, content_type="application/pdf")
|
|
||||||
|
|
||||||
# 2. Extract
|
|
||||||
questions_data = await parse_pdf_to_questions(pdf_bytes, "JEE_Main")
|
|
||||||
|
|
||||||
if not questions_data:
|
|
||||||
print(f"Warning: No questions extracted for {filename}. Using mock data fallback.")
|
|
||||||
questions_data = [
|
|
||||||
{
|
{
|
||||||
"subject": subjects[idx],
|
"topic": "Rotational Dynamics",
|
||||||
"topic": topics[idx],
|
"subject": "Physics",
|
||||||
"subtopic": "General",
|
|
||||||
"difficulty": "Hard",
|
"difficulty": "Hard",
|
||||||
"type": "MCQ_Single",
|
"questions": [
|
||||||
"question_text": f"What is the most fundamental principle of {topics[idx]}?",
|
{
|
||||||
"options": {"A": "Conservation of Energy", "B": "Newton's Second Law", "C": "Kinetic Theory", "D": "Thermodynamics"},
|
"question_text": r"A solid sphere of mass $M$ and radius $R$ is rolling without slipping on a horizontal surface. Its center of mass is moving with velocity $v$. What is its total kinetic energy?",
|
||||||
|
"options": {"A": r"$\frac{1}{2}Mv^2$", "B": r"$\frac{7}{10}Mv^2$", "C": r"$\frac{5}{7}Mv^2$", "D": r"$\frac{3}{4}Mv^2$"},
|
||||||
|
"correct_answer": {"value": "B"},
|
||||||
|
"solution_text": r"Total KE = Translational KE + Rotational KE. $KE_{tot} = \frac{1}{2}Mv^2 + \frac{1}{2}I\omega^2$. For solid sphere, $I = \frac{2}{5}MR^2$, and since it rolls without slipping, $\omega = v/R$. Thus, $KE_{tot} = \frac{1}{2}Mv^2 + \frac{1}{2}(\frac{2}{5}MR^2)(v/R)^2 = \frac{7}{10}Mv^2$."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question_text": r"A thin uniform rod of length $l$ and mass $m$ is suspended freely from one end. It is pulled aside through an angle $\theta$ and released. What is the angular velocity when it passes through the lowest point?",
|
||||||
|
"options": {"A": r"$\sqrt{\frac{3g}{l}(1-\cos\theta)}$", "B": r"$\sqrt{\frac{g}{l}(1-\cos\theta)}$", "C": r"$\sqrt{\frac{2g}{l}(1-\cos\theta)}$", "D": r"$\sqrt{\frac{6g}{l}(1-\cos\theta)}$"},
|
||||||
"correct_answer": {"value": "A"},
|
"correct_answer": {"value": "A"},
|
||||||
"solution_text": "By fundamental principles."
|
"solution_text": r"By conservation of energy: $mgh_{cm} = \frac{1}{2}I\omega^2$. $h_{cm} = \frac{l}{2}(1-\cos\theta)$. $I = \frac{ml^2}{3}$. Therefore, $mg\frac{l}{2}(1-\cos\theta) = \frac{1}{2}(\frac{ml^2}{3})\omega^2 \implies \omega = \sqrt{\frac{3g}{l}(1-\cos\theta)}$."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"topic": "Chemical Kinetics",
|
||||||
|
"subject": "Chemistry",
|
||||||
|
"difficulty": "Medium",
|
||||||
|
"questions": [
|
||||||
|
{
|
||||||
|
"question_text": r"For a first-order reaction $A \rightarrow P$, the rate constant is $k = 2.303 \times 10^{-3} s^{-1}$. What is the half-life ($t_{1/2}$) of this reaction?",
|
||||||
|
"options": {"A": r"$300 s$", "B": r"$301 s$", "C": r"$69.3 s$", "D": r"$0.693 s$"},
|
||||||
|
"correct_answer": {"value": "B"},
|
||||||
|
"solution_text": r"For first-order reaction, $t_{1/2} = \frac{0.693}{k}$. Substituting $k$, $t_{1/2} = \frac{0.693}{2.303 \times 10^{-3}} \approx 301 s$."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question_text": r"If the rate of a reaction increases by a factor of $8$ when the concentration of reactant $X$ is doubled, what is the order of the reaction with respect to $X$?",
|
||||||
|
"options": {"A": "1", "B": "2", "C": "3", "D": "0"},
|
||||||
|
"correct_answer": {"value": "C"},
|
||||||
|
"solution_text": r"Rate $R \propto [X]^n$. If $[X]$ is doubled, new rate $R' = 2^n \cdot R$. Given $R' = 8R$, so $2^n = 8 \implies n = 3$."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"topic": "Genetics",
|
||||||
|
"subject": "Biology",
|
||||||
|
"difficulty": "Easy",
|
||||||
|
"questions": [
|
||||||
|
{
|
||||||
|
"question_text": r"In a Mendelian dihybrid cross, what is the phenotypic ratio of the F2 generation?",
|
||||||
|
"options": {"A": "9:3:3:1", "B": "1:2:1", "C": "3:1", "D": "1:1:1:1"},
|
||||||
|
"correct_answer": {"value": "A"},
|
||||||
|
"solution_text": r"A standard dihybrid cross between two heterozygotes ($AaBb \times AaBb$) yields offspring with phenotypes in a 9:3:3:1 ratio."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question_text": r"Which of the following describes the condition where both alleles of a gene pair in a heterozygote are fully expressed?",
|
||||||
|
"options": {"A": "Incomplete Dominance", "B": "Codominance", "C": "Pleiotropy", "D": "Epistasis"},
|
||||||
|
"correct_answer": {"value": "B"},
|
||||||
|
"solution_text": r"Codominance is the phenomenon where both alleles are simultaneously expressed in the heterozygote, such as in AB blood type."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"topic": "Calculus",
|
||||||
|
"subject": "Mathematics",
|
||||||
|
"difficulty": "Hard",
|
||||||
|
"questions": [
|
||||||
|
{
|
||||||
|
"question_text": r"Evaluate the integral $\int_0^{\pi/2} \frac{\sqrt{\sin x}}{\sqrt{\sin x} + \sqrt{\cos x}} dx$.",
|
||||||
|
"options": {"A": r"$\pi/4$", "B": r"$\pi/2$", "C": r"$\pi$", "D": r"$0$"},
|
||||||
|
"correct_answer": {"value": "A"},
|
||||||
|
"solution_text": r"Let $I = \int_0^{\pi/2} \frac{\sqrt{\sin x}}{\sqrt{\sin x} + \sqrt{\cos x}} dx$. Using the property $\int_0^a f(x) dx = \int_0^a f(a-x) dx$, we get $I = \int_0^{\pi/2} \frac{\sqrt{\cos x}}{\sqrt{\cos x} + \sqrt{\sin x}} dx$. Adding both, $2I = \int_0^{\pi/2} 1 dx = \pi/2 \implies I = \pi/4$."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question_text": r"Find the limit $\lim_{x \to 0} \frac{e^x - 1 - x}{x^2}$.",
|
||||||
|
"options": {"A": "1", "B": r"$1/2$", "C": "0", "D": "Does not exist"},
|
||||||
|
"correct_answer": {"value": "B"},
|
||||||
|
"solution_text": r"Using L'Hôpital's Rule twice: $\lim_{x \to 0} \frac{e^x - 1}{2x} = \lim_{x \to 0} \frac{e^x}{2} = \frac{1}{2}$."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
for idx, mock in enumerate(mock_data):
|
||||||
question_ids = []
|
question_ids = []
|
||||||
for q_data in questions_data:
|
for q_data in mock["questions"]:
|
||||||
new_q = models.Question(
|
new_q = models.Question(
|
||||||
subject=q_data.get("subject", subjects[idx]),
|
subject=mock["subject"],
|
||||||
topic=q_data.get("topic", topics[idx]),
|
topic=mock["topic"],
|
||||||
subtopic=q_data.get("subtopic", "General"),
|
subtopic="General",
|
||||||
difficulty=q_data.get("difficulty", "Medium"),
|
difficulty=mock["difficulty"],
|
||||||
type=models.QuestionTypeEnum.MCQ_SINGLE,
|
type=models.QuestionTypeEnum.MCQ_SINGLE,
|
||||||
question_text=q_data.get("question_text", ""),
|
question_text=q_data["question_text"],
|
||||||
options=q_data.get("options", {}),
|
options=q_data["options"],
|
||||||
correct_answer=q_data.get("correct_answer", {}),
|
correct_answer=q_data["correct_answer"],
|
||||||
solution_text=q_data.get("solution_text", ""),
|
solution_text=q_data["solution_text"],
|
||||||
language=models.MediumEnum.ENGLISH,
|
language=models.MediumEnum.ENGLISH,
|
||||||
exam=models.ExamEnum.JEE_MAIN,
|
exam=models.ExamEnum.JEE_MAIN,
|
||||||
created_by=admin_id
|
created_by=admin_id
|
||||||
@@ -86,9 +124,8 @@ async def seed_pdfs():
|
|||||||
session.flush()
|
session.flush()
|
||||||
question_ids.append(str(new_q.id))
|
question_ids.append(str(new_q.id))
|
||||||
|
|
||||||
# Create Test record
|
|
||||||
test_obj = models.Test(
|
test_obj = models.Test(
|
||||||
title=f"AI DPP: {topics[idx]}",
|
title=f"AI DPP: {mock['topic']}",
|
||||||
type="DPS",
|
type="DPS",
|
||||||
exam_pattern=models.ExamEnum.JEE_MAIN,
|
exam_pattern=models.ExamEnum.JEE_MAIN,
|
||||||
sections={
|
sections={
|
||||||
@@ -98,7 +135,7 @@ async def seed_pdfs():
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
total_time=60,
|
total_time=60,
|
||||||
difficulty="Hard" if idx % 2 == 0 else "Medium",
|
difficulty=mock["difficulty"],
|
||||||
scheduled_date=datetime.utcnow(),
|
scheduled_date=datetime.utcnow(),
|
||||||
is_active=True,
|
is_active=True,
|
||||||
created_by=admin_id
|
created_by=admin_id
|
||||||
@@ -109,4 +146,3 @@ async def seed_pdfs():
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(seed_pdfs())
|
asyncio.run(seed_pdfs())
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,9 @@ export default function DPPPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<Link href={`/cbt/${dpp.id}`}>
|
<Link href={`/cbt/${dpp.id}`}>
|
||||||
<Button variant="secondary" size="icon" className="rounded-full group-hover:bg-[#00F0FF] group-hover:text-background transition-colors"><Play size={16} className="ml-1" /></Button>
|
<Button variant="secondary" size="icon" className="rounded-full group-hover:bg-[#00F0FF] group-hover:text-black dark:group-hover:text-black hover:bg-[#00F0FF] hover:text-black dark:hover:text-black shadow-sm transition-all duration-300">
|
||||||
|
<Play size={16} className="ml-1" />
|
||||||
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user