buffett.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. """
  2. 智能股票分析助手 — 巴菲特投资评估API路由
  3. 提供巴菲特价值投资框架查询、投资评估接口。
  4. """
  5. import asyncio
  6. import json
  7. from fastapi import APIRouter, Query
  8. from fastapi.responses import StreamingResponse
  9. from pydantic import BaseModel, Field
  10. from app.services import buffett_service
  11. from app.utils.response import success_response, error_response
  12. router = APIRouter(prefix="/buffett", tags=["巴菲特投资评估"])
  13. class BuffettEvaluateRequest(BaseModel):
  14. """巴菲特投资评估请求"""
  15. stock_code: str = Field(..., description="6位股票代码", min_length=4, max_length=10)
  16. stock_name: str = Field(default="", description="股票名称(可选)")
  17. include_market: bool = Field(default=True, description="是否包含行情数据")
  18. include_financial: bool = Field(default=True, description="是否包含财务数据")
  19. @router.get("/framework")
  20. async def get_buffett_framework():
  21. """获取巴菲特投资评估框架
  22. 返回完整的巴菲特价值投资思维体系,包括:
  23. - 8问快速筛选清单
  24. - 护城河分析五类型
  25. - 管理层评估三维度
  26. - 财务指标模板(ROIC、所有者收益、现金转化率)
  27. - 估值方法与安全边际
  28. - 风险评估分类
  29. - 卖出四条标准
  30. """
  31. result = buffett_service.get_buffett_framework()
  32. return success_response(
  33. data={
  34. "framework": result["framework"],
  35. "description": result["description"],
  36. },
  37. message="巴菲特投资评估框架已就绪",
  38. )
  39. @router.post("/evaluate")
  40. async def evaluate_stock(body: BuffettEvaluateRequest):
  41. """使用巴菲特投资框架评估股票
  42. 构建巴菲特风格的价值投资评估上下文,返回结构化评估模板和参考框架。
  43. - **stock_code**: 6位股票代码,如 600519(贵州茅台)
  44. - **stock_name**: 股票名称(可选,用于报告标题)
  45. - **include_market**: 是否尝试获取行情数据
  46. - **include_financial**: 是否尝试获取财务数据
  47. """
  48. if not body.stock_code or len(body.stock_code.strip()) < 4:
  49. return error_response(code=400, message="请输入有效的股票代码")
  50. # 构建数据上下文(尝试收集数据,不因单个数据失败而中断)
  51. data_context = {}
  52. errors = []
  53. if body.include_market:
  54. try:
  55. from app.services import market_service
  56. market_result = market_service.get_stock_quote(body.stock_code.strip())
  57. if market_result.get("success"):
  58. data_context["market"] = market_result
  59. except Exception as e:
  60. errors.append(f"行情数据获取失败: {e}")
  61. if body.include_financial:
  62. try:
  63. from app.services import market_service
  64. financial_result = market_service.get_stock_financial(body.stock_code.strip())
  65. if financial_result.get("success"):
  66. data_context["financial"] = financial_result
  67. except Exception as e:
  68. errors.append(f"财务数据获取失败: {e}")
  69. # 执行巴菲特评估
  70. result = buffett_service.evaluate_with_buffett(
  71. stock_code=body.stock_code.strip(),
  72. stock_name=body.stock_name.strip() or "",
  73. data_context=data_context,
  74. )
  75. if not result["success"]:
  76. return error_response(code=500, message=result.get("error", "评估失败"))
  77. data_warnings = ""
  78. if errors:
  79. data_warnings = "; ".join(errors)
  80. slim_ctx = buffett_service.slim_evaluation_context_for_api(result["evaluation_context"])
  81. return success_response(
  82. data={
  83. "stock_code": result["stock_code"],
  84. "stock_name": result["stock_name"],
  85. "evaluation_context": slim_ctx,
  86. "report_template": result["report_template"],
  87. "data_warnings": data_warnings or None,
  88. },
  89. message=f"已构建 {result['stock_name'] or result['stock_code']} 的巴菲特评估上下文",
  90. )
  91. @router.post("/report/generate-ai")
  92. async def generate_ai_buffett_report(body: BuffettEvaluateRequest):
  93. """一键生成巴菲特价值投资评估报告(LLM,同步 JSON)"""
  94. if not body.stock_code or len(body.stock_code.strip()) < 4:
  95. return error_response(code=400, message="请输入有效的股票代码")
  96. result = await asyncio.to_thread(
  97. buffett_service.generate_buffett_ai_report,
  98. body.stock_code.strip(),
  99. (body.stock_name or "").strip(),
  100. )
  101. if not result.get("success"):
  102. return error_response(
  103. code=503,
  104. message=result.get("error") or "AI 评估报告生成失败",
  105. data={"stock_code": body.stock_code.strip()},
  106. )
  107. return success_response(
  108. data={
  109. "stock_code": body.stock_code.strip(),
  110. "stock_name": (body.stock_name or "").strip(),
  111. "report_markdown": result["report_markdown"],
  112. },
  113. message="巴菲特 AI 评估报告已生成",
  114. )
  115. def _buffett_ai_report_ndjson_bytes(stock_code: str, stock_name: str):
  116. for evt in buffett_service.iter_buffett_ai_report_events(stock_code, stock_name):
  117. line = json.dumps(evt, ensure_ascii=False) + "\n"
  118. yield line.encode("utf-8")
  119. @router.post("/report/generate-ai/stream")
  120. async def stream_ai_buffett_report(body: BuffettEvaluateRequest):
  121. """流式生成巴菲特 AI 评估报告(NDJSON:meta → delta → … → done)"""
  122. if not body.stock_code or len(body.stock_code.strip()) < 4:
  123. return error_response(code=400, message="请输入有效的股票代码")
  124. return StreamingResponse(
  125. _buffett_ai_report_ndjson_bytes(
  126. body.stock_code.strip(),
  127. (body.stock_name or "").strip(),
  128. ),
  129. media_type="application/x-ndjson",
  130. headers={
  131. "Cache-Control": "no-cache",
  132. "Connection": "keep-alive",
  133. "X-Accel-Buffering": "no",
  134. },
  135. )
  136. @router.post("/evaluate/stream")
  137. async def stream_buffett_evaluate(body: BuffettEvaluateRequest):
  138. """实现方案约定路径 POST /api/v1/buffett/evaluate/stream,等价于 report/generate-ai/stream"""
  139. if not body.stock_code or len(body.stock_code.strip()) < 4:
  140. return error_response(code=400, message="请输入有效的股票代码")
  141. return StreamingResponse(
  142. _buffett_ai_report_ndjson_bytes(
  143. body.stock_code.strip(),
  144. (body.stock_name or "").strip(),
  145. ),
  146. media_type="application/x-ndjson",
  147. headers={
  148. "Cache-Control": "no-cache",
  149. "Connection": "keep-alive",
  150. "X-Accel-Buffering": "no",
  151. },
  152. )
  153. @router.get("/report/template")
  154. async def get_report_template(
  155. code: str = Query(..., description="股票代码", min_length=4),
  156. name: str = Query(default="", description="股票名称"),
  157. ):
  158. """获取巴菲特风格评估报告模板
  159. 返回包含所有必填章节的Markdown格式报告模板,可直接用于填写分析结果。
  160. - **code**: 6位股票代码
  161. - **name**: 股票名称(可选)
  162. """
  163. template = buffett_service._build_buffett_report_template(code, name)
  164. return success_response(
  165. data={"template": template, "stock_code": code, "stock_name": name},
  166. message="报告模板已生成",
  167. )
  168. @router.get("/reference/{ref_name}")
  169. async def get_reference_file(
  170. ref_name: str,
  171. ):
  172. """获取巴菲特投资思维参考文件内容
  173. 可获取的参考文件:
  174. - 01-thinking-frameworks (思维框架)
  175. - 02-investment-philosophy (投资哲学)
  176. - 03-business-moat (企业护城河)
  177. - 04-management-governance (管理层治理)
  178. - 05-financial-metrics (财务指标)
  179. - 06-valuation-capital (估值与资本)
  180. - 07-risk-behavior (风险与行为)
  181. - 08-industry-playbooks (行业手册)
  182. - **ref_name**: 参考文件名,如 "03-business-moat"
  183. """
  184. content = buffett_service.load_buffett_reference(ref_name)
  185. if content is None:
  186. return error_response(code=404, message=f"参考文件 '{ref_name}' 不存在或无法读取")
  187. # 截取前5000字符返回
  188. preview = content[:5000]
  189. is_truncated = len(content) > 5000
  190. return success_response(
  191. data={
  192. "ref_name": ref_name,
  193. "content": preview,
  194. "full_length": len(content),
  195. "truncated": is_truncated,
  196. },
  197. message=f"参考文件 {ref_name}({'预览前5000字符' if is_truncated else '完整内容'})",
  198. )