Spaces:
Configuration error
Configuration error
| from datetime import datetime, timedelta, timezone | |
| from typing import Optional | |
| from app.core.logging_config import get_logger | |
| from app.db.redis import get_redis | |
| from app.repositories.session_repo import SessionRepository | |
| from app.repositories.class_repo import ClassRepository | |
| from app.schemas.teacher import SessionResponse, SessionStart | |
| logger = get_logger("app.session") | |
| class SessionService: | |
| def __init__(self) -> None: | |
| self.session_repo = SessionRepository() | |
| self.class_repo = ClassRepository() | |
| async def start_session(self, data: SessionStart, teacher_id: str) -> Optional[SessionResponse]: | |
| subject_class = await self.class_repo.get_by_id(data.academic_class_id) | |
| if not subject_class or subject_class.teacherId != teacher_id: | |
| return None | |
| active = await self.session_repo.get_active_session_by_class(data.academic_class_id) | |
| if active: | |
| await self.close_session(active.id) | |
| now = datetime.now(timezone.utc) | |
| session = await self.session_repo.create( | |
| class_id=data.academic_class_id, start_time=now, end_time=now + timedelta(minutes=data.duration_minutes) | |
| ) | |
| return SessionResponse.model_validate(session) | |
| async def stop_session(self, session_id: str, teacher_id: str) -> bool: | |
| session = await self.session_repo.get_by_id(session_id) | |
| if not session or not session.isActive: | |
| return False | |
| subject_class = await self.class_repo.get_by_id(session.academicClassId) | |
| if not subject_class or subject_class.teacherId != teacher_id: | |
| return False | |
| await self.close_session(session_id) | |
| return True | |
| async def close_session(self, session_id: str) -> None: | |
| """Mark session as inactive, delete from Redis, and mark all unsubmitted students as Absent or Excused.""" | |
| session = await self.session_repo.get_by_id(session_id) | |
| if not session or not session.isActive: | |
| return | |
| # 1. Deactivate in DB | |
| await self.session_repo.deactivate(session_id) | |
| # 2. Delete from Redis | |
| try: | |
| await get_redis().delete(f"session:{session_id}") | |
| except Exception as e: | |
| logger.warning("Failed to delete session from Redis: %s", e) | |
| # 3. Get enrollments and existing attendance | |
| from app.db.client import db | |
| from app.services.gamification_service import GamificationService | |
| enrollments = await db.enrollment.find_many( | |
| where={"academicClassId": session.academicClassId}, | |
| include={"student": {"include": {"leaveRequests": True}}} | |
| ) | |
| attendance_records = await db.attendance.find_many(where={"sessionId": session_id}) | |
| submitted_student_ids = {a.studentId for a in attendance_records} | |
| gamification_service = GamificationService() | |
| sess_start = session.startTime.replace(tzinfo=timezone.utc) if session.startTime.tzinfo is None else session.startTime | |
| for enrollment in enrollments: | |
| student = enrollment.student | |
| if not student or student.id in submitted_student_ids: | |
| continue | |
| # Check if student was on approved leave during the session | |
| on_leave = False | |
| for leave in student.leaveRequests: | |
| if leave.status == "APPROVED": | |
| l_start = leave.startDate.replace(tzinfo=timezone.utc) if leave.startDate.tzinfo is None else leave.startDate | |
| l_end = leave.endDate.replace(tzinfo=timezone.utc) if leave.endDate.tzinfo is None else leave.endDate | |
| if l_start <= sess_start <= l_end: | |
| on_leave = True | |
| break | |
| # Create the record | |
| status_val = "Excused" if on_leave else "Absent" | |
| remarks_val = "Excused via approved leave request" if on_leave else "Session ended without student submission" | |
| try: | |
| await db.attendance.create(data={ | |
| "studentId": student.id, | |
| "sessionId": session_id, | |
| "status": status_val, | |
| "faceScore": 0.0, | |
| "livenessScore": 0.0, | |
| "backgroundScore": 0.0, | |
| "finalAiScore": 0.0, | |
| "gpsLatitude": 0.0, | |
| "gpsLongitude": 0.0, | |
| "remarks": remarks_val, | |
| }) | |
| except Exception as e: | |
| logger.warning("Failed to create default attendance for student %s: %s", student.id, e) | |
| continue | |
| # Recalculate streak | |
| try: | |
| await gamification_service.recalculate_student_streak(student.id) | |
| except Exception as e: | |
| logger.warning("Failed to recalculate streak for student %s: %s", student.id, e) | |