Files
auv/backend/routes/themes.py
T
Sakurasan bb2be17ae2 feat: 热点穿透题材数从50提升到100,支持缓存定向清理
- 热点穿透top由50提升到100(涨幅榜+热度榜各取前100合并),保证题材数≥100
- themes.py graph路由top上限le由60放宽到200
- daily_collector HOTMAP_TOP_N同步到100与页面一致
- auvops.sh新增cache-clear-graph子命令定向清理theme_graph缓存
2026-08-18 04:14:17 +08:00

114 lines
4.1 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 fastapi import APIRouter, Query, HTTPException
from fastapi.responses import JSONResponse
from database import get_connection, dict_from_row
from services import themes
router = APIRouter()
# 上游反代/CDN 可能按 path 缓存,显式禁止缓存
_NO_CACHE_HEADERS = {"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0"}
@router.get("", summary="题材列表")
async def theme_list(
sort_field: int = Query(1, ge=1, le=5, description="排序字段:1=涨幅 3=强度 4=热度排名 5=成交额"),
asc: bool = Query(False, description="True=升序, False=降序"),
):
if sort_field not in (1, 3, 4, 5):
raise HTTPException(status_code=400, detail="排序字段仅支持 1/3/4/5")
data = await themes.fetch_theme_list(sort_field, asc)
return JSONResponse(
{"data": data, "count": len(data), "sort_field": sort_field, "asc": asc},
headers=_NO_CACHE_HEADERS,
)
@router.get("/graph", summary="热点穿透:题材-股票网状关系图")
async def theme_graph(
sort_field: int = Query(1, description="题材排序:1=涨幅 4=热度"),
top: int = Query(30, ge=1, le=200, description="题材数量"),
limit: int = Query(1000, ge=100, le=2000, description="下发的股票节点上限(按穿透度取前 N 只)"),
):
if sort_field not in (1, 4):
raise HTTPException(status_code=400, detail="排序字段仅支持 1(涨幅)/4(热度)")
result = await themes.fetch_theme_graph(sort_field, top, limit)
return JSONResponse(result, headers=_NO_CACHE_HEADERS)
@router.get("/history", summary="指定交易日题材涨幅前20")
async def theme_history(date: str = Query(..., description="交易日 YYYY-MM-DD")):
conn = get_connection()
try:
rows = conn.execute(
"SELECT * FROM daily_top_themes WHERE trade_date = ? ORDER BY rank ASC", (date,)
).fetchall()
items = [dict_from_row(r) for r in rows]
return JSONResponse({"date": date, "items": items}, headers=_NO_CACHE_HEADERS)
finally:
conn.close()
@router.get("/{theme_code}/news", summary="题材相关新闻(分页)")
async def theme_news(
theme_code: str,
page_num: int = Query(1, ge=1, description="页码"),
page_size: int = Query(10, ge=1, le=50, description="每页条数"),
max_eu_time: str = Query("", description="分页游标(上一页返回的 maxEuTime)"),
):
result = await themes.fetch_theme_news(theme_code, page_num, max_eu_time, page_size)
if result is None:
return JSONResponse(
{"data": None, "theme_code": theme_code},
headers=_NO_CACHE_HEADERS,
)
return JSONResponse(
{"data": result, "theme_code": theme_code},
headers=_NO_CACHE_HEADERS,
)
@router.get("/{theme_code}/quote", summary="单题材实时行情(强度/热度/涨幅)")
async def theme_quote(theme_code: str):
result = await themes.fetch_theme_quote(theme_code)
if result is None:
return JSONResponse(
{"data": None, "theme_code": theme_code},
headers=_NO_CACHE_HEADERS,
)
return JSONResponse(
{"data": result, "theme_code": theme_code},
headers=_NO_CACHE_HEADERS,
)
@router.get("/{theme_code}/detail", summary="题材详情")
async def theme_detail(theme_code: str):
data = await themes.fetch_theme_detail(theme_code)
if not data:
return JSONResponse(
{"data": None, "theme_code": theme_code},
headers=_NO_CACHE_HEADERS,
)
return JSONResponse(
{"data": data, "theme_code": theme_code},
headers=_NO_CACHE_HEADERS,
)
@router.get("/{theme_code}/stocks", summary="题材相关股票(全部)")
async def theme_stocks(theme_code: str):
result = await themes.fetch_theme_stocks(theme_code)
return JSONResponse(
{
"data": result.get("stockList", []),
"statistic": result.get("statistic", {}),
"total": result.get("total", 0),
"theme_code": theme_code,
},
headers=_NO_CACHE_HEADERS,
)