""" 报告生成服务 处理股票基本面分析报告的生成和管理 """ 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 )