mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
feat(preferences): 复盘推送 review_push_mode 门控
This commit is contained in:
@@ -19,7 +19,7 @@ from fastapi import APIRouter, HTTPException, Query, Request
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.services import auction_benchmark, dragon_tiger, market_recap_reports
|
||||
from app.services import auction_benchmark, dragon_tiger, market_recap_reports, preferences
|
||||
from app.services.market_recap import recap_market_stream
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -131,8 +131,8 @@ def list_reports(request: Request):
|
||||
|
||||
|
||||
@router.post("/reports")
|
||||
def save_report(request: Request, req: SaveReportRequest):
|
||||
"""保存一条复盘报告。"""
|
||||
def save_report(request: Request, req: SaveReportRequest, push: bool = False):
|
||||
"""保存一条复盘报告。push=True 或 review_push_mode=auto 时才推送到外部渠道。"""
|
||||
report = market_recap_reports.save_report({
|
||||
"as_of": req.as_of,
|
||||
"focus": req.focus,
|
||||
@@ -141,13 +141,14 @@ def save_report(request: Request, req: SaveReportRequest):
|
||||
"emotion_score": req.emotion_score,
|
||||
"emotion_label": req.emotion_label,
|
||||
})
|
||||
# 推送到飞书(可选): 与定时复盘共用同一开关 review_push_enabled 与 _maybe_push_review。
|
||||
# 推送门控: manual 模式需显式 push=True; auto 模式保持归档即推。
|
||||
# 内部 try/except 静默降级, 不影响归档返回值。
|
||||
from app.jobs.daily_pipeline import _maybe_push_review
|
||||
_maybe_push_review(req.content, {
|
||||
"as_of": req.as_of,
|
||||
"emotion_label": req.emotion_label,
|
||||
})
|
||||
if push or preferences.get_review_push_mode() == "auto":
|
||||
from app.jobs.daily_pipeline import _maybe_push_review
|
||||
_maybe_push_review(req.content, {
|
||||
"as_of": req.as_of,
|
||||
"emotion_label": req.emotion_label,
|
||||
})
|
||||
return {"ok": True, "report": report}
|
||||
|
||||
|
||||
|
||||
@@ -944,9 +944,11 @@ async def _run_scheduled_review(repo) -> None:
|
||||
quote_service.push_review_event(json.dumps(
|
||||
{"type": "done", "archived": True}, ensure_ascii=False))
|
||||
|
||||
# 推送到飞书(可选): 运行时读取配置, 用户改设置下次触发即生效。
|
||||
# 推送门控: review_push_mode=manual 时定时复盘只归档不推送,
|
||||
# 由用户对当日报告显式确认后才推; auto 时保持既有自动推送行为。
|
||||
# 失败静默降级, 不影响已归档的报告。
|
||||
_maybe_push_review(content, meta)
|
||||
if _prefs.get_review_push_mode() == "auto":
|
||||
_maybe_push_review(content, meta)
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.exception("scheduled review failed: %s", e)
|
||||
# 兜底: 异常时通知前端停止「生成中」状态, 避免页面卡在 streaming
|
||||
|
||||
@@ -684,6 +684,26 @@ def set_review_push_channels(channels: list[str]) -> list[str]:
|
||||
return cleaned
|
||||
|
||||
|
||||
REVIEW_PUSH_MODES = frozenset({"auto", "manual"})
|
||||
|
||||
|
||||
def get_review_push_mode() -> str:
|
||||
"""复盘推送触发方式: auto=归档后自动推; manual=仅显式 push。默认 manual。
|
||||
|
||||
定时复盘与手动保存复盘共用此开关。manual 时定时路径只归档不推送,
|
||||
手动路径需 save_report 显式传 push=True 才推。
|
||||
"""
|
||||
mode = load().get("review_push_mode", "manual")
|
||||
return mode if mode in REVIEW_PUSH_MODES else "manual"
|
||||
|
||||
|
||||
def set_review_push_mode(mode: str) -> str:
|
||||
"""保存复盘推送触发方式, 白名单外的值回退 manual。"""
|
||||
mode = mode if mode in REVIEW_PUSH_MODES else "manual"
|
||||
save({"review_push_mode": mode})
|
||||
return mode
|
||||
|
||||
|
||||
|
||||
# ===== 实时监控 =====
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""复盘推送触发方式 review_push_mode 最小 demo — 验证 auto/manual 白名单与默认值。
|
||||
|
||||
运行方式(在 backend/ 目录下, 已安装依赖):
|
||||
python -m scripts.demo_recap_push_mode
|
||||
|
||||
隔离到临时偏好文件, 不污染真实偏好。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from app.services import preferences
|
||||
|
||||
|
||||
def main() -> None:
|
||||
prefs_path = Path(tempfile.mkdtemp()) / "preferences.json"
|
||||
preferences._path = lambda: prefs_path # noqa: SLF001 - demo 隔离
|
||||
preferences._invalidate_cache()
|
||||
|
||||
print("== 复盘推送模式 review_push_mode ==")
|
||||
print(f" 默认 = {preferences.get_review_push_mode()} (期望 manual)")
|
||||
print(f" 设为 auto = {preferences.set_review_push_mode('auto')}")
|
||||
print(f" 非法值回退 = {preferences.set_review_push_mode('bogus')} (期望 manual)")
|
||||
|
||||
print("\n全部通过")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,32 @@
|
||||
"""复盘推送触发方式 review_push_mode 测试 — auto/manual 白名单与默认值。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.services import preferences
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolated(tmp_path, monkeypatch):
|
||||
path = tmp_path / "preferences.json"
|
||||
monkeypatch.setattr(preferences, "_path", lambda: path)
|
||||
preferences._invalidate_cache()
|
||||
yield path
|
||||
preferences._invalidate_cache()
|
||||
|
||||
|
||||
def test_review_push_mode_defaults_to_manual():
|
||||
assert preferences.get_review_push_mode() == "manual"
|
||||
|
||||
|
||||
def test_set_and_get_review_push_mode():
|
||||
assert preferences.set_review_push_mode("auto") == "auto"
|
||||
assert preferences.get_review_push_mode() == "auto"
|
||||
|
||||
assert preferences.set_review_push_mode("manual") == "manual"
|
||||
assert preferences.get_review_push_mode() == "manual"
|
||||
|
||||
|
||||
def test_set_review_push_mode_rejects_invalid_value():
|
||||
assert preferences.set_review_push_mode("bogus") == "manual"
|
||||
assert preferences.get_review_push_mode() == "manual"
|
||||
Reference in New Issue
Block a user