diff --git a/backend/data/providers/tushare_dashboard.py b/backend/data/providers/tushare_dashboard.py index b7e98c1..c2fb4b4 100644 --- a/backend/data/providers/tushare_dashboard.py +++ b/backend/data/providers/tushare_dashboard.py @@ -41,13 +41,16 @@ class DashboardMixin: raise TushareError(f"No daily data returned for {trade_date}") notices: list[str] = [] + limit_data_source = "official" try: limit_rows = self._load_limit_lists(trade_date) previous_limit_rows = self._load_limit_type(previous_trade_date, "U") if not limit_rows: + limit_data_source = "derived" notices.append("涨跌停高级接口当日数据尚未更新,已使用日线数据推算。") limit_rows = self._derive_limits(trade_date, daily) except TushareError as exc: + limit_data_source = "derived" notices.append(f"涨跌停高级接口不可用,已使用日线数据推算:{exc}") limit_rows = self._derive_limits(trade_date, daily) previous_daily = self._load_daily(previous_trade_date) @@ -79,6 +82,7 @@ class DashboardMixin: "trade_date": _display_date(trade_date), "previous_trade_date": _display_date(previous_trade_date), "source": "tushare", + "limit_data_source": limit_data_source, "updated_at": datetime.now().astimezone().isoformat(timespec="seconds"), "notice": ";".join(notices), }, diff --git a/backend/features/market/service.py b/backend/features/market/service.py index e266057..e372b8f 100644 --- a/backend/features/market/service.py +++ b/backend/features/market/service.py @@ -198,6 +198,11 @@ class MarketServiceMixin: raise TushareError("公共行情尚未配置") dashboard = self._tushare_client().dashboard(normalized_date) + if (dashboard.get("meta") or {}).get("limit_data_source") == "derived": + raise TushareError( + str((dashboard.get("meta") or {}).get("notice") or "官方涨跌停数据尚未返回") + ) + dashboard["meta"]["source"] = source dashboard["meta"]["requested_date"] = self._display_compact_date(normalized_date) dashboard = self._enrich_dashboard_sentiment(dashboard, normalized_date) diff --git a/backend/jobs/service.py b/backend/jobs/service.py index 216d22a..15dccc4 100644 --- a/backend/jobs/service.py +++ b/backend/jobs/service.py @@ -7,6 +7,16 @@ from datetime import date from backend.bootstrap.config import normalize_date +def _verified_dashboard_result(dashboard: dict[str, object]) -> dict[str, object]: + meta = dashboard.get("meta") or {} + if isinstance(meta, dict) and meta.get("carried_forward"): + return { + "status": "failed", + "error": str(meta.get("notice") or "未获取到所选日期的最新行情"), + } + return dashboard + + class JobServiceMixin: def start_background_jobs(self) -> threading.Thread: return self.jobs.start_scheduler( @@ -26,7 +36,7 @@ class JobServiceMixin: started = self.jobs.submit( "market.refresh", key, - lambda: self.sync_dashboard(normalized), + lambda: _verified_dashboard_result(self.sync_dashboard(normalized)), {"trade_date": normalized, "trigger": "administrator"}, ) return {"started": started, "job_key": key if started else ""} diff --git a/config/architecture-inventory.json b/config/architecture-inventory.json index d06e930..66e9883 100644 --- a/config/architecture-inventory.json +++ b/config/architecture-inventory.json @@ -486,8 +486,8 @@ }, { "path": "backend/data/providers/tushare_dashboard.py", - "bytes": 28051, - "lines": 644 + "bytes": 28234, + "lines": 648 }, { "path": "backend/data/providers/tushare_industries.py", @@ -769,6 +769,11 @@ "bytes": 2299, "lines": 57 }, + { + "path": "backend/jobs/service.py", + "bytes": 2219, + "lines": 60 + }, { "path": "backend/features/screener/regime.py", "bytes": 2202, @@ -799,11 +804,6 @@ "bytes": 1919, "lines": 45 }, - { - "path": "backend/jobs/service.py", - "bytes": 1833, - "lines": 50 - }, { "path": "backend/features/system/routes.py", "bytes": 1791, diff --git a/tests/test_admin_refresh_status.py b/tests/test_admin_refresh_status.py new file mode 100644 index 0000000..634f89e --- /dev/null +++ b/tests/test_admin_refresh_status.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +import unittest + +from backend.jobs.service import _verified_dashboard_result + + +class AdminRefreshStatusTests(unittest.TestCase): + def test_carried_snapshot_is_reported_as_failed_job(self): + result = _verified_dashboard_result( + {"meta": {"carried_forward": True, "notice": "官方涨跌停数据尚未返回"}} + ) + + self.assertEqual(result["status"], "failed") + self.assertEqual(result["error"], "官方涨跌停数据尚未返回") + + def test_current_snapshot_is_reported_as_successful_job(self): + dashboard = {"meta": {"trade_date": "2026-08-28", "carried_forward": False}} + + self.assertIs(_verified_dashboard_result(dashboard), dashboard) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_realtime_dashboard.py b/tests/test_realtime_dashboard.py index 1114dc9..6977a4f 100644 --- a/tests/test_realtime_dashboard.py +++ b/tests/test_realtime_dashboard.py @@ -111,6 +111,25 @@ class RealtimeDashboardTests(unittest.TestCase): self.assertEqual(quote["amount_billion"], 3.0) self.assertAlmostEqual(quote["turnover_rate"], 0.01) + def test_close_dashboard_marks_official_limit_data(self): + dashboard = self.client.dashboard("20260720") + + self.assertEqual(dashboard["meta"]["limit_data_source"], "official") + + def test_close_dashboard_marks_derived_limit_data_as_incomplete(self): + original_query = self.client.query + + def query(api_name, params=None, fields=""): + if api_name == "limit_list_d": + return [] + return original_query(api_name, params, fields) + + self.client.query = query + dashboard = self.client.dashboard("20260720") + + self.assertEqual(dashboard["meta"]["limit_data_source"], "derived") + self.assertIn("日线数据推算", dashboard["meta"]["notice"]) + if __name__ == "__main__": unittest.main()