chore: 死代码与重复逻辑清理
删除未引用/未调用符号: - PredictionRepository(无引用) - is_correct_1x2(无调用者) - LeagueOut(路由用 list[dict]) - IngestResponse(IngestBzzoiroResponse 已替代) - SecurityCheckError(从未 raise,assert_security_on_startup 用 sys.exit) - short_write(仅自引用,全仓库无外部调用) - fetchIngestJobs(列表函数无页面使用,单数 fetchIngestJob 仍保留) - clear_prompt_cache(无入口) 去重: - eval._actual_1x2 改为委托 utils.actual_1x2(单一权威源) 全量测试 270 通过,业务行为不变。
This commit is contained in:
@@ -370,13 +370,6 @@ export function fetchIngestJob(jobId: string): Promise<IngestJob> {
|
|||||||
return api.get<IngestJob>(`${API_BASE}/admin/ingest/jobs/${jobId}`)
|
return api.get<IngestJob>(`${API_BASE}/admin/ingest/jobs/${jobId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 最近采集任务列表(最新在前)
|
|
||||||
*/
|
|
||||||
export function fetchIngestJobs(limit = 20): Promise<IngestJob[]> {
|
|
||||||
return api.get<IngestJob[]>(`${API_BASE}/admin/ingest/jobs?limit=${limit}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 比赛详情(含最近预测摘要)
|
* 比赛详情(含最近预测摘要)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,13 +7,6 @@ from typing import Any
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
class LeagueOut(BaseModel):
|
|
||||||
id: int
|
|
||||||
code: str
|
|
||||||
name: str
|
|
||||||
country: str | None
|
|
||||||
|
|
||||||
|
|
||||||
class MatchOut(BaseModel):
|
class MatchOut(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
league_code: str | None
|
league_code: str | None
|
||||||
@@ -130,13 +123,6 @@ class TeamAliasOut(BaseModel):
|
|||||||
original_alias: str
|
original_alias: str
|
||||||
|
|
||||||
|
|
||||||
class IngestResponse(BaseModel):
|
|
||||||
leagues: dict
|
|
||||||
total_inserted: int
|
|
||||||
total_updated: int
|
|
||||||
errors: list[str] = []
|
|
||||||
|
|
||||||
|
|
||||||
class IngestBzzoiroResponse(BaseModel):
|
class IngestBzzoiroResponse(BaseModel):
|
||||||
"""POST /api/v1/ingest/bzzoiro 响应:兼容原 message 字段,新增 job_id 供轮询。"""
|
"""POST /api/v1/ingest/bzzoiro 响应:兼容原 message 字段,新增 job_id 供轮询。"""
|
||||||
|
|
||||||
|
|||||||
@@ -30,10 +30,6 @@ _MIN_SECRET_KEY_LEN = 16
|
|||||||
_WEAK_DB_PATTERNS = ("football:football@", "admin:admin@", "password@", "123456@")
|
_WEAK_DB_PATTERNS = ("football:football@", "admin:admin@", "password@", "123456@")
|
||||||
|
|
||||||
|
|
||||||
class SecurityCheckError(Exception):
|
|
||||||
"""生产环境安全校验失败。"""
|
|
||||||
|
|
||||||
|
|
||||||
async def _auth_configured() -> bool:
|
async def _auth_configured() -> bool:
|
||||||
"""运行时鉴权是否已配置(含数据库密码哈希/.env 明文/API Key)。"""
|
"""运行时鉴权是否已配置(含数据库密码哈希/.env 明文/API Key)。"""
|
||||||
if await get_admin_password_hash():
|
if await get_admin_password_hash():
|
||||||
|
|||||||
@@ -68,25 +68,6 @@ async def short_read():
|
|||||||
yield session
|
yield session
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def short_write():
|
|
||||||
"""短生命周期 write session: 提交后立即释放。
|
|
||||||
|
|
||||||
用法:
|
|
||||||
async with short_write() as session:
|
|
||||||
session.add(pred)
|
|
||||||
await session.commit()
|
|
||||||
# session 已关闭,连接已释放
|
|
||||||
"""
|
|
||||||
async with AsyncSessionLocal() as session:
|
|
||||||
try:
|
|
||||||
yield session
|
|
||||||
await session.commit()
|
|
||||||
except Exception:
|
|
||||||
await session.rollback()
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
async def init_db() -> None:
|
async def init_db() -> None:
|
||||||
"""验证数据库连接(不建表)。
|
"""验证数据库连接(不建表)。
|
||||||
|
|
||||||
|
|||||||
@@ -215,17 +215,3 @@ class LeagueRepository:
|
|||||||
async def add(self, league: League) -> None:
|
async def add(self, league: League) -> None:
|
||||||
self._session.add(league)
|
self._session.add(league)
|
||||||
await self._session.flush()
|
await self._session.flush()
|
||||||
|
|
||||||
|
|
||||||
class PredictionRepository:
|
|
||||||
"""预测记录数据访问。"""
|
|
||||||
|
|
||||||
def __init__(self, session: AsyncSession) -> None:
|
|
||||||
self._session = session
|
|
||||||
|
|
||||||
async def get_by_id(self, prediction_id: int) -> Prediction | None:
|
|
||||||
return await self._session.get(Prediction, prediction_id)
|
|
||||||
|
|
||||||
async def add(self, prediction: Prediction) -> None:
|
|
||||||
self._session.add(prediction)
|
|
||||||
await self._session.flush()
|
|
||||||
|
|||||||
+3
-6
@@ -7,6 +7,7 @@ from sqlalchemy import func, or_, select
|
|||||||
|
|
||||||
from src.db.models import Prediction, Match, League
|
from src.db.models import Prediction, Match, League
|
||||||
from src.db.unit_of_work import get_uow
|
from src.db.unit_of_work import get_uow
|
||||||
|
from src.llm.utils import actual_1x2
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -33,12 +34,8 @@ async def settle_prediction(prediction_id: int, home_goals: int, away_goals: int
|
|||||||
|
|
||||||
|
|
||||||
def _actual_1x2(home: int, away: int) -> str:
|
def _actual_1x2(home: int, away: int) -> str:
|
||||||
"""根据实际比分返胜平负。"""
|
"""根据实际比分返胜平负(委托 utils.actual_1x2 单一权威源)。"""
|
||||||
if home > away:
|
return actual_1x2(home, away)
|
||||||
return "1"
|
|
||||||
if home == away:
|
|
||||||
return "X"
|
|
||||||
return "2"
|
|
||||||
|
|
||||||
|
|
||||||
def _build_filters(
|
def _build_filters(
|
||||||
|
|||||||
@@ -172,20 +172,6 @@ async def _set_cached(match_id: int, provider: str, model: str, version: str, tp
|
|||||||
await _cache_backend.set(key, result, _CACHE_TTL_SEC)
|
await _cache_backend.set(key, result, _CACHE_TTL_SEC)
|
||||||
|
|
||||||
|
|
||||||
def clear_prompt_cache() -> None:
|
|
||||||
"""清空 prompt 模板缓存(供开发/热更新时手动调用)。
|
|
||||||
|
|
||||||
lru_cache 的模板缓存是进程级的,改完 .md 需要重启进程才能生效;
|
|
||||||
提供显式清理入口,避免"改了模板却看不到变化"的困惑(见审查报告 P2-5)。
|
|
||||||
同时清空预测响应缓存(内存后端);Redis 后端因共享不清除。
|
|
||||||
"""
|
|
||||||
_load_prompt_template.cache_clear()
|
|
||||||
if isinstance(_cache_backend, _MemoryCache):
|
|
||||||
_cache_backend._store.clear()
|
|
||||||
logger.info("prompt 模板缓存 + 预测响应缓存(内存)已清空")
|
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache(maxsize=8)
|
|
||||||
def _load_prompt_template(version: str = "v1") -> str:
|
def _load_prompt_template(version: str = "v1") -> str:
|
||||||
"""缓存 prompt 模板(进程生命周期内每个版本只读一次)。"""
|
"""缓存 prompt 模板(进程生命周期内每个版本只读一次)。"""
|
||||||
path = _PROMPT_DIR / f"match_prediction_{version}.md"
|
path = _PROMPT_DIR / f"match_prediction_{version}.md"
|
||||||
|
|||||||
+1
-9
@@ -3,17 +3,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
|
|
||||||
def actual_1x2(home: int, away: int) -> str:
|
def actual_1x2(home: int, away: int) -> str:
|
||||||
"""实际比分 → 胜平负。
|
"""实际比分 → 胜平负(单一权威源:backtest.py 与 eval.py 共用)。"""
|
||||||
|
|
||||||
单一权威源: backtest.py 和 eval.py 共用,避免重复定义。
|
|
||||||
"""
|
|
||||||
if home > away:
|
if home > away:
|
||||||
return "1"
|
return "1"
|
||||||
if home == away:
|
if home == away:
|
||||||
return "X"
|
return "X"
|
||||||
return "2"
|
return "2"
|
||||||
|
|
||||||
|
|
||||||
def is_correct_1x2(pred: str | None, actual: str) -> bool:
|
|
||||||
"""预测是否命中胜平负。"""
|
|
||||||
return pred == actual
|
|
||||||
|
|||||||
Reference in New Issue
Block a user