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

1from fastapi import APIRouter, Depends, HTTPException, Query, Request, status 

2from server.authentication.auth0_bearer import Auth0Bearer 

3from server.services.student.student_dashboard import DashboardService 

4 

5 

6router = APIRouter() 

7 

8dashboard_service = DashboardService() 

9 

10 

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) 

25 

26 

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) 

52 

53 

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) 

77 

78 

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) 

105 

106 

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) 

129 

130 

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) 

157 

158 

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)