806 lines
33 KiB
Python
806 lines
33 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
|
|
from .financial_data_processor import FinancialDataProcessor
|
|
|
|
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 generate_report_async(self, symbol: str, market: str):
|
|
"""异步生成报告(用于后台任务)"""
|
|
try:
|
|
# 获取现有报告
|
|
existing_report = await self._get_existing_report(symbol, market)
|
|
if not existing_report:
|
|
logger.error(f"未找到要生成的报告记录: {symbol} ({market})")
|
|
return
|
|
|
|
# 生成报告内容
|
|
await self._generate_report_content(existing_report)
|
|
|
|
except Exception as e:
|
|
logger.error(f"异步报告生成失败: {symbol} ({market}) - {str(e)}")
|
|
# 更新报告状态为失败
|
|
try:
|
|
existing_report = await self._get_existing_report(symbol, market)
|
|
if existing_report:
|
|
existing_report.status = "failed"
|
|
await self.db.commit()
|
|
except Exception:
|
|
pass # 忽略状态更新失败
|
|
|
|
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 = {
|
|
"symbol": report.symbol,
|
|
"market": report.market,
|
|
"report_id": str(report.id),
|
|
"generation_timestamp": datetime.utcnow().isoformat()
|
|
}
|
|
|
|
# 按顺序执行各个分析模块,实现依赖关系管理
|
|
successful_modules = 0
|
|
failed_modules = 0
|
|
|
|
for module_config in self.analysis_modules:
|
|
try:
|
|
# 检查模块依赖关系
|
|
if self._check_module_dependencies(module_config, analysis_context):
|
|
await self._execute_analysis_module(
|
|
report, module_config, data_fetcher, ai_analyzer, analysis_context
|
|
)
|
|
successful_modules += 1
|
|
logger.info(f"模块执行成功: {module_config['type']}")
|
|
else:
|
|
# 依赖不满足,跳过模块
|
|
await self._mark_module_skipped(report.id, module_config["type"], "依赖条件不满足")
|
|
logger.warning(f"模块跳过: {module_config['type']} - 依赖条件不满足")
|
|
|
|
except Exception as e:
|
|
logger.error(f"分析模块执行失败: {module_config['type']} - {str(e)}")
|
|
failed_modules += 1
|
|
|
|
# 实现错误处理和重试机制
|
|
retry_success = await self._retry_module_execution(
|
|
report, module_config, data_fetcher, ai_analyzer, analysis_context, str(e)
|
|
)
|
|
|
|
if retry_success:
|
|
successful_modules += 1
|
|
logger.info(f"模块重试成功: {module_config['type']}")
|
|
else:
|
|
# 标记模块为失败,但继续执行其他模块
|
|
await self._mark_module_failed(report.id, module_config["type"], str(e))
|
|
logger.error(f"模块重试失败: {module_config['type']}")
|
|
|
|
# 完成报告生成和数据库保存
|
|
await self.progress_tracker.start_step(report.id, "保存报告")
|
|
|
|
# 计算报告完成度
|
|
completion_rate = successful_modules / len(self.analysis_modules) if self.analysis_modules else 0
|
|
|
|
# 根据完成度决定报告状态
|
|
if completion_rate >= 0.8: # 80%以上模块成功
|
|
report.status = "completed"
|
|
elif completion_rate >= 0.5: # 50%以上模块成功
|
|
report.status = "partial"
|
|
else:
|
|
report.status = "failed"
|
|
|
|
report.updated_at = datetime.utcnow()
|
|
|
|
# 保存报告到数据库
|
|
await self._save_report_to_database(report, analysis_context)
|
|
|
|
await self.progress_tracker.complete_step(report.id, "保存报告", True)
|
|
|
|
logger.info(f"报告生成完成: {report.symbol} ({report.market}), 状态: {report.status}, 成功模块: {successful_modules}/{len(self.analysis_modules)}")
|
|
|
|
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:
|
|
# 创建财务数据处理器
|
|
processor = FinancialDataProcessor(data_fetcher)
|
|
|
|
# 处理财务数据
|
|
processed_data = await processor.process_financial_data(symbol, market)
|
|
|
|
return processed_data
|
|
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()
|
|
|
|
def _check_module_dependencies(self, module_config: Dict[str, Any], analysis_context: Dict[str, Any]) -> bool:
|
|
"""检查模块依赖关系"""
|
|
module_type = module_config["type"]
|
|
|
|
# 定义模块依赖关系
|
|
dependencies = {
|
|
AnalysisModuleType.TRADING_VIEW_CHART: [], # 无依赖
|
|
AnalysisModuleType.FINANCIAL_DATA: [], # 无依赖
|
|
AnalysisModuleType.BUSINESS_INFO: ["financial_data"], # 依赖财务数据
|
|
AnalysisModuleType.FUNDAMENTAL_ANALYSIS: ["financial_data", "business_info"], # 依赖财务数据和业务信息
|
|
AnalysisModuleType.BULLISH_ANALYSIS: ["financial_data", "business_info"],
|
|
AnalysisModuleType.BEARISH_ANALYSIS: ["financial_data", "business_info"],
|
|
AnalysisModuleType.MARKET_ANALYSIS: ["financial_data"],
|
|
AnalysisModuleType.NEWS_ANALYSIS: ["financial_data"],
|
|
AnalysisModuleType.TRADING_ANALYSIS: ["financial_data"],
|
|
AnalysisModuleType.INSIDER_ANALYSIS: ["financial_data"],
|
|
AnalysisModuleType.FINAL_CONCLUSION: ["fundamental_analysis", "bullish_analysis", "bearish_analysis"] # 依赖主要分析结果
|
|
}
|
|
|
|
required_deps = dependencies.get(module_type, [])
|
|
|
|
# 检查所有依赖是否都已满足
|
|
for dep in required_deps:
|
|
if dep not in analysis_context:
|
|
logger.warning(f"模块 {module_type} 缺少依赖: {dep}")
|
|
return False
|
|
|
|
return True
|
|
|
|
async def _retry_module_execution(
|
|
self,
|
|
report: Report,
|
|
module_config: Dict[str, Any],
|
|
data_fetcher,
|
|
ai_analyzer,
|
|
analysis_context: Dict[str, Any],
|
|
original_error: str
|
|
) -> bool:
|
|
"""重试模块执行"""
|
|
module_type = module_config["type"]
|
|
max_retries = 2
|
|
|
|
for retry_count in range(max_retries):
|
|
try:
|
|
logger.info(f"重试模块执行 ({retry_count + 1}/{max_retries}): {module_type}")
|
|
|
|
# 等待一段时间再重试
|
|
await asyncio.sleep(2 * (retry_count + 1))
|
|
|
|
# 重新执行模块
|
|
await self._execute_analysis_module(
|
|
report, module_config, data_fetcher, ai_analyzer, analysis_context
|
|
)
|
|
|
|
logger.info(f"模块重试成功: {module_type}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.warning(f"模块重试失败 ({retry_count + 1}/{max_retries}): {module_type} - {str(e)}")
|
|
|
|
if retry_count == max_retries - 1:
|
|
# 最后一次重试也失败了
|
|
logger.error(f"模块重试全部失败: {module_type}")
|
|
return False
|
|
|
|
return False
|
|
|
|
async def _mark_module_skipped(self, report_id: UUID, module_type: str, reason: 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 = "skipped"
|
|
module.completed_at = datetime.utcnow()
|
|
module.error_message = reason
|
|
await self.db.flush()
|
|
|
|
async def _save_report_to_database(self, report: Report, analysis_context: Dict[str, Any]):
|
|
"""保存报告到数据库"""
|
|
try:
|
|
# 添加报告元数据到上下文
|
|
analysis_context["report_metadata"] = {
|
|
"generation_completed_at": datetime.utcnow().isoformat(),
|
|
"total_modules": len(self.analysis_modules),
|
|
"successful_modules": len([k for k in analysis_context.keys() if k not in ["symbol", "market", "report_id", "generation_timestamp", "report_metadata"]]),
|
|
"report_status": report.status
|
|
}
|
|
|
|
# 提交数据库事务
|
|
await self.db.commit()
|
|
|
|
logger.info(f"报告保存成功: {report.id}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"报告保存失败: {report.id} - {str(e)}")
|
|
await self.db.rollback()
|
|
raise DatabaseError(f"报告保存失败: {str(e)}")
|
|
|
|
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
|
|
) |