Spaces:
Sleeping
Sleeping
| from fastapi import APIRouter, Depends | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| from sqlalchemy import select, func | |
| from ..database import get_db | |
| from ..models import TokenUsage, Student | |
| router = APIRouter(prefix="/api/analytics", tags=["Analytics"]) | |
| # GPT-4o-mini pricing (per 1M tokens) | |
| INPUT_COST_PER_M = 0.15 | |
| OUTPUT_COST_PER_M = 0.60 | |
| EVENT_LABELS = { | |
| "syllabus_analysis": "Syllabus Analysis", | |
| "test_generation": "Test Paper Generation", | |
| "answer_evaluation": "Answer Evaluation", | |
| "performance_analysis": "Performance Analysis", | |
| "targeted_test_generation": "Targeted Test Generation", | |
| } | |
| def calc_cost(input_tokens: int, output_tokens: int) -> float: | |
| return round( | |
| (input_tokens / 1_000_000) * INPUT_COST_PER_M | |
| + (output_tokens / 1_000_000) * OUTPUT_COST_PER_M, | |
| 6, | |
| ) | |
| async def get_overview(db: AsyncSession = Depends(get_db)): | |
| result = await db.execute(select(TokenUsage).order_by(TokenUsage.created_at.desc())) | |
| rows = result.scalars().all() | |
| total_input = sum(r.input_tokens for r in rows) | |
| total_output = sum(r.output_tokens for r in rows) | |
| total_tokens = sum(r.total_tokens for r in rows) | |
| by_event: dict[str, dict] = {} | |
| for r in rows: | |
| et = r.event_type | |
| if et not in by_event: | |
| by_event[et] = {"event_type": et, "label": EVENT_LABELS.get(et, et), | |
| "count": 0, "input_tokens": 0, "output_tokens": 0, "total_tokens": 0} | |
| by_event[et]["count"] += 1 | |
| by_event[et]["input_tokens"] += r.input_tokens | |
| by_event[et]["output_tokens"] += r.output_tokens | |
| by_event[et]["total_tokens"] += r.total_tokens | |
| for et in by_event: | |
| by_event[et]["cost_usd"] = calc_cost(by_event[et]["input_tokens"], by_event[et]["output_tokens"]) | |
| return { | |
| "total_events": len(rows), | |
| "total_input_tokens": total_input, | |
| "total_output_tokens": total_output, | |
| "total_tokens": total_tokens, | |
| "estimated_cost_usd": calc_cost(total_input, total_output), | |
| "by_event_type": sorted(by_event.values(), key=lambda x: x["total_tokens"], reverse=True), | |
| "model": rows[0].model if rows else "β", | |
| } | |
| async def get_by_student(db: AsyncSession = Depends(get_db)): | |
| result = await db.execute(select(TokenUsage).where(TokenUsage.student_id.isnot(None))) | |
| rows = result.scalars().all() | |
| students_result = await db.execute(select(Student).order_by(Student.created_at.asc())) | |
| students = students_result.scalars().all() | |
| student_code_map = {s.id: f"STU-{i+1:03d}" for i, s in enumerate(students)} | |
| by_student: dict[str, dict] = {} | |
| for r in rows: | |
| sid = r.student_id | |
| if sid not in by_student: | |
| by_student[sid] = { | |
| "student_id": sid, | |
| "student_name": r.student_name or "Unknown", | |
| "student_code": student_code_map.get(sid, "β"), | |
| "total_tokens": 0, | |
| "input_tokens": 0, | |
| "output_tokens": 0, | |
| "event_count": 0, | |
| "by_event": {}, | |
| } | |
| by_student[sid]["total_tokens"] += r.total_tokens | |
| by_student[sid]["input_tokens"] += r.input_tokens | |
| by_student[sid]["output_tokens"] += r.output_tokens | |
| by_student[sid]["event_count"] += 1 | |
| et = r.event_type | |
| by_student[sid]["by_event"][et] = by_student[sid]["by_event"].get(et, 0) + r.total_tokens | |
| for sid in by_student: | |
| by_student[sid]["cost_usd"] = calc_cost( | |
| by_student[sid]["input_tokens"], by_student[sid]["output_tokens"] | |
| ) | |
| return sorted(by_student.values(), key=lambda x: x["total_tokens"], reverse=True) | |
| async def get_events(limit: int = 100, db: AsyncSession = Depends(get_db)): | |
| result = await db.execute( | |
| select(TokenUsage).order_by(TokenUsage.created_at.desc()).limit(limit) | |
| ) | |
| rows = result.scalars().all() | |
| students_result = await db.execute(select(Student).order_by(Student.created_at.asc())) | |
| students = students_result.scalars().all() | |
| student_code_map = {s.id: f"STU-{i+1:03d}" for i, s in enumerate(students)} | |
| return [ | |
| { | |
| "id": r.id, | |
| "event_type": r.event_type, | |
| "label": EVENT_LABELS.get(r.event_type, r.event_type), | |
| "student_name": r.student_name, | |
| "student_code": student_code_map.get(r.student_id, "β") if r.student_id else "β", | |
| "subject": r.subject, | |
| "grade": r.grade, | |
| "input_tokens": r.input_tokens, | |
| "output_tokens": r.output_tokens, | |
| "total_tokens": r.total_tokens, | |
| "cost_usd": calc_cost(r.input_tokens, r.output_tokens), | |
| "model": r.model, | |
| "extra_info": r.extra_info, | |
| "created_at": r.created_at.isoformat(), | |
| } | |
| for r in rows | |
| ] | |