This commit is contained in:
kimheesu 2025-07-07 14:22:27 +09:00
parent fbd96e9c18
commit ab1b0120c2
73 changed files with 35353 additions and 44998 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ eval_data/
*.egg-info/ *.egg-info/
results/ results/
.env .env
tradingagents/dataflows/data_cache/

View File

@ -1,3 +1,8 @@
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '../../..'))
import logging
from sqlmodel import Session 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
@ -9,19 +14,23 @@ from datetime import datetime
from tradingagents.graph.trading_graph import TradingAgentsGraph from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.default_config import DEFAULT_CONFIG from tradingagents.default_config import DEFAULT_CONFIG
from analysis.application.websocket_manager import WebSocketManager
from analysis.infra.db_models.analysis import AnalysisStatus
logger = logging.getLogger(__name__)
class AnalysisService: class AnalysisService:
def __init__( def __init__(
self, self,
analysis_repo: IAnalysisRepository, analysis_repo: IAnalysisRepository,
session: Session, session: Session,
ulid: ULID ulid: ULID,
websocket_manager: WebSocketManager
): ):
self.analysis_repo = analysis_repo self.analysis_repo = analysis_repo
self.session = session self.session = session
self.ulid = ulid self.ulid = ulid
self.websocket_manager = websocket_manager
def get_analysis_list( def get_analysis_list(
self, self,
@ -46,6 +55,15 @@ class AnalysisService:
return analysis return analysis
def get_analysis_sessions_by_member(
self,
member_id: str
) -> list[AnalysisVO]:
analyses = self.analysis_repo.find_by_member_id(member_id)
if not analyses:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Analysis not found")
return analyses
def create_analysis( def create_analysis(
self, self,
member_id: str, member_id: str,
@ -67,14 +85,20 @@ class AnalysisService:
backend_url=request.backend_url, backend_url=request.backend_url,
shallow_thinker=request.shallow_thinker, shallow_thinker=request.shallow_thinker,
deep_thinker=request.deep_thinker, deep_thinker=request.deep_thinker,
status="pending", status=AnalysisStatus.PENDING,
created_at=now, created_at=now,
updated_at=now updated_at=now
) )
saved_analysis = self.analysis_repo.save(analysis_vo) saved_analysis = self.analysis_repo.save(analysis_vo)
if not saved_analysis:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to save analysis")
self.session.commit() self.session.commit()
# Register analysis with websocket manager
self.websocket_manager.register_analysis(saved_analysis.id, member_id)
# 백그라운드에서 분석 실행 # 백그라운드에서 분석 실행
background_tasks.add_task(self._run_analysis, saved_analysis.id) background_tasks.add_task(self._run_analysis, saved_analysis.id)
@ -83,48 +107,58 @@ class AnalysisService:
async def _run_analysis(self, analysis_id: str): async def _run_analysis(self, analysis_id: str):
"""백그라운드에서 실제 분석을 실행하는 메서드""" """백그라운드에서 실제 분석을 실행하는 메서드"""
try: try:
# 분석 상태를 RUNNING으로 변경 analysis = AnalysisVO(
analysis = self.analysis_repo.find_by_id(analysis_id) id=analysis_id,
if analysis: status=AnalysisStatus.RUNNING,
analysis.status = "running" updated_at=datetime.now()
analysis.updated_at = datetime.now() )
self.analysis_repo.update(analysis)
self.session.commit()
# 분석 정보 조회 analysis = self.analysis_repo.update(analysis)
analysis = self.analysis_repo.find_by_id(analysis_id)
if not analysis: if not analysis:
return raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Analysis not found")
await self.websocket_manager.send_analysis_update(
analysis_id=analysis_id,
update_type="status_changed",
data={"status": "running", "message": "Analysis started"}
)
# TradingAgentsGraph 설정 및 실행 # TradingAgentsGraph 설정 및 실행
config = self._create_config(analysis) if analysis:
config = self._create_config(analysis)
# 분석 실행 (실제 구현) # 분석 실행 (실제 구현)
await self._execute_trading_analysis(analysis_id, analysis, config) await self._execute_trading_analysis(analysis_id, analysis, config)
# 분석 완료 상태로 변경 # 완료 상태로 업데이트
analysis = self.analysis_repo.find_by_id(analysis_id) completed_analysis = AnalysisVO(
if analysis: id=analysis_id,
analysis.status = "completed" status=AnalysisStatus.COMPLETED,
analysis.completed_at = datetime.now() completed_at=datetime.now(),
analysis.updated_at = datetime.now() updated_at=datetime.now()
self.analysis_repo.update(analysis) )
self.session.commit() self.analysis_repo.update(completed_analysis)
self.session.commit()
except Exception as e: except Exception as e:
# 에러 발생 시 실패 상태로 변경 now = datetime.now()
analysis = self.analysis_repo.find_by_id(analysis_id) updates = AnalysisVO(
if analysis: status=AnalysisStatus.FAILED,
analysis.status = "failed" error_message=str(e),
analysis.error_message = str(e) completed_at = now,
analysis.completed_at = datetime.now() updated_at = now
analysis.updated_at = datetime.now() )
self.analysis_repo.update(analysis)
self.session.commit() self.analysis_repo.update(updates)
self.session.commit()
def _create_config(self, analysis: AnalysisVO) -> dict: def _create_config(self, analysis: AnalysisVO) -> dict:
"""분석 설정을 생성하는 메서드""" """분석 설정을 생성하는 메서드"""
config = DEFAULT_CONFIG.copy() if DEFAULT_CONFIG else {} config = {}
config.update({ config.update({
"max_debate_rounds": analysis.research_depth, "max_debate_rounds": analysis.research_depth,
"max_risk_discuss_rounds": analysis.research_depth, "max_risk_discuss_rounds": analysis.research_depth,
@ -138,12 +172,17 @@ class AnalysisService:
async def _execute_trading_analysis(self, analysis_id: str, analysis: AnalysisVO, config: dict): async def _execute_trading_analysis(self, analysis_id: str, analysis: AnalysisVO, config: dict):
"""실제 TradingAgentsGraph를 실행하는 메서드""" """실제 TradingAgentsGraph를 실행하는 메서드"""
try: try:
logger.info(f"Starting trading analysis for {analysis_id} with ticker {analysis.ticker}")
logger.info(f"Analysts selected: {analysis.analysts_selected}")
logger.info(f"Config: {config}")
# TradingAgentsGraph 초기화 # TradingAgentsGraph 초기화
graph = TradingAgentsGraph( graph = TradingAgentsGraph(
analysis.analysts_selected, analysis.analysts_selected,
config=config, config=config,
debug=True debug=True
) )
logger.info("TradingAgentsGraph initialized successfully")
# 초기 상태 생성 # 초기 상태 생성
init_agent_state = graph.propagator.create_initial_state( init_agent_state = graph.propagator.create_initial_state(
@ -153,8 +192,12 @@ class AnalysisService:
args = graph.propagator.get_graph_args() args = graph.propagator.get_graph_args()
# 분석 실행 및 결과 처리 # 분석 실행 및 결과 처리
logger.info("Starting graph execution...")
trace = [] trace = []
chunk_count = 0
async for chunk in graph.graph.astream(init_agent_state, **args): async for chunk in graph.graph.astream(init_agent_state, **args):
chunk_count += 1
logger.info(f"Processing chunk {chunk_count}: {list(chunk.keys()) if chunk else 'Empty chunk'}")
trace.append(chunk) trace.append(chunk)
# 실시간으로 분석 결과 업데이트 # 실시간으로 분석 결과 업데이트
@ -167,12 +210,17 @@ class AnalysisService:
# 최종 보고서 생성 # 최종 보고서 생성
final_report = self._generate_final_report(final_state) final_report = self._generate_final_report(final_state)
analysis.final_trade_decision = final_decision
analysis.final_report = final_report
# 최종 결과 저장 # 최종 결과 저장
self.analysis_repo.update(analysis_id, { updates = AnalysisVO(
"final_trade_decision": final_decision, id=analysis_id,
"final_report": final_report final_trade_decision=final_decision,
}) final_report=final_report
)
self.analysis_repo.update(updates)
self.session.commit() self.session.commit()
except Exception as e: except Exception as e:
@ -207,7 +255,10 @@ class AnalysisService:
# 업데이트가 있는 경우 저장 # 업데이트가 있는 경우 저장
if updates: if updates:
self.analysis_repo.update(analysis_id, updates) # analysis_id를 포함한 AnalysisVO 객체 생성
updates["id"] = analysis_id
updates_vo = AnalysisVO(**updates)
self.analysis_repo.update(updates_vo)
self.session.commit() self.session.commit()
def _generate_final_report(self, final_state: dict) -> str: def _generate_final_report(self, final_state: dict) -> str:
@ -245,3 +296,4 @@ class AnalysisService:
report_parts.append(f"{final_state['risk_debate_state']['judge_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." return "\n\n".join(report_parts) if report_parts else "No analysis results available."

View File

@ -0,0 +1,65 @@
from typing import Dict, Set
from fastapi import WebSocket
import json
from datetime import datetime
class WebSocketManager:
def __init__(self):
# Store active connections by member_id
self.active_connections: Dict[str, Set[WebSocket]] = {}
# Store analysis_id to member_id mapping
self.analysis_member_map: Dict[str, str] = {}
async def connect(self, websocket: WebSocket, member_id: str):
await websocket.accept()
if member_id not in self.active_connections:
self.active_connections[member_id] = set()
self.active_connections[member_id].add(websocket)
def disconnect(self, websocket: WebSocket, member_id: str):
if member_id in self.active_connections:
self.active_connections[member_id].discard(websocket)
if not self.active_connections[member_id]:
del self.active_connections[member_id]
def register_analysis(self, analysis_id: str, member_id: str):
"""Register which member owns which analysis"""
self.analysis_member_map[analysis_id] = member_id
async def send_analysis_update(self, analysis_id: str, update_type: str, data: dict):
"""Send analysis update to the member who owns the analysis"""
member_id = self.analysis_member_map.get(analysis_id)
if not member_id:
return
message = {
"type": "analysis_update",
"analysis_id": analysis_id,
"update_type": update_type,
"data": data,
"timestamp": datetime.now().isoformat()
}
await self.send_to_member(member_id, message)
async def send_to_member(self, member_id: str, message: dict|str):
"""Send message to all connections of a specific member"""
if member_id not in self.active_connections:
return
dead_connections = set()
for connection in self.active_connections[member_id]:
try:
if isinstance(message, dict):
await connection.send_json(message)
else:
await connection.send_text(message)
except Exception:
dead_connections.add(connection)
# Clean up dead connections
for connection in dead_connections:
self.disconnect(connection, member_id)

View File

@ -1,19 +1,20 @@
from pydantic import BaseModel from pydantic import BaseModel, field_validator
from datetime import datetime from datetime import datetime, date
from typing import List, Dict from typing import List, Dict, Union
from analysis.infra.db_models.analysis import AnalysisStatus
class Analysis(BaseModel): class Analysis(BaseModel):
id: str | None = None id: str
member_id: str member_id: str | None = None
ticker: str ticker: str | None = None
analysis_date: str analysis_date: date | None = None
analysts_selected: List[str] = [] analysts_selected: list[str] = []
research_depth: int = 3 research_depth: int = 3
llm_provider: str = "openai" llm_provider: str = "openai"
backend_url: str = "https://api.openai.com/v1" backend_url: str = "https://api.openai.com/v1"
shallow_thinker: str = "gpt-4o-mini" shallow_thinker: str = "gpt-4o"
deep_thinker: str = "gpt-4o" deep_thinker: str = "o3"
status: str status: AnalysisStatus = AnalysisStatus.PENDING
# 개별 분석가 리포트들 # 개별 분석가 리포트들
market_report: str | None = None market_report: str | None = None
@ -33,5 +34,5 @@ class Analysis(BaseModel):
# 실행 결과 정보 # 실행 결과 정보
error_message: str | None = None error_message: str | None = None
completed_at: datetime | None = None completed_at: datetime | None = None
created_at: datetime created_at: datetime | None = None
updated_at: datetime updated_at: datetime | None = None

View File

@ -2,7 +2,7 @@ from datetime import datetime,date
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlmodel import SQLModel, Field, JSON, Relationship from sqlmodel import SQLModel, Field, JSON, Relationship
import enum import enum
from sqlalchemy import Column from sqlalchemy import Column, Text
# TYPE_CHECKING을 사용해서 circular import 방지 # TYPE_CHECKING을 사용해서 circular import 방지
if TYPE_CHECKING: if TYPE_CHECKING:
@ -32,22 +32,22 @@ class Analysis(SQLModel, table=True):
status: AnalysisStatus = Field(default=AnalysisStatus.PENDING) status: AnalysisStatus = Field(default=AnalysisStatus.PENDING)
# 개별 분석가 리포트들 # 개별 분석가 리포트들
market_report: str | None = Field(default=None, description="Market Analyst 리포트") market_report: str | None = Field(default=None, sa_column=Column(Text), description="Market Analyst 리포트")
sentiment_report: str | None = Field(default=None, description="Social Analyst 리포트") sentiment_report: str | None = Field(default=None, sa_column=Column(Text), description="Social Analyst 리포트")
news_report: str | None = Field(default=None, description="News Analyst 리포트") news_report: str | None = Field(default=None, sa_column=Column(Text), description="News Analyst 리포트")
fundamentals_report: str | None = Field(default=None, description="Fundamentals Analyst 리포트") fundamentals_report: str | None = Field(default=None, sa_column=Column(Text), description="Fundamentals Analyst 리포트")
# 팀별 의사결정 과정 # 팀별 의사결정 과정
investment_debate_state: dict | None = Field(default=None, sa_column=Column(JSON), description="Research Team 토론 과정") investment_debate_state: dict | None = Field(default=None, sa_column=Column(JSON), description="Research Team 토론 과정")
trader_investment_plan: str | None = Field(default=None, description="Trading Team 계획") trader_investment_plan: str | None = Field(default=None, sa_column=Column(Text), description="Trading Team 계획")
risk_debate_state: dict | None = Field(default=None, sa_column=Column(JSON), description="Risk Management Team 토론 과정") risk_debate_state: dict | None = Field(default=None, sa_column=Column(JSON), description="Risk Management Team 토론 과정")
# 최종 결과물 # 최종 결과물
final_trade_decision: str | None = Field(default=None, description="최종 거래 결정") final_trade_decision: str | None = Field(default=None, sa_column=Column(Text), description="최종 거래 결정")
final_report: str | None = Field(default=None, description="전체 통합 리포트") final_report: str | None = Field(default=None, sa_column=Column(Text), description="전체 통합 리포트")
# 실행 결과 정보 # 실행 결과 정보
error_message: str | None = None error_message: str | None = Field(default=None, sa_column=Column(Text))
completed_at: datetime | None = None completed_at: datetime | None = None
created_at : datetime = Field(nullable=False) created_at : datetime = Field(nullable=False)
updated_at : datetime = Field(nullable=False) updated_at : datetime = Field(nullable=False)

View File

@ -28,30 +28,7 @@ class AnalysisRepository(IAnalysisRepository):
def save(self, analysis: AnalysisVO) -> AnalysisVO: def save(self, analysis: AnalysisVO) -> AnalysisVO:
new_analysis = Analysis( new_analysis = Analysis(
id=analysis.id, **analysis.model_dump()
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.add(new_analysis)
@ -67,14 +44,12 @@ class AnalysisRepository(IAnalysisRepository):
return None return None
# AnalysisVO의 데이터를 SQLModel 객체에 업데이트 # AnalysisVO의 데이터를 SQLModel 객체에 업데이트
vo_data = analysis_vo.sqlmodel_dump(exclude_unset=True) analysis_data = analysis_vo.model_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() analysis.updated_at = datetime.now()
self.session.add(analysis) analysis.sqlmodel_update(analysis_data)
self.session.flush() self.session.flush()
self.session.refresh(analysis)
return AnalysisVO(**row_to_dict(analysis)) return AnalysisVO(**row_to_dict(analysis))

View File

@ -1,5 +1,5 @@
from typing import Annotated from typing import Annotated
from fastapi import APIRouter, Depends, BackgroundTasks, HTTPException, status from fastapi import APIRouter, Depends, BackgroundTasks, HTTPException, status, WebSocket, WebSocketDisconnect
from analysis.interface.dto import ( from analysis.interface.dto import (
AnalysisSessionResponse, AnalysisSessionResponse,
TradingAnalysisRequest, TradingAnalysisRequest,
@ -9,6 +9,7 @@ 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
from utils.containers import Container from utils.containers import Container
from analysis.application.websocket_manager import WebSocketManager
router = APIRouter(prefix="/analysis", tags=["analysis"]) router = APIRouter(prefix="/analysis", tags=["analysis"])
@ -40,6 +41,7 @@ def start_analysis_session(
): ):
""" """
새로운 분석 세션을 시작합니다. 새로운 분석 세션을 시작합니다.
""" """
try: try:
new_analysis = analysis_service.create_analysis(current_member.id, request, background_tasks) new_analysis = analysis_service.create_analysis(current_member.id, request, background_tasks)
@ -69,7 +71,7 @@ def get_analysis_result(
return AnalysisResultResponse( return AnalysisResultResponse(
id=analysis.id, id=analysis.id,
ticker=analysis.ticker, ticker=analysis.ticker,
analysis_date=analysis.analysis_date, analysis_date=analysis.analysis_date.isoformat() if hasattr(analysis.analysis_date, 'isoformat') else str(analysis.analysis_date),
status=analysis.status, status=analysis.status,
market_report=analysis.market_report, market_report=analysis.market_report,
sentiment_report=analysis.sentiment_report, sentiment_report=analysis.sentiment_report,
@ -106,3 +108,30 @@ def get_analysis_status(
"updated_at": analysis.updated_at.isoformat(), "updated_at": analysis.updated_at.isoformat(),
"error_message": analysis.error_message "error_message": analysis.error_message
} }
@router.websocket("/ws")
@inject
async def websocket_endpoint(
websocket: WebSocket,
current_member: Annotated[CurrentMember, Depends(get_current_member)],
websocket_manager: Annotated[WebSocketManager, Depends(Provide[Container.websocket_manager])]
):
"""
WebSocket endpoint for real-time analysis updates
"""
try:
# Connect the websocket
await websocket_manager.connect(websocket, current_member.id)
try:
# Keep connection alive
while True:
# Wait for messages from client (like ping/pong)
data = await websocket.receive_text()
# Echo back for heartbeat
if data == "ping":
await websocket.send_text("pong")
except WebSocketDisconnect:
websocket_manager.disconnect(websocket, current_member.id)
except Exception as e:
await websocket.close(code=1011, reason=str(e))

View File

@ -11,14 +11,14 @@ class AnalystType(str, Enum):
FUNDAMENTALS = "fundamentals" FUNDAMENTALS = "fundamentals"
class TradingAnalysisRequest(BaseModel): class TradingAnalysisRequest(BaseModel):
ticker: str ticker: str = "NVDA"
analysis_date: str analysis_date: str = "2025-07-07"
analysts: List[AnalystType] analysts: List[AnalystType] = [AnalystType.MARKET, AnalystType.SOCIAL, AnalystType.NEWS, AnalystType.FUNDAMENTALS]
research_depth: int = 3 research_depth: int = 3
llm_provider: str = "openai" llm_provider: str = "openai"
backend_url: str = "https://api.openai.com/v1" backend_url: str = "https://api.openai.com/v1"
shallow_thinker: str = "gpt-4o-mini" shallow_thinker: str = "gpt-4o-mini"
deep_thinker: str = "gpt-4o" deep_thinker: str = "gpt-4o-mini"
class AnalysisSessionResponse(BaseModel): class AnalysisSessionResponse(BaseModel):
id : str id : str

View File

@ -5,6 +5,16 @@ from utils.containers import Container
from analysis.interface.controller.analysis_controller import router as analysis_router from analysis.interface.controller.analysis_controller import router as analysis_router
from member.interface.controller.member_controller import router as member_router from member.interface.controller.member_controller import router as member_router
import logging
# 로깅 설정
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(), # 콘솔 출력
]
)

View File

@ -14,12 +14,12 @@ class MemberService:
self, self,
member_repo: IMemberRepository, member_repo: IMemberRepository,
crypto: Crypto, crypto: Crypto,
db_session: Session, session: Session,
ulid: ULID ulid: ULID
): ):
self.member_repo = member_repo self.member_repo = member_repo
self.crypto = crypto self.crypto = crypto
self.db_session = db_session self.db_session = session
self.ulid = ulid self.ulid = ulid
def create_member( def create_member(

View File

@ -6,6 +6,8 @@ from utils.containers import Container
from dependency_injector.wiring import inject, Provide from dependency_injector.wiring import inject, Provide
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from utils.auth import get_current_member, CurrentMember, get_admin_member from utils.auth import get_current_member, CurrentMember, get_admin_member
from analysis.interface.dto import AnalysisSessionResponse
from analysis.application.analysis_service import AnalysisService
router = APIRouter(prefix="/members", tags=["members"]) router = APIRouter(prefix="/members", tags=["members"])
@ -67,12 +69,3 @@ def get_member(
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Member not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Member not found")
return member return member
# @router.get("/analysis-sessions", response_model=list[AnalysisSessionResponse])
# @inject
# def get_member_analysis_sessions(
# current_member: Annotated[CurrentMember | None, Depends(get_current_member)] = None,
# member_service: Annotated[MemberService | None, Depends(Provide[Container.member_service])] = None
# ):
# result = member_service.get_analysis_sessions_by_member(current_member.id)
# return result

View File

@ -5,6 +5,7 @@ from member.infra.repository.member_repo import MemberRepository
from member.application.member_service import MemberService from member.application.member_service import MemberService
from analysis.application.analysis_service import AnalysisService from analysis.application.analysis_service import AnalysisService
from analysis.infra.repository.analysis_repo import AnalysisRepository from analysis.infra.repository.analysis_repo import AnalysisRepository
from analysis.application.websocket_manager import WebSocketManager
from ulid import ULID from ulid import ULID
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
@ -12,32 +13,37 @@ class Container(containers.DeclarativeContainer):
packages=["member", "analysis"] packages=["member", "analysis"]
) )
db_session = providers.Resource(get_session) session = providers.Resource(get_session)
crypto = providers.Factory(Crypto) crypto = providers.Factory(Crypto)
ulid = providers.Factory(ULID) ulid = providers.Factory(ULID)
member_repo = providers.Factory( member_repo = providers.Factory(
MemberRepository, MemberRepository,
session=db_session session=session
) )
member_service = providers.Factory( member_service = providers.Factory(
MemberService, MemberService,
member_repo=member_repo, member_repo=member_repo,
crypto=crypto, crypto=crypto,
db_session=db_session, session=session,
ulid=ulid ulid=ulid
) )
analysis_repo = providers.Factory( analysis_repo = providers.Factory(
AnalysisRepository, AnalysisRepository,
session=db_session session=session
)
websocket_manager = providers.Singleton(
WebSocketManager
) )
analysis_service = providers.Factory( analysis_service = providers.Factory(
AnalysisService, AnalysisService,
analysis_repo=analysis_repo, analysis_repo=analysis_repo,
db_session=db_session, session=session,
ulid=ulid ulid=ulid,
websocket_manager=websocket_manager
) )