安全加固 + 数据管线修复 + 质量改进(第2批)
安全加固: - /api/v1/predict 限流改用 get_client_ip 防 X-Forwarded-For 伪造 - 新增 TRUST_PROXY_HEADERS/REQUIRE_ADMIN_AUTH 配置,默认 fail-closed - 生产环境管理接口未配置鉴权时拒绝(503),不再放行 数据管线修复: - injuries IntegrityError 改用 begin_nested(SAVEPOINT)隔离批次 - injuries 空名单 vs 未配置语义区分(has_data 精确标记) - bzzoiro 统计字段映射(shots/possession/cards) + 入库条件放宽 - available_at 语义收紧(回测防泄漏) - team_names NFKD 去变音双重查找 + 补齐变体键 预测系统改进: - multi-agent 全专家失败时 status=degraded 跳过终裁 - agent_weights 独立持久化到 predictions 表 新增迁移: - 0014_predictions_agent_weights.py 新增测试(8个文件): - test_ip_spoofing.py: 限流防伪造 - test_require_admin_fail_closed.py: 生产 fail-closed - test_injuries_integrity_rollback.py: SAVEPOINT 隔离 - test_injuries_slice_semantic.py: 空名单 vs 未配置 - test_available_at.py: 回测防泄漏 - test_bzzoirot_stats.py: 统计字段映射 - test_team_names_normalize.py: NFKD 变体 - test_multi_agent_degraded.py: 全失败 degraded - test_agent_weights_persist.py: 权重持久化
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
"""回归测试: team_names.normalize NFKD 去变音导致同一俱乐部映射成两个队名。
|
||||
|
||||
验证:
|
||||
1. München / Munich / Munchen → 同一规范名
|
||||
2. Atlético / Atletico → 同一规范名
|
||||
3. Köln / Koln → 同一规范名
|
||||
4. Mönchengladbach / Monchengladbach / M'gladbach → 同一规范名
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from src.data.team_names import normalize
|
||||
|
||||
|
||||
class TestNormalizeVariants:
|
||||
"""同一俱乐部的不同变音写法应映射到同一规范名。"""
|
||||
|
||||
def test_bayern_munich_variants(self):
|
||||
"""Bayern München / Munich / Munchen → 同一规范名。"""
|
||||
canonical = normalize("Bayern München")
|
||||
assert canonical == "Bayern München"
|
||||
assert normalize("Bayern Munich") == canonical
|
||||
assert normalize("Bayern Munchen") == canonical
|
||||
|
||||
def test_atletico_madrid_variants(self):
|
||||
"""Atlético Madrid / Atletico Madrid → 同一规范名。"""
|
||||
canonical = normalize("Atlético Madrid")
|
||||
assert canonical == "Atlético Madrid"
|
||||
assert normalize("Atletico Madrid") == canonical
|
||||
|
||||
def test_koln_variants(self):
|
||||
"""FC Köln / FC Koln → 同一规范名。"""
|
||||
canonical = normalize("FC Köln")
|
||||
assert canonical == "FC Köln"
|
||||
assert normalize("FC Koln") == canonical
|
||||
|
||||
def test_monchengladbach_variants(self):
|
||||
"""Mönchengladbach / Monchengladbach / M'gladbach → 同一规范名。"""
|
||||
canonical = normalize("Borussia Mönchengladbach")
|
||||
assert canonical == "Borussia Mönchengladbach"
|
||||
assert normalize("Borussia Monchengladbach") == canonical
|
||||
assert normalize("Borussia M'gladbach") == canonical
|
||||
|
||||
def test_leganes_variants(self):
|
||||
"""Leganés / Leganes → 同一规范名。"""
|
||||
canonical = normalize("Leganés")
|
||||
assert canonical == "Leganés"
|
||||
assert normalize("Leganes") == canonical
|
||||
|
||||
def test_alaves_variants(self):
|
||||
"""Deportivo Alavés / Alaves → 同一规范名。"""
|
||||
canonical = normalize("Deportivo Alavés")
|
||||
assert canonical == "Deportivo Alavés"
|
||||
assert normalize("Deportivo Alaves") == canonical
|
||||
|
||||
def test_saint_etienne_variants(self):
|
||||
"""AS Saint-Étienne / Saint-Etienne → 同一规范名。"""
|
||||
canonical = normalize("AS Saint-Étienne")
|
||||
assert canonical == "AS Saint-Étienne"
|
||||
assert normalize("Saint-Etienne") == canonical
|
||||
|
||||
def test_empty_and_none(self):
|
||||
"""空字符串应返回空。"""
|
||||
assert normalize("") == ""
|
||||
assert normalize(None) == ""
|
||||
|
||||
def test_original_name_takes_priority(self):
|
||||
"""原始名(带变音)应优先于 NFKD 去变音名。"""
|
||||
# "Bayern München" 在 map 中有键,应直接命中
|
||||
# 而不是先 NFKD 成 "Bayern Munchen" 再查
|
||||
result = normalize("Bayern München")
|
||||
assert result == "Bayern München"
|
||||
Reference in New Issue
Block a user