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, )