refactor: fix P0/P1/P2 security and performance issues

P0 fixes:
- CORS: replace wildcard methods/headers with configurable lists
- deps.py: remove unsafe global _warned_unset variable

P1 fixes:
- http_client: read default timeout from Settings
- bzzoiro: replace sync urllib with async httpx
- bzzoiro: normalize validation failures use warning level only
- db pool: read pool config from Settings (default 5+10)
- backtest: add asyncio.Semaphore(8) for concurrent execution
- predict/context_builder: add backtest parameter for cutoff buffer

P2 improvements:
- injuries: enforce int conversion for player_id/fixture_id
- injuries: use system temp dir for cache
- utils.py: extract shared actual_1x2/is_correct_1x2
- validation: downgrade 1x2 mismatch log to debug
- docker-compose: use env vars for all credentials
- .env.example: add POSTGRES_USER/PASSWORD/PORT, API_PORT
This commit is contained in:
shangfangjian
2026-09-16 02:38:25 +08:00
parent fa69795d69
commit 983b620659
14 changed files with 154 additions and 109 deletions
+13 -6
View File
@@ -291,32 +291,39 @@ async def injuries_slice(header: MatchHeader, *, before=None) -> SliceResult:
# 单 agent 路径: 拼接全部切片(行为与旧版一致)
# ============================================================
async def build_context(match_id: int, *, form_last: int = 5, h2h_last: int = 5) -> MatchContext:
async def build_context(match_id: int, *, form_last: int = 5, h2h_last: int = 5, backtest: bool = False) -> MatchContext:
"""单 agent 路径的完整上下文: 拼接全部切片(before=比赛时间,防未来信息)。
has_stats / has_injuries 直接取切片显式声明的 has_data,
不再靠文案子串匹配(见审查报告 P2-1)。
P2-6: backtest=True 时 cutoff = match_date - 1天,确保只用赛前数据。
"""
header = await load_match_header(match_id)
# P2-6: 回测模式下 cutoff 提前 1 天,防止比赛日数据泄漏
cutoff = header.match_dt
if backtest and header.match_dt:
from datetime import timedelta
cutoff = header.match_dt - timedelta(days=1)
parts = [header_text(header), ""]
form_res = await form_slice(header, limit=form_last, before=header.match_dt)
form_res = await form_slice(header, limit=form_last, before=cutoff)
parts.append(form_res.text)
parts.append("")
h2h_res = await h2h_slice(header, limit=h2h_last, before=header.match_dt)
h2h_res = await h2h_slice(header, limit=h2h_last, before=cutoff)
parts.append(h2h_res.text)
parts.append("")
stats_res = await stats_slice(header, before=header.match_dt)
stats_res = await stats_slice(header, before=cutoff)
parts.append(stats_res.text)
parts.append("")
home_away_res = await home_away_slice(header, before=header.match_dt)
home_away_res = await home_away_slice(header, before=cutoff)
parts.append(home_away_res.text)
parts.append("")
injuries_res = await injuries_slice(header, before=header.match_dt)
injuries_res = await injuries_slice(header, before=cutoff)
parts.append(injuries_res.text)
return MatchContext(