Spaces:
Sleeping
Sleeping
| 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 | |
| ] | |
| 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), | |
| } | |
| 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(), | |
| } | |
| 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, | |
| } | |
| 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), | |
| } | |