statistics.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. """
  2. Statistics API router.
  3. Provides endpoints for project and platform statistics.
  4. """
  5. import json
  6. from fastapi import APIRouter, HTTPException, status, Request
  7. from database import get_db_connection
  8. from schemas.statistics import (
  9. ProjectStatisticsResponse,
  10. OverviewStatisticsResponse,
  11. UserTaskStats
  12. )
  13. router = APIRouter(
  14. prefix="/api/statistics",
  15. tags=["statistics"]
  16. )
  17. def calculate_project_statistics(project_id: str) -> ProjectStatisticsResponse:
  18. """
  19. 计算项目统计信息。
  20. Args:
  21. project_id: 项目ID
  22. Returns:
  23. ProjectStatisticsResponse 包含项目统计信息
  24. """
  25. with get_db_connection() as conn:
  26. cursor = conn.cursor()
  27. # 获取项目信息
  28. cursor.execute("""
  29. SELECT id, name FROM projects WHERE id = ?
  30. """, (project_id,))
  31. project_row = cursor.fetchone()
  32. if not project_row:
  33. raise HTTPException(
  34. status_code=status.HTTP_404_NOT_FOUND,
  35. detail=f"项目 '{project_id}' 不存在"
  36. )
  37. project_name = project_row["name"]
  38. # 任务统计
  39. cursor.execute("""
  40. SELECT
  41. COUNT(*) as total_tasks,
  42. SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_tasks,
  43. SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress_tasks,
  44. SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_tasks
  45. FROM tasks
  46. WHERE project_id = ?
  47. """, (project_id,))
  48. task_row = cursor.fetchone()
  49. total_tasks = task_row["total_tasks"] or 0
  50. completed_tasks = task_row["completed_tasks"] or 0
  51. in_progress_tasks = task_row["in_progress_tasks"] or 0
  52. pending_tasks = task_row["pending_tasks"] or 0
  53. # 计算数据条数(从任务的 data.items 中统计)
  54. cursor.execute("""
  55. SELECT data FROM tasks WHERE project_id = ?
  56. """, (project_id,))
  57. total_items = 0
  58. task_rows = cursor.fetchall()
  59. for row in task_rows:
  60. try:
  61. data = json.loads(row["data"]) if isinstance(row["data"], str) else row["data"]
  62. items = data.get("items", [])
  63. total_items += len(items)
  64. except:
  65. pass
  66. # 标注数量
  67. cursor.execute("""
  68. SELECT COUNT(*) as annotation_count
  69. FROM annotations a
  70. JOIN tasks t ON a.task_id = t.id
  71. WHERE t.project_id = ?
  72. """, (project_id,))
  73. annotation_row = cursor.fetchone()
  74. annotated_items = annotation_row["annotation_count"] or 0
  75. # 计算完成率
  76. task_completion_rate = 0.0
  77. if total_tasks > 0:
  78. task_completion_rate = round(completed_tasks / total_tasks * 100, 2)
  79. data_completion_rate = 0.0
  80. if total_items > 0:
  81. data_completion_rate = round(annotated_items / total_items * 100, 2)
  82. # 人员统计
  83. cursor.execute("""
  84. SELECT
  85. u.id as user_id,
  86. u.username,
  87. COUNT(DISTINCT t.id) as assigned_tasks,
  88. SUM(CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END) as completed_tasks,
  89. SUM(CASE WHEN t.status = 'in_progress' THEN 1 ELSE 0 END) as in_progress_tasks,
  90. SUM(CASE WHEN t.status = 'pending' THEN 1 ELSE 0 END) as pending_tasks,
  91. COUNT(DISTINCT a.id) as annotation_count
  92. FROM users u
  93. LEFT JOIN tasks t ON t.assigned_to = u.id AND t.project_id = ?
  94. LEFT JOIN annotations a ON a.task_id = t.id AND a.user_id = u.id
  95. WHERE u.role = 'annotator' AND t.id IS NOT NULL
  96. GROUP BY u.id, u.username
  97. ORDER BY assigned_tasks DESC
  98. """, (project_id,))
  99. user_stats = []
  100. for user_row in cursor.fetchall():
  101. assigned = user_row["assigned_tasks"] or 0
  102. completed = user_row["completed_tasks"] or 0
  103. completion_rate = 0.0
  104. if assigned > 0:
  105. completion_rate = round(completed / assigned * 100, 2)
  106. user_stats.append(UserTaskStats(
  107. user_id=user_row["user_id"],
  108. username=user_row["username"],
  109. assigned_tasks=assigned,
  110. completed_tasks=completed,
  111. in_progress_tasks=user_row["in_progress_tasks"] or 0,
  112. pending_tasks=user_row["pending_tasks"] or 0,
  113. annotation_count=user_row["annotation_count"] or 0,
  114. completion_rate=completion_rate
  115. ))
  116. return ProjectStatisticsResponse(
  117. project_id=project_id,
  118. project_name=project_name,
  119. total_tasks=total_tasks,
  120. completed_tasks=completed_tasks,
  121. in_progress_tasks=in_progress_tasks,
  122. pending_tasks=pending_tasks,
  123. total_items=total_items,
  124. annotated_items=annotated_items,
  125. task_completion_rate=task_completion_rate,
  126. data_completion_rate=data_completion_rate,
  127. user_stats=user_stats
  128. )
  129. def calculate_overview_statistics() -> OverviewStatisticsResponse:
  130. """
  131. 计算平台总览统计信息。
  132. Returns:
  133. OverviewStatisticsResponse 包含平台统计信息
  134. """
  135. with get_db_connection() as conn:
  136. cursor = conn.cursor()
  137. # 项目统计
  138. cursor.execute("SELECT COUNT(*) as total FROM projects")
  139. total_projects = cursor.fetchone()["total"] or 0
  140. # 任务统计
  141. cursor.execute("""
  142. SELECT
  143. COUNT(*) as total_tasks,
  144. SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_tasks,
  145. SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress_tasks,
  146. SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_tasks
  147. FROM tasks
  148. """)
  149. task_row = cursor.fetchone()
  150. total_tasks = task_row["total_tasks"] or 0
  151. completed_tasks = task_row["completed_tasks"] or 0
  152. in_progress_tasks = task_row["in_progress_tasks"] or 0
  153. pending_tasks = task_row["pending_tasks"] or 0
  154. # 用户统计
  155. cursor.execute("""
  156. SELECT
  157. COUNT(*) as total_users,
  158. SUM(CASE WHEN role = 'admin' THEN 1 ELSE 0 END) as admin_count,
  159. SUM(CASE WHEN role = 'annotator' THEN 1 ELSE 0 END) as annotator_count
  160. FROM users
  161. """)
  162. user_row = cursor.fetchone()
  163. total_users = user_row["total_users"] or 0
  164. admin_count = user_row["admin_count"] or 0
  165. annotator_count = user_row["annotator_count"] or 0
  166. # 标注统计
  167. cursor.execute("SELECT COUNT(*) as total FROM annotations")
  168. total_annotations = cursor.fetchone()["total"] or 0
  169. # 计算整体完成率
  170. overall_completion_rate = 0.0
  171. if total_tasks > 0:
  172. overall_completion_rate = round(completed_tasks / total_tasks * 100, 2)
  173. return OverviewStatisticsResponse(
  174. total_projects=total_projects,
  175. total_tasks=total_tasks,
  176. completed_tasks=completed_tasks,
  177. in_progress_tasks=in_progress_tasks,
  178. pending_tasks=pending_tasks,
  179. total_users=total_users,
  180. admin_count=admin_count,
  181. annotator_count=annotator_count,
  182. total_annotations=total_annotations,
  183. overall_completion_rate=overall_completion_rate
  184. )
  185. @router.get("/overview", response_model=OverviewStatisticsResponse)
  186. async def get_overview_statistics(request: Request):
  187. """
  188. 获取平台总览统计信息。
  189. 需要管理员权限。
  190. """
  191. user = getattr(request.state, "user", None)
  192. if not user:
  193. raise HTTPException(
  194. status_code=status.HTTP_401_UNAUTHORIZED,
  195. detail="未认证"
  196. )
  197. if user["role"] != "admin":
  198. raise HTTPException(
  199. status_code=status.HTTP_403_FORBIDDEN,
  200. detail="只有管理员可以查看平台统计"
  201. )
  202. return calculate_overview_statistics()
  203. @router.get("/projects/{project_id}", response_model=ProjectStatisticsResponse)
  204. async def get_project_statistics(request: Request, project_id: str):
  205. """
  206. 获取项目统计信息。
  207. 包含任务统计、数据统计和人员统计。
  208. """
  209. user = getattr(request.state, "user", None)
  210. if not user:
  211. raise HTTPException(
  212. status_code=status.HTTP_401_UNAUTHORIZED,
  213. detail="未认证"
  214. )
  215. return calculate_project_statistics(project_id)