Files
xiaobaifupan/next/backend/features/market/insights/popularity.py
T

112 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import json
from typing import Any
def build_popularity(
trade_date: str,
ths_rows: tuple[dict[str, Any], ...],
dc_rows: tuple[dict[str, Any], ...],
previous_ths: tuple[dict[str, Any], ...],
previous_dc: tuple[dict[str, Any], ...],
) -> dict[str, Any]:
ths = _normalize(ths_rows, "热股", previous_ths)
dc = _normalize(dc_rows, "A股市场", previous_dc)
ths_map = {str(item["identifier"]): item for item in ths}
dc_map = {str(item["identifier"]): item for item in dc}
combined = []
for identifier in set(ths_map) | set(dc_map):
ths_item = ths_map.get(identifier)
dc_item = dc_map.get(identifier)
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": list((ths_item or {}).get("concepts") or []),
}
)
combined.sort(
key=lambda item: (bool(item["dual_source"]), float(item["score"])), reverse=True
)
for rank, item in enumerate(combined, start=1):
item["rank"] = rank
return {
"trade_date": trade_date,
"summary": {
"ths_count": len(ths),
"dc_count": len(dc),
"dual_count": sum(bool(item["dual_source"]) for item in combined),
},
"combined": combined[:200],
"ths": ths,
"dc": dc,
}
def _normalize(
rows: tuple[dict[str, Any], ...],
data_type: str,
previous_rows: tuple[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
identifier = str(row.get("ts_code") or "")
rank = int(_number(row.get("rank")))
if not identifier or rank <= 0:
continue
prior = previous.get(identifier)
items.append(
{
"rank": rank,
"identifier": identifier,
"code": identifier.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 if prior else None,
"concepts": _concepts(row.get("concept")),
"reason": str(row.get("rank_reason") or ""),
"rank_time": str(row.get("rank_time") or ""),
}
)
return sorted(items, key=lambda item: int(item["rank"]))
def _concepts(value: Any) -> list[str]:
if isinstance(value, list):
return [str(item).strip() 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).strip() for item in parsed if str(item).strip()]
except json.JSONDecodeError:
pass
return [part.strip() for part in text.replace("", ",").split(",") if part.strip()]
def _number(value: Any) -> float:
try:
number = float(value)
return number if number == number else 0.0
except (TypeError, ValueError):
return 0.0