fix(watchlist): 保留冷缓存自选占位行

This commit is contained in:
shy3130
2026-07-10 15:07:14 +08:00
parent be626c73e1
commit fdee661d47
2 changed files with 74 additions and 11 deletions
+4 -6
View File
@@ -126,9 +126,6 @@ def watchlist_enriched(
etf_symbols = [s for s in symbols if s in etf_set]
df_e, cache_date = repo.get_enriched_latest()
# 保持原契约: 自选含股票但股票 enriched 未就绪 (预热中) → 返回"未就绪"而非部分结果
if stock_symbols and df_e.is_empty():
return {"rows": [], "as_of": None, "elapsed_ms": 0}
# 以自选列表为主表 LEFT JOIN enriched, 保证自选的每一只都返回一行;
# 不在 enriched 缓存里的标的 (新股/冷门股/新用户未同步) 指标为 null, 前端渲染为 "—".
@@ -147,12 +144,13 @@ def watchlist_enriched(
etf_date = None
if etf_symbols:
df_etf_all, etf_date = repo.get_enriched_latest_asset("etf")
etf_watchlist_df = pl.DataFrame({"symbol": etf_symbols})
if not df_etf_all.is_empty():
# ETF 同样以自选为主表 LEFT JOIN, 缺失标的指标为 null
etf_watchlist_df = pl.DataFrame({"symbol": etf_symbols})
df_etf = etf_watchlist_df.join(df_etf_all, on="symbol", how="left")
if not df_etf.is_empty():
df = df_etf if df.is_empty() else pl.concat([df, df_etf], how="diagonal_relaxed")
else:
df_etf = etf_watchlist_df
df = df_etf if df.is_empty() else pl.concat([df, df_etf], how="diagonal_relaxed")
# as_of 取两类缓存中较旧者, 避免把旧的 ETF 行标成股票缓存日期
dates = [d for d in (cache_date if stock_symbols else None, etf_date) if d is not None]
+70 -5
View File
@@ -93,17 +93,19 @@ def test_watchlist_symbol_not_in_enriched_still_returned(monkeypatch):
def test_all_watchlist_missing_from_enriched(monkeypatch):
"""极端情况: 自选全是 enriched 没覆盖的 (新用户冷启动场景)."""
"""股票 enriched 缓存未就绪时, 自选仍返回占位行."""
syms = ["000001", "000002"]
monkeypatch.setattr(wl_api.watchlist, "list_symbols",
lambda: [{"symbol": "000001"}, {"symbol": "000002"}])
lambda: [{"symbol": s} for s in syms])
repo = _FakeRepo(
enriched_df=pl.DataFrame(schema={"symbol": pl.Utf8}), # 空 schema, 模拟未就绪
enriched_df=pl.DataFrame(schema={"symbol": pl.Utf8}),
enriched_date=None,
)
# 注: 原契约 stock_symbols 非空且 enriched 空 → 返回未就绪. 这是设计, 不变.
res = wl_api.watchlist_enriched(_make_request(repo), ext_columns=None)
assert res["rows"] == []
assert [r["symbol"] for r in res["rows"]] == syms
assert all(r.get("close") is None for r in res["rows"])
assert res["as_of"] is None
@@ -147,3 +149,66 @@ def test_etf_not_in_enriched_still_returned(monkeypatch):
row_missing = next(r for r in res["rows"] if r["symbol"] == "599999")
assert row_missing["close"] is None
def test_all_etf_watchlist_missing_from_enriched(monkeypatch):
"""ETF enriched 缓存未就绪时, 自选仍返回占位行."""
syms = ["510300", "599999"]
monkeypatch.setattr(wl_api.watchlist, "list_symbols",
lambda: [{"symbol": s} for s in syms])
repo = _FakeRepo(
enriched_df=pl.DataFrame(schema={"symbol": pl.Utf8}),
enriched_date=None,
etf_df=pl.DataFrame(schema={"symbol": pl.Utf8}),
etf_date=None,
etf_set=set(syms),
)
res = wl_api.watchlist_enriched(_make_request(repo), ext_columns=None)
assert [r["symbol"] for r in res["rows"]] == syms
assert all(r.get("close") is None for r in res["rows"])
assert res["as_of"] is None
def test_mixed_watchlist_keeps_pending_stock_rows(monkeypatch):
"""股票缓存未就绪不应影响 ETF 行, 且保持自选原始顺序."""
syms = ["510300", "000001", "510500"]
monkeypatch.setattr(wl_api.watchlist, "list_symbols",
lambda: [{"symbol": s} for s in syms])
repo = _FakeRepo(
enriched_df=pl.DataFrame(schema={"symbol": pl.Utf8}),
enriched_date=None,
etf_df=_enriched_df([("510300", 4.0, 0.5, 1e8), ("510500", 6.0, -0.2, 2e8)]),
etf_date="2026-07-08",
etf_set={"510300", "510500"},
)
res = wl_api.watchlist_enriched(_make_request(repo), ext_columns=None)
assert [r["symbol"] for r in res["rows"]] == syms
assert next(r for r in res["rows"] if r["symbol"] == "000001").get("close") is None
assert next(r for r in res["rows"] if r["symbol"] == "510300")["close"] == 4.0
assert res["as_of"] == "2026-07-08"
def test_mixed_watchlist_keeps_pending_etf_rows(monkeypatch):
"""ETF 缓存未就绪不应影响股票行, 且保持自选原始顺序."""
syms = ["510300", "000001", "510500"]
monkeypatch.setattr(wl_api.watchlist, "list_symbols",
lambda: [{"symbol": s} for s in syms])
repo = _FakeRepo(
enriched_df=_enriched_df([("000001", 15.0, 0.3, 2e9)]),
enriched_date="2026-07-08",
etf_df=pl.DataFrame(schema={"symbol": pl.Utf8}),
etf_date=None,
etf_set={"510300", "510500"},
)
res = wl_api.watchlist_enriched(_make_request(repo), ext_columns=None)
assert [r["symbol"] for r in res["rows"]] == syms
assert next(r for r in res["rows"] if r["symbol"] == "000001")["close"] == 15.0
assert all(next(r for r in res["rows"] if r["symbol"] == symbol).get("close") is None
for symbol in ("510300", "510500"))
assert res["as_of"] == "2026-07-08"