feat: 球队实体一致性 — 归一化咽喉 + team_aliases 别名机制
events/standings 创建 Team 前均经 team_names.normalize(已有,确认), TeamRepository.get_or_create 收敛为归一化唯一咽喉 + info 日志。 新增 team_aliases 表(NFKD 归一别名 → teams.id FK CASCADE), 定位三步链:normalize(name) → teams.name → team_aliases → insert。 不自动合并历史重复队;提供 POST /api/v1/admin/teams/aliases 显式添加。 迁移 0020_team_aliases + Admin 别名管理端点(admin_teams.py)。 全量测试 270 通过。
This commit is contained in:
+45
-10
@@ -114,24 +114,59 @@ class TeamRepository:
|
||||
async def get_or_create(self, name: str, *, name_zh: str | None = None) -> Team:
|
||||
"""按名获取球队,不存在则创建(name_zh 供 bzzoiro 管线写中文名)。
|
||||
|
||||
归一化咽喉:所有入库 Team.name 必须经过 team_names.normalize,
|
||||
此处统一收敛,避免各调用点散落归一化逻辑导致重复 Team。
|
||||
归一化咽喉 + 别名查找,三步定位:
|
||||
1) normalize(name) → 查 teams.name
|
||||
2) 查 team_aliases(以 normalize(name) 为幂等键)→ 复用已映射的 teams.id
|
||||
3) 都没有 → insert 新 Team(归一名)
|
||||
创建新 Team 时 info 打出原始名与归一后的规范名,便于排查重名。
|
||||
不自动合并历史重复队;需显式添加别名。
|
||||
"""
|
||||
from src.data.team_names import normalize as normalize_name
|
||||
from src.db.models import TeamAlias
|
||||
|
||||
normalized = normalize_name(name) or name.strip()
|
||||
|
||||
# 1) 归一名直查 teams
|
||||
team = await self.get_by_name(normalized)
|
||||
if team is None:
|
||||
logger.info(
|
||||
"创建新 Team: %s -> %s",
|
||||
name, normalized,
|
||||
)
|
||||
team = Team(name=normalized, name_zh=name_zh)
|
||||
self._session.add(team)
|
||||
await self._session.flush()
|
||||
if team is not None:
|
||||
return team
|
||||
|
||||
# 2) 别名查找:normalize(别名) 作为幂等键,命中即复用已有 Team
|
||||
alias = await self._session.get(TeamAlias, normalized)
|
||||
if alias is not None:
|
||||
team = await self._session.get(Team, alias.team_id)
|
||||
if team is not None:
|
||||
logger.info("Team 别名命中: %s -> %s(已有 id=%s)", name, normalized, team.id)
|
||||
return team
|
||||
|
||||
# 3) 新建 Team(归一名)
|
||||
logger.info("创建新 Team: %s -> %s", name, normalized)
|
||||
team = Team(name=normalized, name_zh=name_zh)
|
||||
self._session.add(team)
|
||||
await self._session.flush()
|
||||
return team
|
||||
|
||||
async def add_alias(self, alias: str, team_id: int) -> TeamAlias:
|
||||
"""为已有 Team 添加别名。
|
||||
|
||||
幂等:以 normalize(alias) 为 PK,重复添加同一别名会 upsert。
|
||||
不自动合并历史重复队,仅建立别名映射。
|
||||
"""
|
||||
from src.data.team_names import normalize as normalize_name
|
||||
from src.db.models import TeamAlias
|
||||
|
||||
normalized = normalize_name(alias) or alias.strip()
|
||||
existing = await self._session.get(TeamAlias, normalized)
|
||||
if existing is not None:
|
||||
existing.team_id = team_id # 允许重新指向
|
||||
existing.original_alias = alias
|
||||
await self._session.flush()
|
||||
return existing
|
||||
row = TeamAlias(alias_normalized=normalized, team_id=team_id, original_alias=alias)
|
||||
self._session.add(row)
|
||||
await self._session.flush()
|
||||
return row
|
||||
|
||||
async def get_all_by_names(self, names: list[str]) -> dict[str, Team]:
|
||||
"""批量获取球队,返回 name → Team 映射。"""
|
||||
if not names:
|
||||
|
||||
Reference in New Issue
Block a user