Files
web/docs/plans/2026-03-09-score-system-design.md
T
xuyueandClaude Opus 5.5 47d4679984 chore: 合并为单仓库,统一部署流程
- 根目录加入 docker-compose、Caddyfile、文档等原本不在仓库里的文件
- 前后端两份 GitHub Actions 合并成根目录的一个 deploy workflow:
  构建前端上传到 build/,再在服务器上 git pull 并重建容器

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 16:02:12 +08:00

2.8 KiB
Raw Blame History

挑战分数系统设计

日期: 2026-03-09 状态: 已审批

背景

Submission.score 已有加权平均评分逻辑,Profile.total_score 字段存在但从未激活。分数计算出来后无任何展示或用途,需要设计让分数真正发挥作用的功能。

目标

  • 学生:看到自己各题得分和在全班的排名
  • 老师:快速了解哪些学生表现好,可按条件导出成绩
  • 为未来解锁机制预留接口

方案选择

采用方案 A(积分展示中心),在 Challenge 模型预留 pass_score 字段供日后解锁功能使用。


数据层

Profile.recalculate_total_score()

替换现有 update_total_score() 的累加逻辑,改为从数据库重算:

def recalculate_total_score(self):
    from submission.models import Submission
    from django.db.models import Max, Sum
    # 每道挑战取最高分,再求和
    total = (
        Submission.objects
        .filter(user=self.user, task__task_type='challenge')
        .values('task_id')
        .annotate(best=Max('score'))
        .aggregate(total=Sum('best'))['total']
    ) or 0.0
    self.total_score = total
    self.save(update_fields=['total_score'])

Rating post_save 信号

在现有 update_submission_score_on_save 之后,追加调用:

instance.submission.user.profile.recalculate_total_score()

Challenge.pass_score 字段

pass_score = models.FloatField(null=True, blank=True, verbose_name="通过分数线")

默认 null,不影响现有功能,为解锁机制预留。

用户最佳分数定义

同一用户同一挑战取 score 最高的提交作为该题成绩。


API 层

1. 排行榜

GET /api/account/leaderboard/
  • 无需登录
  • 返回所有用户按 total_score 降序,含排名、用户名、总分

2. 我的各题得分

GET /api/submission/my-scores/
  • 需登录
  • 返回当前用户每道挑战的最高分提交(challenge display、title、score、提交时间)

3. 成绩导出

GET /api/submission/export/?task_id=&username=
  • 仅限 admin/super 角色
  • 复用现有 SubmissionFilter 参数
  • 返回 CSV 文件流,每行:用户名、挑战题号、挑战标题、最高分、提交时间

前端

改动点 说明
挑战列表卡片 显示该用户最高得分(灰色小标,未评分不显示)
新页面 /leaderboard 排名/用户名/总分表格,所有人可访问,放主页导航
新页面 /my-scores 我的各题得分列表,登录后可见,放主页用户区域
Submissions 页面 现有筛选栏旁加"导出 CSV"按钮,触发浏览器下载

不在本期范围内

  • 解锁机制(pass_score 字段已预留,后续单独设计)
  • 班级/分组维度的统计
  • 实时分数推送(WebSocket)