Fundamental_Analysis/backend/app/services/report_generator.py
2025-10-21 14:30:08 +08:00

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
)