import argparse import os import tempfile from datetime import datetime from typing import List, Optional, Dict, Any from fastapi import FastAPI, HTTPException, File, UploadFile from pydantic import BaseModel from industry_info import industry_info from investment_company import investment_company from similar_investors import SimilarInvestors from stock_news import StockNews from stock_price import StockPrice from theme_info import theme_info from portfolio import build_final_prompt, chatgpt, read_file_text, load_json app = FastAPI(title="Portfolio Analysis") # 데이터 파일 경로 설정 DATA_PATHS = { "similar_investors_us": "/work/portfolio/data/SEC_Filing_Manager.csv", "similar_investors_ko": "/work/portfolio/data/ETF.csv", "investment_company": "/work/portfolio/data/investment_company.jsonl", "industry_info": "/work/portfolio/data/industry_info.csv", "theme_info":"/work/portfolio/data/theme_info.csv" } # Request Models class IndustryInfoRequest(BaseModel): stock: List[str] = ["Meta"] class InvestmentCompanyRequest(BaseModel): name: str = "GEODE CAPITAL MANAGEMENT, LLC" class SimilarInvestorsRequest(BaseModel): csv_text: str = """종목코드,종목명,매입주가,보유주식수\nNVDA,NVIDIA Corporation,159.69,167\nAMZN,"Amazon.com, Inc.",211.58,140""" region: str top: int = 1 class StockNewsRequest(BaseModel): stock: str = "META" period: int = 1 class StockPriceRequest(BaseModel): stock: str = "META" class ThemeInfoRequest(BaseModel): stock: List[str] = ["Meta"] class PortfolioRequest(BaseModel): date:str = datetime.now().strftime("%Y년 %m월 %d일") user_name: str = "이서준" csv_text: str = "종목코드,종목명,매입주가,보유주식수\nAAPL,Apple Inc.,150,10" json_text: Dict[str, Any] = {"similar_investors":[{"ID":"37833100","NAME":"LAZARI CAPITAL MANAGEMENT, INC.","COMPANY":"APPLE INC","VALUE":33972687.0,"AMOUNT":165583,"PERCENTAGE":12.8}],"stock_price":{"META":[{"Date":"2025-10-20","Name":"Meta Platforms","Open":721.2,"High":733.8,"Low":720.2,"Close":732.2,"Volume":8900200},{"Date":"2025-10-21","Name":"Meta Platforms","Open":736.0,"High":738.5,"Low":728.8,"Close":733.3,"Volume":7647300},{"Date":"2025-10-22","Name":"Meta Platforms","Open":733.8,"High":740.6,"Low":724.0,"Close":733.4,"Volume":8734500}]},"stock_news":{"META":[{"Name":"Meta Platforms","date":"2025-11-20","time":"08:37","news":"딥러닝 거장 얀 르쿤, Meta Platforms 떠나 AI 스타트업 설립 선언\n“오늘날 AI는 인간 수준의 지능을 향해 가기 위해 반드시 새로운 접근이 필요하다.” 딥러닝의 개척자 얀 르쿤(Yann LeCun)이 Meta를 떠나며 남긴 이 코멘트는 AI 업계..."}]},"industry_info":[{"stock":"Meta","description":"Industry: Internet Content & Information | Sector: Communication Services"}],"theme_info":[{"stock":"Meta","desc":"Theme: Artificial Intelligence | Theme 2: \"Social Media\" | Theme 3: \"Virtual Reality\""}]} # 1. Industry Info API @app.post("/industry_info") def get_industry_info(request: IndustryInfoRequest): """종목의 산업군/섹터 검색""" try: print(f"[INFO] Processing stocks: {request.stock}") # industry_info 객체 생성 ic = industry_info(DATA_PATHS["industry_info"]) all_records = [] for stock in request.stock: print(f"[INFO] 처리 중: {stock}") result = ic.make(stock) if not result: print(f"[WARN] 종목 처리 실패: {stock}") continue full_name = result["stock"] desc = ic.get(full_name) if desc: print(f"[INFO] {full_name} 조회 완료") all_records.append({ "stock": full_name, "description": desc }) else: print(f"[WARN] {full_name} description 없음") return { "success": True, "data": all_records, "count": len(all_records) } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # 2. Investment Company API @app.post("/investment_company") def get_investment_company(request: InvestmentCompanyRequest): """투자회사 정보 조회""" try: print(f"[INFO] Processing company: {request.name}") ic = investment_company(DATA_PATHS["investment_company"]) ic.make() desc = ic.get(request.name) return { "success": True, "data": { "company_name": request.name, "description": desc } } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # 3. Similar Investors API @app.post("/similar_investors") def get_similar_investors(request: SimilarInvestorsRequest): """사용자와 유사한 투자회사 검색 (CSV 문자열 입력)""" try: print(f"[INFO] Processing CSV text (length={len(request.csv_text)}), top: {request.top}") # SimilarInvestors 준비 si = SimilarInvestors(DATA_PATHS["similar_investors_ko"], DATA_PATHS["similar_investors_us"]) if request.region == 'ko': created_path = si.make_ko() elif request.region == 'us': created_path = si.make_us() else: print("[INFO] region 입력 오류.") raise HTTPException(status_code=400, detail="region은 'ko' 또는 'us' 여야 합니다.") print(f"[INFO] 데이터 경로: {created_path}") # 1) csv_text 유효성 검사 (간단) header = request.csv_text.strip().splitlines()[0] required_cols = ["종목코드", "종목명", "매입주가", "보유주식수"] for col in required_cols: if col not in header.split(","): raise HTTPException(status_code=400, detail=f"CSV 헤더에 '{col}' 컬럼이 없습니다.") print("1완료 \n") # 2) 임시 파일로 저장하여 기존 si.get 재사용 with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as tmp: tmp_path = tmp.name tmp.write(request.csv_text) print(f"[INFO] 임시 CSV 파일 생성: {tmp_path}") try: result = si.get(tmp_path, request.region, output_path=None, top_n=request.top) count = sum(len(rows) for rows in result.values()) finally: # 임시 파일 삭제 if os.path.exists(tmp_path): os.remove(tmp_path) print(f"[INFO] 임시 파일 삭제 완료: {tmp_path}") return { "success": True, "data": result, "count": count, "top_n": request.top } except HTTPException as e: raise e except Exception as e: import traceback traceback.print_exc() raise HTTPException(status_code=500, detail=str(e)) # 4. Stock News API @app.post("/stock_news") def get_stock_news(request: StockNewsRequest): """기업 뉴스 크롤링""" try: print(f"[INFO] Processing stock: {request.stock}, period: {request.period}") data_folder = "data_stock_news" sn = StockNews(data_folder) # make 함수 실행 created = sn.make(request.stock, request.period) print(f"[INFO] 생성된 건수: {len(created) if created is not None else 0}") # get 함수 실행 loaded = sn.get(request.stock) print(f"[INFO] 로드된 건수: {len(loaded) if loaded is not None else 0}") stock = sn.normalize_ticker(request.stock) records = loaded.to_dict('records') if loaded is not None else [] return { "success": True, "data": { stock: records }, "count": len(records) } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # 5. Stock Price API @app.post("/stock_price") def get_stock_price(request: StockPriceRequest): """주가 데이터 조회""" try: stocks = [t.strip().upper() for t in request.stock.split(",") if t.strip()] print(f"[INFO] Processing {len(stocks)} stocks") data_folder = "data_stock_price/" sp = StockPrice(data_folder) stock_to_records = {} for stock in stocks: df = sp.make(stock) if df is None: print(f"[WARN] 생성 실패: {stock}") continue df = sp.get(stock) stock_to_records[sp.normalize_ticker(stock)] = df.to_dict('records') return { "success": True, "data": stock_to_records, "stocks_processed": len(stock_to_records) } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # 6. Theme Info API @app.post("/theme_info") def get_theme_info(request: ThemeInfoRequest): """종목의 테마 검색""" try: print(f"[INFO] Processing stocks: {request.stock}") ti = theme_info(DATA_PATHS["theme_info"]) # make 함수 실행 make_results = [] for stock in request.stock: print(f"[INFO] 처리 중: {stock}") rows = ti.make(stock) if rows: make_results.extend(rows) # get 함수 실행 all_records = [] for row in make_results: official_name = row["NAME"] desc = ti.get(official_name) all_records.append({ "stock": official_name, "desc": desc }) print(f"[INFO] {official_name} 조회 완료") return { "success": True, "data": all_records, "count": len(all_records) } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) # 7. Portfolio Report API @app.post("/report") def generate_portfolio_report(request: PortfolioRequest): try: print(f"[INFO] Generating report for user: {request.user_name}") csv_text = request.csv_text json_obj = request.json_text print("[INFO] 입력 데이터 로드 완료") final_prompt = build_final_prompt(request.date, request.user_name, csv_text, json_obj) print("[INFO] 프롬프트 생성 완료") report_text = chatgpt(final_prompt) print("[INFO] 리포트 생성 완료") return { "success": True, "data": { "user_name": request.user_name, "report": report_text } } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('--port', type=int, default=8080) args = parser.parse_args() import uvicorn uvicorn.run(app, host="0.0.0.0", port=args.port)