from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from app.api.auth import get_current_user_id from app.api.schemas import ( AssessmentDetailResponse, SpeechAggregatesResponse, SpeechAssessmentResponse, StatsResponse, UserResponse, ) from app.db.session import get_session_factory from app.services.grammar_exercise import get_exercise_count from app.services.pronunciation import get_error_count from app.services.speech_assessment import ( get_assessment, get_recent_assessments, get_score_aggregates, ) from app.services.stats import get_stats from app.services.user import get_or_create_user router = APIRouter(tags=["user"]) @router.get("/user", response_model=UserResponse) async def get_user(user_id: int = Depends(get_current_user_id)) -> UserResponse: """Get current user profile.""" factory = get_session_factory() async with factory() as s: user = await get_or_create_user(s, user_id) return UserResponse( telegram_id=user.telegram_id, level=user.level, interface_language=user.interface_language, created_at=user.created_at, ) @router.get("/stats", response_model=StatsResponse) async def get_user_stats(user_id: int = Depends(get_current_user_id)) -> StatsResponse: """Get learning statistics.""" factory = get_session_factory() async with factory() as s: user = await get_or_create_user(s, user_id) st = await get_stats(s, user_id) pron_count = await get_error_count(s, user_id) gram_count = await get_exercise_count(s, user_id) assessed = user.last_assessed_at.strftime("%d %b %Y") if user.last_assessed_at else "not yet" return StatsResponse( level=user.level, last_assessed=assessed, total_messages=st.total_messages, total_corrections=st.total_corrections, accuracy_pct=st.accuracy_pct, streak_days=st.streak_days, words_saved=st.words_saved, pronunciation_errors=pron_count, grammar_exercises=gram_count, ) @router.get("/assessment/latest", response_model=SpeechAssessmentResponse | None) async def get_latest_assessment( user_id: int = Depends(get_current_user_id), ) -> SpeechAssessmentResponse | None: """Get the most recent speech assessment (6 metrics + transcript), or null.""" factory = get_session_factory() async with factory() as s: rows = await get_recent_assessments(s, user_id, limit=1) if not rows: return None a = rows[0] return SpeechAssessmentResponse( accuracy=a.accuracy, fluency=a.fluency, prosody=a.prosody, vocabulary=a.vocabulary, grammar=a.grammar, topic=a.topic, stress_accuracy=a.stress_accuracy, transcript=a.transcript, created_at=a.created_at, ) @router.get("/assessment/phrase/{assessment_id}", response_model=AssessmentDetailResponse) async def get_phrase_analysis( assessment_id: int, user_id: int = Depends(get_current_user_id), ) -> AssessmentDetailResponse: """Full per-phrase analysis: six metrics, transcript, grammar fixes, pronunciation.""" factory = get_session_factory() async with factory() as s: user = await get_or_create_user(s, user_id) a = await get_assessment(s, assessment_id, user_id) if a is None: raise HTTPException(status_code=404, detail="assessment not found") return AssessmentDetailResponse( id=a.id, dialog_turn_id=a.dialog_turn_id, accuracy=a.accuracy, fluency=a.fluency, prosody=a.prosody, vocabulary=a.vocabulary, grammar=a.grammar, topic=a.topic, stress_accuracy=a.stress_accuracy, transcript=a.transcript, corrections=a.corrections or [], pronunciation_notes=a.pronunciation_notes or [], stress_notes=a.stress_notes or [], peaks=a.peaks, duration_sec=a.duration_sec, level=user.level, created_at=a.created_at, ) @router.get("/assessment/aggregates", response_model=SpeechAggregatesResponse) async def get_assessment_aggregates( user_id: int = Depends(get_current_user_id), ) -> SpeechAggregatesResponse: """Get averaged speech metrics over the user's recent turns.""" factory = get_session_factory() async with factory() as s: agg = await get_score_aggregates(s, user_id) return SpeechAggregatesResponse( accuracy=agg["accuracy"], fluency=agg["fluency"], prosody=agg["prosody"], vocabulary=agg["vocabulary"], grammar=agg["grammar"], topic=agg["topic"], stress_accuracy=agg["stress_accuracy"], count=agg["count"] or 0, )