feat: standings Bronze 血缘补齐 + 队名归一 MVP
standings 成功 upsert 后写入 RawEvent(source_record_id=standings:{league}:{season})
+ DataLineage(target_table=standings),与 events/stats 管线对称。
队名归一化收敛到 TeamRepository.get_or_create 唯一咽喉点,
创建新 Team 时 info 日志打出原始名与归一后名;
Admin 新增 GET /api/v1/admin/team-name-duplicates 只读接口,
启发式列出近似重名候选(大小写变体/子串/前缀碰撞),不做自动合并。
This commit is contained in:
+19
-3
@@ -5,6 +5,10 @@ Repository 只负责查询,不负责事务提交。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from datetime import datetime
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
@@ -108,10 +112,22 @@ class TeamRepository:
|
||||
return (await self._session.execute(stmt)).scalar_one_or_none()
|
||||
|
||||
async def get_or_create(self, name: str, *, name_zh: str | None = None) -> Team:
|
||||
"""按名获取球队,不存在则创建(name_zh 供 bzzoiro 管线写中文名)。"""
|
||||
team = await self.get_by_name(name)
|
||||
"""按名获取球队,不存在则创建(name_zh 供 bzzoiro 管线写中文名)。
|
||||
|
||||
归一化咽喉:所有入库 Team.name 必须经过 team_names.normalize,
|
||||
此处统一收敛,避免各调用点散落归一化逻辑导致重复 Team。
|
||||
创建新 Team 时 info 打出原始名与归一后的规范名,便于排查重名。
|
||||
"""
|
||||
from src.data.team_names import normalize as normalize_name
|
||||
|
||||
normalized = normalize_name(name) or name.strip()
|
||||
team = await self.get_by_name(normalized)
|
||||
if team is None:
|
||||
team = Team(name=name, name_zh=name_zh)
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user