"""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: # issue_number:按报告日期序数作为总期号 row = conn.execute( """SELECT r.*, (SELECT COUNT(*) FROM ai_reports WHERE trade_date <= r.trade_date) AS issue_number FROM ai_reports r ORDER BY r.trade_date DESC, r.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, " "generation_count, created_at, updated_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})