refactor: establish standalone application boundary
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
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()]
|
||||
Reference in New Issue
Block a user