chore(P3): baseline 落库下沉 + MatchPredictPanel 拆分 + 多 worker/CSRF 文档
P3-2 baseline 落库从路由下沉到服务层(predict_baseline 内直接落库),
删除路由层 _persist_baseline,三种模式统一 result.prediction_id,对外 JSON 不变。
P3-1 MatchPredictPanel.PredictionPanel 拆为 OutcomePanel/AgentsPanel/ReasoningPanel
三个子组件,本文件保留 PredictModal/PredictProgress/Spinner,对外导出路径不变。
P3-3 docs 加 ⚠️ 多 worker 陷阱红字 + STRICT_SINGLE_WORKER 环境变量(启动期强制拒绝多 worker)。
P3-4 docs 新增「同站部署 vs 跨站 CSRF」节。
This commit is contained in:
+29
-2
@@ -5,6 +5,7 @@
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@@ -12,6 +13,24 @@ import pytest
|
||||
from src.llm.baseline import _avg_goals, predict_baseline
|
||||
|
||||
|
||||
class _FakeUoW:
|
||||
"""P3-2:baseline 在服务层落库,测试需 mock get_uow。"""
|
||||
|
||||
async def __aenter__(self):
|
||||
return SimpleNamespace(
|
||||
execute=lambda *a, **k: SimpleNamespace(scalar_one_or_none=lambda: None),
|
||||
add=lambda *a, **k: None,
|
||||
flush=lambda *a, **k: None,
|
||||
)
|
||||
|
||||
async def __aexit__(self, *a):
|
||||
return None
|
||||
|
||||
|
||||
async def _fake_upsert(session, **kw):
|
||||
return SimpleNamespace(id=1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_avg_goals_no_data_returns_zero():
|
||||
"""无历史数据时场均进球为 0(不抛异常)。"""
|
||||
@@ -68,7 +87,9 @@ async def test_predict_baseline_no_llm():
|
||||
match_status = "scheduled"
|
||||
|
||||
with patch("src.llm.baseline._avg_goals", fake_avg), \
|
||||
patch("src.llm.baseline.AsyncSessionLocal") as SLC:
|
||||
patch("src.llm.baseline.AsyncSessionLocal") as SLC, \
|
||||
patch("src.db.unit_of_work.get_uow", _FakeUoW), \
|
||||
patch("src.llm.baseline._upsert_prediction", _fake_upsert):
|
||||
class FakeSession:
|
||||
async def get(self, cls, mid):
|
||||
return FakeMatch()
|
||||
@@ -93,6 +114,8 @@ async def test_predict_baseline_no_llm():
|
||||
assert result.pred_1x2 == "X"
|
||||
assert result.subjective_confidence == 0.5
|
||||
assert "非投注建议" in result.reasoning
|
||||
# P3-2:服务层落库,回填真实 prediction_id
|
||||
assert result.prediction_id == 1
|
||||
# 确认未调用任何 LLM 相关模块
|
||||
assert "home_10" in captured and "away_20" in captured
|
||||
|
||||
@@ -112,7 +135,9 @@ async def test_predict_baseline_clamps_to_range():
|
||||
match_status = "scheduled"
|
||||
|
||||
with patch("src.llm.baseline._avg_goals", fake_avg), \
|
||||
patch("src.llm.baseline.AsyncSessionLocal") as SLC:
|
||||
patch("src.llm.baseline.AsyncSessionLocal") as SLC, \
|
||||
patch("src.db.unit_of_work.get_uow", _FakeUoW), \
|
||||
patch("src.llm.baseline._upsert_prediction", _fake_upsert):
|
||||
class FakeSession:
|
||||
async def get(self, cls, mid):
|
||||
return FakeMatch()
|
||||
@@ -128,3 +153,5 @@ async def test_predict_baseline_clamps_to_range():
|
||||
assert result.pred_home_goals == 10.0 # clamped
|
||||
assert result.pred_away_goals == 0.0 # clamped
|
||||
assert result.pred_1x2 == "1" # 10:0 主胜
|
||||
# P3-2:服务层落库,回填真实 prediction_id
|
||||
assert result.prediction_id == 1
|
||||
|
||||
Reference in New Issue
Block a user