""" Public dashboard endpoint — no authentication required. Used by the TV view to get the initial session snapshot before WebSocket connects. """ from datetime import date from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from sqlalchemy.orm import selectinload from app.dependencies import get_db from app.models.child import Child from app.models.morning_routine import MorningRoutineItem from app.models.schedule import ScheduleBlock, ScheduleTemplate from app.models.subject import Subject # noqa: F401 — needed for selectinload chain from app.models.session import DailySession, TimerEvent from app.schemas.session import DashboardSnapshot from app.utils.timer import compute_block_elapsed router = APIRouter(prefix="/api/dashboard", tags=["dashboard"]) @router.get("/{child_id}", response_model=DashboardSnapshot) async def get_dashboard(child_id: int, db: AsyncSession = Depends(get_db)): child_result = await db.execute(select(Child).where(Child.id == child_id, Child.is_active == True)) child = child_result.scalar_one_or_none() if not child: raise HTTPException(status_code=404, detail="Child not found") # Get today's active session session_result = await db.execute( select(DailySession) .where( DailySession.child_id == child_id, DailySession.session_date == date.today(), DailySession.is_active == True, ) .options(selectinload(DailySession.current_block)) .limit(1) ) session = session_result.scalar_one_or_none() blocks = [] completed_ids = [] block_elapsed_seconds = 0 day_start_time = None day_end_time = None if session and session.template_id: blocks_result = await db.execute( select(ScheduleBlock) .where(ScheduleBlock.template_id == session.template_id) .options(selectinload(ScheduleBlock.subject).selectinload(Subject.options)) .order_by(ScheduleBlock.time_start) ) blocks = blocks_result.scalars().all() template_result = await db.execute( select(ScheduleTemplate).where(ScheduleTemplate.id == session.template_id) ) template = template_result.scalar_one_or_none() if template: day_start_time = template.day_start_time day_end_time = template.day_end_time events_result = await db.execute( select(TimerEvent).where( TimerEvent.session_id == session.id, TimerEvent.event_type == "complete", ) ) completed_ids = [e.block_id for e in events_result.scalars().all() if e.block_id] # Compute elapsed seconds and paused state for the current block from timer_events is_paused = False if session and session.current_block_id: block_elapsed_seconds, is_paused = await compute_block_elapsed( db, session.id, session.current_block_id ) routine_result = await db.execute( select(MorningRoutineItem) .where(MorningRoutineItem.user_id == child.user_id) .order_by(MorningRoutineItem.order_index, MorningRoutineItem.id) ) morning_routine = [item.text for item in routine_result.scalars().all()] return DashboardSnapshot( session=session, child=child, blocks=blocks, completed_block_ids=completed_ids, block_elapsed_seconds=block_elapsed_seconds, is_paused=is_paused, day_start_time=day_start_time, day_end_time=day_end_time, morning_routine=morning_routine, )