From cfc3b423dcd2204aeeb0699611c74a49aaf28a63 Mon Sep 17 00:00:00 2001 From: wangtian <1582714703@qq.com> Date: Wed, 23 Sep 2026 09:46:15 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E8=AE=A1=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _debug.py | 15 ++ api/statistics_api.py | 126 ++++++++++++++++ dao/statistics_dao.py | 222 +++++++++++++++++++++++++++++ main.py | 3 + model/statistics_model.py | 118 +++++++++++++++ model/student_info_region_model.py | 2 +- schema/statistics_schema.py | 125 ++++++++++++++++ 7 files changed, 610 insertions(+), 1 deletion(-) create mode 100644 _debug.py create mode 100644 api/statistics_api.py create mode 100644 dao/statistics_dao.py create mode 100644 model/statistics_model.py create mode 100644 schema/statistics_schema.py diff --git a/_debug.py b/_debug.py new file mode 100644 index 0000000..3d19426 --- /dev/null +++ b/_debug.py @@ -0,0 +1,15 @@ +from database import engine +from sqlalchemy import text +conn = engine.connect() + +tables = conn.execute(text("SHOW TABLES")).fetchall() +print('Tables:', tables) + +for t in tables: + tname = t[0] + rows = conn.execute(text(f"SHOW COLUMNS FROM `{tname}` WHERE Field = 'is_deleted'")).fetchall() + if rows: + vals = conn.execute(text(f"SELECT is_deleted, COUNT(*) FROM `{tname}` GROUP BY is_deleted")).fetchall() + print(f" {tname}: type={rows[0][1]}, values={vals}") + +conn.close() \ No newline at end of file diff --git a/api/statistics_api.py b/api/statistics_api.py new file mode 100644 index 0000000..32c2734 --- /dev/null +++ b/api/statistics_api.py @@ -0,0 +1,126 @@ +from fastapi import APIRouter, Depends, Query +from database import get_db +from dao.statistics_dao import StatisticsDao +from schema.statistics_schema import ( + ResponseModel, ResponseModel1, ResponseModel2, ResponseModel3, + ResponseModel4, ResponseModel5, ResponseModel6, ClassStatResponse +) +from math import ceil + +BasicInformationAPI = APIRouter(tags=['统计分析']) + + +@BasicInformationAPI.get('/basic-information', summary='查询年龄区间内的学员信息') +def get_students_by_age_range_api( + n: int = Query(..., description='页码,从1开始', ge=1), + m: int = Query(..., description='每页条数', ge=1), + min_age: int = Query(..., description='最小年龄', ge=0), + max_age: int = Query(..., description='最大年龄', le=100), + db = Depends(get_db)): + if min_age > max_age: + return ResponseModel(code=400, + message='最小年龄不能大于最大年龄', + total=0, + total_pages=0, + data=[]) + + req, total = StatisticsDao.get_students_by_age_range_dao(n, m, min_age, max_age, db) + + return ResponseModel(code=200, + message='查询成功', + total=total, + total_pages=ceil(total / m) if total > 0 else 0, + data=req) + + +@BasicInformationAPI.get('/basic-information/{class_id}', summary='统计每个班级的人数以及男生/女生的人数') +def get_students_by_class_id_api(class_id: str, + db = Depends(get_db)): + total_count, male_count, female_count = StatisticsDao.get_students_by_class_id_dao(class_id, db) + return ClassStatResponse(class_id=class_id, + total_count=total_count, + male_count=male_count, + female_count=female_count) + + +@BasicInformationAPI.get('/scores', summary='在某个分数段的学生') +def get_students_by_score_api( + n: int = Query(..., description='页码,从1开始', ge=1), + m: int = Query(..., description='每页条数', ge=1), + score: float = Query(ge=0, le=100), + db = Depends(get_db)): + if score < 0 or score > 100: + return ResponseModel1(code=400, + message='分数需要在0~100!', + total=0, + total_pages=0, + data=[]) + req, total = StatisticsDao.get_students_by_score_dao(n, m, score, db) + return ResponseModel1(code=200, + message='查询成功', + total=total, + total_pages=ceil(total / m) if total > 0 else 0, + data=req) + + +@BasicInformationAPI.get('/scores_no_pass', summary='查询有n门成绩不合格的学生') +def get_students_by_no_pass_api( + n: int = Query(..., description='页码,从1开始', ge=1), + m: int = Query(..., description='每页条数', ge=1), + fail_count: int = Query(..., description='不及格门数', ge=1), + db = Depends(get_db)): + req, total = StatisticsDao.get_student_by_no_pass_dao(n, m, fail_count, db) + return ResponseModel2(code=200, + message='查询成功', + total=total, + total_pages=ceil(total / m) if total > 0 else 0, + data=req) + + +@BasicInformationAPI.get('/scores_avg', summary='统计每次考试每个班级的平均分(从高到低排序)') +def get_class_exam_avg_api( + n: int = Query(..., description='页码,从1开始', ge=1), + m: int = Query(..., description='每页条数', ge=1), + db = Depends(get_db)): + req, total = StatisticsDao.get_class_exam_avg_score_dao(n, m, db) + return ResponseModel3(code=200, + message='查询成功', + total=total, + total_pages=ceil(total / m) if total > 0 else 0, + data=req) + + +@BasicInformationAPI.get('/salary_top', summary='查询就业薪资最高的前m名学生') +def get_salary_top_api( + m: int = Query(5, description='查询前m名', ge=1), + db = Depends(get_db)): + req = StatisticsDao.get_salary_top_dao(m, db) + return ResponseModel4(code=200, + message='查询成功', + data=req) + + +@BasicInformationAPI.get('/time_size', summary='统计每个学生的就业时长(offer下发时间-就业开放时间)') +def get_time_size_api( + n: int = Query(..., description='页码,从1开始', ge=1), + m: int = Query(..., description='每页条数', ge=1), + db = Depends(get_db)): + req, total = StatisticsDao.get_time_size_dao(n, m, db) + return ResponseModel5(code=200, + message='查询成功', + total=total, + total_pages=ceil(total / m) if total > 0 else 0, + data=req) + + +@BasicInformationAPI.get('/class_avg_time_size', summary='统计每个班级的平均就业时长(只统计进入就业阶段的学生)') +def get_class_avg_time_size_api( + n: int = Query(..., description='页码,从1开始', ge=1), + m: int = Query(..., description='每页条数', ge=1), + db = Depends(get_db)): + req, total = StatisticsDao.get_class_avg_time_size_dao(n, m, db) + return ResponseModel6(code=200, + message='查询成功', + total=total, + total_pages=ceil(total / m) if total > 0 else 0, + data=req) \ No newline at end of file diff --git a/dao/statistics_dao.py b/dao/statistics_dao.py new file mode 100644 index 0000000..33bcc14 --- /dev/null +++ b/dao/statistics_dao.py @@ -0,0 +1,222 @@ +from model.student_info_region_model import Student_info +from model.score_model import ScoreRecord +from model.class_model import Class_info +from model.employment_model import Employment +from sqlalchemy import func, text + +GENDER_MAP = {0: '女', 1: '男'} +STATUS_MAP = {0: '在读', 1: '休学', 2: '退学', 3: '毕业', 4: '结业'} +EDUCATION_MAP = {1: '大专', 2: '本科', 3: '硕士研究生', 4: '博士研究生'} + + +class StatisticsDao: + @staticmethod + def get_students_by_age_range_dao(n: int, m: int, min_age: int, max_age: int, db): + try: + age = func.timestampdiff(text('YEAR'), Student_info.birthday, func.curdate()) + q = db.query(Student_info). \ + filter(Student_info.is_deleted == '0'). \ + filter(age >= min_age). \ + filter(age <= max_age) + total = q.count() + req = q.offset((n - 1) * m).limit(m).all() + except Exception as e: + db.rollback() + raise e + else: + data = [ + { + "student_id": s.student_id, + "student_name": s.student_name, + "gender": GENDER_MAP.get(s.gender, str(s.gender)), + "id_card": s.id_card, + "birthday": s.birthday, + "ethnicity": s.ethnicity, + "region_id": s.region_id, + "phone": s.phone, + "major": s.major, + "class_id": s.class_id, + "enrollment_date": s.enrollment_date, + "graduation_date": s.graduation_date, + "student_status": STATUS_MAP.get(s.student_status, str(s.student_status)), + "education_level": EDUCATION_MAP.get(s.education_level, str(s.education_level)), + } + for s in req + ] + return data, total + + @staticmethod + def get_students_by_class_id_dao(class_id: str, db): + try: + q = db.query(Student_info).filter(Student_info.class_id == class_id). \ + filter(Student_info.is_deleted == '0') + total_count = q.count() + male_count = q.filter(Student_info.gender == 1).count() + female_count = q.filter(Student_info.gender == 0).count() + except Exception as e: + db.rollback() + raise e + else: + return total_count, male_count, female_count + + @staticmethod + def get_students_by_score_dao(n: int, m: int, score: float, db): + try: + q = (db.query( + Student_info.student_name, + Student_info.student_id, + func.min(ScoreRecord.score).label('score') + ).join(ScoreRecord, Student_info.student_id == ScoreRecord.student_id) + .filter(Student_info.is_deleted == '0') + .filter(ScoreRecord.is_deleted == 0) + .group_by(Student_info.student_id, Student_info.student_name) + .having(func.min(ScoreRecord.score) > score)) + total = q.count() + req = q.offset((n - 1) * m).limit(m).all() + except Exception as e: + db.rollback() + raise e + else: + return [{"student_name": i.student_name, + 'student_id': i.student_id, + "score": float(i.score) if i.score is not None else None} for i in req], total + + @staticmethod + def get_student_by_no_pass_dao(n: int, m: int, fail_count: int, db): + try: + q = (db.query( + Student_info.student_id, + Student_info.student_name, + func.count(ScoreRecord.score_id).label('fail_count') + ).join(ScoreRecord, Student_info.student_id == ScoreRecord.student_id) + .filter(Student_info.is_deleted == '0') + .filter(ScoreRecord.is_deleted == 0) + .filter(ScoreRecord.is_pass == 0) + .group_by(Student_info.student_id, Student_info.student_name) + .having(func.count(ScoreRecord.score_id) >= fail_count)) + total = q.count() + req = q.offset((n - 1) * m).limit(m).all() + except Exception as e: + db.rollback() + raise e + else: + return [{"student_id": i.student_id, + "student_name": i.student_name, + "fail_count": i.fail_count} for i in req], total + + @staticmethod + def get_class_exam_avg_score_dao(n: int, m: int, db): + try: + q = (db.query( + ScoreRecord.course_id, + Student_info.class_id, + Class_info.class_name, + func.avg(func.ifnull(ScoreRecord.score, 0)).label('avg_score') + ).join(Student_info, ScoreRecord.student_id == Student_info.student_id) + .join(Class_info, Student_info.class_id == Class_info.class_id) + .filter(Student_info.is_deleted == '0') + .filter(ScoreRecord.is_deleted == 0) + .filter(Class_info.is_deleted == False) + .group_by(ScoreRecord.course_id, + Student_info.class_id, + Class_info.class_name) + .order_by(func.avg(func.ifnull(ScoreRecord.score, 0)).desc())) + total = q.count() + req = q.offset((n - 1) * m).limit(m).all() + except Exception as e: + db.rollback() + raise e + else: + return [{"course_id": i.course_id, + "class_id": i.class_id, + "class_name": i.class_name, + "avg_score": round(float(i.avg_score), 2)} for i in req], total + + @staticmethod + def get_salary_top_dao(m: int, db): + try: + sq = (db.query( + Employment.student_id, + func.max(Employment.salary).label('max_salary') + ).filter(Employment.is_deleted == 0) + .group_by(Employment.student_id) + .order_by(func.max(Employment.salary).desc()) + .limit(m) + .subquery()) + + q = (db.query( + Student_info.student_name, + Class_info.class_name, + Employment.offer_date, + Employment.company_name, + Employment.salary + ).join(Employment, Student_info.student_id == Employment.student_id) + .join(Class_info, Student_info.class_id == Class_info.class_id) + .join(sq, + (Employment.student_id == sq.c.student_id) & + (Employment.salary == sq.c.max_salary)) + .filter(Student_info.is_deleted == '0') + .filter(Class_info.is_deleted == False) + .filter(Employment.is_deleted == 0)) + + req = q.all() + except Exception as e: + db.rollback() + raise e + else: + return [{"student_name": i.student_name, + "class_name": i.class_name, + "offer_date": i.offer_date, + "company_name": i.company_name, + "salary": float(i.salary) if i.salary is not None else None} for i in req] + + @staticmethod + def get_time_size_dao(n: int, m: int, db): + try: + q = (db.query( + Student_info.student_id, + Student_info.student_name, + Employment.offer_date, + Employment.resume_open_date, + func.datediff(Employment.offer_date, Employment.resume_open_date).label('time_size') + ).join(Employment, Student_info.student_id == Employment.student_id) + .filter(Student_info.is_deleted == '0') + .filter(Employment.is_deleted == 0) + .filter(Employment.resume_open_date.isnot(None))) + + total = q.count() + req = q.offset((n - 1) * m).limit(m).all() + except Exception as e: + db.rollback() + raise e + else: + return [{"student_id": i.student_id, + "student_name": i.student_name, + "offer_date": i.offer_date, + "resume_open_date": i.resume_open_date, + "time_size": i.time_size} for i in req], total + + @staticmethod + def get_class_avg_time_size_dao(n: int, m: int, db): + try: + q = (db.query( + Class_info.class_id, + Class_info.class_name, + func.avg(func.datediff(Employment.offer_date, Employment.resume_open_date)).label('avg_time_size') + ).join(Student_info, Class_info.class_id == Student_info.class_id) + .join(Employment, Student_info.student_id == Employment.student_id) + .filter(Class_info.is_deleted == False) + .filter(Student_info.is_deleted == '0') + .filter(Employment.is_deleted == 0) + .filter(Employment.resume_open_date.isnot(None)) + .group_by(Class_info.class_id, Class_info.class_name)) + + total = q.count() + req = q.offset((n - 1) * m).limit(m).all() + except Exception as e: + db.rollback() + raise e + else: + return [{"class_id": i.class_id, + "class_name": i.class_name, + "avg_time_size": round(float(i.avg_time_size), 2)} for i in req], total \ No newline at end of file diff --git a/main.py b/main.py index b089b60..5011d61 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,7 @@ from api.teacher_api import TeacherAPI from api.teacher_course_api import TeacherCourseAPI from api.course_api import course_api from api.score_api import score_api +from api.statistics_api import BasicInformationAPI from starlette.middleware.cors import CORSMiddleware @@ -35,6 +36,8 @@ app.include_router(TeacherCourseAPI,tags=['老师课程信息']) app.include_router(score_api,tags=['成绩信息'],prefix='/score') app.include_router(course_api,tags=['课程信息'],prefix='/course') + +app.include_router(BasicInformationAPI) app.include_router(employment_router) diff --git a/model/statistics_model.py b/model/statistics_model.py new file mode 100644 index 0000000..debfa21 --- /dev/null +++ b/model/statistics_model.py @@ -0,0 +1,118 @@ +from sqlalchemy import * +from database import DATETIME,Base,Column,Integer,String + + +class StudentInfo(Base): + """学生基本信息表""" + __tablename__ = 'student_info' + + student_id = Column(String(20), primary_key=True, comment='学生id') + student_name = Column(String(50), nullable=False, comment='学生姓名') + gender = Column(Enum('男', '女'), nullable=False, comment='性别') + id_card = Column(String(18), nullable=False, comment='身份证号') + birthday = Column(Date, nullable=False, comment='出生日期') + ethnicity = Column(String(20), nullable=False, comment='民族') + region_id = Column(String(20), ForeignKey('region.regio_id'),nullable=False, comment='籍贯编码,连接地区表') + phone = Column(String(11), nullable=False, comment='手机号') + major = Column(String(20), nullable=False, comment='就读专业') + class_id = Column(String(20), nullable=False, comment='班级,连接班级表') + enrollment_date = Column(Date, nullable=False, comment='入学日期') + graduation_date = Column(Date, nullable=False, comment='毕业日期') + student_status = Column(Enum('在读', '休学', '退学', '毕业'), nullable=False, comment='学籍状态') + education_level = Column(Enum('大专', '本科', '硕士研究生', '博士研究生'), nullable=False, comment='学历') + is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除,0=未删除, 1=已删除') + create_time = Column(Date, nullable=False, comment='创建时间') + update_time = Column(Date, nullable=False, comment='更新时间') + + +class Region(Base): + """地区维度表""" + __tablename__ = 'region' + + regio_id = Column(String(50), primary_key=True, comment='地区编码') + province = Column(String(50), nullable=False, comment='省/直辖市') + city = Column(String(50), nullable=False, comment='市') + district = Column(String(50), nullable=False, comment='区,县') + is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除') + create_time = Column(DATETIME, nullable=False, comment='创建时间') + update_time = Column(DATETIME, nullable=False, comment='更新时间') + +class EmploymentInfo(Base): + """学生就业信息表""" + __tablename__ = 'employment_info' + + emp_id = Column(String(50), primary_key=True, comment='就业记录ID') + student_id = Column(String(20), ForeignKey('student_info.student_id'), nullable=False, + comment='学号,连接学生表') + regio_id = Column(String(50), ForeignKey('region.regio_id'), nullable=False, comment='就业地区编码,连接地区表') + job_name = Column(String(50), nullable=False, comment='就业岗位名称') + company_name = Column(String(50), nullable=False, comment='就业公司名称') + salary = Column(Float, nullable=False, comment='就业月薪(元)') + offer_date = Column(Date, nullable=False, comment='offer下发/签约时间') + part_time = Column(Date, nullable=False, comment='就业时间') + resume_open_date = Column(Date, nullable=True, comment='开放简历时间') + employment_status = Column(Enum('1', '2', '3', '4'), nullable=False, + comment='就业状态:1=未就业, 2,=已就业,3=升学, 4=灵活就业') + is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除') + create_time = Column(DATETIME, nullable=False, comment='创建时间') + update_time = Column(DATETIME, nullable=False, comment='更新时间') + +class StudentScore(Base): + """学生考核成绩表""" + __tablename__ = 'student_score' + + score_id = Column(Integer, primary_key=True, autoincrement=True, comment='成绩记录自增主键') + student_id = Column(String(20), ForeignKey('student_info.student_id'),nullable=False, + comment='学号,连接学生表') + course_id = Column(String(20), ForeignKey('course_info.course_id'),nullable=False, comment='课程编号,连接课程表') + exam_type = Column(Enum('期中', '期末', '月考', '周考', '模拟考'), nullable=False, + comment='考试类型:1=期中, 2=期末, 3=月考, 4=周考,5=模拟考') + score = Column(Float, nullable=True, comment='考试分数0~100,缺考为NULL') + score_level = Column(String(10), nullable=False, comment='成绩等级') + is_pass = Column(Enum('0', '1'), nullable=False, comment='是否及格:1=及格, 0=不及格(可由score>=60判断)') + exam_date = Column(Date, nullable=False, comment='考试日期') + exam_category = Column(Enum('首考', '重考', '补考'), nullable=False, comment='考试分类:1=首考, 2=补考, 3=重考') + is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除') + create_time = Column(DATETIME, nullable=False, comment='创建时间') + update_time = Column(DATETIME, nullable=False, comment='更新时间') + +class ClassInfo(Base): + """班级信息表""" + __tablename__ = 'class_info' + + class_id = Column(String(50), primary_key=True, comment='班级业务编码') + class_name = Column(String(50), nullable=False, comment='班级名称') + grade_year = Column(Date, nullable=False, comment='入学年级') + status = Column(Enum('1', '2', '3'), nullable=False, comment='班级状态:1=在读 2=已毕业 3=已停用') + tags = Column(String(100), nullable=False, comment='存储多维标签,例如:["重点班", "科技特长", "2026届"]') + is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除') + create_time = Column(DATETIME, nullable=False, comment='创建时间') + update_time = Column(DATETIME, nullable=False, comment='更新时间') + +class TeacherInfo(Base): + """教师基本信息表""" + __tablename__ = 'teacher_info' + + teacher_id = Column(String(20), primary_key=True, comment='教师编号') + teacher_name = Column(String(20), nullable=False, comment='教师姓名') + gender = Column(Enum('男', '女'), nullable=False, comment='性别') + birthday = Column(Date, nullable=False, comment='出生日期') + region_id = Column(String(50), ForeignKey('region.regio_id'), nullable=False, comment='籍贯地区编码') + email = Column(String(50), nullable=False, comment='邮箱') + phone = Column(String(11), nullable=False, comment='手机号码') + graduation_school = Column(String(50), nullable=False, comment='毕业学校') + teacher_rank = Column(Enum('1', '2', '3', '4'), nullable=False, comment='职称:1=初级, 2=中级, 3=高级, 4=特级') + is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除') + create_time = Column(DATETIME, nullable=False, comment='创建时间') + update_time = Column(DATETIME, nullable=False, comment='更新时间') + +class CourseInfo(Base): + """课程基础信息""" + __tablename__ = 'course_info' + + course_id = Column(String(30), primary_key=True, comment='课程编号') + course_name = Column(String(30), nullable=False, comment='课程名称') + course_type = Column(Enum('1', '2', '3'), nullable=False, comment='课程类型:1 必修课 2 选修课 3 实训课') + is_deleted = Column(Enum('0', '1'), nullable=False, comment='逻辑删除:0=未删除, 1=已删除') + create_time = Column(DATETIME, nullable=False, comment='创建时间') + update_time = Column(DATETIME, nullable=False, comment='更新时间') diff --git a/model/student_info_region_model.py b/model/student_info_region_model.py index 69cbb8a..ea159b0 100644 --- a/model/student_info_region_model.py +++ b/model/student_info_region_model.py @@ -2,7 +2,7 @@ from database import Base, engine as db_engine # 从 sqlalchemy 导入 Enum,并别名为 SAEnum,用于定义数据库枚举列 -from sqlalchemy import Enum as SAEnum, Integer +from sqlalchemy import Enum as SAEnum, Integer, String # 从 sqlalchemy 导入常用组件:inspect(检查表是否存在)、Column(列)、 # VARCHAR/BIGINT/Date/DateTime(字段类型)、func(SQL 函数,如 now()) diff --git a/schema/statistics_schema.py b/schema/statistics_schema.py new file mode 100644 index 0000000..20607e0 --- /dev/null +++ b/schema/statistics_schema.py @@ -0,0 +1,125 @@ +from pydantic import BaseModel, ConfigDict +from datetime import date +from typing import List, Optional + + +class StudentInfo1(BaseModel): + model_config = ConfigDict(from_attributes=True) + student_id: str + student_name: str + gender: str + id_card: str + birthday: Optional[date] = None + ethnicity: Optional[str] = None + region_id: Optional[str] = None + phone: Optional[str] = None + major: str + class_id: str + enrollment_date: Optional[date] = None + graduation_date: Optional[date] = None + student_status: str + education_level: str + + +class ResponseModel(BaseModel): + code: int + message: str = 'ok' + total: int + total_pages: int + data: List[StudentInfo1] + + +class ClassStatResponse(BaseModel): + code: int = 200 + message: str = 'ok' + class_id: str + total_count: int + male_count: int + female_count: int + + +class StudentInfo2(BaseModel): + student_id: str + student_name: str + score: Optional[float] = None + + +class ResponseModel1(BaseModel): + code: int + message: str = 'ok' + total: int + total_pages: int + data: List[StudentInfo2] + + +class StudentInfo3(BaseModel): + student_id: str + student_name: str + fail_count: int + + +class ResponseModel2(BaseModel): + code: int + message: str = 'ok' + total: int + total_pages: int + data: List[StudentInfo3] + + +class ClassExamAvg(BaseModel): + course_id: str + class_id: str + class_name: str + avg_score: float + + +class ResponseModel3(BaseModel): + code: int + message: str = 'ok' + total: int + total_pages: int + data: List[ClassExamAvg] + + +class SalaryTop(BaseModel): + student_name: str + class_name: str + company_name: str + offer_date: Optional[date] = None + salary: Optional[float] = None + + +class ResponseModel4(BaseModel): + code: int + message: str = 'ok' + data: List[SalaryTop] + + +class TimeSize(BaseModel): + student_id: str + student_name: str + offer_date: Optional[date] = None + resume_open_date: Optional[date] = None + time_size: Optional[int] = None + + +class ResponseModel5(BaseModel): + code: int + message: str = 'ok' + total: int + total_pages: int + data: List[TimeSize] + + +class ClassAvgTimeSize(BaseModel): + class_id: str + class_name: str + avg_time_size: float + + +class ResponseModel6(BaseModel): + code: int + message: str = 'ok' + total: int + total_pages: int + data: List[ClassAvgTimeSize] \ No newline at end of file