安全加固 + 数据管线修复 + 质量改进(第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:
@@ -177,6 +177,29 @@ def normalize_bzzoiro(raw: dict, league_type: str) -> NormalizedMatch | None:
|
||||
m.match_stage = _rn_name
|
||||
elif _rn:
|
||||
m.match_stage = f"第 {_rn} 轮"
|
||||
|
||||
# 统计字段映射(API 字段名 → NormalizedMatch)
|
||||
# API 可能提供的字段:home_shots/away_shots, shots_on_target, corners, possession, xg, cards
|
||||
# API 没有的字段保持 None,不伪造
|
||||
m.home_shots = _to_int(raw.get("home_shots", raw.get("shots_home")))
|
||||
m.away_shots = _to_int(raw.get("away_shots", raw.get("shots_away")))
|
||||
m.home_shots_on_target = _to_int(raw.get("home_shots_on_target", raw.get("sot_home")))
|
||||
m.away_shots_on_target = _to_int(raw.get("away_shots_on_target", raw.get("sot_away")))
|
||||
m.home_corners = _to_int(raw.get("home_corners", raw.get("corners_home")))
|
||||
m.away_corners = _to_int(raw.get("away_corners", raw.get("corners_away")))
|
||||
# 控球率:API 通常只给 home 值,away = 100 - home
|
||||
possession_home = _to_float(raw.get("home_possession", raw.get("possession")))
|
||||
if possession_home is not None:
|
||||
m.home_possession = possession_home
|
||||
# xG
|
||||
m.home_xg = _to_float(raw.get("home_xg", raw.get("xg_home", raw.get("expected_goals_home"))))
|
||||
m.away_xg = _to_float(raw.get("away_xg", raw.get("xg_away", raw.get("expected_goals_away"))))
|
||||
# 牌
|
||||
m.home_yellow_cards = _to_int(raw.get("home_yellow_cards", raw.get("yellow_cards_home")))
|
||||
m.away_yellow_cards = _to_int(raw.get("away_yellow_cards", raw.get("yellow_cards_away")))
|
||||
m.home_red_cards = _to_int(raw.get("home_red_cards", raw.get("red_cards_home")))
|
||||
m.away_red_cards = _to_int(raw.get("away_red_cards", raw.get("red_cards_away")))
|
||||
|
||||
if m.match_status == "finished" and m.home_goals is None:
|
||||
m.match_status = "scheduled"
|
||||
return m
|
||||
|
||||
Reference in New Issue
Block a user