fix(HEL-412): 刷新降级不再整次失败,并补齐准备中提示
手动刷新与自动补跑共用可用数据判定:日线推算或上一交易日快照记为部分/准备中成功,避免前端误报刷新失败。HTTP JSON 解析错误不再把请求正文写入日志。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
0d13066386
commit
5085cacf0d
@@ -79,6 +79,8 @@ class MarketServiceMixin:
|
||||
if not force:
|
||||
snapshot = self.database.get_snapshot(normalized_date)
|
||||
if snapshot and str((snapshot.get("meta") or {}).get("source") or "") != "demo":
|
||||
if self._should_retry_incomplete_snapshot(snapshot, normalized_date):
|
||||
return self.sync_dashboard(normalized_date)
|
||||
snapshot = copy.deepcopy(snapshot)
|
||||
if normalized_date != now.strftime("%Y%m%d"):
|
||||
snapshot.setdefault("meta", {}).update(
|
||||
@@ -97,6 +99,8 @@ class MarketServiceMixin:
|
||||
"dashboard_request_v1", normalized_date
|
||||
)
|
||||
if resolved and str((resolved.get("meta") or {}).get("source") or "") != "demo":
|
||||
if self._should_retry_incomplete_snapshot(resolved, normalized_date):
|
||||
return self.sync_dashboard(normalized_date)
|
||||
resolved = copy.deepcopy(resolved)
|
||||
resolved.setdefault("meta", {})["requested_date"] = self._display_compact_date(
|
||||
normalized_date
|
||||
@@ -138,6 +142,68 @@ class MarketServiceMixin:
|
||||
def _display_compact_date(compact: str) -> str:
|
||||
return f"{compact[:4]}-{compact[4:6]}-{compact[6:8]}"
|
||||
|
||||
@staticmethod
|
||||
def _chinese_month_day(value: str) -> str:
|
||||
compact = str(value or "").replace("-", "").replace("/", "")
|
||||
if len(compact) < 8 or not compact[:8].isdigit():
|
||||
return "最近可用交易日"
|
||||
return f"{int(compact[4:6])} 月 {int(compact[6:8])} 日"
|
||||
|
||||
@classmethod
|
||||
def _preparing_display_notice(cls, actual_date: str, requested_date: str) -> str:
|
||||
shown = cls._chinese_month_day(actual_date)
|
||||
requested = str(requested_date or "").replace("-", "")
|
||||
if requested == date.today().strftime("%Y%m%d"):
|
||||
return f"今日数据正在准备,当前展示 {shown}"
|
||||
return f"所选日期数据尚未到齐,当前展示 {shown}"
|
||||
|
||||
@staticmethod
|
||||
def _snapshot_age_seconds(meta: dict[str, Any]) -> float:
|
||||
raw = str(meta.get("updated_at") or "")
|
||||
if not raw:
|
||||
return 10**9
|
||||
try:
|
||||
updated_at = datetime.fromisoformat(raw)
|
||||
except ValueError:
|
||||
return 10**9
|
||||
now = datetime.now().astimezone()
|
||||
if updated_at.tzinfo is None:
|
||||
updated_at = updated_at.replace(tzinfo=now.tzinfo)
|
||||
return (now - updated_at.astimezone(now.tzinfo)).total_seconds()
|
||||
|
||||
def _should_retry_incomplete_snapshot(
|
||||
self, snapshot: dict[str, Any], requested_date: str
|
||||
) -> bool:
|
||||
if requested_date != date.today().strftime("%Y%m%d"):
|
||||
return False
|
||||
meta = snapshot.get("meta") or {}
|
||||
incomplete = (
|
||||
meta.get("limit_data_source") == "derived"
|
||||
or bool(meta.get("carried_forward"))
|
||||
or str(meta.get("trade_date") or "").replace("-", "") != requested_date
|
||||
)
|
||||
return incomplete and self._snapshot_age_seconds(meta) >= 60
|
||||
|
||||
def _annotate_data_status(self, dashboard: dict[str, Any]) -> dict[str, Any]:
|
||||
meta = dashboard.setdefault("meta", {})
|
||||
notice = str(meta.get("notice") or "")
|
||||
requested = str(meta.get("requested_date") or "").replace("-", "")
|
||||
actual = str(meta.get("trade_date") or "").replace("-", "")
|
||||
if meta.get("limit_data_source") == "derived" and not meta.get("carried_forward"):
|
||||
meta["data_status"] = "partial"
|
||||
meta["display_notice"] = notice or "部分正式数据尚未到齐,当前展示日线推算结果"
|
||||
elif meta.get("carried_forward"):
|
||||
if "非交易日" in notice or "盘前" in notice:
|
||||
meta["data_status"] = "carried"
|
||||
meta["display_notice"] = notice
|
||||
else:
|
||||
meta["data_status"] = "preparing"
|
||||
meta["display_notice"] = self._preparing_display_notice(actual, requested)
|
||||
else:
|
||||
meta["data_status"] = "official"
|
||||
meta.setdefault("display_notice", "")
|
||||
return dashboard
|
||||
|
||||
def _carry_dashboard(
|
||||
self, snapshot: dict[str, Any], requested_date: str, reason: str
|
||||
) -> dict[str, Any]:
|
||||
@@ -152,7 +218,7 @@ class MarketServiceMixin:
|
||||
"notice": reason,
|
||||
}
|
||||
)
|
||||
return carried
|
||||
return self._annotate_data_status(carried)
|
||||
|
||||
def _realtime_snapshot_due(
|
||||
self,
|
||||
@@ -197,14 +263,14 @@ class MarketServiceMixin:
|
||||
if not self.configured:
|
||||
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 "官方涨跌停数据尚未返回")
|
||||
meta = dashboard.setdefault("meta", {})
|
||||
meta["source"] = source
|
||||
meta["requested_date"] = self._display_compact_date(normalized_date)
|
||||
if meta.get("limit_data_source") == "derived":
|
||||
meta.setdefault(
|
||||
"notice",
|
||||
"涨跌停高级接口当日数据尚未更新,已使用日线数据推算。",
|
||||
)
|
||||
|
||||
dashboard["meta"]["source"] = source
|
||||
dashboard["meta"]["requested_date"] = self._display_compact_date(normalized_date)
|
||||
dashboard = self._enrich_dashboard_sentiment(dashboard, normalized_date)
|
||||
record_count = self._record_count(dashboard)
|
||||
actual_date = normalize_date(
|
||||
@@ -233,8 +299,11 @@ class MarketServiceMixin:
|
||||
except TushareError as exc:
|
||||
fallback = self.database.get_latest_real_snapshot(normalized_date)
|
||||
if fallback:
|
||||
actual = str((fallback.get("meta") or {}).get("trade_date") or "")
|
||||
carried = self._carry_dashboard(
|
||||
fallback, normalized_date, f"最新行情暂不可用,沿用最近收盘快照:{exc}"
|
||||
fallback,
|
||||
normalized_date,
|
||||
self._preparing_display_notice(actual, normalized_date),
|
||||
)
|
||||
self.database.finish_sync(
|
||||
sync_id, "fallback", self._record_count(carried), str(exc), "tushare"
|
||||
@@ -1160,7 +1229,7 @@ class MarketServiceMixin:
|
||||
"storage": "sqlite",
|
||||
"cached": cached,
|
||||
}
|
||||
return result
|
||||
return self._annotate_data_status(result)
|
||||
|
||||
@staticmethod
|
||||
def _record_count(dashboard: dict[str, Any]) -> int:
|
||||
|
||||
Reference in New Issue
Block a user