feat(scoring): 新增「队内最差(最后一名冲线)」聚合口径;版本 dev_0.5.11
## 需求(主人原话)
> 记分通道团体名次的名次来源缺一个模式,添加一个「按队内最低分」。
> 有一个特殊项目是取团队的最后一人的成绩(最后一名冲线)来排名的。
## 口径(这里最容易写反,所以定死)
新增 `aggregate_mode = 'worst'`。**不叫 `min`/`max`** —— 它的方向是**派生**的,叫 `min` 会让
「越大越好」的那半项目被写错。**跟着项目的比较方向自动取**:
| 项目 `compare` | 含义 | 队内最差 = |
| --- | --- | --- |
| `asc`(越小越好,如时间) | 行军 / 越野 | **max** = 最后一名冲线的那个人 |
| `desc`(越大越好,如次数) | 引体向上 | **min** = 做得最少的那个 |
方向判据**各只有一处**:Go 的 `domain.worseValue`、前端预览的 `worseThan`,
两边都有 asc/desc 双向用例钉着。规则包里写 `min`/`max` 会被校验**拦下并报错**。
## 改动
- **迁移 `0027`**:SQLite 改不了已有的 `CHECK`,按官方做法**重建 `event_score_channels`**,
取值放开到 `avg/sum/worst`;⚠️ 重建时**先把子表 `channel_ranks` 寄存到临时表再原样灌回**
(父表被 DROP 会级联清空名次 —— 这一步漏了就会把已发布的名次全丢)。
- **引擎**:`scoreByAggregate` 支持第三种口径。
- **接口 / 规则包**:读、写、校验、快照、导出五处都认第三个取值。
- **界面**:「聚合口径」由**两个按钮变三个**(平均 / 总和 / **队内最低分**),
提示文案写明两种方向,不丢一个 `worst` 让人猜。
- **前端预览同步**(`web/src/admin/api/scoring.ts`)—— 前一版刚修完「预览与后端算分不一致」,
这次不允许再开一个新口子。
- 文档:`docs/使用手册.md`、`docs/升级说明.md`、`devdocs/18` 各补一处。
## 两条边界(都是刻意的)
- **弃权 / 失格不算参与,且它的成绩不代表队伍**:队里 5 / 6 / 弃赛(0) → 队内最差是 **5**,不是 0。
否则谁都可以挂一个弃赛的人在队里垫底。
- **一个有效成绩都没有的主体一律不参与排名**(`avg_mode` 在这一口径下不起作用)。
若按 0 参与:`asc` 里 0 是全场最快、`desc` 里 0 是全场最差 —— 两边都会让没完赛的队白拿名次。
## 验证
- **老口径逐字节不变**:新增 golden `TestAggregateModesLegacyUnchanged`(`avg` / `sum` 结果与改动前一致)。
- 全量自检两轮**全绿、`EXIT=0`**:服务端 14 包 / 前端 **953 例** 0 fail / Console 96 例 0 fail。
- 迁移用例覆盖:老行原样、子表名次一行不少、id 不漂、非法值仍拒、幂等、级联删除仍在。
## 版本
`dev_0.5.10` → **`dev_0.5.11`**(含数据迁移 `0027`);安装器素材同步重生成(照例只动 `brand.bmp` 两张)。
This commit is contained in:
1 parent
a85115c96e
commit
48208f395b
30 files changed
+1394
-85
No files matched your search
+10
-4
@@ -80,7 +80,7 @@
|
||||
2. 每个小项带状态徽标:**已发布** 或 **待录入**;
|
||||
3. 多个组别时,表格上方出现组别页签,切换组别分别录入;
|
||||
4. 组别页签旁边有**批次下拉**(`全部` / `第 1 批` / `第 2 批` / … / `未分批`),默认 `全部`(`未分批` 只在确实还有未分批的行时才出现)。选某一批后表格只显示那一批的人,方便录入员**只在自己这一批里找名字**,不用在一大堆人里翻;
|
||||
5. 配了「按队内平均」通道的小项(例如「个人+团体」项目的团体通道),工具栏下方会出现 **「平均值口径」单选**:`只算有效成绩`(没有有效成绩的人不进分母)或 `全部报名人员都计入`(没成绩的按 0 计入平均)。点选后立即保存到该小项,不需要另外点保存;这是小项级设置,该小项的所有组别共用。
|
||||
5. 配了「按队内平均」通道的小项(例如「个人+团体」项目的团体通道),工具栏下方会出现 **「平均值口径」单选**:`只算有效成绩`(没有有效成绩的人不进分母)或 `全部报名人员都计入`(没成绩的按 0 计入平均)。点选后立即保存到该小项,不需要另外点保存;这是小项级设置,该小项的所有组别共用。⚠️ 该小项的聚合口径选了 **「队内最低分」** 时这个单选**对它不起作用**:那种口径取的是队内最差的一份成绩,一个人都没有效成绩的单位一律不参与排名(见 10.4 第 5 条)。
|
||||
|
||||
**批次录入进度条**:工具栏正下方有一条横跨整行的进度条,显示当前筛选范围的录入进度,例如 `第 2 批 已录 5/8 · 3 条有错`(有错的行既不算「已录」,也会在发布时被防呆拦下,所以要单独报出来);**全部录完时文字变成「已录完 8/8」、进度条变绿**。分母就是当前筛选范围的行数——选了某一批就只数这一批。它和发布防呆、行上的状态徽标用的是**同一套判定**,不会出现「进度条说录完了、发布却说缺人」。
|
||||
|
||||
@@ -701,11 +701,17 @@
|
||||
2. 点「新增通道」逐条配置,同一「名次范围 + 名次来源」只能有一个通道;
|
||||
3. 点「保存全部」整体替换该小项的通道配置;
|
||||
4. 标记为「不计分」的小项,通道可以保存但不参与积分计算;
|
||||
5. 「名次来源」选「按队内平均」时,把同一个单位(或个人)在该组别的成绩**汇总成一个值**后排名,并列按积分模板的并列口径处理(默认顺延)。汇总口径在该行的**「聚合口径」列**上选,两个按钮点一下即切换:
|
||||
5. 「名次来源」选「按队内平均」时,把同一个单位(或个人)在该组别的成绩**汇总成一个值**后排名,并列按积分模板的并列口径处理(默认顺延)。汇总口径在该行的**「聚合口径」列**上选,三个按钮点一下即切换:
|
||||
- **平均**(默认):团体成绩 = 该单位成员成绩的**平均值**。分母取录入页选的「平均值口径」——`只算有效成绩`时是本单位有有效成绩的条数(一个人都没有效成绩的单位不参与团体排名、不得分),`全部报名人员都计入`时是本单位在该组别的报名条数(没成绩的按 0 计入);
|
||||
- **总和**:团体成绩 = 该单位成员成绩的**总和**(没成绩的人按 0 加与不加都一样)。
|
||||
- **各队人数相同时,两种口径排出来的名次完全相同**;人数不同时可能不同(这时按你们要的口径选,默认是「平均」)。改完点「保存全部」生效,该小项立即重算;
|
||||
- 报名表里 `status` 不是 `registered` 的报名项(弃赛 / 取消)既不算参与,也不进分母;
|
||||
- **队内最低分**:团体成绩 = 该单位成员里**最差的那一份**成绩——**取的是谁,由小项的「比较方式」决定**:
|
||||
- 比较方式 = **升序**(时间这类**越小越好**的项目,例如越野 / 行军):取**跑得最慢的那个人**,也就是**最后一名冲线**的那个人的成绩;
|
||||
- 比较方式 = **降序**(次数这类**越大越好**的项目,例如引体向上):取**做得最少的那个**。
|
||||
|
||||
现场用途:赛会规则写「取团队最后一人的成绩」的项目就是它。⚠️ **别按字面理解成"取最小的那个"**——时间项目里"最小"是最快的人,正好是反的;
|
||||
- **各队人数相同时,平均与总和排出来的名次完全相同**;人数不同时可能不同(这时按你们要的口径选,默认是「平均」)。「队内最低分」与它们都可能不同(它只看队里最差的那一份,与人数无关)。改完点「保存全部」生效,该小项立即重算;
|
||||
- 报名表里 `status` 不是 `registered` 的报名项(弃赛 / 取消)既不算参与,也不进分母;**「队内最低分」下弃赛的人的成绩也不代表队伍**(弃赛那一格填得再差也不算),队伍成绩只在实际出赛的人里取;
|
||||
- **「队内最低分」下,一个有效成绩都没有的单位不参与团体排名、也不得分**(没人冲线就没有"最后一名冲线"可言;这一条与「平均值口径」选哪个无关);
|
||||
- 不是「按队内平均」的通道这一列显示 `—`(对它没有意义);
|
||||
6. 每条通道有一个「计入单位总分」开关。**新建通道时的默认值按小项类型给**:**普通个人项目 / 团体项目默认勾选**;**「个人 + 团体」项目的个人通道默认不勾**(这类项目的个人分本来就只记个人榜、不并入单位总分)。关掉后该通道的分只记在个人头上,不进单位总分——「个人+团体」项目的个人通道就靠它做到只记个人;**所以现场要留意自己那一行的实际勾选状态**;
|
||||
7. 每条通道还有一个**「奖牌通道」**列,里面是两个开关(都默认打开):
|
||||
|
||||
+40
-1
@@ -6,10 +6,49 @@
|
||||
|
||||
---
|
||||
|
||||
## dev_0.5.9(当前版本)
|
||||
## dev_0.5.11
|
||||
|
||||
### 一句话
|
||||
|
||||
**这不是一次「数据迁移级」的变更:没有任何一行已有数据被改动,升级后不用重配、不用重新导入规则包**,已有的名次、积分、榜单与导出件**一个字不变**。只是给「按队内平均」的通道**多了一个可选的汇总口径**——**「队内最低分」**(队内最差的那一份成绩):越野 / 行军这类**取团队最后一人的成绩(最后一名冲线)**的项目现在能直接配出来,不用手工录名次。
|
||||
|
||||
(库里有一条结构迁移 `0027`,它只做一件事:把「聚合口径」这一列允许的取值从 `avg / sum` 放开到 `avg / sum / worst`。老通道的值原样保留、默认仍是「平均」,所以升级后榜单与你上次看到的一模一样。)
|
||||
|
||||
### ① 新能力:团体成绩可以按「队内最低分」算
|
||||
|
||||
- **在哪配**:「基础数据 → 计分通道」,名次来源是「按队内平均」的那一行,**「聚合口径」列**现在有**三个按钮**:**平均 / 总和 / 队内最低分**。点「队内最低分」→ 点「保存全部」生效,该小项立即重算;
|
||||
- **它算的是什么**:团体成绩 = 该单位成员里**最差的那一份**成绩。⚠️ **取的是谁,由小项的「比较方式」决定**,这是最容易搞反的地方:
|
||||
|
||||
| 小项的「比较方式」 | 项目例子 | 「队内最低分」取的是 |
|
||||
| --- | --- | --- |
|
||||
| **升序**(越小越好) | 越野 / 行军(时间) | **跑得最慢的那个人**——也就是**最后一名冲线**的那个人的成绩 |
|
||||
| **降序**(越大越好) | 引体向上(次数) | **做得最少的那个** |
|
||||
|
||||
- 所以它**不是**「取最小的那一格」:时间项目里"最小"是最快的人,正好反了。界面上按钮旁边鼠标停一下就有这句提示;
|
||||
- **弃赛 / 失格的人不算**:队伍成绩只在实际出赛的人里取最差的那一份(弃赛那一格填得再差也不代表队伍);
|
||||
- **一个有效成绩都没有的单位不参与团体排名、也不得分**(没人冲线就没有"最后一名冲线"可言)。这一条**与「平均值口径」选哪个无关**——「队内最低分」没有"分母"这回事;
|
||||
- 它和已有的两个口径**互不影响**:不改口径的通道仍然按「平均」(默认)跑,**升级后榜单不会变**。
|
||||
|
||||
### ② 规则包里也能写
|
||||
|
||||
规则包的通道上写 `aggregateMode: worst` 即可(不写 = `avg`,与以前一致)。**不认识的值会被拦下报错**,不会静默按「平均」处理——特别地,**`min` / `max` 不是合法取值**:方向由小项的「比较方式」决定,写死成一个方向的项目一半会算反,所以要写 `worst`。
|
||||
|
||||
### ③ 升级后 5 分钟核对
|
||||
|
||||
1. 打开「基础数据 → 计分通道」,选中一个名次来源是「按队内平均」的小项:**「聚合口径」那一列应该是三个按钮,默认停在「平均」**;
|
||||
2. 随便找一个小项,什么都不改点一次「保存全部」,然后看单位总分榜 / 名次表:**与升级前一致**(老口径一字不变);
|
||||
3. 要试新口径的话:挑一个**时间类(比较方式 = 升序)**的小项,把它的团体通道切成「队内最低分」并保存,榜单上该单位的团体成绩应等于**该单位里最慢那个人的成绩**(不是平均、也不是最快那个);改用「平均」再保存一次,应立刻退回原来的名次;
|
||||
4. 其余照旧:**不需要重新导入规则包,也不需要重配任何东西**。
|
||||
|
||||
### ④ 详细说明在哪
|
||||
|
||||
《使用手册》10.4 计分通道 → 第 5 条(聚合口径三个按钮与两种方向的对照)。
|
||||
|
||||
---
|
||||
|
||||
## dev_0.5.9
|
||||
|
||||
### 一句话
|
||||
**没有数据迁移,升级后不用重配任何东西**:只改**提示与模板上的说明**。以前在团体项目的报名表里把队员姓名填进「姓名」列,导入时只回一句「填了队伍 / 队员名单,但这一行有运动员姓名,按个人报名处理」——**没告诉你该怎么改**,于是现场反复换填法重试。现在这两条提示会**直接写出两步动作**(要报团体就把姓名列留空、号码布填到「队员名单」列;确实是个人报名就把那一格删掉),下载的模板里**「姓名」与「队员名单」两列表头也各挂了一条说明**(鼠标停上去或点中该列的格子就能看到)。**导入表、模板结构、已有数据一个字都没动。**
|
||||
|
||||
### ① 两条导入提示改成「能照着改」的话
|
||||
|
||||
Reference in new issue
Block a user