[add] claude
This commit is contained in:
parent
b94bcf9bcd
commit
fbd96e9c18
|
|
@ -2,27 +2,246 @@ from sqlmodel import Session
|
||||||
from analysis.domain.repository.analysis_repo import IAnalysisRepository
|
from analysis.domain.repository.analysis_repo import IAnalysisRepository
|
||||||
from ulid import ULID
|
from ulid import ULID
|
||||||
from analysis.domain.analysis import Analysis as AnalysisVO
|
from analysis.domain.analysis import Analysis as AnalysisVO
|
||||||
|
from analysis.interface.dto import TradingAnalysisRequest, AnalysisProgressUpdate
|
||||||
from fastapi import HTTPException, status, BackgroundTasks
|
from fastapi import HTTPException, status, BackgroundTasks
|
||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||||
|
from tradingagents.default_config import DEFAULT_CONFIG
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AnalysisService:
|
class AnalysisService:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
analysis_repo: IAnalysisRepository,
|
analysis_repo: IAnalysisRepository,
|
||||||
db_session: Session,
|
session: Session,
|
||||||
ulid: ULID
|
ulid: ULID
|
||||||
):
|
):
|
||||||
self.analysis_repo = analysis_repo
|
self.analysis_repo = analysis_repo
|
||||||
self.db_session = db_session
|
self.session = session
|
||||||
self.ulid = ulid
|
self.ulid = ulid
|
||||||
|
|
||||||
def get_analysis_list(
|
def get_analysis_list(
|
||||||
self,
|
self,
|
||||||
member_id: str
|
member_id: str
|
||||||
)->list[AnalysisVO]:
|
) -> list[AnalysisVO]:
|
||||||
analyses = self.analysis_repo.find_by_member_id(member_id)
|
analyses = self.analysis_repo.find_by_member_id(member_id)
|
||||||
if not analyses:
|
if not analyses:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Analysis not found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Analysis not found")
|
||||||
return analyses
|
return analyses
|
||||||
|
|
||||||
|
def get_analysis_by_id(
|
||||||
|
self,
|
||||||
|
analysis_id: str,
|
||||||
|
member_id: str
|
||||||
|
) -> AnalysisVO:
|
||||||
|
analysis = self.analysis_repo.find_by_id(analysis_id)
|
||||||
|
if not analysis:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Analysis not found")
|
||||||
|
|
||||||
|
if analysis.member_id != member_id:
|
||||||
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
|
||||||
|
|
||||||
|
return analysis
|
||||||
|
|
||||||
|
def create_analysis(
|
||||||
|
self,
|
||||||
|
member_id: str,
|
||||||
|
request: TradingAnalysisRequest,
|
||||||
|
background_tasks: BackgroundTasks
|
||||||
|
) -> AnalysisVO:
|
||||||
|
# 분석 요청 생성
|
||||||
|
analysis_id = self.ulid.generate()
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
analysis_vo = AnalysisVO(
|
||||||
|
id=analysis_id,
|
||||||
|
member_id=member_id,
|
||||||
|
ticker=request.ticker,
|
||||||
|
analysis_date=request.analysis_date,
|
||||||
|
analysts_selected=[analyst.value for analyst in request.analysts],
|
||||||
|
research_depth=request.research_depth,
|
||||||
|
llm_provider=request.llm_provider,
|
||||||
|
backend_url=request.backend_url,
|
||||||
|
shallow_thinker=request.shallow_thinker,
|
||||||
|
deep_thinker=request.deep_thinker,
|
||||||
|
status="pending",
|
||||||
|
created_at=now,
|
||||||
|
updated_at=now
|
||||||
|
)
|
||||||
|
|
||||||
|
saved_analysis = self.analysis_repo.save(analysis_vo)
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
# 백그라운드에서 분석 실행
|
||||||
|
background_tasks.add_task(self._run_analysis, saved_analysis.id)
|
||||||
|
|
||||||
|
return saved_analysis
|
||||||
|
|
||||||
|
async def _run_analysis(self, analysis_id: str):
|
||||||
|
"""백그라운드에서 실제 분석을 실행하는 메서드"""
|
||||||
|
try:
|
||||||
|
# 분석 상태를 RUNNING으로 변경
|
||||||
|
analysis = self.analysis_repo.find_by_id(analysis_id)
|
||||||
|
if analysis:
|
||||||
|
analysis.status = "running"
|
||||||
|
analysis.updated_at = datetime.now()
|
||||||
|
self.analysis_repo.update(analysis)
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
# 분석 정보 조회
|
||||||
|
analysis = self.analysis_repo.find_by_id(analysis_id)
|
||||||
|
if not analysis:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TradingAgentsGraph 설정 및 실행
|
||||||
|
config = self._create_config(analysis)
|
||||||
|
|
||||||
|
# 분석 실행 (실제 구현)
|
||||||
|
await self._execute_trading_analysis(analysis_id, analysis, config)
|
||||||
|
|
||||||
|
# 분석 완료 상태로 변경
|
||||||
|
analysis = self.analysis_repo.find_by_id(analysis_id)
|
||||||
|
if analysis:
|
||||||
|
analysis.status = "completed"
|
||||||
|
analysis.completed_at = datetime.now()
|
||||||
|
analysis.updated_at = datetime.now()
|
||||||
|
self.analysis_repo.update(analysis)
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# 에러 발생 시 실패 상태로 변경
|
||||||
|
analysis = self.analysis_repo.find_by_id(analysis_id)
|
||||||
|
if analysis:
|
||||||
|
analysis.status = "failed"
|
||||||
|
analysis.error_message = str(e)
|
||||||
|
analysis.completed_at = datetime.now()
|
||||||
|
analysis.updated_at = datetime.now()
|
||||||
|
self.analysis_repo.update(analysis)
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
def _create_config(self, analysis: AnalysisVO) -> dict:
|
||||||
|
"""분석 설정을 생성하는 메서드"""
|
||||||
|
config = DEFAULT_CONFIG.copy() if DEFAULT_CONFIG else {}
|
||||||
|
config.update({
|
||||||
|
"max_debate_rounds": analysis.research_depth,
|
||||||
|
"max_risk_discuss_rounds": analysis.research_depth,
|
||||||
|
"quick_think_llm": analysis.shallow_thinker,
|
||||||
|
"deep_think_llm": analysis.deep_thinker,
|
||||||
|
"backend_url": analysis.backend_url,
|
||||||
|
"llm_provider": analysis.llm_provider.lower(),
|
||||||
|
})
|
||||||
|
return config
|
||||||
|
|
||||||
|
async def _execute_trading_analysis(self, analysis_id: str, analysis: AnalysisVO, config: dict):
|
||||||
|
"""실제 TradingAgentsGraph를 실행하는 메서드"""
|
||||||
|
try:
|
||||||
|
# TradingAgentsGraph 초기화
|
||||||
|
graph = TradingAgentsGraph(
|
||||||
|
analysis.analysts_selected,
|
||||||
|
config=config,
|
||||||
|
debug=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# 초기 상태 생성
|
||||||
|
init_agent_state = graph.propagator.create_initial_state(
|
||||||
|
analysis.ticker,
|
||||||
|
analysis.analysis_date
|
||||||
|
)
|
||||||
|
args = graph.propagator.get_graph_args()
|
||||||
|
|
||||||
|
# 분석 실행 및 결과 처리
|
||||||
|
trace = []
|
||||||
|
async for chunk in graph.graph.astream(init_agent_state, **args):
|
||||||
|
trace.append(chunk)
|
||||||
|
|
||||||
|
# 실시간으로 분석 결과 업데이트
|
||||||
|
await self._process_analysis_chunk(analysis_id, chunk)
|
||||||
|
|
||||||
|
# 최종 결과 처리
|
||||||
|
if trace:
|
||||||
|
final_state = trace[-1]
|
||||||
|
final_decision = graph.process_signal(final_state.get("final_trade_decision", ""))
|
||||||
|
|
||||||
|
# 최종 보고서 생성
|
||||||
|
final_report = self._generate_final_report(final_state)
|
||||||
|
|
||||||
|
# 최종 결과 저장
|
||||||
|
self.analysis_repo.update(analysis_id, {
|
||||||
|
"final_trade_decision": final_decision,
|
||||||
|
"final_report": final_report
|
||||||
|
})
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"Analysis execution failed: {str(e)}")
|
||||||
|
|
||||||
|
async def _process_analysis_chunk(self, analysis_id: str, chunk: dict):
|
||||||
|
"""분석 중간 결과를 처리하고 저장하는 메서드"""
|
||||||
|
updates = {}
|
||||||
|
|
||||||
|
# 개별 분석가 보고서 업데이트
|
||||||
|
if "market_report" in chunk and chunk["market_report"]:
|
||||||
|
updates["market_report"] = chunk["market_report"]
|
||||||
|
|
||||||
|
if "sentiment_report" in chunk and chunk["sentiment_report"]:
|
||||||
|
updates["sentiment_report"] = chunk["sentiment_report"]
|
||||||
|
|
||||||
|
if "news_report" in chunk and chunk["news_report"]:
|
||||||
|
updates["news_report"] = chunk["news_report"]
|
||||||
|
|
||||||
|
if "fundamentals_report" in chunk and chunk["fundamentals_report"]:
|
||||||
|
updates["fundamentals_report"] = chunk["fundamentals_report"]
|
||||||
|
|
||||||
|
# 팀별 의사결정 과정 업데이트
|
||||||
|
if "investment_debate_state" in chunk and chunk["investment_debate_state"]:
|
||||||
|
updates["investment_debate_state"] = chunk["investment_debate_state"]
|
||||||
|
|
||||||
|
if "trader_investment_plan" in chunk and chunk["trader_investment_plan"]:
|
||||||
|
updates["trader_investment_plan"] = chunk["trader_investment_plan"]
|
||||||
|
|
||||||
|
if "risk_debate_state" in chunk and chunk["risk_debate_state"]:
|
||||||
|
updates["risk_debate_state"] = chunk["risk_debate_state"]
|
||||||
|
|
||||||
|
# 업데이트가 있는 경우 저장
|
||||||
|
if updates:
|
||||||
|
self.analysis_repo.update(analysis_id, updates)
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
def _generate_final_report(self, final_state: dict) -> str:
|
||||||
|
"""최종 통합 보고서를 생성하는 메서드"""
|
||||||
|
report_parts = []
|
||||||
|
|
||||||
|
# Analyst Team Reports
|
||||||
|
if any(final_state.get(section) for section in ["market_report", "sentiment_report", "news_report", "fundamentals_report"]):
|
||||||
|
report_parts.append("## Analyst Team Reports")
|
||||||
|
|
||||||
|
if final_state.get("market_report"):
|
||||||
|
report_parts.append(f"### Market Analysis\n{final_state['market_report']}")
|
||||||
|
if final_state.get("sentiment_report"):
|
||||||
|
report_parts.append(f"### Social Sentiment\n{final_state['sentiment_report']}")
|
||||||
|
if final_state.get("news_report"):
|
||||||
|
report_parts.append(f"### News Analysis\n{final_state['news_report']}")
|
||||||
|
if final_state.get("fundamentals_report"):
|
||||||
|
report_parts.append(f"### Fundamentals Analysis\n{final_state['fundamentals_report']}")
|
||||||
|
|
||||||
|
# Research Team Reports
|
||||||
|
if final_state.get("investment_debate_state"):
|
||||||
|
report_parts.append("## Research Team Decision")
|
||||||
|
debate_state = final_state["investment_debate_state"]
|
||||||
|
if debate_state.get("judge_decision"):
|
||||||
|
report_parts.append(f"{debate_state['judge_decision']}")
|
||||||
|
|
||||||
|
# Trading Team Reports
|
||||||
|
if final_state.get("trader_investment_plan"):
|
||||||
|
report_parts.append("## Trading Team Plan")
|
||||||
|
report_parts.append(f"{final_state['trader_investment_plan']}")
|
||||||
|
|
||||||
|
# Portfolio Management Decision
|
||||||
|
if final_state.get("risk_debate_state") and final_state["risk_debate_state"].get("judge_decision"):
|
||||||
|
report_parts.append("## Portfolio Management Decision")
|
||||||
|
report_parts.append(f"{final_state['risk_debate_state']['judge_decision']}")
|
||||||
|
|
||||||
|
return "\n\n".join(report_parts) if report_parts else "No analysis results available."
|
||||||
|
|
@ -1,11 +1,37 @@
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
class Analysis(BaseModel):
|
class Analysis(BaseModel):
|
||||||
id: str | None = None
|
id: str | None = None
|
||||||
member_id: str
|
member_id: str
|
||||||
ticker: str
|
ticker: str
|
||||||
|
analysis_date: str
|
||||||
|
analysts_selected: List[str] = []
|
||||||
|
research_depth: int = 3
|
||||||
|
llm_provider: str = "openai"
|
||||||
|
backend_url: str = "https://api.openai.com/v1"
|
||||||
|
shallow_thinker: str = "gpt-4o-mini"
|
||||||
|
deep_thinker: str = "gpt-4o"
|
||||||
status: str
|
status: str
|
||||||
|
|
||||||
|
# 개별 분석가 리포트들
|
||||||
|
market_report: str | None = None
|
||||||
|
sentiment_report: str | None = None
|
||||||
|
news_report: str | None = None
|
||||||
|
fundamentals_report: str | None = None
|
||||||
|
|
||||||
|
# 팀별 의사결정 과정
|
||||||
|
investment_debate_state: Dict | None = None
|
||||||
|
trader_investment_plan: str | None = None
|
||||||
|
risk_debate_state: Dict | None = None
|
||||||
|
|
||||||
|
# 최종 결과물
|
||||||
|
final_trade_decision: str | None = None
|
||||||
|
final_report: str | None = None
|
||||||
|
|
||||||
|
# 실행 결과 정보
|
||||||
|
error_message: str | None = None
|
||||||
|
completed_at: datetime | None = None
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
# 여기에 더 많은 필드들이 추가될 수 있습니다.
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from analysis.domain.analysis import Analysis as AnalysisVO
|
from analysis.domain.analysis import Analysis as AnalysisVO
|
||||||
|
from analysis.interface.dto import TradingAnalysisRequest
|
||||||
|
|
||||||
class IAnalysisRepository(ABC):
|
class IAnalysisRepository(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
@ -7,9 +8,13 @@ class IAnalysisRepository(ABC):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def update(self, analysis_id: str, updates: dict) -> AnalysisVO | None:
|
def find_by_id(self, analysis_id: str) -> AnalysisVO | None:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create(self, member_id: str) -> AnalysisVO:
|
def update(self, analysis: AnalysisVO) -> AnalysisVO | None:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def save(self, analysis: AnalysisVO) -> AnalysisVO:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
from analysis.domain.repository.analysis_repo import IAnalysisRepository
|
from analysis.domain.repository.analysis_repo import IAnalysisRepository
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from analysis.domain.analysis import Analysis as AnalysisVO
|
from analysis.domain.analysis import Analysis as AnalysisVO
|
||||||
from analysis.infra.db_models.analysis import Analysis
|
from analysis.infra.db_models.analysis import Analysis, AnalysisStatus
|
||||||
|
from analysis.interface.dto import TradingAnalysisRequest
|
||||||
from utils.db_utils import row_to_dict
|
from utils.db_utils import row_to_dict
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
from datetime import datetime
|
from datetime import datetime, date
|
||||||
import uuid
|
|
||||||
|
|
||||||
class AnalysisRepository(IAnalysisRepository):
|
class AnalysisRepository(IAnalysisRepository):
|
||||||
def __init__(self, session: Session):
|
def __init__(self, session: Session):
|
||||||
|
|
@ -20,14 +20,59 @@ class AnalysisRepository(IAnalysisRepository):
|
||||||
|
|
||||||
return [AnalysisVO(**row_to_dict(analysis)) for analysis in analyses]
|
return [AnalysisVO(**row_to_dict(analysis)) for analysis in analyses]
|
||||||
|
|
||||||
|
def find_by_id(self, analysis_id: str) -> AnalysisVO | None:
|
||||||
|
analysis = self.session.get(Analysis, analysis_id)
|
||||||
|
if not analysis:
|
||||||
|
return None
|
||||||
|
return AnalysisVO(**row_to_dict(analysis))
|
||||||
|
|
||||||
def update(self, analysis_id: str, updates: AnalysisVO) -> AnalysisVO | None:
|
def save(self, analysis: AnalysisVO) -> AnalysisVO:
|
||||||
analysis : Analysis | None = self.session.get(Analysis, analysis_id)
|
new_analysis = Analysis(
|
||||||
|
id=analysis.id,
|
||||||
|
member_id=analysis.member_id,
|
||||||
|
ticker=analysis.ticker,
|
||||||
|
analysis_date=date.fromisoformat(analysis.analysis_date),
|
||||||
|
analysts_selected=analysis.analysts_selected,
|
||||||
|
research_depth=analysis.research_depth,
|
||||||
|
llm_provider=analysis.llm_provider,
|
||||||
|
backend_url=analysis.backend_url,
|
||||||
|
shallow_thinker=analysis.shallow_thinker,
|
||||||
|
deep_thinker=analysis.deep_thinker,
|
||||||
|
status=analysis.status,
|
||||||
|
market_report=analysis.market_report,
|
||||||
|
sentiment_report=analysis.sentiment_report,
|
||||||
|
news_report=analysis.news_report,
|
||||||
|
fundamentals_report=analysis.fundamentals_report,
|
||||||
|
investment_debate_state=analysis.investment_debate_state,
|
||||||
|
trader_investment_plan=analysis.trader_investment_plan,
|
||||||
|
risk_debate_state=analysis.risk_debate_state,
|
||||||
|
final_trade_decision=analysis.final_trade_decision,
|
||||||
|
final_report=analysis.final_report,
|
||||||
|
error_message=analysis.error_message,
|
||||||
|
completed_at=analysis.completed_at,
|
||||||
|
created_at=analysis.created_at,
|
||||||
|
updated_at=analysis.updated_at
|
||||||
|
)
|
||||||
|
|
||||||
|
self.session.add(new_analysis)
|
||||||
|
self.session.flush()
|
||||||
|
self.session.refresh(new_analysis)
|
||||||
|
|
||||||
|
analysis.id = new_analysis.id
|
||||||
|
return analysis
|
||||||
|
|
||||||
|
def update(self, analysis_vo: AnalysisVO) -> AnalysisVO | None:
|
||||||
|
analysis = self.session.get(Analysis, analysis_vo.id)
|
||||||
if not analysis:
|
if not analysis:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
analysis_data = updates.model_dump(exclude_unset=True)
|
# AnalysisVO의 데이터를 SQLModel 객체에 업데이트
|
||||||
analysis.sqlmodel_dump(analysis_data)
|
vo_data = analysis_vo.sqlmodel_dump(exclude_unset=True)
|
||||||
|
for key, value in vo_data.items():
|
||||||
|
if hasattr(analysis, key) and key != 'id': # id는 변경하지 않음
|
||||||
|
setattr(analysis, key, value)
|
||||||
|
|
||||||
|
analysis.updated_at = datetime.now()
|
||||||
self.session.add(analysis)
|
self.session.add(analysis)
|
||||||
self.session.flush()
|
self.session.flush()
|
||||||
self.session.refresh(analysis)
|
self.session.refresh(analysis)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from fastapi import APIRouter, Depends, BackgroundTasks
|
from fastapi import APIRouter, Depends, BackgroundTasks, HTTPException, status
|
||||||
from analysis.interface.dto import AnalysisSessionResponse
|
from analysis.interface.dto import (
|
||||||
|
AnalysisSessionResponse,
|
||||||
|
TradingAnalysisRequest,
|
||||||
|
AnalysisResultResponse
|
||||||
|
)
|
||||||
from utils.auth import get_current_member, CurrentMember
|
from utils.auth import get_current_member, CurrentMember
|
||||||
from dependency_injector.wiring import inject, Provide
|
from dependency_injector.wiring import inject, Provide
|
||||||
from analysis.application.analysis_service import AnalysisService
|
from analysis.application.analysis_service import AnalysisService
|
||||||
|
|
@ -8,28 +12,97 @@ from utils.containers import Container
|
||||||
|
|
||||||
router = APIRouter(prefix="/analysis", tags=["analysis"])
|
router = APIRouter(prefix="/analysis", tags=["analysis"])
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/", response_model=list[AnalysisSessionResponse])
|
||||||
@inject
|
@inject
|
||||||
def get_analysis_list_for_member(
|
def get_analysis_list_for_member(
|
||||||
current_member : Annotated[CurrentMember, Depends(get_current_member)],
|
current_member: Annotated[CurrentMember, Depends(get_current_member)],
|
||||||
analysis_service: Annotated[AnalysisService, Depends(Provide[Container.analysis_service])]
|
analysis_service: Annotated[AnalysisService, Depends(Provide[Container.analysis_service])]
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
현재 로그인한 사용자의 모든 분석 세션 목록을 조회합니다.
|
현재 로그인한 사용자의 모든 분석 세션 목록을 조회합니다.
|
||||||
"""
|
"""
|
||||||
return analysis_service.get_analysis_list(current_member.id)
|
analyses = analysis_service.get_analysis_list(current_member.id)
|
||||||
|
return [
|
||||||
|
AnalysisSessionResponse(
|
||||||
|
id=analysis.id,
|
||||||
|
ticker=analysis.ticker,
|
||||||
|
status=analysis.status
|
||||||
|
) for analysis in analyses
|
||||||
|
]
|
||||||
|
|
||||||
@router.post("/start", status_code=201)
|
@router.post("/start", status_code=201, response_model=AnalysisSessionResponse)
|
||||||
@inject
|
@inject
|
||||||
def start_analysis_session(
|
def start_analysis_session(
|
||||||
current_member : Annotated[CurrentMember, Depends(get_current_member)],
|
request: TradingAnalysisRequest,
|
||||||
|
current_member: Annotated[CurrentMember, Depends(get_current_member)],
|
||||||
analysis_service: Annotated[AnalysisService, Depends(Provide[Container.analysis_service])],
|
analysis_service: Annotated[AnalysisService, Depends(Provide[Container.analysis_service])],
|
||||||
background_tasks: BackgroundTasks
|
background_tasks: BackgroundTasks
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
새로운 분석 세션을 시작합니다.
|
새로운 분석 세션을 시작합니다.
|
||||||
"""
|
"""
|
||||||
new_analysis = analysis_service.create_analysis(current_member.id, background_tasks)
|
try:
|
||||||
return new_analysis
|
new_analysis = analysis_service.create_analysis(current_member.id, request, background_tasks)
|
||||||
|
return AnalysisSessionResponse(
|
||||||
|
id=new_analysis.id,
|
||||||
|
ticker=new_analysis.ticker,
|
||||||
|
status=new_analysis.status
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to start analysis: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.get("/{analysis_id}", response_model=AnalysisResultResponse)
|
||||||
|
@inject
|
||||||
|
def get_analysis_result(
|
||||||
|
analysis_id: str,
|
||||||
|
current_member: Annotated[CurrentMember, Depends(get_current_member)],
|
||||||
|
analysis_service: Annotated[AnalysisService, Depends(Provide[Container.analysis_service])]
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
특정 분석 세션의 결과를 조회합니다.
|
||||||
|
"""
|
||||||
|
analysis = analysis_service.get_analysis_by_id(analysis_id, current_member.id)
|
||||||
|
|
||||||
|
return AnalysisResultResponse(
|
||||||
|
id=analysis.id,
|
||||||
|
ticker=analysis.ticker,
|
||||||
|
analysis_date=analysis.analysis_date,
|
||||||
|
status=analysis.status,
|
||||||
|
market_report=analysis.market_report,
|
||||||
|
sentiment_report=analysis.sentiment_report,
|
||||||
|
news_report=analysis.news_report,
|
||||||
|
fundamentals_report=analysis.fundamentals_report,
|
||||||
|
investment_debate_state=analysis.investment_debate_state,
|
||||||
|
trader_investment_plan=analysis.trader_investment_plan,
|
||||||
|
risk_debate_state=analysis.risk_debate_state,
|
||||||
|
final_trade_decision=analysis.final_trade_decision,
|
||||||
|
final_report=analysis.final_report,
|
||||||
|
created_at=analysis.created_at.isoformat(),
|
||||||
|
completed_at=analysis.completed_at.isoformat() if analysis.completed_at else None,
|
||||||
|
error_message=analysis.error_message
|
||||||
|
)
|
||||||
|
|
||||||
|
@router.get("/{analysis_id}/status")
|
||||||
|
@inject
|
||||||
|
def get_analysis_status(
|
||||||
|
analysis_id: str,
|
||||||
|
current_member: Annotated[CurrentMember, Depends(get_current_member)],
|
||||||
|
analysis_service: Annotated[AnalysisService, Depends(Provide[Container.analysis_service])]
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
분석 진행 상황을 조회합니다.
|
||||||
|
"""
|
||||||
|
analysis = analysis_service.get_analysis_by_id(analysis_id, current_member.id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"analysis_id": analysis.id,
|
||||||
|
"status": analysis.status,
|
||||||
|
"ticker": analysis.ticker,
|
||||||
|
"analysis_date": analysis.analysis_date,
|
||||||
|
"created_at": analysis.created_at.isoformat(),
|
||||||
|
"updated_at": analysis.updated_at.isoformat(),
|
||||||
|
"error_message": analysis.error_message
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,52 @@
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
from typing import List
|
||||||
from analysis.infra.db_models.analysis import AnalysisStatus
|
from analysis.infra.db_models.analysis import AnalysisStatus
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class AnalystType(str, Enum):
|
||||||
|
MARKET = "market"
|
||||||
|
SOCIAL = "social"
|
||||||
|
NEWS = "news"
|
||||||
|
FUNDAMENTALS = "fundamentals"
|
||||||
|
|
||||||
|
class TradingAnalysisRequest(BaseModel):
|
||||||
|
ticker: str
|
||||||
|
analysis_date: str
|
||||||
|
analysts: List[AnalystType]
|
||||||
|
research_depth: int = 3
|
||||||
|
llm_provider: str = "openai"
|
||||||
|
backend_url: str = "https://api.openai.com/v1"
|
||||||
|
shallow_thinker: str = "gpt-4o-mini"
|
||||||
|
deep_thinker: str = "gpt-4o"
|
||||||
|
|
||||||
class AnalysisSessionResponse(BaseModel):
|
class AnalysisSessionResponse(BaseModel):
|
||||||
id : str
|
id : str
|
||||||
ticker : str
|
ticker : str
|
||||||
status : AnalysisStatus
|
status : AnalysisStatus
|
||||||
|
|
||||||
|
class AnalysisProgressUpdate(BaseModel):
|
||||||
|
analysis_id: str
|
||||||
|
current_agent: str
|
||||||
|
status: str
|
||||||
|
progress_percentage: float
|
||||||
|
current_report_section: str | None = None
|
||||||
|
message: str | None = None
|
||||||
|
|
||||||
|
class AnalysisResultResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
ticker: str
|
||||||
|
analysis_date: str
|
||||||
|
status: AnalysisStatus
|
||||||
|
market_report: str | None = None
|
||||||
|
sentiment_report: str | None = None
|
||||||
|
news_report: str | None = None
|
||||||
|
fundamentals_report: str | None = None
|
||||||
|
investment_debate_state: dict | None = None
|
||||||
|
trader_investment_plan: str | None = None
|
||||||
|
risk_debate_state: dict | None = None
|
||||||
|
final_trade_decision: str | None = None
|
||||||
|
final_report: str | None = None
|
||||||
|
created_at: str
|
||||||
|
completed_at: str | None = None
|
||||||
|
error_message: str | None = None
|
||||||
Loading…
Reference in New Issue