Files
stu_teacher/api/statistics.py
T

148 lines
5.9 KiB
Python

# api/statistics.py
# 统计分析模块接口:动态年龄查询、班级统计、成绩统计、就业统计、高级筛选器、聚合统计
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from core.deps import require_roles
from dao.statistics_dao import StatisticsDAO, FilterBuilder
from database import get_db
from scheme.statistics import (
FilterRequest,
FilterResponse,
ClassGenderStat,
AllAboveStudent,
FailStudent,
ClassExamAvg,
TopSalaryStudent,
EmploymentDuration,
ClassAvgDuration,
ScoreVolatility,
EmploymentFunnel,
)
from scheme.students import StudentResponse
router = APIRouter()
# 统计模块统一要求 admin / teacher 角色(各接口通过 Depends(require_roles(...)) 校验)
# ==================== 2.6.1 基本信息动态统计 ====================
@router.get("/students/by-age", response_model=list[StudentResponse], summary="动态年龄范围查询")
async def students_by_age(
op: str = Query(..., description="比较条件:gt/lt/eq/gte/lte/between"),
value: int = Query(None, description="op 为 gt/lt/eq/gte/lte 时必填"),
min_value: int = Query(None, description="op=between 时的下界"),
max_value: int = Query(None, description="op=between 时的上界"),
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
try:
items = StatisticsDAO.students_by_age(db, op=op, value=value, min_value=min_value, max_value=max_value)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
return items
@router.get("/class/gender-stats", response_model=list[ClassGenderStat], summary="多维度班级统计(总人数+男女分布)")
async def class_gender_stats(
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
return StatisticsDAO.class_gender_stats(db)
# ==================== 2.6.2 成绩综合统计 ====================
@router.get("/score/all-above", response_model=list[AllAboveStudent], summary="每次考试都在分数线以上的学生")
async def score_all_above(
line: float = Query(..., ge=0, le=100, description="分数线,如 80"),
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
return StatisticsDAO.students_all_above(db, line)
@router.get("/score/fail", response_model=list[FailStudent], summary="不及格次数>=N的学生(含明细)")
async def score_fail(
times: int = Query(..., ge=1, description="不及格次数阈值,如 2"),
line: float = Query(60.0, ge=0, le=100, description="及格线,默认 60"),
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
return StatisticsDAO.fail_students(db, times, line)
@router.get("/score/class-avg", response_model=list[ClassExamAvg], summary="每次考试每个班级平均分(支持动态排序)")
async def score_class_avg(
exam_id: int = Query(None, ge=1, description="考核序次(不传则返回所有考核)"),
order: str = Query("desc", pattern="^(asc|desc)$", description="按平均分排序方向"),
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
return StatisticsDAO.class_exam_avg(db, exam_id=exam_id, order=order)
# ==================== 2.6.3 就业数据统计 ====================
@router.get("/employment/top-salary", response_model=list[TopSalaryStudent], summary="就业薪资排名 Top N")
async def employment_top_salary(
n: int = Query(..., ge=1, le=100, description="取前 N 名"),
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
return StatisticsDAO.top_salary(db, n)
@router.get("/employment/duration", response_model=list[EmploymentDuration], summary="每个学生的就业时长(天)")
async def employment_duration(
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
return StatisticsDAO.employment_durations(db)
@router.get("/employment/class-avg-duration", response_model=list[ClassAvgDuration], summary="每个班级平均就业时长")
async def employment_class_avg_duration(
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
return StatisticsDAO.class_avg_duration(db)
# ==================== 2.7.1 通用高级筛选器 ====================
@router.post("/filter", response_model=FilterResponse, summary="通用高级筛选器(AND/OR 嵌套规则)")
async def advanced_filter(
body: FilterRequest,
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
try:
total, items = FilterBuilder.query_students(db, body.rules)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
# 组装为字典响应(含班级名称)
data = []
for s in items:
row = StudentResponse.model_validate(s).model_dump()
if s.classes:
row["class_name"] = s.classes.class_name
data.append(row)
return FilterResponse(total=total, items=data)
# ==================== 2.7.2 多维度聚合统计 ====================
@router.get("/score/volatility", response_model=list[ScoreVolatility], summary="成绩波动最大 Top N(最大分差)")
async def score_volatility(
top_n: int = Query(5, ge=1, le=50, description="取前 N 名,默认 5"),
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
return StatisticsDAO.score_volatility(db, top_n)
@router.get("/employment/funnel", response_model=list[EmploymentFunnel], summary="班级就业漏斗(总人数→已就业→高薪→就业率)")
async def employment_funnel(
high_salary_line: float = Query(10000.0, ge=0, description="高薪线,默认 10000"),
db: Session = Depends(get_db),
_user=Depends(require_roles("admin", "teacher")),
):
return StatisticsDAO.employment_funnel(db, high_salary_line)