problem.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import json
  4. from fastapi import APIRouter, Depends, Query, Path
  5. from sqlalchemy import select, func, or_, distinct, asc
  6. from sqlalchemy.ext.asyncio import AsyncSession
  7. from crud.marktask import crud_student_answer, crud_task
  8. from crud.paper import crud_question
  9. from crud.problem import (crud_class_push_record, crud_student_push_record,
  10. crud_student_error_statistic)
  11. from crud.school import crud_class
  12. from models.marktask import MarkTask
  13. from models.paper import PaperQuestion
  14. from models.problem import ClassErrorQuestion, StudentErrorQuestion, StudentErrorPushRecord
  15. from models.user import Admin
  16. from schemas.problem import (ClassErrorQuestionList, StudentErrorQuestionList,
  17. StudentTaskErrorQuestionList, StudentErrorQuestionDetailList,
  18. StudentTaskErrorDetail)
  19. from schemas.problem import (StudentErrorBookQuestionList, ClassErrorPushRecordList)
  20. from utils.depends import get_current_user, get_async_db
  21. router = APIRouter(tags=["错题中心-管理后台"])
  22. @router.get("/cls-errs",
  23. response_model=ClassErrorQuestionList,
  24. response_model_exclude_none=True,
  25. summary="班级错题列表")
  26. async def class_errors(page: int = 1,
  27. size: int = 10,
  28. school_id: int = Query(0, alias="sid", description="学校ID"),
  29. grade_id: int = Query(0, alias="gid", description="年级ID"),
  30. class_id: int = Query(0, alias="cid", description="班级ID"),
  31. task_id: int = Query(0, alias="tid", description="阅卷任务ID"),
  32. kw: str = Query("", description="题目关键字"),
  33. ratio: int = Query(0, description="错题率"),
  34. db: AsyncSession = Depends(get_async_db),
  35. current_user: Admin = Depends(get_current_user)):
  36. # 查询条件
  37. _q = []
  38. if school_id: # 学校ID
  39. _q.append(ClassErrorQuestion.school_id == school_id)
  40. if grade_id: # 年级ID
  41. _q.append(ClassErrorQuestion.grade_id == grade_id)
  42. if class_id: # 班级ID
  43. _q.append(ClassErrorQuestion.class_id == class_id)
  44. if task_id:
  45. _q.append(ClassErrorQuestion.task_id == task_id)
  46. # 班级错题率
  47. if ratio > 0:
  48. _q.append(ClassErrorQuestion.error_ratio >= ratio)
  49. else:
  50. _q.append(ClassErrorQuestion.error_ratio > 0)
  51. if kw: # 题目关键字
  52. _q.append(PaperQuestion.stem.like(f"%{kw}%"))
  53. # 查询总数
  54. count_stmt = select(func.count()).select_from(ClassErrorQuestion).join(
  55. PaperQuestion, ClassErrorQuestion.qid == PaperQuestion.id)
  56. # 从学生答案表中查出所有的错题
  57. error_fields = [
  58. "class_id", "student_count", "error_count", "error_ratio", "task_type", "task_name", "qid",
  59. "answer_dist", "school_name", "grade_name", "class_name"
  60. ]
  61. question_fields = ["stem", "answer", "analysis"]
  62. data_stmt = select(
  63. *[getattr(ClassErrorQuestion, x) for x in error_fields],
  64. *[getattr(PaperQuestion, x) for x in question_fields],
  65. ).select_from(ClassErrorQuestion).join(PaperQuestion,
  66. ClassErrorQuestion.qid == PaperQuestion.id)
  67. if _q:
  68. count_stmt = count_stmt.where(*_q)
  69. data_stmt = data_stmt.where(*_q)
  70. # 总数
  71. count = await crud_student_answer.execute_v2(db, count_stmt)
  72. total = count[0][0]
  73. # 错题列表
  74. data = []
  75. offset = (page - 1) * size
  76. data_stmt = data_stmt.limit(size).offset(offset).order_by(asc(ClassErrorQuestion.qid))
  77. db_questions = await crud_student_answer.execute_v2(db, data_stmt)
  78. for q in db_questions:
  79. temp = {
  80. "student_count": q[1],
  81. "error_count": q[2],
  82. "right_count": q[1] - q[2],
  83. "error_ratio": q[3],
  84. "task_type": q[4],
  85. "task_name": q[5],
  86. "qid": q[6],
  87. "stem": q[11],
  88. "answer": q[12],
  89. "analysis": q[13],
  90. "school_name": q[8],
  91. "grade_name": q[9],
  92. "class_name": q[10]
  93. }
  94. try:
  95. _dist = [{"key": k, "val": v} for k, v in json.loads(q[7]).items()]
  96. _dist.sort(key=lambda x: x["key"])
  97. except json.decoder.JSONDecodeError:
  98. _dist = []
  99. temp["dist"] = _dist
  100. data.append(temp)
  101. return {"data": data, "total": total}
  102. @router.get("/stu-errs",
  103. response_model=StudentErrorQuestionList,
  104. response_model_exclude_none=True,
  105. summary="学生错题列表-综合")
  106. async def get_student_errors(page: int = 1,
  107. size: int = 10,
  108. school_id: int = Query(0, alias="sid", description="学校ID"),
  109. grade_id: int = Query(0, alias="gid", description="年级ID"),
  110. class_id: int = Query(0, alias="cid", description="班级ID"),
  111. kw: str = Query(None, description="关键词"),
  112. db: AsyncSession = Depends(get_async_db),
  113. current_user: Admin = Depends(get_current_user)):
  114. _q = [StudentErrorQuestion.error_ratio > 0]
  115. if class_id: # 班级ID
  116. _q.append(StudentErrorQuestion.class_id == class_id)
  117. if school_id:
  118. _q.append(StudentErrorQuestion.school_id == school_id)
  119. if grade_id:
  120. _q.append(StudentErrorQuestion.grade_id == grade_id)
  121. else: # 通过school_id和grade_id查询class_id
  122. _sq = {}
  123. if school_id:
  124. _sq["school_id"] = school_id
  125. if grade_id:
  126. _sq["grade_id"] = grade_id
  127. if _sq:
  128. total, db_classes = await crud_class.find_all(db, filters=_sq, return_fields=["id"])
  129. if not total:
  130. return {"errcode": 400, "mess": "班级不存在!"}
  131. _q.append(StudentErrorQuestion.class_id.in_([x.id for x in db_classes]))
  132. if kw: # 关键词
  133. _q.append(
  134. or_(StudentErrorQuestion.student_sno == kw, StudentErrorQuestion.student_name == kw))
  135. # 返回结果
  136. offset = (page - 1) * size
  137. stmt = select(func.count(distinct(StudentErrorQuestion.student_id)))\
  138. .select_from(StudentErrorQuestion).where(*_q)
  139. total = (await crud_student_error_statistic.execute_v2(db, stmt))[0][0]
  140. stmt = select(StudentErrorQuestion.student_id, StudentErrorQuestion.student_sno,
  141. StudentErrorQuestion.student_name,
  142. func.sum(StudentErrorQuestion.total_questions).label("total_questions"),
  143. func.sum(StudentErrorQuestion.total_errors).label("total_errors"),
  144. func.sum(StudentErrorQuestion.work_error_count).label("work_error_count"),
  145. func.sum(StudentErrorQuestion.exam_error_count).label("exam_error_count"),
  146. func.avg(StudentErrorQuestion.error_ratio).label("error_ratio"))\
  147. .select_from(StudentErrorQuestion)\
  148. .where(*_q)\
  149. .group_by(StudentErrorQuestion.student_id,
  150. StudentErrorQuestion.student_sno,
  151. StudentErrorQuestion.student_name)\
  152. .offset(offset)\
  153. .limit(size)
  154. # 查询数据
  155. db_errors = await crud_student_error_statistic.execute_v2(db, stmt)
  156. return {"data": db_errors, "total": total}
  157. @router.get("/stu-errs/{sid}/tasks",
  158. response_model=StudentTaskErrorQuestionList,
  159. response_model_exclude_none=True,
  160. summary="学生错题-任务错题统计")
  161. async def get_student_task_errors(page: int = 1,
  162. size: int = 10,
  163. student_id: int = Path(..., alias="sid", description="学生ID"),
  164. task_id: int = Query(0, alias="tid", description="任务ID"),
  165. db: AsyncSession = Depends(get_async_db),
  166. current_user: Admin = Depends(get_current_user)):
  167. # 查询条件
  168. _q = [StudentErrorQuestion.student_id == student_id, StudentErrorQuestion.error_ratio > 0]
  169. if task_id:
  170. _q.append(StudentErrorQuestion.task_id == task_id)
  171. # 查询错题统计数据
  172. offset = (page - 1) * size
  173. stmt = select(func.count(distinct(StudentErrorQuestion.task_id))) \
  174. .select_from(StudentErrorQuestion).where(*_q)
  175. total = (await crud_student_error_statistic.execute_v2(db, stmt))[0][0]
  176. stmt = select(
  177. StudentErrorQuestion.student_task_id, MarkTask.name, MarkTask.mtype,
  178. StudentErrorQuestion.total_questions, StudentErrorQuestion.work_error_count,
  179. StudentErrorQuestion.exam_error_count, StudentErrorQuestion.error_ratio, MarkTask.id)\
  180. .select_from(StudentErrorQuestion).join(MarkTask, StudentErrorQuestion.task_id == MarkTask.id)\
  181. .where(*_q).offset(offset).limit(size)
  182. # 查询数据
  183. data = []
  184. db_objs = await crud_student_error_statistic.execute_v2(db, stmt)
  185. for item in db_objs:
  186. temp = {
  187. "student_task_id": item[0],
  188. "task_name": item[1],
  189. "task_type": item[2],
  190. "total_count": item[3],
  191. "error_count": item[4] if item[2] == "work" else item[5],
  192. "error_ratio": item[6],
  193. "task_id": item[7]
  194. }
  195. data.append(temp)
  196. return {"data": data, "total": total}
  197. @router.get("/stu-errs/{sid}/tasks/{tid}/error-info",
  198. response_model=StudentTaskErrorDetail,
  199. response_model_exclude_none=True,
  200. summary="学生阅卷任务错题信息(学生错题顶部使用)")
  201. async def get_task_error_info(student_id: int = Path(..., alias="sid", description="学生ID"),
  202. student_task_id: int = Path(..., alias="tid", description="学生阅卷任务ID"),
  203. db: AsyncSession = Depends(get_async_db),
  204. current_user: Admin = Depends(get_current_user)):
  205. db_obj = await crud_student_error_statistic.find_one(db,
  206. filters={
  207. "student_id": student_id,
  208. "student_task_id": student_task_id
  209. })
  210. db_task = await crud_task.find_one(db, filters={"id": db_obj.task_id})
  211. data = {
  212. "student_sno": db_obj.student_sno,
  213. "student_name": db_obj.student_name,
  214. "total_questions": db_obj.total_questions,
  215. "total_errors": db_obj.total_errors,
  216. "error_ratio": db_obj.error_ratio,
  217. "task_id": db_obj.task_id,
  218. "task_name": db_task.name
  219. }
  220. if not db_obj:
  221. return {"errcode": 404, "mess": "阅卷任务不存在!"}
  222. return {"data": data}
  223. @router.get("/stu-errs/{sid}/tasks/{tid}/questions",
  224. response_model=StudentErrorQuestionDetailList,
  225. response_model_exclude_none=True,
  226. summary="学生错题-错误试题列表")
  227. async def get_personal_errors(page: int = 1,
  228. size: int = 10,
  229. student_id: int = Path(..., alias="sid", description="学生ID"),
  230. student_task_id: int = Path(..., alias="tid", description="学生阅卷任务ID"),
  231. db: AsyncSession = Depends(get_async_db),
  232. current_user: Admin = Depends(get_current_user)):
  233. # 返回结果
  234. data = []
  235. _q = {"incorrect": 1, "student_id": student_id, "student_task_id": student_task_id}
  236. question_ids = []
  237. question_dict = {}
  238. counter = 0
  239. offset = (page - 1) * size
  240. total, db_errors = await crud_student_answer.find_all(
  241. db,
  242. filters=_q,
  243. return_fields=["pid", "qid", "task_id", "task_name", "mtype", "qimg", "marked_img"],
  244. limit=size,
  245. offset=offset)
  246. for x in db_errors:
  247. question_dict[x.qid] = counter
  248. question_ids.append(x.qid)
  249. temp = {
  250. "pid": x.pid,
  251. "qid": x.qid,
  252. "marked_img": x.marked_img or x.qimg,
  253. "task_id": x.task_id,
  254. "task_name": x.task_name,
  255. "task_type": x.mtype.value
  256. }
  257. data.append(temp)
  258. counter += 1
  259. # 查询试题信息
  260. if data:
  261. _q = [PaperQuestion.pid == data[0]['pid']]
  262. if len(question_ids) == 1:
  263. _q.append(PaperQuestion.id == question_ids[0])
  264. else:
  265. _q.append(PaperQuestion.id.in_(question_ids))
  266. _, db_questions = await crud_question.find_all(
  267. db, filters=_q, return_fields=["id", "answer", "analysis", "level", "lpoints"])
  268. for x in db_questions:
  269. data[question_dict[x.id]].update({
  270. "answer": x.answer,
  271. "analysis": x.analysis,
  272. "level": x.level,
  273. "lpoints": x.lpoints
  274. })
  275. return {"data": data, "total": total}
  276. @router.get("/cls-push-errs",
  277. response_model=ClassErrorPushRecordList,
  278. response_model_exclude_none=True,
  279. summary="错题推送记录列表")
  280. async def get_cls_push_records(page: int = 1,
  281. size: int = 10,
  282. school_id: int = Query(0, alias="sid", description="学校ID"),
  283. grade_id: int = Query(0, alias="gid", description="年级ID"),
  284. class_id: int = Query(0, alias="cid", description="班级ID"),
  285. task_id: int = Query(0, alias="tid", description="阅卷任务ID"),
  286. db: AsyncSession = Depends(get_async_db),
  287. current_user: Admin = Depends(get_current_user)):
  288. _q = {}
  289. if school_id:
  290. _q["school_id"] = school_id
  291. if grade_id:
  292. _q["grade_id"] = grade_id
  293. if class_id:
  294. _q["school_id"] = class_id
  295. if task_id:
  296. _q["task_id"] = task_id
  297. # 查询并返回
  298. total, db_records = await crud_class_push_record.find_all(db,
  299. filters=_q,
  300. limit=size,
  301. offset=(page - 1) * size)
  302. return {"data": db_records, "total": total}
  303. @router.get("/stu-errs/{sprid}/errbook",
  304. response_model=StudentErrorBookQuestionList,
  305. response_model_exclude_none=True,
  306. summary="学生错题本")
  307. async def get_student_error_book(page: int = 1,
  308. size: int = 10,
  309. sprid: int = Path(..., description="学生错题推送记录ID"),
  310. db: AsyncSession = Depends(get_async_db),
  311. current_user: Admin = Depends(get_current_user)):
  312. # 分页
  313. offset = (page - 1) * size
  314. # 学生错题推送记录
  315. db_error = await crud_student_push_record.find_one(db, filters={"id": sprid})
  316. if db_error:
  317. # 根据学生错题推送记录中的试题ID去获取相应的试题
  318. _q = []
  319. if len(db_error.push_error_ids) == 1:
  320. _q.append(StudentErrorPushRecord.id == db_error.push_error_ids[0])
  321. else:
  322. _q.append(StudentErrorPushRecord.id.in_(db_error.push_error_ids))
  323. total, db_questions = await crud_question.find_all(db,
  324. filters=_q,
  325. limit=size,
  326. offset=offset)
  327. return {"data": db_questions, "total": total}
  328. else:
  329. return {"errcode": 404, "mess": "推送记录不存在!"}
  330. @router.get("/stu-errs/{qid}/rel-questions",
  331. response_model=StudentErrorBookQuestionList,
  332. response_model_exclude_none=True,
  333. summary="举一反三试题列表")
  334. async def get_related_questions(qid: int = Path(..., description="试题ID"),
  335. db: AsyncSession = Depends(get_async_db),
  336. current_user: Admin = Depends(get_current_user)):
  337. # 举一反三,使用知识点查询+难度查询
  338. db_question = await crud_question.find_one(db, filters={"id": qid})
  339. _q = [
  340. PaperQuestion.id != qid, PaperQuestion.level == db_question.level,
  341. PaperQuestion.lpoints == db_question.lpoints
  342. ]
  343. total, db_questions = await crud_question.find_all(db, filters=_q, limit=3)
  344. return {"data": db_questions, "total": total}