feat(realtime): 首用门禁 — 本地无数据时禁止开启实时行情

日K/enriched 均空 (与看板首用弹窗同口径) 时开启实时行情返回 409 并给
同步指引, 不落偏好。展示/监控/梯队全部依赖本地数据底座, 空数据下轮询
只是空转耗配额且缺 instruments 维表。只拦开启不影响关闭, 已开后清库
下次重开自然被拦。
This commit is contained in:
shy3130
2026-08-31 21:45:07 +08:00
parent 320969497a
commit ed2f81c312
2 changed files with 41 additions and 2 deletions
+10
View File
@@ -932,6 +932,16 @@ def update_realtime_quotes(req: RealtimeQuotesPrefs, request: Request) -> dict:
qs.disable()
_sync_depth_polling(False)
return {"realtime_quotes_enabled": False, "realtime_allowed": False}
if req.realtime_quotes_enabled:
# 首用门禁: 本地完全无数据 (日K/enriched 均空, 与看板首用弹窗同口径) 时
# 禁止开启 — 实时行情的展示 (enriched 底座 + 快照覆盖)、监控、连板梯队
# 全部依赖本地数据, 空数据下轮询只是空转耗配额, 且无 instruments 维表。
repo = getattr(request.app.state, "repo", None)
if repo is not None and repo.latest_daily_date() is None and repo.latest_enriched_date() is None:
raise HTTPException(
status_code=409,
detail="本地暂无行情数据,请先在「数据」页同步后再开启实时行情",
)
if req.realtime_quotes_enabled and qs and qs.is_paused():
# 管道/数据修正运行期间禁止开启实时行情 — 防止写盘竞态
raise HTTPException(status_code=409, detail="数据同步运行中,实时行情已临时暂停,请稍后再开启")
+31 -2
View File
@@ -227,14 +227,19 @@ def test_describe_and_issue_dataclass():
# ── 开实时行情门禁 (钩子2) ──────────────────────────────────────────
def _gate_state(tmp_path, quote_service, repo):
def _gate_state(tmp_path, quote_service, repo, *, has_data=True):
from types import SimpleNamespace
return SimpleNamespace(
app=SimpleNamespace(state=SimpleNamespace(
quote_service=quote_service,
depth_service=None,
repo=SimpleNamespace(store=SimpleNamespace(data_dir=tmp_path)),
repo=SimpleNamespace(
store=SimpleNamespace(data_dir=tmp_path),
# 首用门禁判据: 日K/enriched 最近日期 (None = 本地无数据)
latest_daily_date=lambda: date(2026, 8, 28) if has_data else None,
latest_enriched_date=lambda: None,
),
capabilities=None,
))
)
@@ -298,6 +303,30 @@ def test_realtime_gate_blocks_on_snapshot_and_launches_repair(tmp_path, monkeypa
assert saved == {}
def test_realtime_gate_blocks_when_no_local_data(tmp_path, monkeypatch):
"""首用门禁: 日K/enriched 均无数据时禁止开启实时行情 (409 + 同步指引),
且不落偏好 (开关不生效)。"""
from fastapi import HTTPException
from app.api import settings as settings_api
saved = {}
monkeypatch.setattr(
"app.services.preferences.save", lambda payload: saved.update(payload),
)
qs = _QuoteServiceStub()
request = _gate_state(tmp_path, qs, repo=None, has_data=False)
req = settings_api.RealtimeQuotesPrefs(realtime_quotes_enabled=True)
with pytest.raises(HTTPException) as exc_info:
settings_api.update_realtime_quotes(req, request)
assert exc_info.value.status_code == 409
assert "同步" in exc_info.value.detail
assert saved == {} # 未开启
def test_realtime_gate_allows_clean_data(tmp_path, monkeypatch):
from app.api import settings as settings_api