Files
012-kaopeilian/backend/app/schemas/exam.py
yuliang_guo 64a70d5c2c
All checks were successful
continuous-integration/drone/push Build is passing
fix: 修复考试API路由冲突和响应验证问题
1. 调整路由顺序:将/records和/statistics放在/{exam_id}之前
2. 修复RecentExamItem.start_time允许None值
2026-01-31 11:26:54 +08:00

318 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
考试相关的Schema定义
"""
from typing import List, Optional, Dict, Any
from datetime import datetime
from pydantic import BaseModel, Field
class StartExamRequest(BaseModel):
"""开始考试请求"""
course_id: int = Field(..., description="课程ID")
count: int = Field(10, ge=1, le=100, description="题目数量")
class StartExamResponse(BaseModel):
"""开始考试响应"""
exam_id: int = Field(..., description="考试ID")
class ExamAnswer(BaseModel):
"""考试答案"""
question_id: str = Field(..., description="题目ID")
answer: str = Field(..., description="答案")
class SubmitExamRequest(BaseModel):
"""提交考试请求"""
exam_id: int = Field(..., description="考试ID")
answers: List[ExamAnswer] = Field(..., description="答案列表")
class SubmitExamResponse(BaseModel):
"""提交考试响应"""
exam_id: int = Field(..., description="考试ID")
total_score: float = Field(..., description="总分")
pass_score: float = Field(..., description="及格分")
is_passed: bool = Field(..., description="是否通过")
correct_count: int = Field(..., description="正确题数")
total_count: int = Field(..., description="总题数")
accuracy: float = Field(..., description="正确率")
class QuestionInfo(BaseModel):
"""题目信息"""
id: str = Field(..., description="题目ID")
type: str = Field(..., description="题目类型")
title: str = Field(..., description="题目标题")
content: Optional[str] = Field(None, description="题目内容")
options: Optional[Dict[str, Any]] = Field(None, description="选项")
score: float = Field(..., description="分值")
class ExamResultInfo(BaseModel):
"""答题结果信息"""
question_id: int = Field(..., description="题目ID")
user_answer: Optional[str] = Field(None, description="用户答案")
is_correct: bool = Field(..., description="是否正确")
score: float = Field(..., description="得分")
class ExamDetailResponse(BaseModel):
"""考试详情响应"""
id: int = Field(..., description="考试ID")
course_id: int = Field(..., description="课程ID")
exam_name: str = Field(..., description="考试名称")
question_count: int = Field(..., description="题目数量")
total_score: float = Field(..., description="总分")
pass_score: float = Field(..., description="及格分")
start_time: Optional[str] = Field(None, description="开始时间")
end_time: Optional[str] = Field(None, description="结束时间")
duration_minutes: int = Field(..., description="考试时长(分钟)")
status: str = Field(..., description="考试状态")
score: Optional[float] = Field(None, description="得分")
is_passed: Optional[bool] = Field(None, description="是否通过")
questions: Optional[Dict[str, Any]] = Field(None, description="题目数据")
results: Optional[List[ExamResultInfo]] = Field(None, description="答题结果")
answers: Optional[Dict[str, Any]] = Field(None, description="用户答案")
class ExamRecordInfo(BaseModel):
"""考试记录信息"""
id: int = Field(..., description="考试ID")
course_id: int = Field(..., description="课程ID")
exam_name: str = Field(..., description="考试名称")
question_count: int = Field(..., description="题目数量")
total_score: float = Field(..., description="总分")
score: Optional[float] = Field(None, description="得分")
is_passed: Optional[bool] = Field(None, description="是否通过")
status: str = Field(..., description="考试状态")
start_time: Optional[str] = Field(None, description="开始时间")
end_time: Optional[str] = Field(None, description="结束时间")
created_at: str = Field(..., description="创建时间")
# 新增统计字段
accuracy: Optional[float] = Field(None, description="正确率(%)")
correct_count: Optional[int] = Field(None, description="正确题数")
wrong_count: Optional[int] = Field(None, description="错题数")
duration_seconds: Optional[int] = Field(None, description="考试用时(秒)")
course_name: Optional[str] = Field(None, description="课程名称")
question_type_stats: Optional[List[Dict[str, Any]]] = Field(None, description="分题型统计")
class ExamRecordResponse(BaseModel):
"""考试记录列表响应"""
items: List[ExamRecordInfo] = Field(..., description="考试记录列表")
total: int = Field(..., description="总数")
page: int = Field(..., description="当前页")
size: int = Field(..., description="每页数量")
pages: int = Field(..., description="总页数")
# ==================== AI服务响应Schema ====================
class MistakeRecord(BaseModel):
"""错题记录详情"""
question_id: Optional[int] = Field(None, description="题目ID")
knowledge_point_id: Optional[int] = Field(None, description="知识点ID")
question_content: str = Field(..., description="题目内容")
correct_answer: str = Field(..., description="正确答案")
user_answer: str = Field(..., description="用户答案")
class GenerateExamRequest(BaseModel):
"""生成考试试题请求"""
course_id: int = Field(..., description="课程ID")
position_id: Optional[int] = Field(None, description="岗位ID,如果不提供则从用户信息中自动获取")
current_round: int = Field(1, ge=1, le=3, description="当前轮次(1/2/3)")
exam_id: Optional[int] = Field(None, description="已存在的exam_id(第2、3轮传入)")
mistake_records: Optional[str] = Field(None, description="错题记录JSON字符串,第一轮不传此参数,第二三轮传入上一轮错题的JSON字符串")
single_choice_count: int = Field(4, ge=0, le=50, description="单选题数量")
multiple_choice_count: int = Field(2, ge=0, le=30, description="多选题数量")
true_false_count: int = Field(1, ge=0, le=20, description="判断题数量")
fill_blank_count: int = Field(2, ge=0, le=10, description="填空题数量")
essay_count: int = Field(1, ge=0, le=10, description="问答题数量")
difficulty_level: int = Field(3, ge=1, le=5, description="难度系数(1-5)")
class GenerateExamResponse(BaseModel):
"""生成考试试题响应"""
result: str = Field(..., description="试题JSON数组(字符串格式)")
workflow_run_id: Optional[str] = Field(None, description="AI服务调用ID")
task_id: Optional[str] = Field(None, description="任务ID")
exam_id: int = Field(..., description="考试ID真实的数据库ID")
class JudgeAnswerRequest(BaseModel):
"""判断主观题答案请求"""
question: str = Field(..., description="题目内容")
correct_answer: str = Field(..., description="标准答案")
user_answer: str = Field(..., description="用户提交的答案")
analysis: str = Field(..., description="正确答案的解析(来源于试题生成器)")
class JudgeAnswerResponse(BaseModel):
"""判断主观题答案响应"""
is_correct: bool = Field(..., description="是否正确")
correct_answer: str = Field(..., description="标准答案")
feedback: Optional[str] = Field(None, description="判断反馈信息")
class RecordMistakeRequest(BaseModel):
"""记录错题请求"""
exam_id: int = Field(..., description="考试ID")
round: int = Field(1, description="考试轮次(1/2/3)")
question_id: Optional[int] = Field(None, description="题目ID(AI生成的题目可能为空)")
knowledge_point_id: Optional[int] = Field(None, description="知识点ID")
question_content: str = Field(..., description="题目内容")
correct_answer: str = Field(..., description="正确答案")
user_answer: str = Field(..., description="用户答案")
question_type: Optional[str] = Field(None, description="题型(single/multiple/judge/blank/essay)")
class RecordMistakeResponse(BaseModel):
"""记录错题响应"""
id: int = Field(..., description="错题记录ID")
created_at: datetime = Field(..., description="创建时间")
class MistakeRecordItem(BaseModel):
"""错题记录项"""
id: int = Field(..., description="错题记录ID")
question_id: Optional[int] = Field(None, description="题目ID")
knowledge_point_id: Optional[int] = Field(None, description="知识点ID")
question_content: str = Field(..., description="题目内容")
correct_answer: str = Field(..., description="正确答案")
user_answer: str = Field(..., description="用户答案")
created_at: datetime = Field(..., description="创建时间")
class GetMistakesResponse(BaseModel):
"""获取错题记录响应"""
mistakes: List[MistakeRecordItem] = Field(..., description="错题列表")
# ==================== 成绩报告和错题本相关Schema ====================
class RoundScores(BaseModel):
"""三轮得分"""
round1: Optional[float] = Field(None, description="第一轮得分")
round2: Optional[float] = Field(None, description="第二轮得分")
round3: Optional[float] = Field(None, description="第三轮得分")
class ExamReportOverview(BaseModel):
"""成绩报告概览"""
avg_score: float = Field(..., description="平均成绩(基于round1_score)")
total_exams: int = Field(..., description="考试总数")
pass_rate: float = Field(..., description="及格率")
total_questions: int = Field(..., description="答题总数")
class ExamTrendItem(BaseModel):
"""成绩趋势项"""
date: str = Field(..., description="日期(YYYY-MM-DD)")
avg_score: float = Field(..., description="平均分")
class SubjectStatItem(BaseModel):
"""科目统计项"""
course_id: int = Field(..., description="课程ID")
course_name: str = Field(..., description="课程名称")
avg_score: float = Field(..., description="平均分")
exam_count: int = Field(..., description="考试次数")
max_score: float = Field(..., description="最高分")
min_score: float = Field(..., description="最低分")
pass_rate: float = Field(..., description="及格率")
class RecentExamItem(BaseModel):
"""最近考试记录项"""
id: int = Field(..., description="考试ID")
course_id: int = Field(..., description="课程ID")
course_name: str = Field(..., description="课程名称")
score: Optional[float] = Field(None, description="最终得分")
total_score: float = Field(..., description="总分")
is_passed: Optional[bool] = Field(None, description="是否通过")
duration_seconds: Optional[int] = Field(None, description="考试用时(秒)")
start_time: Optional[str] = Field(None, description="开始时间")
end_time: Optional[str] = Field(None, description="结束时间")
round_scores: RoundScores = Field(..., description="三轮得分")
class ExamReportResponse(BaseModel):
"""成绩报告响应"""
overview: ExamReportOverview = Field(..., description="概览数据")
trends: List[ExamTrendItem] = Field(..., description="趋势数据")
subjects: List[SubjectStatItem] = Field(..., description="科目分析")
recent_exams: List[RecentExamItem] = Field(..., description="最近考试记录")
class MistakeListItem(BaseModel):
"""错题列表项"""
id: int = Field(..., description="错题记录ID")
exam_id: int = Field(..., description="考试ID")
course_id: int = Field(..., description="课程ID")
course_name: str = Field(..., description="课程名称")
question_content: str = Field(..., description="题目内容")
correct_answer: str = Field(..., description="正确答案")
user_answer: str = Field(..., description="用户答案")
question_type: Optional[str] = Field(None, description="题型")
knowledge_point_id: Optional[int] = Field(None, description="知识点ID")
knowledge_point_name: Optional[str] = Field(None, description="知识点名称")
created_at: datetime = Field(..., description="创建时间")
class MistakeListResponse(BaseModel):
"""错题列表响应"""
items: List[MistakeListItem] = Field(..., description="错题列表")
total: int = Field(..., description="总数")
page: int = Field(..., description="当前页")
size: int = Field(..., description="每页数量")
pages: int = Field(..., description="总页数")
class MistakeByCourse(BaseModel):
"""按课程统计错题"""
course_id: int = Field(..., description="课程ID")
course_name: str = Field(..., description="课程名称")
count: int = Field(..., description="错题数量")
class MistakeByType(BaseModel):
"""按题型统计错题"""
type: str = Field(..., description="题型代码")
type_name: str = Field(..., description="题型名称")
count: int = Field(..., description="错题数量")
class MistakeByTime(BaseModel):
"""按时间统计错题"""
week: int = Field(..., description="最近一周")
month: int = Field(..., description="最近一月")
quarter: int = Field(..., description="最近三月")
class MistakesStatisticsResponse(BaseModel):
"""错题统计响应"""
total: int = Field(..., description="错题总数")
by_course: List[MistakeByCourse] = Field(..., description="按课程统计")
by_type: List[MistakeByType] = Field(..., description="按题型统计")
by_time: MistakeByTime = Field(..., description="按时间统计")
class UpdateRoundScoreRequest(BaseModel):
"""更新轮次得分请求"""
round: int = Field(..., ge=1, le=3, description="轮次(1/2/3)")
score: float = Field(..., ge=0, le=100, description="得分")
is_final: bool = Field(False, description="是否为最终轮次(如果是,则同时更新总分和状态)")