Files
xs_system/api/statistics_api.py
T
2026-09-23 09:28:16 +08:00

182 lines
7.6 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.
# ============================================================
# API 接口层 — 对外暴露的 HTTP 路由
# 职责:
# 1. 定义 URL 路径、请求方法(GET/POST)和参数校验规则
# 2. 调用 dao 层获取数据
# 3. 用 schema 层的 ResponseModel 封装统一响应格式返回给前端
# 依赖注入:
# db = Depends(get_db) — 由 FastAPI 自动为每个请求创建并回收数据库会话
# ============================================================
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, ResponseModel7, ResponseModel8
)
from math import ceil
# 统一路由前缀分组,所有接口在 Swagger 里归到"统计分析"标签下
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)
):
"""
按年龄区间分页查询学员基本信息。
前端传入 min_age / max_age,后端换算为出生日期区间后在 student_info 表中过滤。
"""
# 参数合法性校验:最小年龄不能大于最大年龄
if min_age > max_age:
return {'code': 400, 'message': '最小年龄不能大于最大年龄', 'total': 0, 'total_pages': 0, 'data': []}
# 调用 DAO 层查询
req, total = StatisticsDao.get_students_by_age_range_dao(n, m, min_age, max_age, db)
# 计算总页数:ceil 向上取整;total 为 0 时避免除以 0
total_pages = ceil(total / m) if total > 0 else 0
return ResponseModel(code=200,
message='查询成功',
total=total,
total_pages=total_pages,
data=req)
@BasicInformationAPI.get('/basic-information/{class_id}', summary='统计指定班级的人数以及男生/女生人数')
def get_students_by_class_id_api(class_id: str, db=Depends(get_db)):
"""
根据班级编号查询该班级的总人数、男生人数、女生人数。
URL 路径参数 class_id 由 FastAPI 自动解析。
"""
data = StatisticsDao.get_students_by_class_id_dao(class_id, db)
return ResponseModel7(code=200,
message='查询成功',
data=data)
@BasicInformationAPI.get('/class-stats', summary='统计所有班级的人数以及男生/女生人数')
def get_class_stats_api(db=Depends(get_db)):
"""
一次性返回所有班级的统计结果(不需要分页,数据量通常可控)。
"""
data = StatisticsDao.get_class_stats_dao(db)
return ResponseModel8(code=200,
message='查询成功',
data=data)
@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, description='分数阈值,0~100'),
db=Depends(get_db)
):
"""
查询最低单科成绩 > score 的学生(即所有考试成绩都超过阈值)。
例如 score=60,表示没有任何一门不及格的学生。
"""
if score < 0 or score > 100:
return {'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='查询不及格门数 >= fail_count 的学生')
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)
):
"""
统计每个学生不及格门数,返回不及格门数 >= fail_count 的学生名单。
"""
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)
):
"""
三表联查 student_score → student_info → class_info,
按 course_id + class_id 分组,计算每个班级每门课的平均分,按平均分降序排列。
"""
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 名,默认5', ge=1),
db=Depends(get_db)
):
"""
查询就业薪资最高的前 m 名学生的姓名、班级、就业公司、就业时间和薪资。
每人只取自己历史最高薪资那条记录。
"""
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)
):
"""
计算每个学生从开放简历到拿到 offer 的天数差 time_size。
只统计开放简历时间不为空的记录。
"""
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)
):
"""
按班级分组,计算进入就业阶段(resume_open_date 不为空)学生的平均就业时长。
"""
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)