SoumyajitSen94298's picture
Deploy: FastAPI backend with Docker
01757c2
Raw
History Blame Contribute Delete
9.65 kB
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from ..database import get_db
from ..models import Student, Syllabus, TestPlan, TestPaper, TestAttempt, AnalysisReport, TokenUsage
from ..services import ai_service
from ..services.graph_service import generate_all_graphs
import uuid
router = APIRouter(prefix="/api/analysis", tags=["Analysis"])
def gen_id():
return str(uuid.uuid4())
async def _gather_attempts(student_id: str, syllabus_id: str, db: AsyncSession) -> list[dict]:
plan_result = await db.execute(select(TestPlan).where(TestPlan.syllabus_id == syllabus_id))
plan = plan_result.scalar_one_or_none()
if not plan:
return []
papers_result = await db.execute(select(TestPaper).where(TestPaper.test_plan_id == plan.id))
paper_ids = {p.id: p for p in papers_result.scalars().all()}
attempts_result = await db.execute(
select(TestAttempt).where(
TestAttempt.student_id == student_id,
TestAttempt.test_paper_id.in_(list(paper_ids.keys()))
).order_by(TestAttempt.attempted_at)
)
attempts = attempts_result.scalars().all()
return [
{
"test_title": paper_ids[a.test_paper_id].title,
"percentage": a.percentage,
"score": a.score,
"total_marks": a.total_marks,
"is_targeted": paper_ids[a.test_paper_id].is_targeted,
"evaluation": a.evaluation,
"attempted_at": a.attempted_at.isoformat(),
}
for a in attempts
]
@router.post("/generate/{student_id}/{syllabus_id}")
async def generate_analysis(student_id: str, syllabus_id: str, db: AsyncSession = Depends(get_db)):
student_result = await db.execute(select(Student).where(Student.id == student_id))
student = student_result.scalar_one_or_none()
if not student:
raise HTTPException(404, "Student not found")
syllabus_result = await db.execute(select(Syllabus).where(Syllabus.id == syllabus_id))
syllabus = syllabus_result.scalar_one_or_none()
if not syllabus:
raise HTTPException(404, "Syllabus not found")
attempts = await _gather_attempts(student_id, syllabus_id, db)
if not attempts:
raise HTTPException(400, "No test attempts found for this student/syllabus combination")
existing_result = await db.execute(
select(AnalysisReport).where(
AnalysisReport.student_id == student_id,
AnalysisReport.syllabus_id == syllabus_id
).order_by(AnalysisReport.version.desc())
)
existing = existing_result.scalars().first()
prev_report = None
if existing:
prev_report = {
"version": existing.version,
"overall_score": existing.overall_score,
"weaknesses": existing.weaknesses,
"strengths": existing.strengths,
}
analysis, usage = await ai_service.analyze_performance(
student_name=student.name,
subject=syllabus.subject,
grade=syllabus.grade,
syllabus_topics=syllabus.topics,
all_attempts=attempts,
previous_report=prev_report,
)
version = (existing.version + 1) if existing else 1
report = AnalysisReport(
id=gen_id(),
student_id=student_id,
syllabus_id=syllabus_id,
topic_performance=analysis["topic_performance"],
overall_score=analysis["overall_score"],
strengths=analysis["strengths"],
weaknesses=analysis["weaknesses"],
recommendations=analysis["recommendations"],
narrative=analysis["narrative"],
version=version,
tests_analyzed=len(attempts),
)
db.add(report)
db.add(TokenUsage(
id=gen_id(),
event_type="performance_analysis",
student_id=student_id,
student_name=student.name,
syllabus_id=syllabus_id,
subject=syllabus.subject,
grade=syllabus.grade,
input_tokens=usage["input_tokens"],
output_tokens=usage["output_tokens"],
total_tokens=usage["total_tokens"],
model=usage["model"],
extra_info={"tests_analyzed": len(attempts), "report_version": version},
))
await db.commit()
return {
"report_id": report.id,
"version": version,
"overall_score": analysis["overall_score"],
"topic_performance": analysis["topic_performance"],
"strengths": analysis["strengths"],
"weaknesses": analysis["weaknesses"],
"recommendations": analysis["recommendations"],
"narrative": analysis["narrative"],
"tests_analyzed": len(attempts),
}
@router.get("/{student_id}/{syllabus_id}")
async def get_analysis(student_id: str, syllabus_id: str, db: AsyncSession = Depends(get_db)):
result = await db.execute(
select(AnalysisReport).where(
AnalysisReport.student_id == student_id,
AnalysisReport.syllabus_id == syllabus_id
).order_by(AnalysisReport.version.desc())
)
report = result.scalars().first()
if not report:
raise HTTPException(404, "No analysis report found. Generate one first.")
return {
"report_id": report.id,
"version": report.version,
"overall_score": report.overall_score,
"topic_performance": report.topic_performance,
"strengths": report.strengths,
"weaknesses": report.weaknesses,
"recommendations": report.recommendations,
"narrative": report.narrative,
"tests_analyzed": report.tests_analyzed,
"updated_at": report.updated_at.isoformat(),
}
@router.get("/graphs/{student_id}/{syllabus_id}")
async def get_graphs(student_id: str, syllabus_id: str, db: AsyncSession = Depends(get_db)):
report_result = await db.execute(
select(AnalysisReport).where(
AnalysisReport.student_id == student_id,
AnalysisReport.syllabus_id == syllabus_id
).order_by(AnalysisReport.version.desc())
)
report = report_result.scalars().first()
if not report:
raise HTTPException(404, "Generate analysis report first")
attempts = await _gather_attempts(student_id, syllabus_id, db)
graphs = generate_all_graphs(report.topic_performance, attempts)
return {
"student_id": student_id,
"syllabus_id": syllabus_id,
"graphs": graphs,
}
@router.post("/targeted-test/{student_id}/{syllabus_id}")
async def generate_targeted_test(student_id: str, syllabus_id: str, db: AsyncSession = Depends(get_db)):
student_result = await db.execute(select(Student).where(Student.id == student_id))
student = student_result.scalar_one_or_none()
if not student:
raise HTTPException(404, "Student not found")
syllabus_result = await db.execute(select(Syllabus).where(Syllabus.id == syllabus_id))
syllabus = syllabus_result.scalar_one_or_none()
if not syllabus:
raise HTTPException(404, "Syllabus not found")
report_result = await db.execute(
select(AnalysisReport).where(
AnalysisReport.student_id == student_id,
AnalysisReport.syllabus_id == syllabus_id
).order_by(AnalysisReport.version.desc())
)
report = report_result.scalars().first()
if not report:
raise HTTPException(400, "Generate analysis report first before creating targeted tests")
weak_topics = []
weak_subtopics = []
for tp in report.topic_performance:
if tp.get("overall_score") is not None and tp["overall_score"] < 60:
weak_topics.append(tp["topic"])
for st in tp.get("subtopic_scores", []):
if st.get("score") is not None and st["score"] < 60:
weak_subtopics.append(st["name"])
if not weak_topics and not weak_subtopics:
weak_topics = [tp["topic"] for tp in report.topic_performance[:2]]
paper_data, usage = await ai_service.generate_targeted_test(
subject=syllabus.subject,
grade=syllabus.grade,
student_name=student.name,
weak_topics=weak_topics,
weak_subtopics=weak_subtopics,
topic_details=syllabus.topics,
)
plan_result = await db.execute(select(TestPlan).where(TestPlan.syllabus_id == syllabus_id))
plan = plan_result.scalar_one_or_none()
paper = TestPaper(
id=gen_id(),
test_plan_id=plan.id,
sequence_number=0,
title=paper_data["title"],
questions=paper_data["questions"],
total_marks=paper_data["total_marks"],
duration_minutes=paper_data["duration_minutes"],
topics_covered=paper_data["topics_covered"],
is_targeted=True,
target_student_id=student_id,
)
db.add(paper)
db.add(TokenUsage(
id=gen_id(),
event_type="targeted_test_generation",
student_id=student_id,
student_name=student.name,
syllabus_id=syllabus_id,
subject=syllabus.subject,
grade=syllabus.grade,
input_tokens=usage["input_tokens"],
output_tokens=usage["output_tokens"],
total_tokens=usage["total_tokens"],
model=usage["model"],
extra_info={"weak_topics": weak_topics, "question_count": len(paper_data["questions"])},
))
await db.commit()
return {
"test_paper_id": paper.id,
"title": paper.title,
"total_marks": paper.total_marks,
"duration_minutes": paper.duration_minutes,
"topics_covered": paper.topics_covered,
"focused_weak_areas": weak_topics + weak_subtopics,
"questions_count": len(paper.questions),
}