643 lines
26 KiB
Python
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
|
|
) |