e329d3398a
Add FastAPI/React app with Docker deployment, Ubuntu one-click install, and docs for junior/senior high score tracking and mistake bank. Co-authored-by: Cursor <cursoragent@cursor.com>
67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
from sqlalchemy.orm import Session, joinedload
|
|
|
|
from app.core.config import settings
|
|
from app.models.user import ExamRecord, Subject, SubjectScore
|
|
from app.schemas import ExamTypeEnum, TrendPoint, TrendResponse
|
|
|
|
|
|
def build_trend(db: Session, student_id, subject_id: int) -> TrendResponse:
|
|
subject = db.get(Subject, subject_id)
|
|
if subject is None:
|
|
raise ValueError("科目不存在")
|
|
|
|
scores = (
|
|
db.query(SubjectScore)
|
|
.join(ExamRecord)
|
|
.options(joinedload(SubjectScore.exam_record))
|
|
.filter(
|
|
ExamRecord.student_id == student_id,
|
|
SubjectScore.subject_id == subject_id,
|
|
)
|
|
.order_by(ExamRecord.exam_date.asc(), ExamRecord.created_at.asc())
|
|
.all()
|
|
)
|
|
|
|
threshold = settings.FLUCTUATION_THRESHOLD
|
|
points: list[TrendPoint] = []
|
|
prev_ratio: float | None = None
|
|
|
|
for score in scores:
|
|
exam = score.exam_record
|
|
ratio = float(score.ratio)
|
|
delta = None if prev_ratio is None else ratio - prev_ratio
|
|
direction = None
|
|
is_volatile = False
|
|
|
|
if delta is not None:
|
|
if delta > 0:
|
|
direction = "up"
|
|
elif delta < 0:
|
|
direction = "down"
|
|
else:
|
|
direction = "flat"
|
|
is_volatile = abs(delta) >= threshold
|
|
|
|
points.append(
|
|
TrendPoint(
|
|
exam_id=exam.id,
|
|
exam_type=ExamTypeEnum(exam.exam_type.value),
|
|
exam_date=exam.exam_date,
|
|
title=exam.title,
|
|
ratio=ratio,
|
|
ratio_percent=round(ratio * 100, 2),
|
|
delta=delta,
|
|
delta_percent=round(delta * 100, 2) if delta is not None else None,
|
|
is_volatile=is_volatile,
|
|
direction=direction,
|
|
)
|
|
)
|
|
prev_ratio = ratio
|
|
|
|
return TrendResponse(
|
|
subject_id=subject_id,
|
|
subject_name=subject.name,
|
|
threshold=threshold,
|
|
points=points,
|
|
)
|