| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- """
- 智能股票分析助手 — 巴菲特投资评估API路由
- 提供巴菲特价值投资框架查询、投资评估接口。
- """
- import asyncio
- import json
- from fastapi import APIRouter, Query
- from fastapi.responses import StreamingResponse
- from pydantic import BaseModel, Field
- from app.services import buffett_service
- from app.utils.response import success_response, error_response
- router = APIRouter(prefix="/buffett", tags=["巴菲特投资评估"])
- class BuffettEvaluateRequest(BaseModel):
- """巴菲特投资评估请求"""
- stock_code: str = Field(..., description="6位股票代码", min_length=4, max_length=10)
- stock_name: str = Field(default="", description="股票名称(可选)")
- include_market: bool = Field(default=True, description="是否包含行情数据")
- include_financial: bool = Field(default=True, description="是否包含财务数据")
- @router.get("/framework")
- async def get_buffett_framework():
- """获取巴菲特投资评估框架
- 返回完整的巴菲特价值投资思维体系,包括:
- - 8问快速筛选清单
- - 护城河分析五类型
- - 管理层评估三维度
- - 财务指标模板(ROIC、所有者收益、现金转化率)
- - 估值方法与安全边际
- - 风险评估分类
- - 卖出四条标准
- """
- result = buffett_service.get_buffett_framework()
- return success_response(
- data={
- "framework": result["framework"],
- "description": result["description"],
- },
- message="巴菲特投资评估框架已就绪",
- )
- @router.post("/evaluate")
- async def evaluate_stock(body: BuffettEvaluateRequest):
- """使用巴菲特投资框架评估股票
- 构建巴菲特风格的价值投资评估上下文,返回结构化评估模板和参考框架。
- - **stock_code**: 6位股票代码,如 600519(贵州茅台)
- - **stock_name**: 股票名称(可选,用于报告标题)
- - **include_market**: 是否尝试获取行情数据
- - **include_financial**: 是否尝试获取财务数据
- """
- if not body.stock_code or len(body.stock_code.strip()) < 4:
- return error_response(code=400, message="请输入有效的股票代码")
- # 构建数据上下文(尝试收集数据,不因单个数据失败而中断)
- data_context = {}
- errors = []
- if body.include_market:
- try:
- from app.services import market_service
- market_result = market_service.get_stock_quote(body.stock_code.strip())
- if market_result.get("success"):
- data_context["market"] = market_result
- except Exception as e:
- errors.append(f"行情数据获取失败: {e}")
- if body.include_financial:
- try:
- from app.services import market_service
- financial_result = market_service.get_stock_financial(body.stock_code.strip())
- if financial_result.get("success"):
- data_context["financial"] = financial_result
- except Exception as e:
- errors.append(f"财务数据获取失败: {e}")
- # 执行巴菲特评估
- result = buffett_service.evaluate_with_buffett(
- stock_code=body.stock_code.strip(),
- stock_name=body.stock_name.strip() or "",
- data_context=data_context,
- )
- if not result["success"]:
- return error_response(code=500, message=result.get("error", "评估失败"))
- data_warnings = ""
- if errors:
- data_warnings = "; ".join(errors)
- slim_ctx = buffett_service.slim_evaluation_context_for_api(result["evaluation_context"])
- return success_response(
- data={
- "stock_code": result["stock_code"],
- "stock_name": result["stock_name"],
- "evaluation_context": slim_ctx,
- "report_template": result["report_template"],
- "data_warnings": data_warnings or None,
- },
- message=f"已构建 {result['stock_name'] or result['stock_code']} 的巴菲特评估上下文",
- )
- @router.post("/report/generate-ai")
- async def generate_ai_buffett_report(body: BuffettEvaluateRequest):
- """一键生成巴菲特价值投资评估报告(LLM,同步 JSON)"""
- if not body.stock_code or len(body.stock_code.strip()) < 4:
- return error_response(code=400, message="请输入有效的股票代码")
- result = await asyncio.to_thread(
- buffett_service.generate_buffett_ai_report,
- body.stock_code.strip(),
- (body.stock_name or "").strip(),
- )
- if not result.get("success"):
- return error_response(
- code=503,
- message=result.get("error") or "AI 评估报告生成失败",
- data={"stock_code": body.stock_code.strip()},
- )
- return success_response(
- data={
- "stock_code": body.stock_code.strip(),
- "stock_name": (body.stock_name or "").strip(),
- "report_markdown": result["report_markdown"],
- },
- message="巴菲特 AI 评估报告已生成",
- )
- def _buffett_ai_report_ndjson_bytes(stock_code: str, stock_name: str):
- for evt in buffett_service.iter_buffett_ai_report_events(stock_code, stock_name):
- line = json.dumps(evt, ensure_ascii=False) + "\n"
- yield line.encode("utf-8")
- @router.post("/report/generate-ai/stream")
- async def stream_ai_buffett_report(body: BuffettEvaluateRequest):
- """流式生成巴菲特 AI 评估报告(NDJSON:meta → delta → … → done)"""
- if not body.stock_code or len(body.stock_code.strip()) < 4:
- return error_response(code=400, message="请输入有效的股票代码")
- return StreamingResponse(
- _buffett_ai_report_ndjson_bytes(
- body.stock_code.strip(),
- (body.stock_name or "").strip(),
- ),
- media_type="application/x-ndjson",
- headers={
- "Cache-Control": "no-cache",
- "Connection": "keep-alive",
- "X-Accel-Buffering": "no",
- },
- )
- @router.post("/evaluate/stream")
- async def stream_buffett_evaluate(body: BuffettEvaluateRequest):
- """实现方案约定路径 POST /api/v1/buffett/evaluate/stream,等价于 report/generate-ai/stream"""
- if not body.stock_code or len(body.stock_code.strip()) < 4:
- return error_response(code=400, message="请输入有效的股票代码")
- return StreamingResponse(
- _buffett_ai_report_ndjson_bytes(
- body.stock_code.strip(),
- (body.stock_name or "").strip(),
- ),
- media_type="application/x-ndjson",
- headers={
- "Cache-Control": "no-cache",
- "Connection": "keep-alive",
- "X-Accel-Buffering": "no",
- },
- )
- @router.get("/report/template")
- async def get_report_template(
- code: str = Query(..., description="股票代码", min_length=4),
- name: str = Query(default="", description="股票名称"),
- ):
- """获取巴菲特风格评估报告模板
- 返回包含所有必填章节的Markdown格式报告模板,可直接用于填写分析结果。
- - **code**: 6位股票代码
- - **name**: 股票名称(可选)
- """
- template = buffett_service._build_buffett_report_template(code, name)
- return success_response(
- data={"template": template, "stock_code": code, "stock_name": name},
- message="报告模板已生成",
- )
- @router.get("/reference/{ref_name}")
- async def get_reference_file(
- ref_name: str,
- ):
- """获取巴菲特投资思维参考文件内容
- 可获取的参考文件:
- - 01-thinking-frameworks (思维框架)
- - 02-investment-philosophy (投资哲学)
- - 03-business-moat (企业护城河)
- - 04-management-governance (管理层治理)
- - 05-financial-metrics (财务指标)
- - 06-valuation-capital (估值与资本)
- - 07-risk-behavior (风险与行为)
- - 08-industry-playbooks (行业手册)
- - **ref_name**: 参考文件名,如 "03-business-moat"
- """
- content = buffett_service.load_buffett_reference(ref_name)
- if content is None:
- return error_response(code=404, message=f"参考文件 '{ref_name}' 不存在或无法读取")
- # 截取前5000字符返回
- preview = content[:5000]
- is_truncated = len(content) > 5000
- return success_response(
- data={
- "ref_name": ref_name,
- "content": preview,
- "full_length": len(content),
- "truncated": is_truncated,
- },
- message=f"参考文件 {ref_name}({'预览前5000字符' if is_truncated else '完整内容'})",
- )
|