mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 23:44:16 +08:00
_load_concept_map_df 正常路径返回 (map_df, count) 元组, 但缓存只存了 裸 map_df —— 600s TTL 内二次访问命中缓存返回 DataFrame, 调用方按元组 解包把两列拆成两个 Series, 概念/行业分析二次打开必报错 (issue #186 报告者定位)。 - 缓存与返回值同构: 存 (map_df, count) 元组, 类型注解同步 - 移除 market_mainline / rps_rotation 两处针对旧 bug 的防御性兼容层 - 删除失效的 bare-DataFrame 兼容测试, 新增缓存契约回归测试 (3 次连跑通过)
71 lines
2.5 KiB
Python
71 lines
2.5 KiB
Python
"""_load_concept_map_df 缓存契约回归 (#186)。
|
|
|
|
旧 bug: 正常路径返回 (map_df, count) 元组, 但缓存只存了裸 map_df,
|
|
600s 内第二次调用命中缓存返回 DataFrame, 调用方按元组解包会把两列
|
|
拆成两个 Series, 概念/行业分析二次访问必报错 (issue 截图定位)。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import types
|
|
|
|
import pytest
|
|
|
|
from app.services import rps_rotation
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_map_cache():
|
|
rps_rotation._map_cache.clear()
|
|
rps_rotation._map_ts.clear()
|
|
yield
|
|
rps_rotation._map_cache.clear()
|
|
rps_rotation._map_ts.clear()
|
|
|
|
|
|
def _fake_repo(tmp_path):
|
|
return types.SimpleNamespace(store=types.SimpleNamespace(data_dir=tmp_path))
|
|
|
|
|
|
def _patch_ext(monkeypatch, rows: list[dict]) -> None:
|
|
"""替身 ext 配置读取: 免落盘, 聚焦缓存契约本身。"""
|
|
config = types.SimpleNamespace(id="ext_gn_ths")
|
|
monkeypatch.setattr(rps_rotation.ExtConfigStore, "load_all", lambda self: [config])
|
|
monkeypatch.setattr(
|
|
rps_rotation, "_dimension_field",
|
|
lambda cfg, kind: "所属概念" if kind == "concept" else None,
|
|
)
|
|
monkeypatch.setattr(rps_rotation, "_read_ext_rows", lambda data_dir, cfg, field: rows)
|
|
monkeypatch.setattr(
|
|
rps_rotation, "_symbol_keys", lambda row, cfg: [row["symbol"].upper()]
|
|
)
|
|
|
|
|
|
def test_map_cache_hit_returns_same_tuple(tmp_path, monkeypatch):
|
|
_patch_ext(monkeypatch, [
|
|
{"symbol": "s1.SH", "所属概念": "人工智能"},
|
|
{"symbol": "s2.SH", "所属概念": "芯片"},
|
|
])
|
|
first = rps_rotation._load_concept_map_df(_fake_repo(tmp_path), "concept")
|
|
assert isinstance(first, tuple) and len(first) == 2
|
|
map_df, count = first
|
|
assert count == 2
|
|
assert sorted(map_df["_sym_up"].to_list()) == ["S1.SH", "S2.SH"]
|
|
|
|
# 旧 bug: 命中缓存返回裸 DataFrame (只缓存了 map_df), 元组解包变两个 Series
|
|
second = rps_rotation._load_concept_map_df(_fake_repo(tmp_path), "concept")
|
|
assert isinstance(second, tuple) and len(second) == 2
|
|
assert second[0].equals(map_df)
|
|
assert second[1] == count
|
|
|
|
|
|
def test_map_cache_isolated_by_kind(tmp_path, monkeypatch):
|
|
_patch_ext(monkeypatch, [
|
|
{"symbol": "s1.SH", "所属概念": "人工智能"},
|
|
])
|
|
repo = _fake_repo(tmp_path)
|
|
concept = rps_rotation._load_concept_map_df(repo, "concept")
|
|
industry = rps_rotation._load_concept_map_df(repo, "industry")
|
|
assert concept[1] == 1
|
|
assert industry[1] == 0
|
|
assert industry[0].is_empty()
|