129 lines
4.5 KiB
Python
129 lines
4.5 KiB
Python
from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import func
|
|
from typing import List, Optional
|
|
|
|
from database import get_db
|
|
from models import Student, Class, Score, Employment
|
|
|
|
router = APIRouter(prefix="/api/stats", tags=["stats"])
|
|
|
|
|
|
@router.get("/classes")
|
|
def class_stats(db: Session = Depends(get_db)):
|
|
"""按班级统计学生人数和性别分布"""
|
|
results = (
|
|
db.query(
|
|
Student.class_id,
|
|
Class.name.label("class_name"),
|
|
func.count(Student.id).label("total"),
|
|
func.sum(func.if_(Student.sex == 1, 1, 0)).label("male"),
|
|
func.sum(func.if_(Student.sex == 2, 1, 0)).label("female"),
|
|
)
|
|
.join(Class, Student.class_id == Class.id)
|
|
.filter(Student.is_deleted == False, Class.is_deleted == False)
|
|
.group_by(Student.class_id, Class.name)
|
|
.all()
|
|
)
|
|
return [
|
|
{
|
|
"class_id": r.class_id,
|
|
"class_name": r.class_name,
|
|
"total_students": r.total,
|
|
"male_count": r.male,
|
|
"female_count": r.female,
|
|
}
|
|
for r in results
|
|
]
|
|
|
|
|
|
@router.get("/scores")
|
|
def score_stats(class_id: Optional[int] = None, score_num: Optional[str] = None, db: Session = Depends(get_db)):
|
|
"""成绩统计:平均分、最高分、最低分、人数"""
|
|
query = (
|
|
db.query(
|
|
Score.num.label("score_num"),
|
|
Student.class_id,
|
|
Class.num.label("class_num"),
|
|
Class.name.label("class_name"),
|
|
func.avg(Score.score).label("avg_score"),
|
|
func.max(Score.score).label("max_score"),
|
|
func.min(Score.score).label("min_score"),
|
|
func.count(Score.id).label("count"),
|
|
)
|
|
.join(Student, Score.sid == Student.id)
|
|
# 添加班级名称
|
|
.join(Class, Student.class_id == Class.id)
|
|
.filter(Student.is_deleted == False, Score.is_deleted == False)
|
|
.group_by(Score.num, Student.class_id)
|
|
)
|
|
if class_id:
|
|
query = query.filter(Student.class_id == class_id)
|
|
if score_num:
|
|
query = query.filter(Score.num == score_num)
|
|
results = query.all()
|
|
return [
|
|
{
|
|
"score_num": r.score_num,
|
|
"class_id": r.class_id,
|
|
"class_num": r.class_num,
|
|
"class_name": r.class_name,
|
|
"avg_score": float(r.avg_score),
|
|
"max_score": float(r.max_score),
|
|
"min_score": float(r.min_score),
|
|
"count": r.count,
|
|
}
|
|
for r in results
|
|
]
|
|
|
|
|
|
@router.get("/employment")
|
|
def employment_stats(class_id: Optional[int] = None, db: Session = Depends(get_db)):
|
|
"""就业统计:已就业人数、总人数、平均薪资、平均签约周期"""
|
|
from sqlalchemy import cast, Integer
|
|
|
|
query = (
|
|
db.query(
|
|
Employment.class_id,
|
|
Class.name.label("class_name"),
|
|
func.count(Employment.id).label("employed_count"),
|
|
func.count(Student.id).label("total_count"),
|
|
func.avg(Employment.employment_salary).label("avg_salary"),
|
|
)
|
|
.join(Class, Employment.class_id == Class.id)
|
|
.join(Student, Employment.sid == Student.id)
|
|
.filter(Employment.is_deleted == False, Student.is_deleted == False, Class.is_deleted == False)
|
|
.group_by(Employment.class_id, Class.name)
|
|
)
|
|
if class_id:
|
|
query = query.filter(Employment.class_id == class_id)
|
|
results = query.all()
|
|
|
|
output = []
|
|
for r in results:
|
|
# 计算平均签约周期(天数)
|
|
durations = (
|
|
db.query(
|
|
func.ceil(
|
|
func.cast(func.julianday(Employment.offer_recived_time) - func.julianday(Employment.employment_open_time), Integer)
|
|
)
|
|
)
|
|
.filter(
|
|
Employment.class_id == r.class_id,
|
|
Employment.is_deleted == False,
|
|
Employment.employment_open_time.isnot(None),
|
|
Employment.offer_recived_time.isnot(None),
|
|
)
|
|
.all()
|
|
)
|
|
avg_days = sum(d[0] for d in durations if d[0] is not None) / len(durations) if durations else None
|
|
output.append({
|
|
"class_id": r.class_id,
|
|
"class_name": r.class_name,
|
|
"employed_count": r.employed_count,
|
|
"total_count": r.total_count,
|
|
"avg_salary": float(r.avg_salary) if r.avg_salary else None,
|
|
"avg_duration_days": round(avg_days, 1) if avg_days is not None else None,
|
|
})
|
|
return output
|