Fundamental_Analysis/backend/app/services/report_generator.py

643 lines
26 KiB
Python

"""
报告生成服务
处理股票基本面分析报告的生成和管理
"""
from typing import Dict, Any, Optional, List
from uuid import UUID
from datetime import datetime
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
import asyncio
import logging
from ..models.report import Report
from ..models.analysis_module import AnalysisModule
from ..schemas.report import ReportResponse, AnalysisModuleSchema
from ..core.exceptions import (
ReportGenerationError,
DataSourceError,
AIAnalysisError,
DatabaseError
)
from .progress_tracker import ProgressTracker
from .data_fetcher import DataFetcherFactory
from .ai_analyzer import AIAnalyzerFactory
from .config_manager import ConfigManager
logger = logging.getLogger(__name__)
class AnalysisModuleType:
"""分析模块类型常量"""
TRADING_VIEW_CHART = "trading_view_chart"
FINANCIAL_DATA = "financial_data"
BUSINESS_INFO = "business_info"
FUNDAMENTAL_ANALYSIS = "fundamental_analysis"
BULLISH_ANALYSIS = "bullish_analysis"
BEARISH_ANALYSIS = "bearish_analysis"
MARKET_ANALYSIS = "market_analysis"
NEWS_ANALYSIS = "news_analysis"
TRADING_ANALYSIS = "trading_analysis"
INSIDER_ANALYSIS = "insider_analysis"
FINAL_CONCLUSION = "final_conclusion"
class ReportGenerator:
"""报告生成器"""
def __init__(self, db_session: AsyncSession, config_manager: ConfigManager):
self.db = db_session
self.config_manager = config_manager
self.progress_tracker = ProgressTracker(db_session)
# 定义分析模块配置
self.analysis_modules = [
{
"type": AnalysisModuleType.TRADING_VIEW_CHART,
"title": "TradingView图表",
"order": 1,
"step_name": "获取财务数据"
},
{
"type": AnalysisModuleType.FINANCIAL_DATA,
"title": "财务数据分析",
"order": 2,
"step_name": "获取财务数据"
},
{
"type": AnalysisModuleType.BUSINESS_INFO,
"title": "业务信息分析",
"order": 3,
"step_name": "生成业务信息"
},
{
"type": AnalysisModuleType.FUNDAMENTAL_ANALYSIS,
"title": "基本面分析(景林模型)",
"order": 4,
"step_name": "执行基本面分析"
},
{
"type": AnalysisModuleType.BULLISH_ANALYSIS,
"title": "看涨分析师观点",
"order": 5,
"step_name": "执行看涨分析"
},
{
"type": AnalysisModuleType.BEARISH_ANALYSIS,
"title": "看跌分析师观点",
"order": 6,
"step_name": "执行看跌分析"
},
{
"type": AnalysisModuleType.MARKET_ANALYSIS,
"title": "市场分析师观点",
"order": 7,
"step_name": "执行市场分析"
},
{
"type": AnalysisModuleType.NEWS_ANALYSIS,
"title": "新闻分析师观点",
"order": 8,
"step_name": "执行新闻分析"
},
{
"type": AnalysisModuleType.TRADING_ANALYSIS,
"title": "交易分析师观点",
"order": 9,
"step_name": "执行交易分析"
},
{
"type": AnalysisModuleType.INSIDER_ANALYSIS,
"title": "内部人与机构动向分析",
"order": 10,
"step_name": "执行内部人分析"
},
{
"type": AnalysisModuleType.FINAL_CONCLUSION,
"title": "最终结论",
"order": 11,
"step_name": "生成最终结论"
}
]
async def generate_report(self, symbol: str, market: str, force_regenerate: bool = False) -> ReportResponse:
"""生成股票分析报告"""
try:
# 检查是否存在现有报告
existing_report = await self._get_existing_report(symbol, market)
if existing_report and not force_regenerate:
if existing_report.status == "completed":
logger.info(f"返回现有报告: {symbol} ({market})")
return await self._build_report_response(existing_report)
elif existing_report.status == "generating":
logger.info(f"报告正在生成中: {symbol} ({market})")
return await self._build_report_response(existing_report)
# 创建新报告或重新生成
if existing_report and force_regenerate:
report = existing_report
report.status = "generating"
report.updated_at = datetime.utcnow()
# 清理现有的分析模块
await self._cleanup_existing_modules(report.id)
else:
report = await self._create_new_report(symbol, market)
# 初始化进度追踪
await self.progress_tracker.initialize_progress(report.id)
# 异步生成报告内容
asyncio.create_task(self._generate_report_content(report))
return await self._build_report_response(report)
except Exception as e:
logger.error(f"报告生成失败: {symbol} ({market}) - {str(e)}")
if isinstance(e, (ReportGenerationError, DataSourceError, AIAnalysisError)):
raise
raise ReportGenerationError(f"报告生成失败: {str(e)}")
async def get_report(self, symbol: str, market: str) -> Optional[ReportResponse]:
"""获取现有报告"""
try:
report = await self._get_existing_report(symbol, market)
if report:
return await self._build_report_response(report)
return None
except Exception as e:
logger.error(f"获取报告失败: {symbol} ({market}) - {str(e)}")
raise ReportGenerationError(f"获取报告失败: {str(e)}")
async def get_report_by_id(self, report_id: UUID) -> Optional[ReportResponse]:
"""根据ID获取报告"""
try:
result = await self.db.execute(
select(Report).where(Report.id == report_id)
)
report = result.scalar_one_or_none()
if report:
return await self._build_report_response(report)
return None
except Exception as e:
logger.error(f"获取报告失败: {report_id} - {str(e)}")
raise ReportGenerationError(f"获取报告失败: {str(e)}")
async def _get_existing_report(self, symbol: str, market: str) -> Optional[Report]:
"""获取现有报告"""
result = await self.db.execute(
select(Report).where(
Report.symbol == symbol,
Report.market == market
)
)
return result.scalar_one_or_none()
async def _create_new_report(self, symbol: str, market: str) -> Report:
"""创建新报告"""
report = Report(
symbol=symbol,
market=market,
status="generating"
)
self.db.add(report)
await self.db.flush()
return report
async def _cleanup_existing_modules(self, report_id: UUID):
"""清理现有的分析模块"""
result = await self.db.execute(
select(AnalysisModule).where(AnalysisModule.report_id == report_id)
)
modules = result.scalars().all()
for module in modules:
await self.db.delete(module)
await self.db.flush()
async def _generate_report_content(self, report: Report):
"""生成报告内容(异步执行)"""
try:
logger.info(f"开始生成报告内容: {report.symbol} ({report.market})")
# 开始初始化步骤
await self.progress_tracker.start_step(report.id, "初始化报告")
# 创建分析模块记录
await self._create_analysis_modules(report)
await self.progress_tracker.complete_step(report.id, "初始化报告", True)
# 获取配置
data_source_config = await self.config_manager.get_data_source_config(report.market)
gemini_config = await self.config_manager.get_gemini_config()
# 创建数据获取器和AI分析器
data_fetcher = DataFetcherFactory.create_fetcher(
data_source_config["type"],
data_source_config
)
ai_analyzer = AIAnalyzerFactory.create_gemini_analyzer(
gemini_config["api_key"],
gemini_config
)
# 存储分析结果的上下文
analysis_context = {}
# 按顺序执行各个分析模块
for module_config in self.analysis_modules:
try:
await self._execute_analysis_module(
report, module_config, data_fetcher, ai_analyzer, analysis_context
)
except Exception as e:
logger.error(f"分析模块执行失败: {module_config['type']} - {str(e)}")
# 标记模块为失败,但继续执行其他模块
await self._mark_module_failed(report.id, module_config["type"], str(e))
# 完成报告生成
await self.progress_tracker.start_step(report.id, "保存报告")
report.status = "completed"
report.updated_at = datetime.utcnow()
await self.db.commit()
await self.progress_tracker.complete_step(report.id, "保存报告", True)
logger.info(f"报告生成完成: {report.symbol} ({report.market})")
except Exception as e:
logger.error(f"报告生成过程失败: {report.symbol} ({report.market}) - {str(e)}")
# 标记报告为失败状态
report.status = "failed"
report.updated_at = datetime.utcnow()
await self.db.commit()
# 标记当前步骤为失败
try:
progress = await self.progress_tracker.get_progress(report.id)
current_step = progress.current_step_name
await self.progress_tracker.complete_step(report.id, current_step, False, str(e))
except Exception:
pass # 忽略进度更新失败
async def _create_analysis_modules(self, report: Report):
"""创建分析模块记录"""
for module_config in self.analysis_modules:
module = AnalysisModule(
report_id=report.id,
module_type=module_config["type"],
module_order=module_config["order"],
title=module_config["title"],
status="pending"
)
self.db.add(module)
await self.db.flush()
async def _execute_analysis_module(
self,
report: Report,
module_config: Dict[str, Any],
data_fetcher,
ai_analyzer,
analysis_context: Dict[str, Any]
):
"""执行单个分析模块"""
module_type = module_config["type"]
step_name = module_config["step_name"]
logger.info(f"执行分析模块: {module_type}")
# 开始步骤
await self.progress_tracker.start_step(report.id, step_name)
# 标记模块开始
await self._mark_module_started(report.id, module_type)
try:
# 根据模块类型执行相应的分析
if module_type == AnalysisModuleType.FINANCIAL_DATA:
content = await self._execute_financial_data_module(
report.symbol, report.market, data_fetcher
)
analysis_context["financial_data"] = content
elif module_type == AnalysisModuleType.TRADING_VIEW_CHART:
content = await self._execute_trading_view_module(
report.symbol, report.market
)
elif module_type == AnalysisModuleType.BUSINESS_INFO:
content = await self._execute_business_info_module(
report.symbol, report.market, ai_analyzer, analysis_context
)
analysis_context["business_info"] = content
elif module_type == AnalysisModuleType.FUNDAMENTAL_ANALYSIS:
content = await self._execute_fundamental_analysis_module(
report.symbol, report.market, ai_analyzer, analysis_context
)
analysis_context["fundamental_analysis"] = content
elif module_type == AnalysisModuleType.BULLISH_ANALYSIS:
content = await self._execute_bullish_analysis_module(
report.symbol, report.market, ai_analyzer, analysis_context
)
analysis_context["bullish_analysis"] = content
elif module_type == AnalysisModuleType.BEARISH_ANALYSIS:
content = await self._execute_bearish_analysis_module(
report.symbol, report.market, ai_analyzer, analysis_context
)
analysis_context["bearish_analysis"] = content
elif module_type == AnalysisModuleType.MARKET_ANALYSIS:
content = await self._execute_market_analysis_module(
report.symbol, report.market, ai_analyzer, analysis_context
)
analysis_context["market_analysis"] = content
elif module_type == AnalysisModuleType.NEWS_ANALYSIS:
content = await self._execute_news_analysis_module(
report.symbol, report.market, ai_analyzer, analysis_context
)
analysis_context["news_analysis"] = content
elif module_type == AnalysisModuleType.TRADING_ANALYSIS:
content = await self._execute_trading_analysis_module(
report.symbol, report.market, ai_analyzer, analysis_context
)
analysis_context["trading_analysis"] = content
elif module_type == AnalysisModuleType.INSIDER_ANALYSIS:
content = await self._execute_insider_analysis_module(
report.symbol, report.market, ai_analyzer, analysis_context
)
analysis_context["insider_analysis"] = content
elif module_type == AnalysisModuleType.FINAL_CONCLUSION:
content = await self._execute_final_conclusion_module(
report.symbol, report.market, ai_analyzer, analysis_context
)
analysis_context["final_conclusion"] = content
else:
raise ReportGenerationError(f"未知的分析模块类型: {module_type}")
# 保存模块内容
await self._save_module_content(report.id, module_type, content)
# 标记模块完成
await self._mark_module_completed(report.id, module_type)
# 完成步骤
await self.progress_tracker.complete_step(report.id, step_name, True)
except Exception as e:
logger.error(f"分析模块执行失败: {module_type} - {str(e)}")
# 标记模块失败
await self._mark_module_failed(report.id, module_type, str(e))
# 完成步骤(失败)
await self.progress_tracker.complete_step(report.id, step_name, False, str(e))
raise e
async def _execute_financial_data_module(self, symbol: str, market: str, data_fetcher) -> Dict[str, Any]:
"""执行财务数据分析模块"""
try:
# 获取财务数据
financial_data = await data_fetcher.fetch_financial_data(symbol, market)
# 获取市场数据
market_data = await data_fetcher.fetch_market_data(symbol, market)
return {
"financial_data": financial_data.dict(),
"market_data": market_data.dict(),
"summary": {
"data_source": financial_data.data_source,
"last_updated": financial_data.last_updated.isoformat(),
"data_quality": "good" # 可以添加数据质量评估逻辑
}
}
except Exception as e:
raise DataSourceError(f"财务数据获取失败: {str(e)}")
async def _execute_trading_view_module(self, symbol: str, market: str) -> Dict[str, Any]:
"""执行TradingView图表模块"""
# 生成TradingView图表配置
return {
"chart_config": {
"symbol": symbol,
"market": market,
"interval": "1D",
"theme": "light",
"style": "1", # 蜡烛图
"toolbar_bg": "#f1f3f6",
"enable_publishing": False,
"withdateranges": True,
"hide_side_toolbar": False,
"allow_symbol_change": False,
"studies": [
"MASimple@tv-basicstudies", # 移动平均线
"Volume@tv-basicstudies" # 成交量
]
},
"display_settings": {
"width": "100%",
"height": 500,
"autosize": True
}
}
async def _execute_business_info_module(self, symbol: str, market: str, ai_analyzer, analysis_context: Dict[str, Any]) -> Dict[str, Any]:
"""执行业务信息分析模块"""
try:
financial_data = analysis_context.get("financial_data", {})
result = await ai_analyzer.analyze_business_info(symbol, market, financial_data)
return result.content
except Exception as e:
raise AIAnalysisError(f"业务信息分析失败: {str(e)}")
async def _execute_fundamental_analysis_module(self, symbol: str, market: str, ai_analyzer, analysis_context: Dict[str, Any]) -> Dict[str, Any]:
"""执行基本面分析模块"""
try:
financial_data = analysis_context.get("financial_data", {})
business_info = analysis_context.get("business_info", {})
result = await ai_analyzer.analyze_fundamental(symbol, market, financial_data, business_info)
return result.content
except Exception as e:
raise AIAnalysisError(f"基本面分析失败: {str(e)}")
async def _execute_bullish_analysis_module(self, symbol: str, market: str, ai_analyzer, analysis_context: Dict[str, Any]) -> Dict[str, Any]:
"""执行看涨分析模块"""
try:
result = await ai_analyzer.analyze_bullish_case(symbol, market, analysis_context)
return result.content
except Exception as e:
raise AIAnalysisError(f"看涨分析失败: {str(e)}")
async def _execute_bearish_analysis_module(self, symbol: str, market: str, ai_analyzer, analysis_context: Dict[str, Any]) -> Dict[str, Any]:
"""执行看跌分析模块"""
try:
result = await ai_analyzer.analyze_bearish_case(symbol, market, analysis_context)
return result.content
except Exception as e:
raise AIAnalysisError(f"看跌分析失败: {str(e)}")
async def _execute_market_analysis_module(self, symbol: str, market: str, ai_analyzer, analysis_context: Dict[str, Any]) -> Dict[str, Any]:
"""执行市场分析模块"""
try:
result = await ai_analyzer.analyze_market_sentiment(symbol, market, analysis_context)
return result.content
except Exception as e:
raise AIAnalysisError(f"市场分析失败: {str(e)}")
async def _execute_news_analysis_module(self, symbol: str, market: str, ai_analyzer, analysis_context: Dict[str, Any]) -> Dict[str, Any]:
"""执行新闻分析模块"""
try:
result = await ai_analyzer.analyze_news_catalysts(symbol, market, analysis_context)
return result.content
except Exception as e:
raise AIAnalysisError(f"新闻分析失败: {str(e)}")
async def _execute_trading_analysis_module(self, symbol: str, market: str, ai_analyzer, analysis_context: Dict[str, Any]) -> Dict[str, Any]:
"""执行交易分析模块"""
try:
result = await ai_analyzer.analyze_trading_dynamics(symbol, market, analysis_context)
return result.content
except Exception as e:
raise AIAnalysisError(f"交易分析失败: {str(e)}")
async def _execute_insider_analysis_module(self, symbol: str, market: str, ai_analyzer, analysis_context: Dict[str, Any]) -> Dict[str, Any]:
"""执行内部人分析模块"""
try:
result = await ai_analyzer.analyze_insider_institutional(symbol, market, analysis_context)
return result.content
except Exception as e:
raise AIAnalysisError(f"内部人分析失败: {str(e)}")
async def _execute_final_conclusion_module(self, symbol: str, market: str, ai_analyzer, analysis_context: Dict[str, Any]) -> Dict[str, Any]:
"""执行最终结论模块"""
try:
# 收集所有分析结果
all_analyses = []
for key, value in analysis_context.items():
if key != "financial_data": # 排除原始财务数据
all_analyses.append({
"analysis_type": key,
"content": value
})
result = await ai_analyzer.generate_final_conclusion(symbol, market, all_analyses)
return result.content
except Exception as e:
raise AIAnalysisError(f"最终结论生成失败: {str(e)}")
async def _mark_module_started(self, report_id: UUID, module_type: str):
"""标记模块开始"""
result = await self.db.execute(
select(AnalysisModule).where(
AnalysisModule.report_id == report_id,
AnalysisModule.module_type == module_type
)
)
module = result.scalar_one_or_none()
if module:
module.status = "running"
module.started_at = datetime.utcnow()
await self.db.flush()
async def _mark_module_completed(self, report_id: UUID, module_type: str):
"""标记模块完成"""
result = await self.db.execute(
select(AnalysisModule).where(
AnalysisModule.report_id == report_id,
AnalysisModule.module_type == module_type
)
)
module = result.scalar_one_or_none()
if module:
module.status = "completed"
module.completed_at = datetime.utcnow()
await self.db.flush()
async def _mark_module_failed(self, report_id: UUID, module_type: str, error_message: str):
"""标记模块失败"""
result = await self.db.execute(
select(AnalysisModule).where(
AnalysisModule.report_id == report_id,
AnalysisModule.module_type == module_type
)
)
module = result.scalar_one_or_none()
if module:
module.status = "failed"
module.completed_at = datetime.utcnow()
module.error_message = error_message
await self.db.flush()
async def _save_module_content(self, report_id: UUID, module_type: str, content: Dict[str, Any]):
"""保存模块内容"""
result = await self.db.execute(
select(AnalysisModule).where(
AnalysisModule.report_id == report_id,
AnalysisModule.module_type == module_type
)
)
module = result.scalar_one_or_none()
if module:
module.content = content
await self.db.flush()
async def _build_report_response(self, report: Report) -> ReportResponse:
"""构建报告响应"""
# 获取分析模块
result = await self.db.execute(
select(AnalysisModule)
.where(AnalysisModule.report_id == report.id)
.order_by(AnalysisModule.module_order)
)
modules = result.scalars().all()
# 转换为响应模式
module_schemas = [
AnalysisModuleSchema(
id=module.id,
module_type=module.module_type,
module_order=module.module_order,
title=module.title,
content=module.content,
status=module.status,
started_at=module.started_at,
completed_at=module.completed_at,
error_message=module.error_message
)
for module in modules
]
return ReportResponse(
id=report.id,
symbol=report.symbol,
market=report.market,
status=report.status,
created_at=report.created_at,
updated_at=report.updated_at,
analysis_modules=module_schemas
)