from __future__ import annotations import json from datetime import datetime, timedelta, timezone from typing import TYPE_CHECKING, Any, Callable from backend.data.providers.ifind_client import IfindHttpClient from backend.data.providers.tushare_client import TushareClient, TushareError if TYPE_CHECKING: from database import ReviewDatabase CHINA_TIMEZONE = timezone(timedelta(hours=8)) def _display_date(value: str) -> str: text = str(value or "").replace("-", "") if len(text) != 8: return str(value or "") return f"{text[:4]}-{text[4:6]}-{text[6:]}" class MarketInsightsContextMixin: def __init__( self, database: ReviewDatabase, client: TushareClient, now_provider: Callable[[], datetime] | None = None, ifind: IfindHttpClient | None = None, ) -> None: self.database = database self.client = client self._now_provider = now_provider or (lambda: datetime.now(CHINA_TIMEZONE)) self.ifind = ifind def _trade_context(self, requested_date: str) -> tuple[str, str]: """Resolve trading dates without making cached feature pages depend on Tushare uptime.""" requested = str(requested_date or "").replace("-", "") try: return self.client.resolve_trade_context(requested) except TushareError: latest = self.database.get_latest_real_snapshot(requested) or {} trade_date = str( (latest.get("meta") or {}).get("trade_date") or latest.get("_snapshot_date") or requested ).replace("-", "") previous = self.database.get_latest_real_snapshot(trade_date, strictly_before=True) or {} previous_date = str( (previous.get("meta") or {}).get("trade_date") or previous.get("_snapshot_date") or "" ).replace("-", "") return trade_date, previous_date def _latest_feature_snapshot(self, kind: str, trade_date: str) -> dict[str, Any] | None: return self.database.get_latest_data_snapshot(kind, "", trade_date) def _stock_master(self) -> dict[str, dict[str, Any]]: rows = self.database.list_stock_master() if not rows: rows = self.client.query( "stock_basic", {"list_status": "L"}, "ts_code,name,industry,market,list_date", ) self.database.upsert_stock_master(rows) rows = self.database.list_stock_master() return {str(row.get("ts_code") or ""): row for row in rows} @staticmethod def _parse_concepts(value: Any) -> list[str]: if isinstance(value, list): return [str(item) for item in value if str(item).strip()] text = str(value or "").strip() if not text: return [] try: parsed = json.loads(text) if isinstance(parsed, list): return [str(item) for item in parsed if str(item).strip()] except json.JSONDecodeError: pass return [part.strip() for part in text.split(",") if part.strip()]