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:
shangfangjian
2026-09-21 23:28:46 +08:00
parent 317a5e338a
commit 66f0844798
5 changed files with 685 additions and 5 deletions
+5 -2
View File
@@ -125,7 +125,7 @@ class _FakeDb:
async def test_r2_standings_actually_upserts(monkeypatch):
"""行为测试: 喂一份积分榜 payload,断言真的构造了 Standing 且计数 > 0。"""
import src.data.bzzoiro as bz
from src.db.models import League, Standing, Team
from src.db.models import DataLineage, League, RawEvent, Standing, Team
payload = {
"season": {"start_date": "2025-08-01", "end_date": "2026-05-31"},
@@ -166,7 +166,10 @@ async def test_r2_standings_actually_upserts(monkeypatch):
standings = [o for o in db.added if isinstance(o, Standing)]
assert len(standings) == 2, "应真的构造 Standing 行"
assert all(isinstance(o, (Standing, Team)) for o in db.added)
# standings 采集接线 Bronze 后(RawEvent + DataLineage),add 的对象类型白名单随之放宽
assert all(
isinstance(o, (Standing, Team, RawEvent, DataLineage)) for o in db.added
)
first = standings[0]
assert first.league_id == 42