Article
2026-08-11-襷の系譜-选手关系标签化与算分方案
襷の系譜 选手关系标签化与算分方案
日期:2026-08-11
1. 目标
本文档用于沉淀 选手关系 下一步迭代中的三件事:
- 关系标签体系
- 关系缓存 payload / schema
- 第一版算分方案
本次重点不是直接改前端表现,而是先把“标签语义”和“关系信号结构”定下来。
2. 本次结论
2.1 信息类标签
信息类标签先定为:
同出身地中学同队 · X年同中学经历高中同队 · X年同高中经历大学同队 · X年同大学经历同实业团经历
说明:
同队 · X年只用于中学 / 高中 / 大学同校经历表示双方都曾在同一组织,不要求时间重叠同实业团经历第一版只保留经历类标签,不计算重叠年数
2.2 比赛类标签
比赛类标签先定为:
多次对决长期交手跨阶段交手
说明:
- 标签对用户展示时保持稳定语义
- 标签背后保留数值字段,用于排序和后续算分
- 不把功能状态做成标签,例如“可查看详细对战”
3. 信息类标签的模型边界
3.1 为什么 teamOverlapYears 值得加入
当前 Membership 已经具备较稳定的时间边界:
junior_high_school、high_school、university三类组织的起止日期完整度较高- 学籍边界天然适合按年表达
PlayerRelationCache本身是派生 JSON 缓存,扩字段成本低
因此:
中学同队 · X年高中同队 · X年大学同队 · X年
都适合直接建立在现有 Membership 事实之上。
3.2 为什么实业团不做 X年
当前 corporate_team 的时间窗口数据明显不如学籍阶段稳定:
- 入队和离队时间缺失较多
- 移籍和过渡期更复杂
- 强行输出
X年容易制造伪精确
因此第一版约束为:
同实业团经历可以展示- 不计算
实业团同队 · X年 - 不把实业团纳入
teamOverlapYears
4. 比赛类标签的制定依据
比赛类标签需要避免两个问题:
- 某个标签覆盖过大,几乎人人都有
- 某个标签覆盖过小,几乎没有展示价值
因此本次先基于线上 PlayerRelationCache 全量关系边做阈值观察。
样本口径:
- 全量关系边:
90,486 - 每条关系边来自
PlayerRelationCache.payload.topRelations matchupYearCount通过双方共同出现的Race去重年份统计得到
4.1 观察结果
关键分布如下:
matchupCount >= 3:11,667matchupCount >= 4:6,132matchupCount >= 5:3,221matchupYearCount >= 2:20,893matchupYearCount >= 3:6,791matchupYearCount >= 4:2,163stageCount >= 2:4,435stageCount >= 3:83
4.2 第一版阈值选择
为了让三个比赛类标签覆盖率相对接近,第一版建议:
多次对决:matchupCount >= 4长期交手:matchupYearCount >= 3跨阶段交手:stageCount >= 2
对应覆盖率大致为:
多次对决:约6.8%长期交手:约7.5%跨阶段交手:约4.9%
这组阈值的好处是:
- 不会像
>= 2年那样过宽 - 也不会像
stageCount >= 3那样过窄 - 三个标签既有重叠,也不会完全变成同一个标签
5. Schema
本次不新建正式业务表,继续沿用 PlayerRelationCache.payload 作为派生关系结构。
建议下一版 relation payload 至少扩展为:
type RelationStageKey =
| "junior_high_school"
| "high_school"
| "university"
| "corporate_team";
type PlayerRelationContextV2 = {
sameHometown: boolean;
sharedOrigins: {
juniorHighSchool: boolean;
highSchool: boolean;
university: boolean;
corporateTeam: boolean;
};
teamOverlapYears: {
juniorHighSchool?: number;
highSchool?: number;
university?: number;
};
};
type PlayerRelationMatchupSignals = {
matchupCount: number;
matchupYearCount: number;
stageCount: number;
ekidenCount: number;
latestMatchAt: string | null;
};
type PlayerRelationLabels = {
info: Array<
| "same_hometown"
| "same_junior_high_team"
| "same_junior_high_origin"
| "same_high_school_team"
| "same_high_school_origin"
| "same_university_team"
| "same_university_origin"
| "same_corporate_team_origin"
>;
matchup: Array<
| "frequent_matchup"
| "long_term_matchup"
| "cross_stage_matchup"
>;
};
type PlayerRelationEntryV2 = {
relatedPersonId: string;
context: PlayerRelationContextV2;
matchupSignals: PlayerRelationMatchupSignals;
labels: PlayerRelationLabels;
hasHeadToHeadDetail: boolean;
latestCompetitionEditionId: string | null;
latestCompetitionName: string | null;
score?: number;
scoreBreakdown?: Record<string, number>;
};
type PlayerRelationCachePayloadV2 = {
personId: string;
generatedAt: string;
topRelations: PlayerRelationEntryV2[];
};
6. 标签生成口径
6.1 信息类标签
same_hometown- 双方
hometown相同
- 双方
same_junior_high_team- 同一中学且时间窗口重叠
same_junior_high_origin- 同一中学经历,但不要求重叠
same_high_school_team- 同一高中且时间窗口重叠
same_high_school_origin- 同一高中经历,但不要求重叠
same_university_team- 同一大学且时间窗口重叠
same_university_origin- 同一大学经历,但不要求重叠
same_corporate_team_origin- 同一实业团经历,不要求重叠
6.2 比赛类标签
frequent_matchupmatchupCount >= 4
long_term_matchupmatchupYearCount >= 3
cross_stage_matchupstageCount >= 2
7. 前端展示文案
信息类:
同出身地中学同队 · 1年中学同队 · 3年同中学经历高中同队 · 3年同高中经历大学同队 · 4年同大学经历同实业团经历
比赛类:
多次对决长期交手跨阶段交手
7.1 关系网络页面命名
关系的独立页面统一命名为 关系网络。
多语言文案建议如下:
ja:関係ネットワークzh:关系网络zh-Hant:關係網絡en:Relationship Networkko:관계 네트워크
8. 算分 V1
本次先固定一个 可解释、可调参、偏保守 的第一版参数。
原则:
- 标签负责语义
- 数值字段负责强弱
同队/同校背景与比赛交手强度都参与排序- 第一版先采用人工可理解的启发式方案,不引入动态稀缺性权重
第一版总公式:
finalScore = infoScore + matchupScore + recencyBonus
8.1 信息类分
sameHometown = +3
sameJuniorHighOrigin = +5
sameHighSchoolOrigin = +6
sameUniversityOrigin = +5
sameCorporateTeamOrigin = +4
sameJuniorHighTeam = 8 + 2 * juniorHighOverlapYears
sameHighSchoolTeam = 10 + 2 * highSchoolOverlapYears
sameUniversityTeam = 9 + 1.5 * universityOverlapYears
口径说明:
同队明显高于同校经历同高中 / 同大学同队是最强的信息类信号同出身地只做轻辅助overlapYears进入信息类分,但不让年数无限放大
例子:
大学同队 · 4年=9 + 1.5 * 4 = 15高中同队 · 3年=10 + 2 * 3 = 16同大学经历=5
8.2 比赛类分
matchupScore =
6 * ln(1 + matchupCount)
+ 5 * ln(1 + matchupYearCount)
+ 4 * max(stageCount - 1, 0)
口径说明:
matchupCount表示对决强度matchupYearCount表示持续性stageCount表示关系跨度- 对
matchupCount和matchupYearCount使用对数压缩,避免10次对决对3次对决形成过度碾压
8.3 最近交手修正
latestMatchAt within 2 years => +2
latestMatchAt within 4 years => +1
older or null => +0
口径说明:
recencyBonus只做轻修正- 不能让“最近交手”压过“长期强关系”
latestMatchAt缺失时不扣分,也不加分
8.4 为什么这一版参数这样定
本次参数是结合当前线上关系网分布定下的保守版本。
已确认的现状包括:
- 全量关系边中,大多数只是普通同场出现
matchupCount = 1的关系非常多sharedTeamStages、sharedUniversity、sharedHighSchool都属于相对稀缺但语义较强的信号latestMatchAt为空的关系也不少,因此最近交手不能给太高权重
因此第一版采取:
- 信息类信号使用固定分层权重
- 比赛类信号使用对数压缩
- 时间只做轻量修正
这一版希望满足的排序直觉是:
大学同队 4年 + 也经常交手应明显靠前只是同大学经历不应压过真实的长期 rival10次对决应高于3次对决跨阶段交手应有稳定辨识度同出身地不能单独把关系顶上去
9. 页面与开放规则
9.1 页面结构
选手关系第一版拆成两层:
选手主页关系板块关系网络独立页面
两层共用同一套关系结果、同一套展示阈值、同一套排序逻辑。
区别仅在于:
- 选手主页关系板块只显示
Top 6 关系网络页面显示全部达标关系并分页
9.2 明星选手门槛
是否开放 关系网络 页面,按选手自身比赛条数判断。
第一版门槛定为:
raceResults >= 8
线上数量参考:
raceResults >= 5:1780raceResults >= 8:1060raceResults >= 10:838raceResults >= 15:484
采用 >= 8 的原因:
- 比
>= 5更收敛,不会把完整关系页开放得过宽 - 比
>= 10更保守地保留中量级选手 - 第一版约
1060人可开放,规模较合适
9.3 非明星选手的关系板块
是否展示选手主页中的关系板块,与是否是明星选手分开判断。
规则:
- 非明星选手如果存在达标关系,主页仍显示关系板块
- 明星选手如果达标关系数为
0,主页不显示关系板块 - 明星选手门槛只决定是否开放
关系网络页面,不决定主页关系板块是否出现
9.4 关系展示阈值
关系是否进入展示集合,按 pair 级 rawScore 判断。
第一版展示阈值定为:
rawScore >= 15
观察结论:
rawScore >= 10过宽,很多中低比赛量选手会留下过多弱关系rawScore >= 20过窄,很多本应有关系感知的选手会掉成0-2条rawScore >= 15更适合作为第一版展示分界线
因此:
- 主页关系板块从
rawScore >= 15的集合中取前6 关系网络页面显示全部rawScore >= 15的关系
9.5 排序规则
第一版只保留一种排序:
关系分降序
内部排序键固定为:
rawScore DESCmatchupCount DESClatestMatchAt DESCrelatedPersonId ASC
9.6 分数展示规则
对外展示两套分值:
rawScore- 内部使用
- 用于阈值判断和排序
displayScore- 前台使用
- 映射到
0-100 - 保留
1位小数
规则:
- 主页关系板块不显示分数
关系网络页面右侧显示displayScore- 展示阈值仍以
rawScore >= 15判定
9.7 标签展示规则
主页关系板块:
- 每条关系最多显示
3个标签 - 不显示分数
关系网络 页面:
- 标签全展示
- 不额外写解释句
- 标签顺序按对当前关系贡献最大的标签优先
- 右侧显示
displayScore
10. 缓存与数据来源策略
10.1 主页关系板块
主页关系板块继续使用 PlayerRelationCache。
原因:
- 主页访问频率更高
- 主页只需要稳定读取
Top 6 - 关系板块不应引入额外在线计算抖动
10.2 关系网络页面
第一版采用与主页一致的数据源策略:
PlayerRelationCache不再只缓存Top 12PlayerRelationCache需要扩展为缓存完整达标关系集合关系网络页面与主页共用同一份缓存结果
不采用:
- 主页读缓存、独立页面在线重算完整结果
- SQL 先裸排序、页面层再补标签
原因:
- 标签、signals、score 应在进入页面前就成为完整关系对象
- 主页与独立页面应共用统一口径
- 后续分页、筛选、解释和调试都更稳定
10.3 分页
关系网络 页面第一版使用分页展示全部达标关系。
建议:
- 每页
24条 - 先使用普通分页即可
- 先不额外设置很小的总量上限
11. 当前建议的实现顺序
- 扩展
RelationStageKey,纳入junior_high_school - 扩展
PlayerRelationCache.payload结构 - 把
PlayerRelationCache从Top 12扩展为完整达标关系集合缓存 - 落地信息类标签和比赛类标签生成
- 补
rawScore、displayScore与scoreBreakdown - 新增
关系网络页面与分页 - 调整选手页关系板块展示规则与入口文案
12. 结论
本次先定下的不是最终分数,而是:
- 哪些标签要存在
- 哪些数值信号要保留
- 比赛类标签阈值为什么这样定
- 缓存 payload 下一版该长什么样
关系网络页面如何开放与展示- 主页和独立页面如何共用同一套关系对象
这能保证后续即使继续调算分规则,也不会反复推翻标签语义层。