Coverage for server / routes / student / student_dashboard.py: 100%
26 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-04 09:33 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-04 09:33 +0000
1from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
2from server.authentication.auth0_bearer import Auth0Bearer
3from server.services.student.student_dashboard import DashboardService
6router = APIRouter()
8dashboard_service = DashboardService()
11@router.get(
12 "/achievements/fetch",
13 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))],
14 status_code=status.HTTP_200_OK,
15 summary="EI-2966 — As a student, I can fetch my achievement badges",
16 description=(
17 "Returns the student's achievement badges, derived on-the-fly from "
18 "existing submission data. No new schema is introduced — each badge "
19 "is a pure predicate over the student's submitted submissions."
20 ),
21)
22async def achievements_fetch(request: Request) -> dict:
23 """Returns {achievements: [...], earned_count: int, total_count: int}."""
24 return await dashboard_service.achievements_fetch(request)
27@router.get(
28 "/grade-distribution/fetch",
29 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))],
30 status_code=status.HTTP_200_OK,
31 summary="EI-2966 — As a student, I can fetch my grade distribution (histogram buckets)",
32 description=(
33 "Compute the student's grade distribution as histogram buckets (0-59, 60-69, "
34 "70-79, 80-89, 90-100) across all submitted+graded submissions. Backs the "
35 "Score Distribution chart on the Student Dashboard — feature-shipped "
36 "2026-06-15 (EI-2966 backfill chain)."
37 ),
38)
39async def grade_distribution_fetch(request: Request) -> dict:
40 """
41 Returns:
42 dict: {
43 "buckets": [
44 { "label": "<60", "min": 0, "max": 59, "count": int },
45 { "label": "60-69", "min": 60, "max": 69, "count": int },
46 ...
47 ],
48 "total_graded": int,
49 }
50 """
51 return await dashboard_service.grade_distribution_fetch(request)
54@router.get(
55 "/gpa/fetch",
56 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))],
57 status_code=status.HTTP_200_OK,
58 summary="EI-2966 — As a student, I can fetch my GPA (overall + per-class)",
59 description=(
60 "Compute the student's overall + per-class average grade across submitted "
61 "submissions where `grade` is set. Backs the GPA card on the Student "
62 "Dashboard — feature-shipped 2026-06-15 (EI-2966 backfill chain)."
63 ),
64)
65async def gpa_fetch(request: Request) -> dict:
66 """
67 Returns:
68 dict: {
69 "overall_average": float|None, # null when no graded submissions
70 "total_graded": int,
71 "per_class": [
72 { "class_id", "class_title", "class_code", "average", "count" }
73 ],
74 }
75 """
76 return await dashboard_service.gpa_fetch(request)
79@router.get(
80 "/assignments/upcoming/fetch",
81 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))],
82 status_code=status.HTTP_200_OK,
83 summary="EI-2966 — As a student, I can fetch upcoming assignments by due date",
84 description=(
85 "Fetch the student's UPCOMING assignments (`date_close >= now`, sorted "
86 "ascending), capped at `limit` for the Student Dashboard Calendar widget. "
87 "Backs the `assignments/upcoming` data layer used by the dashboard "
88 "Calendar — feature-shipped 2026-06-15 (EI-2966 backfill chain)."
89 ),
90)
91async def upcoming_assignments_fetch(
92 request: Request,
93 limit: int = Query(
94 20,
95 ge=1,
96 le=100,
97 description="Maximum number of upcoming assignments to return (default 20)",
98 ),
99) -> dict:
100 """
101 Returns:
102 dict: { "upcoming": [<assignment minus internal fields>...] }
103 """
104 return await dashboard_service.upcoming_assignments_fetch(request, limit=limit)
107@router.get(
108 "/classes/statistics/fetch",
109 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))],
110 status_code=status.HTTP_200_OK,
111 description="As a student, I can fetch statistics for classes",
112 summary="As a student, I can fetch statistics for classes",
113)
114async def class_statistics_fetch(
115 request: Request,
116 ) -> dict:
117 """
118 Fetch the total number of classes in which the current user (student) is enrolled.
119 Args:
120 request (Request): The incoming FastAPI request object containing user details
121 in `request.state.user_details`.
122 Returns:
123 dict: A dictionary containing the total number of classes the student is enrolled in.
124 Example: {"totalClasses": 3}
125 Raises:
126 HTTPException: Raises a 400 Bad Request if any error occurs during the database query.
127 """
128 return await dashboard_service.class_statistics_fetch(request)
131@router.get(
132 "/assignments/statistics/fetch",
133 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))],
134 status_code=status.HTTP_200_OK,
135 response_description="Get statistics for classes",
136 description="As a student, I can fetch statistics for assignments",
137 summary="As a student, I can fetch statistics for assignments",
138)
139async def assignment_statistics_fetch(request: Request):
140 """
141 Fetch the total number of assignments assigned to classes where the current user (student) is enrolled.
142 This uses an aggregation pipeline to:
143 - Find classes where the student is enrolled.
144 - Lookup assignments linked to those classes.
145 - Filter out deleted assignments.
146 - Return the total count of valid assignments.
147 Args:
148 request (Request): The incoming FastAPI request object containing user details
149 in `request.state.user_details`.
150 Returns:
151 dict: A dictionary containing the total number of assignments.
152 Example: {"totalAssignments": 5}
153 Raises:
154 HTTPException: Raises a 400 Bad Request if any error occurs during the aggregation.
155 """
156 return await dashboard_service.assignment_statistics_fetch(request)
159@router.get(
160 "/submissions/statistics/fetch",
161 dependencies=[Depends(Auth0Bearer(access_levels=["student"]))],
162 status_code=status.HTTP_200_OK,
163 response_description="Get statistics for classes",
164 description="As a student, I can fetch statistics for classes",
165 summary="As a student, I can fetch statistics for classes",
166)
167async def student_statistics_fetch(request: Request) -> dict:
168 """
169 Fetch class-wise submissions for the current student with associated assignment and class details.
170 This method performs the following:
171 - Matches all submissions where the `student_id` matches the current logged-in user and `is_submitted` is True.
172 - Joins with the assignments collection to get `assignment_id` and then with the classes collection to fetch class details.
173 - Groups submissions by class and aggregates all submissions per class.
174 - Returns a structured response containing:
175 - A list of classes, each with its `_id`, `title`, `section`, `class_code`, `assignment_id`, and an array of submissions.
176 - A `totalSubmissions` count across all classes.
177 Args:
178 request (Request): The FastAPI request object that contains `user_details` in `request.state`,
179 including the current student's `uuid`.
180 Raises:
181 HTTPException: If an error occurs during the database aggregation or data processing.
182 """
183 return await dashboard_service.submissions_statistics_fetch(request)