Deploy / deploy (push) Canceled after 0s
按天的连续成就对这里的学生基本拿不到:2025 秋最长连续交题天数的中位数是 1 天, 连续 7 天的只有 3 人。按周算,连续 2/4/8 周分别有约 73%/49%/8% 的学生做到过。 - 新指标 max_ac_week_streak:连续几个自然周(周一起、东八区)每周都至少新通过一道题 (不含比赛)。判题结算时增量累计(比按天那段多一道判断:重判更早的提交不会把连续清零); 后台建成就触发重扫时,先从提交记录全量补算(refreshAcWeekStreakForAll),同 contest_joined - 老的「坚持三天」「坚持一周」不动:下架的成就连已获得的人也看不到 - 修:库里 28 个已不存在的用户留下了 935 条提交(其中 11 人有比赛提交),全量补算往 user_stat 写时撞外键,整次失败——比赛类成就的重扫因此一直是失败的;两处都改为只算 还在的用户。本地修好后重扫「初入赛场」补发了 3 人 - 本地验证:连续 2/4/8 周补发 1086/618/123 人,与独立 SQL 一致 线上要在后台手动建成就(指标选「最长连续新通过周数」),建的时候自动补发。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
/**
|
||
* 成就指标注册表。对齐旧后端 `achievement/metrics.py` 的 METRIC_REGISTRY ——
|
||
* 那边是装饰器注册,这里是一张表,作用一样:**后台下拉框里有什么,取决于代码里注册了什么**。
|
||
*
|
||
* 加一个新维度必须同时改这里和 `achievements.ts` 的计算逻辑并部署。
|
||
* 只加这里而不算,会造出一个谁也拿不到的成就;只算而不加这里,后台就选不到它。
|
||
*/
|
||
export interface AchievementMetric {
|
||
key: string
|
||
name: string
|
||
helpText: string
|
||
/** 元指标:统计的是「已解锁成就数」自身,判定要在其它成就结算完之后再跑一轮 */
|
||
meta?: boolean
|
||
}
|
||
|
||
export const ACHIEVEMENT_METRICS: AchievementMetric[] = [
|
||
{
|
||
key: "accepted_count",
|
||
name: "AC 题目数",
|
||
helpText: "去重后通过的题目数量(不含比赛)",
|
||
},
|
||
{
|
||
key: "mid_ac_count",
|
||
name: "中等题 AC 数",
|
||
helpText: "去重后通过的中等难度题目数(不含比赛)",
|
||
},
|
||
{
|
||
key: "hard_ac_count",
|
||
name: "困难题 AC 数",
|
||
helpText: "去重后通过的困难题目数(不含比赛)",
|
||
},
|
||
{
|
||
key: "submission_count",
|
||
name: "提交总数",
|
||
helpText: "提交次数(不含比赛)",
|
||
},
|
||
{ key: "active_days", name: "活跃天数", helpText: "有过提交的累计天数" },
|
||
{
|
||
key: "max_ac_streak_days",
|
||
name: "最长连续 AC 天数",
|
||
helpText: "连续每天至少 AC 一题的最长天数",
|
||
},
|
||
// 按周不按天:学生跟着课表来,2025 秋最长连续交题天数的中位数是 1 天、连续 7 天的
|
||
// 只有 3 人,按天的「坚持一周」基本拿不到;按周的连续 2/4/8 周分别约 73%/49%/8%
|
||
{
|
||
key: "max_ac_week_streak",
|
||
name: "最长连续新通过周数",
|
||
helpText: "连续几个自然周(周一起)每周都至少新通过一道题(不含比赛)",
|
||
},
|
||
{ key: "languages_used", name: "使用语言数", helpText: "用过多少种编程语言" },
|
||
{
|
||
key: "contest_joined",
|
||
name: "参赛场次",
|
||
helpText: "参加过的比赛数量(本指标是比赛维度,不受比赛提交不计入的限制)",
|
||
},
|
||
{ key: "badge_count", name: "题单奖章数", helpText: "获得的题单奖章数量" },
|
||
{
|
||
key: "problemset_completed",
|
||
name: "完成题单数",
|
||
helpText: "完成的题单数量",
|
||
},
|
||
{
|
||
key: "first_try_ac_count",
|
||
name: "一发入魂次数",
|
||
helpText: "首次提交即通过的次数",
|
||
},
|
||
{
|
||
key: "midnight_submissions",
|
||
name: "凌晨提交次数",
|
||
helpText: "0:00–5:00 之间的提交次数",
|
||
},
|
||
{
|
||
key: "early_bird_submissions",
|
||
name: "早起提交次数",
|
||
helpText: "5:00–7:00 之间的提交次数",
|
||
},
|
||
{
|
||
key: "compile_error_count",
|
||
name: "编译错误次数",
|
||
helpText: "累计编译错误的次数",
|
||
},
|
||
{
|
||
key: "max_wa_before_ac",
|
||
name: "屡败屡战",
|
||
helpText: "单题失败最多多少次后终于通过",
|
||
},
|
||
{
|
||
key: "max_ac_in_one_day",
|
||
name: "单日最多 AC",
|
||
helpText: "一天之内最多通过多少题",
|
||
},
|
||
{
|
||
key: "max_code_lines",
|
||
name: "最长代码行数",
|
||
helpText: "提交过的最长代码有多少行",
|
||
},
|
||
{
|
||
key: "achievement_unlocked_count",
|
||
name: "已解锁成就数",
|
||
helpText: "已解锁的成就数量(不含白金档)",
|
||
meta: true,
|
||
},
|
||
]
|
||
|
||
const BY_KEY = new Map(ACHIEVEMENT_METRICS.map((item) => [item.key, item]))
|
||
|
||
export function findMetric(key: string) {
|
||
return BY_KEY.get(key) ?? null
|
||
}
|
||
|
||
export function metricName(key: string) {
|
||
return BY_KEY.get(key)?.name ?? key
|
||
}
|