Skip to content

Commit 561a240

Browse files
authored
[AI] SISC2-37 [FEAT] transform 학습 파일 저장기능 구현 (#71)
* [AI] SISC2-37 [FEAT] transform 학습 파일 저장기능 구현 * [AI] SISC2-37 [FIX] .gitignore 롤백 .gitignore 의도치 않은 삭제 복구 * [AI] SISC2-37 [FIX] DB연결 종료 코드 수정 DB연결 실패시 종료 코드 실행 * [AI] SISC2-37 [STYLE] 오타수정 run_transformer 를 transformer로 변경
1 parent f80909a commit 561a240

23 files changed

Lines changed: 1241 additions & 307 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
.vscode/
55
*.iml
66
.DS_Store
7-
87
# 환경 변수 & 민감 정보
98
.env
109
*.secret
@@ -26,3 +25,5 @@ __pycache__/
2625
.ipynb_checkpoints/
2726
*.pyc
2827
/venv/
28+
/env
29+
/.vs

AI/configs/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{
1+
{
22
"db": {
33
"host": "ep-misty-lab-adgec0kl-pooler.c-2.us-east-1.aws.neon.tech",
44
"user": "neondb_owner",

AI/finder/main.py

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,75 @@
1+
# finder/run_finder.py
2+
import csv
13
import sys
24
import os
3-
4-
from libs.utils import news_processing
5-
from finder import ticker_selector
5+
import time
6+
import requests
67
import pandas as pd
7-
from langchain_community.llms import Ollama
8-
98

9+
# ---- 경로 세팅 ----
1010
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
1111
sys.path.append(project_root)
1212

13+
from libs.utils import news_processing
14+
from finder import ticker_selector
15+
from libs.llm_clients.ollama_client import get_ollama_client # ← 분리된 유틸 임포트
1316

1417
def run_finder():
15-
'''
18+
"""
1619
전체 프로세스를 조율하여 최종 Top 3 투자 종목 반환
17-
'''
20+
"""
1821
# --- 1단계: 의존성 객체 및 데이터 준비 ---
19-
llm = Ollama(model="llama3.2")
20-
2122
try:
22-
stability_df = pd.read_csv('data/stability_score_2025.csv')
23+
llm = get_ollama_client() # ✅ 헬스체크 및 모델 확인 포함
24+
except Exception as e:
25+
print(str(e))
26+
return []
27+
28+
csv_path = os.path.join(project_root, "data", "stability_score_2025.csv")
29+
30+
try:
31+
stability_df = pd.read_csv(csv_path)
2332
except FileNotFoundError:
24-
print("오류: 'data/stability_score_2025.csv' 파일을 찾을 수 없습니다.")
33+
print(f"오류: {csv_path} 파일을 찾을 수 없습니다.")
2534
return []
2635

2736
# --- 2단계: 주간 뉴스 데이터 수집 및 요약 ---
28-
weekly_news_df = news_processing.get_weekly_news_summary(days=5, llm_client=llm)
37+
try:
38+
weekly_news_df = news_processing.get_weekly_news_summary(days=5, llm_client=llm)
39+
except requests.exceptions.ConnectionError as e:
40+
print(f"[LLM 연결 오류] 뉴스 요약 단계에서 LLM 서버 연결 실패: {e}")
41+
return []
42+
except requests.exceptions.Timeout as e:
43+
print(f"[LLM 타임아웃] 뉴스 요약 단계에서 응답 지연: {e}")
44+
return []
45+
except Exception as e:
46+
print(f"[예기치 못한 오류] 뉴스 요약 단계: {e}")
47+
return []
2948

30-
if weekly_news_df.empty:
49+
if weekly_news_df is None or getattr(weekly_news_df, "empty", False):
3150
print("분석할 뉴스 데이터가 없어 프로세스를 종료합니다.")
3251
return []
3352

3453
# --- 3단계: 뉴스 데이터와 재무 데이터를 기반으로 Top 3 종목 선정 ---
35-
top_3_tickers = ticker_selector.select_top_stocks(
36-
news_summary_df=weekly_news_df,
37-
stability_df=stability_df,
38-
llm_client=llm
39-
)
54+
try:
55+
top_3_tickers = ticker_selector.select_top_stocks(
56+
news_summary_df=weekly_news_df,
57+
stability_df=stability_df,
58+
llm_client=llm
59+
)
60+
except requests.exceptions.ConnectionError as e:
61+
print(f"[LLM 연결 오류] 종목 선정 단계에서 LLM 서버 연결 실패: {e}")
62+
return []
63+
except requests.exceptions.Timeout as e:
64+
print(f"[LLM 타임아웃] 종목 선정 단계에서 응답 지연: {e}")
65+
return []
66+
except Exception as e:
67+
print(f"[예기치 못한 오류] 종목 선정 단계: {e}")
68+
return []
4069

4170
print("\n🎉 [Finder 모듈 최종 결과] 투자 추천 Top 3 종목 🎉")
4271
print(top_3_tickers)
43-
4472
return top_3_tickers
4573

4674
if __name__ == '__main__':
47-
run_finder()
75+
run_finder()

AI/libs/core/pipeline.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import os
1+
import os
22
import sys
33
from typing import List, Dict
44
import json
@@ -12,19 +12,19 @@
1212

1313
# --- 모듈 import ---
1414
from finder.main import run_finder
15-
from AI.transformer.main import run_transformer
16-
from AI.libs.utils.fetch_ohlcv import fetch_ohlcv
15+
from transformer.main import run_transformer
16+
from libs.utils.fetch_ohlcv import fetch_ohlcv
1717
from xai.run_xai import run_xai
18-
from AI.libs.utils.get_db_conn import get_db_conn
18+
from libs.utils.get_db_conn import get_db_conn
1919
# ---------------------------------
2020

2121
def run_weekly_finder() -> List[str]:
2222
"""
2323
주간 종목 발굴(Finder)을 실행하고 결과(종목 리스트)를 반환합니다.
2424
"""
2525
print("--- [PIPELINE-STEP 1] Finder 모듈 실행 시작 ---")
26-
top_tickers = run_finder()
27-
# top_tickers = ['AAPL', 'MSFT', 'GOOGL'] # 임시 데이터
26+
#top_tickers = run_finder()
27+
top_tickers = ['AAPL', 'MSFT', 'GOOGL'] # 임시 데이터
2828
print(f"--- [PIPELINE-STEP 1] Finder 모듈 실행 완료 ---")
2929
return top_tickers
3030

@@ -145,12 +145,15 @@ def run_pipeline():
145145
"""
146146
전체 파이프라인(Finder -> Transformer -> XAI)을 실행합니다.
147147
"""
148+
#--- 설정 파일 로드 ---
148149
config : Dict = {}
149150
try:
150151
with open(os.path.join(project_root, 'configs', 'config.json'), 'r') as f:
151152
config = json.load(f)
152153
except FileNotFoundError:
153154
print("[WARN] configs/config.json 파일을 찾을 수 없어 DB 연결이 필요 없는 기능만 작동합니다.")
155+
156+
#--- 파이프라인 단계별 실행 ---
154157
top_tickers = run_weekly_finder()
155158
if not top_tickers:
156159
print("Finder에서 종목을 찾지 못해 파이프라인을 중단합니다.")

AI/libs/llm_clients/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#AI/libs/llm_clients/ollama_client.py
2+
from libs.llm_clients.ollama_client import get_ollama_client
3+
__all__ = ["get_ollama_client"]
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# libs/llm_clients/ollama_client.py
2+
import os
3+
import requests
4+
from typing import Optional
5+
from langchain_community.llms import Ollama
6+
7+
# ---- 기본 설정 (환경변수로 오버라이드 가능) ----
8+
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "http://127.0.0.1:11434")
9+
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "llama3.2")
10+
11+
def _ollama_alive(base_url: str, timeout: float = 3.0) -> bool:
12+
"""
13+
Ollama 서버 헬스체크: /api/tags 로 간단 확인
14+
"""
15+
try:
16+
r = requests.get(f"{base_url}/api/tags", timeout=timeout)
17+
return r.ok
18+
except requests.exceptions.RequestException:
19+
return False
20+
21+
def _model_available(base_url: str, model: str) -> bool:
22+
"""
23+
지정 모델이 로컬 Ollama에 존재하는지 확인
24+
"""
25+
try:
26+
r = requests.get(f"{base_url}/api/tags", timeout=5)
27+
r.raise_for_status()
28+
tags = r.json().get("models", [])
29+
names = {m.get("name") for m in tags if isinstance(m, dict)}
30+
# ollama는 "llama3.2" 또는 "llama3.2:latest" 식으로 존재 가능
31+
return model in names or f"{model}:latest" in names
32+
except Exception:
33+
return False
34+
35+
def get_ollama_client(
36+
model: Optional[str] = None,
37+
base_url: Optional[str] = None,
38+
# langchain 0.2+에서 request_timeout 인자를 직접 받지 않는 경우가 있어 주석 처리
39+
# request_timeout: float = 60.0,
40+
) -> Ollama:
41+
"""
42+
Ollama LangChain LLM 클라이언트 생성
43+
- 서버와 모델 존재 여부를 사전 점검
44+
"""
45+
model = model or OLLAMA_MODEL
46+
base_url = base_url or OLLAMA_BASE_URL
47+
48+
if not _ollama_alive(base_url):
49+
raise RuntimeError(
50+
f"[연결 실패] Ollama 서버에 접속할 수 없습니다. llama3.2 설치 여부 확인해주세요.\n"
51+
f"- base_url: {base_url}\n"
52+
f"- 조치: (1) 'ollama serve' 실행 여부 확인 (2) 방화벽/프록시 (NO_PROXY=localhost,127.0.0.1) (3) 11434 포트 개방\n"
53+
f"- 테스트: curl {base_url}/api/tags"
54+
)
55+
56+
if not _model_available(base_url, model):
57+
raise RuntimeError(
58+
f"[모델 없음] '{model}' 모델이 Ollama에 없습니다.\n"
59+
f"- 조치: ollama pull {model}\n"
60+
f"- 보유 모델 확인: curl {base_url}/api/tags"
61+
)
62+
63+
return Ollama(
64+
model=model,
65+
base_url=base_url,
66+
# 필요 시 model_kwargs로 세부 파라미터 전달 가능
67+
# model_kwargs={"num_ctx": 4096},
68+
)

AI/libs/utils/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# AI/libs/utils/__init__.py
2+
from .fetch_ohlcv import fetch_ohlcv
3+
from .get_db_conn import get_db_conn
4+
5+
__all__ = [
6+
"fetch_ohlcv",
7+
"get_db_conn",
8+
]

AI/libs/utils/fetch_ohlcv.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import psycopg2
2-
import pandas as pd
1+
import pandas as pd
32

43
# DB 접속 커넥션 생성
5-
from AI.libs.utils.get_db_conn import get_db_conn
4+
from .get_db_conn import get_db_conn
65

76
# OHLCV 데이터 불러오기
87
def fetch_ohlcv(
@@ -23,12 +22,12 @@ def fetch_ohlcv(
2322
config (dict): DB 접속 정보 포함한 설정
2423
2524
Returns:
26-
DataFrame: 컬럼 = [date, open, high, low, close, volume]
25+
DataFrame: 컬럼 = [ticker, date, open, high, low, close, volume, adjusted_close]
2726
"""
2827
conn = get_db_conn(config)
2928

3029
query = """
31-
SELECT date, open, high, low, close, volume
30+
SELECT ticker, date, open, high, low, close, adjusted_close, volume
3231
FROM public.price_data
3332
WHERE ticker = %s
3433
AND date BETWEEN %s AND %s

AI/requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pandas
1+
pandas
22
psycopg2-binary
33
langchain-community
44
tqdm
@@ -11,3 +11,4 @@ yfinance
1111
groq
1212
requests
1313
beautifulsoup4
14+
pathlib

AI/tests/quick_db_check.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# quick_db_check.py
2+
import os
3+
import sys
4+
import json
5+
from typing import Dict, Union
6+
7+
import psycopg2
8+
9+
10+
# --- 프로젝트 루트 경로 설정 ---------------------------------------------------
11+
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
12+
sys.path.append(project_root)
13+
14+
# --- 설정 파일 로드 ------------------------------------------------------------
15+
cfg_path = os.path.join(project_root, "configs", "config.json")
16+
17+
config: Dict = {}
18+
if os.path.isfile(cfg_path):
19+
with open(cfg_path, "r", encoding="utf-8") as f:
20+
config = json.load(f)
21+
print("[INFO] configs/config.json 로드 완료")
22+
else:
23+
print(f"[WARN] 설정 파일이 없습니다: {cfg_path}")
24+
25+
db_cfg: Union[str, Dict] = (config or {}).get("db", {})
26+
27+
# --- DB 연결 테스트 ------------------------------------------------------------
28+
conn = None
29+
try:
30+
# db 설정이 dict면 키워드 인자로, 문자열(DSN)이면 그대로 사용
31+
if isinstance(db_cfg, dict):
32+
conn = psycopg2.connect(**db_cfg) # 예: {"host": "...", ...}
33+
else:
34+
conn = psycopg2.connect(dsn=str(db_cfg))
35+
36+
with conn:
37+
with conn.cursor() as cur:
38+
cur.execute("SELECT version();")
39+
print("✅ 연결 성공:", cur.fetchone()[0])
40+
41+
cur.execute("SELECT current_database(), current_user;")
42+
db, user = cur.fetchone()
43+
print(f"ℹ️ DB/USER: {db} / {user}")
44+
except Exception as e:
45+
print("❌ 연결 실패:", repr(e))
46+
finally:
47+
if conn is not None:
48+
conn.close()
49+

0 commit comments

Comments
 (0)