110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
"""AI 分析报告路由(/api/ai-analysis)
|
||
|
||
提供:
|
||
- 报告列表
|
||
- 报告详情
|
||
- 手动触发分析(12小时频率限制)
|
||
- 重新生成报告
|
||
- 检查某日是否有报告
|
||
"""
|
||
|
||
import json
|
||
from datetime import datetime, timezone, timedelta
|
||
|
||
from fastapi import APIRouter, HTTPException
|
||
from fastapi.responses import JSONResponse
|
||
|
||
from database import get_connection, dict_from_row
|
||
|
||
_CST = timezone(timedelta(hours=8))
|
||
_NO_CACHE_HEADERS = {"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0"}
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
def _has_ai_report(trade_date: str) -> bool:
|
||
conn = get_connection()
|
||
try:
|
||
row = conn.execute(
|
||
"SELECT 1 FROM ai_reports WHERE trade_date = ? AND report_type = 'daily' LIMIT 1",
|
||
(trade_date,)
|
||
).fetchone()
|
||
return row is not None
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
def _can_trigger(trade_date: str) -> tuple[bool, str]:
|
||
"""检查是否可以触发分析(同一交易日内可随时重新生成)"""
|
||
return True, ""
|
||
|
||
|
||
@router.get("/ai-analysis/latest", summary="最新 AI 分析报告")
|
||
async def get_latest_report():
|
||
conn = get_connection()
|
||
try:
|
||
row = conn.execute(
|
||
"SELECT * FROM ai_reports ORDER BY trade_date DESC, id DESC LIMIT 1"
|
||
).fetchone()
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="暂无分析报告")
|
||
item = dict_from_row(row)
|
||
item["toolsUsed"] = json.loads(item.pop("tools_used") or "[]")
|
||
return JSONResponse({"data": item}, headers=_NO_CACHE_HEADERS)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
@router.get("/ai-analysis", summary="AI 分析报告列表")
|
||
async def list_reports():
|
||
conn = get_connection()
|
||
try:
|
||
rows = conn.execute(
|
||
"SELECT id, trade_date, report_type, title, summary, tools_used, model, tokens_used, created_at "
|
||
"FROM ai_reports ORDER BY trade_date DESC, id DESC LIMIT 50"
|
||
).fetchall()
|
||
items = []
|
||
for r in rows:
|
||
item = dict_from_row(r)
|
||
item["toolsUsed"] = json.loads(item.pop("tools_used") or "[]")
|
||
items.append(item)
|
||
return JSONResponse({"data": items}, headers=_NO_CACHE_HEADERS)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
@router.get("/ai-analysis/{report_id}", summary="AI 分析报告详情")
|
||
async def get_report(report_id: int):
|
||
conn = get_connection()
|
||
try:
|
||
row = conn.execute("SELECT * FROM ai_reports WHERE id = ?", (report_id,)).fetchone()
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="报告不存在")
|
||
item = dict_from_row(row)
|
||
item["toolsUsed"] = json.loads(item.pop("tools_used") or "[]")
|
||
return JSONResponse({"data": item}, headers=_NO_CACHE_HEADERS)
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
@router.get("/ai-analysis/check/{trade_date}", summary="检查某日是否有 AI 分析报告")
|
||
async def check_report(trade_date: str):
|
||
has = _has_ai_report(trade_date)
|
||
return JSONResponse({"data": {"hasReport": has, "tradeDate": trade_date}})
|
||
|
||
|
||
@router.post("/ai-analysis/{report_id}/regenerate", summary="重新生成 AI 分析报告")
|
||
async def regenerate_report(report_id: int):
|
||
conn = get_connection()
|
||
try:
|
||
row = conn.execute("SELECT trade_date FROM ai_reports WHERE id = ?", (report_id,)).fetchone()
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="报告不存在")
|
||
trade_date = row["trade_date"]
|
||
finally:
|
||
conn.close()
|
||
|
||
from services.ai_service import collect_ai_analysis
|
||
result = await collect_ai_analysis(trade_date)
|
||
return JSONResponse({"data": result})
|