diff --git a/alembic/versions/0017_mode_baseline.py b/alembic/versions/0017_mode_baseline.py new file mode 100644 index 0000000..ad00d13 --- /dev/null +++ b/alembic/versions/0017_mode_baseline.py @@ -0,0 +1,39 @@ +"""扩展 ck_mode_enum 约束支持 baseline 模式 + +Revision ID: 0017_mode_baseline +Revises: 0016_schedules +Create Date: 2026-09-21 + +Code Review High-1/2 修复: + 业务支持 mode='baseline'(非 LLM 统计基线),但 DB CHECK 约束只允许 + ('single', 'multi'),导致 baseline 预测写入时 CheckViolation。 + run_type='baseline' 改为 'live'(代码侧),约束无需扩展。 +""" + +from typing import Sequence, Union + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = '0017_mode_baseline' +down_revision: Union[str, None] = '0016_schedules' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.drop_constraint('ck_mode_enum', 'predictions', type_='check') + op.create_check_constraint( + 'ck_mode_enum', 'predictions', + "mode IN ('single', 'multi', 'baseline')", + ) + + +def downgrade() -> None: + # 先清理 baseline 数据再恢复旧约束 + op.execute("DELETE FROM predictions WHERE mode = 'baseline'") + op.drop_constraint('ck_mode_enum', 'predictions', type_='check') + op.create_check_constraint( + 'ck_mode_enum', 'predictions', + "mode IN ('single', 'multi')", + ) diff --git a/src/api/deps.py b/src/api/deps.py index 6f04641..79f98a2 100644 --- a/src/api/deps.py +++ b/src/api/deps.py @@ -187,7 +187,7 @@ async def rate_limit_predict(request: Request) -> None: client_ip = get_client_ip(request) if not _predict_limiter.is_allowed(client_ip): - logger.warning("rate limit exceeded for %s", ip) + logger.warning("rate limit exceeded for %s", client_ip) raise HTTPException( status_code=429, detail="请求过于频繁,请稍后再试(每分钟最多 10 次)", diff --git a/src/api/routes/auth.py b/src/api/routes/auth.py index 62b1ddc..6ee6862 100644 --- a/src/api/routes/auth.py +++ b/src/api/routes/auth.py @@ -83,6 +83,8 @@ async def login(body: LoginIn, request: Request, response: Response): raise HTTPException(status_code=401, detail="密码错误") _fail_times.pop(ip, None) + # Code Review High-4: 生产环境(HHTTPS)下 Cookie 必须带 Secure,防中间人窃取 + secure = settings.APP_ENV == "production" response.set_cookie( key=SESSION_COOKIE, value=create_session_token(await get_session_secret()), @@ -90,6 +92,7 @@ async def login(body: LoginIn, request: Request, response: Response): httponly=True, samesite="lax", path="/", + secure=secure, ) logger.info("管理员登录成功 (ip=%s)", ip) return {"ok": True, "expires_in_hours": settings.ADMIN_SESSION_TTL_HOURS} diff --git a/src/api/routes/predict.py b/src/api/routes/predict.py index 2e19ef8..bcf99c1 100644 --- a/src/api/routes/predict.py +++ b/src/api/routes/predict.py @@ -116,7 +116,7 @@ async def _persist_baseline(match_id: int, baseline: dict) -> int: provider_name="baseline", model="baseline", mode="baseline", - run_type="baseline", + run_type="live", # baseline 是 live 预测的变体,符合 ck_run_type_enum values={ "prompt_version": "baseline_v1", "prompt_tokens": 0, diff --git a/src/api/schemas.py b/src/api/schemas.py index 06e6693..69b4122 100644 --- a/src/api/schemas.py +++ b/src/api/schemas.py @@ -48,6 +48,7 @@ class PredictRequest(BaseModel): prompt_version: str | None = None mode: str = Field( "multi", + pattern="^(multi|single|baseline)$", description="multi(默认,5专家+终裁) | single(单次) | baseline(极简统计基线,不调用 LLM)", ) use_cache: bool = True diff --git a/src/db/models.py b/src/db/models.py index 5feb4b1..e5145cb 100644 --- a/src/db/models.py +++ b/src/db/models.py @@ -249,7 +249,7 @@ class Prediction(Base): CheckConstraint("pred_away_goals >= 0", name="ck_pred_away_goals_nonneg"), CheckConstraint("subjective_confidence >= 0 AND subjective_confidence <= 1", name="ck_confidence_range"), CheckConstraint("pred_1x2 IN ('1', 'X', '2')", name="ck_pred_1x2_enum"), - CheckConstraint("mode IN ('single', 'multi')", name="ck_mode_enum"), + CheckConstraint("mode IN ('single', 'multi', 'baseline')", name="ck_mode_enum"), CheckConstraint("status IN ('success', 'failed', 'degraded')", name="ck_status_enum"), CheckConstraint("run_type IN ('live', 'backtest')", name="ck_run_type_enum"), ) diff --git a/src/llm/agents/orchestrator.py b/src/llm/agents/orchestrator.py index 6f2172a..b131c35 100644 --- a/src/llm/agents/orchestrator.py +++ b/src/llm/agents/orchestrator.py @@ -263,7 +263,7 @@ async def predict_match_multi( # 所有专家无数据/均失败:跳过终裁,标记 degraded logger.warning( "预测降级 match=%s mode=%s status=degraded experts=%d/%d 均无有效数据", - match_id, "multi", ok_reports, len(reports), + match_id, "multi", len(ok_reports), len(reports), ) # 无有效专家时不调用 aggregator provider,避免多余开销 # model 使用 settings 默认值占位(无实际 LLM 调用)