refactor: establish standalone application boundary

This commit is contained in:
leefer
2026-08-03 21:42:25 +08:00
parent 656f28a96d
commit d6def3af15
322 changed files with 73872 additions and 44656 deletions
@@ -0,0 +1,156 @@
from __future__ import annotations
import copy
from datetime import datetime, timedelta
from typing import Any
from backend.data.numbers import non_nan_number as _number
from backend.data.providers.tushare_client import TushareError
from backend.features.market.insights_context import _display_date
class MarketPopularityInsightsMixin:
def popularity(self, requested_date: str, force: bool = False) -> dict[str, Any]:
trade_date, previous_date = self._trade_context(requested_date)
if not force:
cached = self.database.get_data_snapshot("popularity_v1", trade_date)
if cached:
result = copy.deepcopy(cached)
result["meta"] = {**result.get("meta", {}), "cached": True}
return result
ths_rows, dc_rows, errors = self._hot_rows(trade_date)
actual_date = trade_date
carried_forward = False
if not ths_rows and not dc_rows and previous_date:
ths_rows, dc_rows, errors = self._hot_rows(previous_date)
actual_date = previous_date
carried_forward = bool(ths_rows or dc_rows)
if not ths_rows and not dc_rows:
fallback = self._latest_feature_snapshot("popularity_v1", trade_date)
if fallback:
result = copy.deepcopy(fallback)
result["meta"] = {
**result.get("meta", {}),
"requested_date": _display_date(requested_date),
"carried_forward": True,
"cached": True,
"notice": "当前榜单暂不可用,展示最近有效快照",
}
return result
return {
"meta": {
"requested_date": _display_date(requested_date),
"trade_date": _display_date(trade_date),
"previous_trade_date": _display_date(previous_date),
"carried_forward": False,
"cached": False,
"notice": "该交易日暂无可用人气榜",
"updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
},
"summary": {"ths_count": 0, "dc_count": 0, "dual_count": 0},
"combined": [], "ths": [], "dc": [],
}
prior_request = (datetime.strptime(actual_date, "%Y%m%d") - timedelta(days=1)).strftime("%Y%m%d")
prior_date, _ = self._trade_context(prior_request)
previous_ths, previous_dc, _ = self._hot_rows(prior_date)
ths = self._normalize_hot(ths_rows, "热股", previous_ths)
dc = self._normalize_hot(dc_rows, "A股市场", previous_dc)
ths_map = {item["ts_code"]: item for item in ths}
dc_map = {item["ts_code"]: item for item in dc}
combined = []
for ts_code in set(ths_map) | set(dc_map):
ths_item = ths_map.get(ts_code)
dc_item = dc_map.get(ts_code)
base = ths_item or dc_item or {}
ths_rank = int(ths_item["rank"]) if ths_item else None
dc_rank = int(dc_item["rank"]) if dc_item else None
score = (
(101 - (ths_rank or 101)) * 0.5
+ (201 - (dc_rank or 201)) * 0.25
)
combined.append(
{
**base,
"ths_rank": ths_rank,
"dc_rank": dc_rank,
"score": round(score, 2),
"dual_source": bool(ths_item and dc_item),
"concepts": (ths_item or {}).get("concepts") or [],
}
)
combined.sort(key=lambda item: (item["dual_source"], item["score"]), reverse=True)
for index, item in enumerate(combined, 1):
item["rank"] = index
result = {
"meta": {
"requested_date": _display_date(requested_date),
"trade_date": _display_date(actual_date),
"previous_trade_date": _display_date(prior_date),
"carried_forward": carried_forward,
"cached": False,
"updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
"notice": "".join(errors),
},
"summary": {
"ths_count": len(ths),
"dc_count": len(dc),
"dual_count": sum(item["dual_source"] for item in combined),
},
"combined": combined[:200],
"ths": ths,
"dc": dc,
}
self.database.save_data_snapshot("popularity_v1", trade_date, "market", result)
return result
def _hot_rows(self, trade_date: str) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[str]]:
errors = []
try:
ths = self.client.query("ths_hot", {"trade_date": trade_date})
except TushareError:
ths = []
errors.append("同花顺榜单暂不可用")
try:
dc = self.client.query("dc_hot", {"trade_date": trade_date})
except TushareError:
dc = []
errors.append("东方财富榜单暂不可用")
return ths, dc, errors
def _normalize_hot(
self,
rows: list[dict[str, Any]],
data_type: str,
previous_rows: list[dict[str, Any]],
) -> list[dict[str, Any]]:
previous = {
str(row.get("ts_code") or ""): int(_number(row.get("rank")))
for row in previous_rows
if str(row.get("data_type") or "") == data_type
}
items = []
for row in rows:
if str(row.get("data_type") or "") != data_type:
continue
rank = int(_number(row.get("rank")))
ts_code = str(row.get("ts_code") or "")
prior_rank = previous.get(ts_code)
items.append(
{
"rank": rank,
"ts_code": ts_code,
"code": ts_code.split(".")[0],
"name": str(row.get("ts_name") or "--"),
"change": round(_number(row.get("pct_change")), 2),
"price": round(_number(row.get("current_price")), 2),
"hot": round(_number(row.get("hot")), 1),
"rank_change": (prior_rank - rank) if prior_rank else None,
"concepts": self._parse_concepts(row.get("concept")),
"reason": str(row.get("rank_reason") or ""),
"rank_time": str(row.get("rank_time") or ""),
}
)
items.sort(key=lambda item: item["rank"])
return items