⚒️ 重大重构 LoveACE V2
引入了 mongodb 对数据库进行了一定程度的数据加密 性能改善 代码简化 统一错误模型和响应 使用 apifox 作为文档
This commit is contained in:
67
loveace/router/endpoint/jwc/model/academic.py
Normal file
67
loveace/router/endpoint/jwc/model/academic.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from loveace.router.endpoint.jwc.utils.zxjxjhh_to_term_format import (
|
||||
convert_zxjxjhh_to_term_format,
|
||||
)
|
||||
|
||||
|
||||
class AcademicInfoTransformer(BaseModel):
|
||||
"""学术信息数据项"""
|
||||
|
||||
completed_courses: int = Field(0, alias="courseNum")
|
||||
failed_courses: int = Field(0, alias="coursePas")
|
||||
gpa: float = Field(0, alias="gpa")
|
||||
current_term: str = Field("", alias="zxjxjhh")
|
||||
pending_courses: int = Field(0, alias="courseNum_bxqyxd")
|
||||
|
||||
def to_academic_info(self) -> "AcademicInfo":
|
||||
"""转换为 AcademicInfo"""
|
||||
return AcademicInfo(
|
||||
completed_courses=self.completed_courses,
|
||||
failed_courses=self.failed_courses,
|
||||
pending_courses=self.pending_courses,
|
||||
gpa=self.gpa,
|
||||
current_term=self.current_term,
|
||||
current_term_name=convert_zxjxjhh_to_term_format(self.current_term),
|
||||
)
|
||||
|
||||
|
||||
class AcademicInfo(BaseModel):
|
||||
"""学术信息数据模型"""
|
||||
|
||||
completed_courses: int = Field(0, description="已修课程数")
|
||||
failed_courses: int = Field(0, description="不及格课程数")
|
||||
pending_courses: int = Field(0, description="本学期待修课程数")
|
||||
gpa: float = Field(0, description="绩点")
|
||||
current_term: str = Field("", description="当前学期")
|
||||
current_term_name: str = Field("", description="当前学期名称")
|
||||
|
||||
|
||||
class TrainingPlanInfoTransformer(BaseModel):
|
||||
"""培养方案响应模型"""
|
||||
|
||||
count: int = 0
|
||||
data: List[List[str]] = []
|
||||
|
||||
|
||||
class TrainingPlanInfo(BaseModel):
|
||||
"""培养方案信息模型"""
|
||||
|
||||
plan_name: str = Field("", description="培养方案名称")
|
||||
major_name: str = Field("", description="专业名称")
|
||||
grade: str = Field("", description="年级")
|
||||
|
||||
|
||||
class CourseSelectionStatusTransformer(BaseModel):
|
||||
"""选课状态响应模型新格式"""
|
||||
|
||||
term_name: str = Field("", alias="zxjxjhm")
|
||||
status_code: str = Field("", alias="retString")
|
||||
|
||||
|
||||
class CourseSelectionStatus(BaseModel):
|
||||
"""选课状态信息"""
|
||||
|
||||
can_select: bool = Field(False, description="是否可以选课")
|
||||
10
loveace/router/endpoint/jwc/model/base.py
Normal file
10
loveace/router/endpoint/jwc/model/base.py
Normal file
@@ -0,0 +1,10 @@
|
||||
class JWCConfig:
|
||||
"""教务系统配置常量"""
|
||||
|
||||
DEFAULT_BASE_URL = "http://jwcxk2-aufe-edu-cn.vpn2.aufe.edu.cn:8118/"
|
||||
|
||||
def to_full_url(self, path: str) -> str:
|
||||
"""将路径转换为完整URL"""
|
||||
if path.startswith("http://") or path.startswith("https://"):
|
||||
return path
|
||||
return self.DEFAULT_BASE_URL.rstrip("/") + "/" + path.lstrip("/")
|
||||
84
loveace/router/endpoint/jwc/model/competition.py
Normal file
84
loveace/router/endpoint/jwc/model/competition.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AwardProject(BaseModel):
|
||||
"""
|
||||
获奖项目信息模型
|
||||
|
||||
表示用户通过创新创业管理平台申报的单个获奖项目
|
||||
"""
|
||||
|
||||
project_id: str = Field("", description="申报ID,唯一标识符")
|
||||
project_name: str = Field("", description="项目名称/赛事名称")
|
||||
level: str = Field("", description="级别(校级/省部级/国家级等)")
|
||||
grade: str = Field("", description="等级/奖项等级(一等奖/二等奖等)")
|
||||
award_date: str = Field("", description="获奖日期,格式为 YYYY/M/D")
|
||||
applicant_id: str = Field("", description="主持人姓名")
|
||||
applicant_name: str = Field("", description="参与人姓名(作为用户)")
|
||||
order: int = Field(0, description="顺序号(多人项目的排序)")
|
||||
credits: float = Field(0.0, description="获奖学分")
|
||||
bonus: float = Field(0.0, description="奖励金额")
|
||||
status: str = Field("", description="申报状态(提交/审核中/已审核等)")
|
||||
verification_status: str = Field(
|
||||
"", description="学校审核状态(通过/未通过/待审核等)"
|
||||
)
|
||||
|
||||
|
||||
class CreditsSummary(BaseModel):
|
||||
"""
|
||||
学分汇总信息模型
|
||||
|
||||
存储用户在创新创业管理平台的各类学分统计
|
||||
"""
|
||||
|
||||
discipline_competition_credits: Optional[float] = Field(
|
||||
None, description="学科竞赛学分"
|
||||
)
|
||||
scientific_research_credits: Optional[float] = Field(
|
||||
None, description="科研项目学分"
|
||||
)
|
||||
transferable_competition_credits: Optional[float] = Field(
|
||||
None, description="可转竞赛类学分"
|
||||
)
|
||||
innovation_practice_credits: Optional[float] = Field(
|
||||
None, description="创新创业实践学分"
|
||||
)
|
||||
ability_certification_credits: Optional[float] = Field(
|
||||
None, description="能力资格认证学分"
|
||||
)
|
||||
other_project_credits: Optional[float] = Field(None, description="其他项目学分")
|
||||
|
||||
|
||||
class CompetitionAwardsResponse(BaseModel):
|
||||
"""
|
||||
获奖项目列表响应模型
|
||||
"""
|
||||
|
||||
student_id: str = Field("", description="学生ID/工号")
|
||||
total_count: int = Field(0, description="获奖项目总数")
|
||||
awards: List[AwardProject] = Field(default_factory=list, description="获奖项目列表")
|
||||
|
||||
|
||||
class CompetitionCreditsSummaryResponse(BaseModel):
|
||||
"""
|
||||
学分汇总响应模型
|
||||
"""
|
||||
|
||||
student_id: str = Field("", description="学生ID/工号")
|
||||
credits_summary: Optional[CreditsSummary] = Field(None, description="学分汇总详情")
|
||||
|
||||
|
||||
class CompetitionFullResponse(BaseModel):
|
||||
"""
|
||||
学科竞赛完整信息响应模型
|
||||
|
||||
整合了获奖项目列表和学分汇总信息,减少网络IO调用
|
||||
在单次请求中返回所有竞赛相关数据
|
||||
"""
|
||||
|
||||
student_id: str = Field("", description="学生ID/工号")
|
||||
total_awards_count: int = Field(0, description="获奖项目总数")
|
||||
awards: List[AwardProject] = Field(default_factory=list, description="获奖项目列表")
|
||||
credits_summary: Optional[CreditsSummary] = Field(None, description="学分汇总详情")
|
||||
65
loveace/router/endpoint/jwc/model/exam.py
Normal file
65
loveace/router/endpoint/jwc/model/exam.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ExamScheduleItem(BaseModel):
|
||||
"""考试安排项目 - 校统考格式"""
|
||||
|
||||
title: str = "" # 考试标题,包含课程名、时间、地点等信息
|
||||
start: str = "" # 考试日期 (YYYY-MM-DD)
|
||||
color: str = "" # 显示颜色
|
||||
|
||||
|
||||
class OtherExamRecord(BaseModel):
|
||||
"""其他考试记录"""
|
||||
|
||||
term_code: str = Field("", alias="ZXJXJHH") # 学期代码
|
||||
term_name: str = Field("", alias="ZXJXJHM") # 学期名称
|
||||
exam_name: str = Field("", alias="KSMC") # 考试名称
|
||||
course_code: str = Field("", alias="KCH") # 课程代码
|
||||
course_name: str = Field("", alias="KCM") # 课程名称
|
||||
class_number: str = Field("", alias="KXH") # 课序号
|
||||
student_id: str = Field("", alias="XH") # 学号
|
||||
student_name: str = Field("", alias="XM") # 姓名
|
||||
exam_location: str = Field("", alias="KSDD") # 考试地点
|
||||
exam_date: str = Field("", alias="KSRQ") # 考试日期
|
||||
exam_time: str = Field("", alias="KSSJ") # 考试时间
|
||||
note: str = Field("", alias="BZ") # 备注
|
||||
row_number: str = Field("", alias="RN") # 行号
|
||||
|
||||
|
||||
class OtherExamResponse(BaseModel):
|
||||
"""其他考试查询响应"""
|
||||
|
||||
page_size: int = Field(0, alias="pageSize")
|
||||
page_num: int = Field(0, alias="pageNum")
|
||||
page_context: Dict[str, int] = Field(default_factory=dict, alias="pageContext")
|
||||
records: Optional[List[OtherExamRecord]] = Field(alias="records")
|
||||
|
||||
|
||||
class UnifiedExamInfo(BaseModel):
|
||||
"""统一考试信息模型 - 对外提供的统一格式"""
|
||||
|
||||
course_name: str = Field("", description="课程名称")
|
||||
exam_date: str = Field("", description="考试日期")
|
||||
exam_time: str = Field("", description="考试时间")
|
||||
exam_location: str = Field("", description="考试地点")
|
||||
exam_type: str = Field("", description="考试类型")
|
||||
note: str = Field("", description="备注")
|
||||
|
||||
|
||||
class ExamInfoResponse(BaseModel):
|
||||
"""考试信息统一响应模型"""
|
||||
|
||||
exams: List[UnifiedExamInfo] = Field(
|
||||
default_factory=list, description="考试信息列表"
|
||||
)
|
||||
total_count: int = Field(0, description="考试总数")
|
||||
|
||||
|
||||
class SeatInfo(BaseModel):
|
||||
"""座位信息模型"""
|
||||
|
||||
course_name: str = Field("", description="课程名称")
|
||||
seat_number: str = Field("", description="座位号")
|
||||
348
loveace/router/endpoint/jwc/model/plan.py
Normal file
348
loveace/router/endpoint/jwc/model/plan.py
Normal file
@@ -0,0 +1,348 @@
|
||||
import re
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class PlanCompletionCourse(BaseModel):
|
||||
"""培养方案课程完成情况"""
|
||||
|
||||
flag_id: str = Field("", description="课程标识ID")
|
||||
flag_type: str = Field("", description="节点类型:001=分类, 002=子分类, kch=课程")
|
||||
course_code: str = Field("", description="课程代码,如 PDA2121005")
|
||||
course_name: str = Field("", description="课程名称")
|
||||
is_passed: bool = Field(False, description="是否通过(基于CSS图标解析)")
|
||||
status_description: str = Field("", description="状态描述:未修读/已通过/未通过")
|
||||
credits: Optional[float] = Field(None, description="学分")
|
||||
score: Optional[str] = Field(None, description="成绩")
|
||||
exam_date: Optional[str] = Field(None, description="考试日期")
|
||||
course_type: str = Field("", description="课程类型:必修/任选等")
|
||||
parent_id: str = Field("", description="父节点ID")
|
||||
level: int = Field(0, description="层级:0=根分类,1=子分类,2=课程")
|
||||
|
||||
@classmethod
|
||||
def from_ztree_node(cls, node: dict) -> "PlanCompletionCourse":
|
||||
"""从 zTree 节点数据创建课程对象"""
|
||||
# 解析name字段中的信息
|
||||
name = node.get("name", "")
|
||||
flag_id = node.get("flagId", "")
|
||||
flag_type = node.get("flagType", "")
|
||||
parent_id = node.get("pId", "")
|
||||
|
||||
# 根据CSS图标判断通过状态
|
||||
is_passed = False
|
||||
status_description = "未修读"
|
||||
|
||||
if "fa-smile-o fa-1x green" in name:
|
||||
is_passed = True
|
||||
status_description = "已通过"
|
||||
elif "fa-meh-o fa-1x light-grey" in name:
|
||||
is_passed = False
|
||||
status_description = "未修读"
|
||||
elif "fa-frown-o fa-1x red" in name:
|
||||
is_passed = False
|
||||
status_description = "未通过"
|
||||
|
||||
# 从name中提取纯文本内容
|
||||
# 移除HTML标签和图标
|
||||
clean_name = re.sub(r"<[^>]*>", "", name)
|
||||
clean_name = re.sub(r" ", " ", clean_name).strip()
|
||||
|
||||
# 解析课程信息
|
||||
course_code = ""
|
||||
course_name = ""
|
||||
credits = None
|
||||
score = None
|
||||
exam_date = None
|
||||
course_type = ""
|
||||
|
||||
if flag_type == "kch": # 课程节点
|
||||
# 解析课程代码:[PDA2121005]形势与政策
|
||||
code_match = re.search(r"\[([^\]]+)\]", clean_name)
|
||||
if code_match:
|
||||
course_code = code_match.group(1)
|
||||
remaining_text = clean_name.split("]", 1)[1].strip()
|
||||
|
||||
# 解析学分信息:[0.3学分]
|
||||
credit_match = re.search(r"\[([0-9.]+)学分\]", remaining_text)
|
||||
if credit_match:
|
||||
credits = float(credit_match.group(1))
|
||||
remaining_text = re.sub(
|
||||
r"\[[0-9.]+学分\]", "", remaining_text
|
||||
).strip()
|
||||
|
||||
# 处理复杂的括号内容
|
||||
# 例如:85.0(20250626 成绩,都没把日期解析上,中国近现代史纲要)
|
||||
# 或者:(任选,87.0(20250119))
|
||||
|
||||
# 找到最外层的括号
|
||||
paren_match = re.search(
|
||||
r"\(([^)]+(?:\([^)]*\)[^)]*)*)\)$", remaining_text
|
||||
)
|
||||
if paren_match:
|
||||
paren_content = paren_match.group(1)
|
||||
course_name_candidate = re.sub(
|
||||
r"\([^)]+(?:\([^)]*\)[^)]*)*\)$", "", remaining_text
|
||||
).strip()
|
||||
|
||||
# 检查括号内容的格式
|
||||
if "," in paren_content:
|
||||
# 处理包含中文逗号的复杂格式
|
||||
parts = paren_content.split(",")
|
||||
|
||||
# 最后一部分可能是课程名
|
||||
last_part = parts[-1].strip()
|
||||
if (
|
||||
re.search(r"[\u4e00-\u9fff]", last_part)
|
||||
and len(last_part) > 1
|
||||
):
|
||||
# 最后一部分包含中文,很可能是真正的课程名
|
||||
course_name = last_part
|
||||
|
||||
# 从前面的部分提取成绩和其他信息
|
||||
remaining_parts = ",".join(parts[:-1])
|
||||
|
||||
# 提取成绩
|
||||
score_match = re.search(r"([0-9.]+)", remaining_parts)
|
||||
if score_match:
|
||||
score = score_match.group(1)
|
||||
|
||||
# 提取日期
|
||||
date_match = re.search(r"(\d{8})", remaining_parts)
|
||||
if date_match:
|
||||
exam_date = date_match.group(1)
|
||||
|
||||
# 提取课程类型(如果有的话)
|
||||
if len(parts) > 2:
|
||||
potential_type = parts[0].strip()
|
||||
if not re.search(r"[0-9.]", potential_type):
|
||||
course_type = potential_type
|
||||
else:
|
||||
# 最后一部分不是课程名,使用括号外的内容
|
||||
course_name = (
|
||||
course_name_candidate
|
||||
if course_name_candidate
|
||||
else "未知课程"
|
||||
)
|
||||
|
||||
# 从整个括号内容提取信息
|
||||
score_match = re.search(r"([0-9.]+)", paren_content)
|
||||
if score_match:
|
||||
score = score_match.group(1)
|
||||
|
||||
date_match = re.search(r"(\d{8})", paren_content)
|
||||
if date_match:
|
||||
exam_date = date_match.group(1)
|
||||
|
||||
elif "," in paren_content:
|
||||
# 处理标准格式:(任选,87.0(20250119))
|
||||
type_score_parts = paren_content.split(",", 1)
|
||||
if len(type_score_parts) == 2:
|
||||
course_type = type_score_parts[0].strip()
|
||||
score_info = type_score_parts[1].strip()
|
||||
|
||||
# 解析成绩和日期
|
||||
score_date_match = re.search(
|
||||
r"([0-9.]+)\((\d{8})\)", score_info
|
||||
)
|
||||
if score_date_match:
|
||||
score = score_date_match.group(1)
|
||||
exam_date = score_date_match.group(2)
|
||||
else:
|
||||
score_match = re.search(r"([0-9.]+)", score_info)
|
||||
if score_match:
|
||||
score = score_match.group(1)
|
||||
|
||||
# 使用括号外的内容作为课程名
|
||||
course_name = (
|
||||
course_name_candidate
|
||||
if course_name_candidate
|
||||
else "未知课程"
|
||||
)
|
||||
|
||||
else:
|
||||
# 括号内只有简单内容
|
||||
course_name = (
|
||||
course_name_candidate
|
||||
if course_name_candidate
|
||||
else "未知课程"
|
||||
)
|
||||
|
||||
# 尝试从括号内容提取成绩
|
||||
score_match = re.search(r"([0-9.]+)", paren_content)
|
||||
if score_match:
|
||||
score = score_match.group(1)
|
||||
|
||||
# 尝试提取日期
|
||||
date_match = re.search(r"(\d{8})", paren_content)
|
||||
if date_match:
|
||||
exam_date = date_match.group(1)
|
||||
else:
|
||||
# 没有括号,直接使用剩余文本作为课程名
|
||||
course_name = remaining_text
|
||||
|
||||
# 清理课程名
|
||||
course_name = re.sub(r"\s+", " ", course_name).strip()
|
||||
course_name = course_name.strip(",,。.")
|
||||
|
||||
# 如果课程名为空或太短,尝试从原始名称提取
|
||||
if not course_name or len(course_name) < 2:
|
||||
chinese_match = re.search(
|
||||
r"[\u4e00-\u9fff]+(?:[\u4e00-\u9fff\s]*[\u4e00-\u9fff]+)*",
|
||||
clean_name,
|
||||
)
|
||||
if chinese_match:
|
||||
course_name = chinese_match.group(0).strip()
|
||||
else:
|
||||
course_name = clean_name
|
||||
else:
|
||||
# 分类节点
|
||||
course_name = clean_name
|
||||
|
||||
# 清理分类名称中的多余括号,但保留重要信息
|
||||
# 如果是包含学分信息的分类名,保留学分信息
|
||||
if not re.search(r"学分", course_name):
|
||||
# 删除分类名称中的统计信息括号,如 "通识通修(已完成20.0/需要20.0)"
|
||||
course_name = re.sub(r"\([^)]*完成[^)]*\)", "", course_name).strip()
|
||||
# 删除其他可能的统计括号
|
||||
course_name = re.sub(
|
||||
r"\([^)]*\d+\.\d+/[^)]*\)", "", course_name
|
||||
).strip()
|
||||
|
||||
# 清理多余的空格和空括号
|
||||
course_name = re.sub(r"\(\s*\)", "", course_name).strip()
|
||||
course_name = re.sub(r"\s+", " ", course_name).strip()
|
||||
|
||||
# 确定层级
|
||||
level = 0
|
||||
if flag_type == "002":
|
||||
level = 1
|
||||
elif flag_type == "kch":
|
||||
level = 2
|
||||
|
||||
return cls(
|
||||
flag_id=flag_id,
|
||||
flag_type=flag_type,
|
||||
course_code=course_code,
|
||||
course_name=course_name,
|
||||
is_passed=is_passed,
|
||||
status_description=status_description,
|
||||
credits=credits,
|
||||
score=score,
|
||||
exam_date=exam_date,
|
||||
course_type=course_type,
|
||||
parent_id=parent_id,
|
||||
level=level,
|
||||
)
|
||||
|
||||
|
||||
class PlanCompletionCategory(BaseModel):
|
||||
"""培养方案分类完成情况"""
|
||||
|
||||
category_id: str = Field("", description="分类ID")
|
||||
category_name: str = Field("", description="分类名称")
|
||||
min_credits: float = Field(0.0, description="最低修读学分")
|
||||
completed_credits: float = Field(0.0, description="通过学分")
|
||||
total_courses: int = Field(0, description="已修课程门数")
|
||||
passed_courses: int = Field(0, description="已及格课程门数")
|
||||
failed_courses: int = Field(0, description="未及格课程门数")
|
||||
missing_required_courses: int = Field(0, description="必修课缺修门数")
|
||||
subcategories: List["PlanCompletionCategory"] = Field(
|
||||
default_factory=list, description="子分类"
|
||||
)
|
||||
courses: List[PlanCompletionCourse] = Field(
|
||||
default_factory=list, description="课程列表"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_ztree_node(cls, node: dict) -> "PlanCompletionCategory":
|
||||
"""从 zTree 节点创建分类对象"""
|
||||
name = node.get("name", "")
|
||||
flag_id = node.get("flagId", "")
|
||||
|
||||
# 移除HTML标签获取纯文本
|
||||
clean_name = re.sub(r"<[^>]*>", "", name)
|
||||
clean_name = re.sub(r" ", " ", clean_name).strip()
|
||||
|
||||
# 解析分类统计信息
|
||||
# 格式:通识通修(最低修读学分:68,通过学分:34.4,已修课程门数:26,已及格课程门数:26,未及格课程门数:0,必修课缺修门数:12)
|
||||
stats_match = re.search(
|
||||
r"([^(]+)\(最低修读学分:([0-9.]+),通过学分:([0-9.]+),已修课程门数:(\d+),已及格课程门数:(\d+),未及格课程门数:(\d+),必修课缺修门数:(\d+)\)",
|
||||
clean_name,
|
||||
)
|
||||
|
||||
if stats_match:
|
||||
category_name = stats_match.group(1)
|
||||
min_credits = float(stats_match.group(2))
|
||||
completed_credits = float(stats_match.group(3))
|
||||
total_courses = int(stats_match.group(4))
|
||||
passed_courses = int(stats_match.group(5))
|
||||
failed_courses = int(stats_match.group(6))
|
||||
missing_required_courses = int(stats_match.group(7))
|
||||
else:
|
||||
# 子分类可能没有完整的统计信息
|
||||
category_name = clean_name
|
||||
min_credits = 0.0
|
||||
completed_credits = 0.0
|
||||
total_courses = 0
|
||||
passed_courses = 0
|
||||
failed_courses = 0
|
||||
missing_required_courses = 0
|
||||
|
||||
return cls(
|
||||
category_id=flag_id,
|
||||
category_name=category_name,
|
||||
min_credits=min_credits,
|
||||
completed_credits=completed_credits,
|
||||
total_courses=total_courses,
|
||||
passed_courses=passed_courses,
|
||||
failed_courses=failed_courses,
|
||||
missing_required_courses=missing_required_courses,
|
||||
)
|
||||
|
||||
|
||||
class PlanCompletionInfo(BaseModel):
|
||||
"""培养方案完成情况总信息"""
|
||||
|
||||
plan_name: str = Field("", description="培养方案名称")
|
||||
major: str = Field("", description="专业名称")
|
||||
grade: str = Field("", description="年级")
|
||||
categories: List[PlanCompletionCategory] = Field(
|
||||
default_factory=list, description="分类列表"
|
||||
)
|
||||
total_categories: int = Field(0, description="总分类数")
|
||||
total_courses: int = Field(0, description="总课程数")
|
||||
passed_courses: int = Field(0, description="已通过课程数")
|
||||
failed_courses: int = Field(0, description="未通过课程数")
|
||||
unread_courses: int = Field(0, description="未修读课程数")
|
||||
|
||||
def calculate_statistics(self):
|
||||
"""计算统计信息"""
|
||||
total_courses = 0
|
||||
passed_courses = 0
|
||||
failed_courses = 0
|
||||
unread_courses = 0
|
||||
|
||||
def count_courses(categories: List[PlanCompletionCategory]):
|
||||
nonlocal total_courses, passed_courses, failed_courses, unread_courses
|
||||
|
||||
for category in categories:
|
||||
for course in category.courses:
|
||||
total_courses += 1
|
||||
if course.is_passed:
|
||||
passed_courses += 1
|
||||
elif course.status_description == "未通过":
|
||||
failed_courses += 1
|
||||
else:
|
||||
unread_courses += 1
|
||||
|
||||
# 递归处理子分类
|
||||
count_courses(category.subcategories)
|
||||
|
||||
count_courses(self.categories)
|
||||
|
||||
self.total_categories = len(self.categories)
|
||||
self.total_courses = total_courses
|
||||
self.passed_courses = passed_courses
|
||||
self.failed_courses = failed_courses
|
||||
self.unread_courses = unread_courses
|
||||
49
loveace/router/endpoint/jwc/model/schedule.py
Normal file
49
loveace/router/endpoint/jwc/model/schedule.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class TimeSlot(BaseModel):
|
||||
"""时间段模型"""
|
||||
|
||||
session: int = Field(..., description="节次")
|
||||
session_name: str = Field(..., description="节次名称")
|
||||
start_time: str = Field(..., description="开始时间,格式:HHMM")
|
||||
end_time: str = Field(..., description="结束时间,格式:HHMM")
|
||||
time_length: str = Field(..., description="时长(分钟)")
|
||||
djjc: int = Field(..., description="大节节次")
|
||||
|
||||
|
||||
class CourseTimeLocation(BaseModel):
|
||||
"""课程时间地点模型"""
|
||||
|
||||
class_day: int = Field(..., description="上课星期几(1-7)")
|
||||
class_sessions: int = Field(..., description="上课节次")
|
||||
continuing_session: int = Field(..., description="持续节次数")
|
||||
class_week: str = Field(..., description="上课周次(24位二进制字符串)")
|
||||
week_description: str = Field(..., description="上课周次描述")
|
||||
campus_name: str = Field(..., description="校区名称")
|
||||
teaching_building_name: str = Field(..., description="教学楼名称")
|
||||
classroom_name: str = Field(..., description="教室名称")
|
||||
|
||||
|
||||
class ScheduleCourse(BaseModel):
|
||||
"""课表课程模型"""
|
||||
|
||||
course_name: str = Field(..., description="课程名称")
|
||||
course_code: str = Field(..., description="课程代码")
|
||||
course_sequence: str = Field(..., description="课程序号")
|
||||
teacher_name: str = Field(..., description="授课教师")
|
||||
course_properties: str = Field(..., description="课程性质")
|
||||
exam_type: str = Field(..., description="考试类型")
|
||||
unit: float = Field(..., description="学分")
|
||||
time_locations: List[CourseTimeLocation] = Field(..., description="时间地点列表")
|
||||
is_no_schedule: bool = Field(False, description="是否无具体时间安排")
|
||||
|
||||
|
||||
class ScheduleData(BaseModel):
|
||||
"""课表数据模型"""
|
||||
|
||||
total_units: float = Field(..., description="总学分")
|
||||
time_slots: List[TimeSlot] = Field(..., description="时间段列表")
|
||||
courses: List[ScheduleCourse] = Field(..., description="课程列表")
|
||||
28
loveace/router/endpoint/jwc/model/score.py
Normal file
28
loveace/router/endpoint/jwc/model/score.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ScoreRecord(BaseModel):
|
||||
"""成绩记录模型"""
|
||||
|
||||
sequence: int = Field(0, description="序号")
|
||||
term_id: str = Field("", description="学期ID")
|
||||
course_code: str = Field("", description="课程代码")
|
||||
course_class: str = Field("", description="课程班级")
|
||||
course_name_cn: str = Field("", description="课程名称(中文)")
|
||||
course_name_en: str = Field("", description="课程名称(英文)")
|
||||
credits: str = Field("", description="学分")
|
||||
hours: int = Field(0, description="学时")
|
||||
course_type: Optional[str] = Field(None, description="课程性质")
|
||||
exam_type: Optional[str] = Field(None, description="考试性质")
|
||||
score: str = Field("", description="成绩")
|
||||
retake_score: Optional[str] = Field(None, description="重修成绩")
|
||||
makeup_score: Optional[str] = Field(None, description="补考成绩")
|
||||
|
||||
|
||||
class TermScoreResponse(BaseModel):
|
||||
"""学期成绩响应模型"""
|
||||
|
||||
total_count: int = Field(0, description="总记录数")
|
||||
records: List[ScoreRecord] = Field(default_factory=list, description="成绩记录列表")
|
||||
20
loveace/router/endpoint/jwc/model/term.py
Normal file
20
loveace/router/endpoint/jwc/model/term.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class TermItem(BaseModel):
|
||||
"""学期信息项"""
|
||||
|
||||
term_code: str = Field(..., description="学期代码")
|
||||
term_name: str = Field(..., description="学期名称")
|
||||
is_current: bool = Field(..., description="是否为当前学期")
|
||||
|
||||
|
||||
class CurrentTermInfo(BaseModel):
|
||||
"""学期周数信息"""
|
||||
|
||||
academic_year: str = Field("", description="学年,如 2025-2026")
|
||||
current_term_name: str = Field("", description="学期,如 秋、春")
|
||||
week_number: int = Field(0, description="当前周数")
|
||||
start_at: str = Field("", description="学期开始时间,格式 YYYY-MM-DD")
|
||||
is_end: bool = Field(False, description="是否为学期结束")
|
||||
weekday: int = Field(0, description="星期几")
|
||||
Reference in New Issue
Block a user